mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-26 02:11:18 +00:00
Stop requiring a filament id snapshot update when filaments change
This commit is contained in:
@@ -55,13 +55,12 @@ def preset(name, filament_id=None, inherits=None, instantiation=True,
|
||||
|
||||
|
||||
class SyntheticTree:
|
||||
"""A throwaway resources/profiles-shaped directory plus a snapshot path."""
|
||||
"""A throwaway resources/profiles-shaped directory."""
|
||||
|
||||
def __init__(self):
|
||||
self.dir = tempfile.mkdtemp(prefix="filament_id_test_")
|
||||
self.profiles = os.path.join(self.dir, "profiles")
|
||||
os.makedirs(self.profiles)
|
||||
self.snapshot = os.path.join(self.dir, "filament_id_snapshot.json")
|
||||
|
||||
def cleanup(self):
|
||||
shutil.rmtree(self.dir, ignore_errors=True)
|
||||
@@ -110,16 +109,6 @@ class SyntheticTree:
|
||||
with open(idx_path, "w", encoding="utf-8") as f:
|
||||
json.dump(index, f, indent=4, ensure_ascii=False)
|
||||
|
||||
def remove_preset(self, vendor, name):
|
||||
os.remove(self.preset_path(vendor, name))
|
||||
idx_path = os.path.join(self.profiles, vendor + ".json")
|
||||
with open(idx_path, encoding="utf-8") as f:
|
||||
index = json.load(f)
|
||||
index["filament_list"] = [
|
||||
e for e in index["filament_list"] if e["name"] != name]
|
||||
with open(idx_path, "w", encoding="utf-8") as f:
|
||||
json.dump(index, f, indent=4, ensure_ascii=False)
|
||||
|
||||
def bytes_map(self):
|
||||
"""{relative path -> file bytes} over every .json in the tree."""
|
||||
raw = {}
|
||||
@@ -135,17 +124,11 @@ class SyntheticTree:
|
||||
|
||||
# -- pipeline wrappers ---------------------------------------------------
|
||||
|
||||
def update_snapshot(self, dry_run=False):
|
||||
buf = io.StringIO()
|
||||
with contextlib.redirect_stdout(buf):
|
||||
rc = afi.update_snapshot(self.profiles, self.snapshot, dry_run)
|
||||
return rc, buf.getvalue()
|
||||
|
||||
def check(self, map_path=None):
|
||||
buf = io.StringIO()
|
||||
kwargs = {} if map_path is None else {"map_path": map_path}
|
||||
with contextlib.redirect_stdout(buf):
|
||||
errors = afi.check_filament_ids(self.profiles, self.snapshot, **kwargs)
|
||||
errors = afi.check_filament_ids(self.profiles, **kwargs)
|
||||
return errors, buf.getvalue()
|
||||
|
||||
# assign() and remint() are the same one pass over the tree — every filament
|
||||
@@ -167,8 +150,6 @@ class SyntheticTree:
|
||||
def cli(self, *argv):
|
||||
"""Run main() against this tree, capturing stdout."""
|
||||
flags = [*argv, "--profiles", self.profiles]
|
||||
if argv and argv[0] in ("check", "update-snapshot"):
|
||||
flags += ["--snapshot", self.snapshot]
|
||||
buf = io.StringIO()
|
||||
with contextlib.redirect_stdout(buf):
|
||||
rc = afi.main(flags)
|
||||
@@ -178,10 +159,10 @@ class SyntheticTree:
|
||||
def make_clean_tree(apla_id="AX01", generic_id="OGFL99"):
|
||||
"""Baseline tree: OFL base+generic, a vendor filament, a clean tuned generic.
|
||||
|
||||
apla_id/generic_id default to arbitrary non-OF placeholders (sanctioned by
|
||||
the snapshot below) since most tests only need "already assigned, don't
|
||||
touch" and never run the checks. TestAssign and the check tests pass real
|
||||
OF-format ids instead (OfCleanTreeCase).
|
||||
apla_id/generic_id default to arbitrary non-OF placeholders since most tests
|
||||
only need "already assigned, don't touch" and never run the checks.
|
||||
TestAssign and the check tests pass real OF-format ids instead
|
||||
(OfCleanTreeCase).
|
||||
"""
|
||||
t = SyntheticTree()
|
||||
t.add_vendor(OFL, [
|
||||
@@ -199,8 +180,6 @@ def make_clean_tree(apla_id="AX01", generic_id="OGFL99"):
|
||||
preset("Generic PLA @P1", inherits="Generic PLA @System",
|
||||
compatible_printers=["P1 0.4 nozzle"]),
|
||||
])
|
||||
rc, _out = t.update_snapshot()
|
||||
assert rc == 0
|
||||
return t
|
||||
|
||||
|
||||
@@ -212,10 +191,9 @@ class SyntheticTreeCase(unittest.TestCase):
|
||||
|
||||
class OfCleanTreeCase(unittest.TestCase):
|
||||
"""Like SyntheticTreeCase, but the baseline filament/generic already carry
|
||||
real OF-format ids (check 1 now rejects "AX01"/"OGFL99" unconditionally,
|
||||
with no snapshot exemption), so an otherwise-untouched tree still passes
|
||||
check_filament_ids. Tests that specifically need a non-OF baseline to
|
||||
remint (TestRemint, TestUpdateSnapshot) keep using SyntheticTreeCase
|
||||
real OF-format ids (check 1 rejects "AX01"/"OGFL99"), so an
|
||||
otherwise-untouched tree passes check_filament_ids. Tests that specifically
|
||||
need a non-OF baseline to remint (TestRemint) keep using SyntheticTreeCase
|
||||
instead.
|
||||
"""
|
||||
def setUp(self):
|
||||
@@ -231,7 +209,7 @@ class OfCleanTreeCase(unittest.TestCase):
|
||||
|
||||
class TestMint(unittest.TestCase):
|
||||
def test_namespace_literal(self):
|
||||
# Frozen: derived from the setting_id namespace; baked into the snapshot.
|
||||
# Frozen: derived from the setting_id namespace; baked into every shipped id.
|
||||
self.assertEqual(afi.FILAMENT_ID_NAMESPACE,
|
||||
uuid.UUID("c4d3ff49-4c32-5534-a3e3-00894157ab97"))
|
||||
|
||||
@@ -450,22 +428,10 @@ class TestChecks(OfCleanTreeCase):
|
||||
self.assertIn('is not a minted "OF" id', out)
|
||||
self.assertIn("BOGUS_9", out)
|
||||
|
||||
def test_check2_new_claim_needs_snapshot_update(self):
|
||||
self.t.write_preset("VendorA", preset("ANEW @P2", inherits="APLA @base",
|
||||
compatible_printers=["P2"]))
|
||||
errors, out = self.t.check()
|
||||
self.assertGreater(errors, 0)
|
||||
self.assertIn('claim "VendorA/ANEW" is not sanctioned', out)
|
||||
self.assertIn("update-snapshot", out)
|
||||
|
||||
def test_check2_vanished_claim_is_stability_error(self):
|
||||
self.t.remove_preset("VendorA", "APLA @P1")
|
||||
errors, out = self.t.check()
|
||||
self.assertGreater(errors, 0)
|
||||
self.assertIn("stability", out)
|
||||
self.assertIn('"VendorA/APLA"', out)
|
||||
|
||||
def test_check2_triple_change_needs_snapshot_update(self):
|
||||
def test_check2_triple_change_needs_a_remint(self):
|
||||
# Correcting a triple changes the product's identity: the old id is no
|
||||
# longer its mint, reported on the root and again under the variant
|
||||
# inheriting it, until generate-id re-mints it.
|
||||
apla_id = afi.generate_filament_id("AVendor", "PLA", "APLA")
|
||||
self.t.write_preset("VendorA", preset("APLA @base", filament_id=apla_id,
|
||||
instantiation=False,
|
||||
@@ -473,26 +439,15 @@ class TestChecks(OfCleanTreeCase):
|
||||
filament_type="PETG"),
|
||||
register=False)
|
||||
errors, out = self.t.check()
|
||||
self.assertGreater(errors, 0)
|
||||
self.assertIn('triple "AVendor/PETG/APLA" is not sanctioned', out)
|
||||
self.assertIn('which records "AVendor/PLA/APLA"', out)
|
||||
# Sanctioning the new triple is not enough: the old id is no longer its
|
||||
# mint (check 3, nothing grandfathered) — the identity fix is a re-mint,
|
||||
# reported on the root and again under the variant inheriting it.
|
||||
rc, _out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 2, out)
|
||||
self.assertIn("does not match the mint of its triple", out)
|
||||
self.assertIn('"APLA @P1" (VendorA/filament/APLA @P1.json) inherits filament_id', out)
|
||||
_changed, errors, out = self.t.remint(["VendorA"])
|
||||
self.assertEqual(errors, 0, out)
|
||||
rc, _out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 0, out)
|
||||
|
||||
def test_check3_of_id_must_match_triple_mint(self):
|
||||
def test_check2_of_id_must_match_triple_mint(self):
|
||||
self.t.write_preset("VendorA", preset("BNEW @base", filament_id="OFZZZZZZ",
|
||||
instantiation=False,
|
||||
filament_vendor="BV", filament_type="PLA"))
|
||||
@@ -503,39 +458,20 @@ class TestChecks(OfCleanTreeCase):
|
||||
self.assertIn("does not match the mint of its triple", out)
|
||||
self.assertIn(afi.generate_filament_id("BV", "PLA", "BNEW"), out)
|
||||
|
||||
def test_check3_no_grandfathering_of_a_wrong_declaration(self):
|
||||
# Sanctioning the tree does not excuse a declaration from its mint.
|
||||
self.t.write_preset("VendorA", preset("CNEW @base", filament_id="OFZZZZZZ",
|
||||
instantiation=False,
|
||||
filament_vendor="CV", filament_type="PLA"))
|
||||
self.t.write_preset("VendorA", preset("CNEW @P1", inherits="CNEW @base",
|
||||
compatible_printers=["P1"]))
|
||||
rc, _out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 2, out) # the declaration, and the variant inheriting it
|
||||
self.assertIn("does not match the mint of its triple", out)
|
||||
self.assertIn('"CNEW @P1" (VendorA/filament/CNEW @P1.json) inherits filament_id', out)
|
||||
|
||||
def test_check3_inherited_id_must_be_the_mint_of_own_triple(self):
|
||||
def test_check2_inherited_id_must_be_the_mint_of_own_triple(self):
|
||||
# A preset of another filament inheriting APLA's root takes APLA's id,
|
||||
# which is not the mint of ITS triple (AVendor/PLA/Tuned PLA).
|
||||
self.t.write_preset("VendorA", preset("Tuned PLA @P1", inherits="APLA @base",
|
||||
compatible_printers=["P1"]))
|
||||
errors, out = self.t.check()
|
||||
self.assertGreater(errors, 0)
|
||||
self.assertEqual(errors, 1, out)
|
||||
self.assertIn('"Tuned PLA @P1" (VendorA/filament/Tuned PLA @P1.json) inherits '
|
||||
'filament_id "%s"' % afi.generate_filament_id("AVendor", "PLA", "APLA"),
|
||||
out)
|
||||
self.assertIn('mints "%s"' % afi.generate_filament_id("AVendor", "PLA", "Tuned PLA"),
|
||||
out)
|
||||
# ... and sanctioning the tree does not excuse it either.
|
||||
rc, _out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 1, out)
|
||||
|
||||
def test_check3_lists_every_preset_inheriting_a_wrong_id(self):
|
||||
def test_check2_lists_every_preset_inheriting_a_wrong_id(self):
|
||||
# A wrong declaration is reported under every preset inheriting it, its
|
||||
# own product's variant and another product alike: each one's effective
|
||||
# id is not the mint of its own triple, and each is listed. Nothing is
|
||||
@@ -552,11 +488,10 @@ class TestChecks(OfCleanTreeCase):
|
||||
self.assertIn('"DNEW @P1" (VendorA/filament/DNEW @P1.json) inherits filament_id', out)
|
||||
self.assertIn('"Other DNEW @P1" (VendorA/filament/Other DNEW @P1.json) inherits '
|
||||
'filament_id', out)
|
||||
# The unsanctioned id (check 2), the declaration (3a), and both presets
|
||||
# inheriting it (3b).
|
||||
self.assertEqual(errors, 4, out)
|
||||
# The declaration (2a), and both presets inheriting it (2b).
|
||||
self.assertEqual(errors, 3, out)
|
||||
|
||||
def test_check3_reports_an_inherited_mismatch_even_when_its_own_product_misdeclares_the_id(self):
|
||||
def test_check2_reports_an_inherited_mismatch_even_when_its_own_product_misdeclares_the_id(self):
|
||||
# "Tuned PLA @P1" inherits APLA's root, so it carries APLA's id: wrong
|
||||
# for its own product however the declarations around it are fixed.
|
||||
# That "Tuned PLA @base" — its own product — misdeclares that same id
|
||||
@@ -573,11 +508,11 @@ class TestChecks(OfCleanTreeCase):
|
||||
'not match the mint of its triple', out)
|
||||
self.assertIn('"Tuned PLA @P1" (VendorA/filament/Tuned PLA @P1.json) inherits '
|
||||
'filament_id', out)
|
||||
# The unsanctioned claim and triple (check 2), the declaration (3a) and
|
||||
# the inherited id (3b): four distinct errors, nothing folded away.
|
||||
self.assertEqual(errors, 4, out)
|
||||
# The declaration (2a) and the inherited id (2b): two distinct errors,
|
||||
# nothing folded away.
|
||||
self.assertEqual(errors, 2, out)
|
||||
|
||||
def test_check3_reports_a_collision_between_two_products(self):
|
||||
def test_check2_reports_a_collision_between_two_products(self):
|
||||
# Two products whose triples mint one id is a base62 collision. There
|
||||
# is no salted or hand-picked second id to fall back on: the check
|
||||
# names both products, and the remedy is a rename so the triples differ.
|
||||
@@ -601,13 +536,12 @@ class TestChecks(OfCleanTreeCase):
|
||||
self.assertIn("V/PLA/X", out)
|
||||
self.assertIn("W/ABS/Y", out)
|
||||
# Each declaration is the mint of its own triple, so the collision is
|
||||
# the only identity error — no product is pushed off its id — and the
|
||||
# unsanctioned id (check 2) is the only other one.
|
||||
# the only error — no product is pushed off its id.
|
||||
self.assertNotIn("does not match the mint", out)
|
||||
self.assertNotIn("inherits filament_id", out)
|
||||
self.assertEqual(errors, 2, out)
|
||||
self.assertEqual(errors, 1, out)
|
||||
|
||||
def test_check3_renamed_tuned_generic_is_an_identity_error(self):
|
||||
def test_check2_renamed_tuned_generic_is_an_identity_error(self):
|
||||
# Riding the OFL generic under another base name: same rule, same error.
|
||||
self.t.write_preset("VendorA", preset("Tuned PLA @P1",
|
||||
inherits="Generic PLA @System",
|
||||
@@ -617,7 +551,7 @@ class TestChecks(OfCleanTreeCase):
|
||||
self.assertIn("Tuned PLA @P1", out)
|
||||
self.assertIn("inherits filament_id", out)
|
||||
|
||||
def test_check3_own_key_on_an_instantiated_preset_is_fine(self):
|
||||
def test_check2_own_key_on_an_instantiated_preset_is_fine(self):
|
||||
# Where the id comes from is irrelevant: a variant may carry the key.
|
||||
apla_id = afi.generate_filament_id("AVendor", "PLA", "APLA")
|
||||
self.t.write_preset("VendorA", preset("APLA @P1", filament_id=apla_id,
|
||||
@@ -627,7 +561,7 @@ class TestChecks(OfCleanTreeCase):
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 0, out)
|
||||
|
||||
def test_check3_inheriting_a_real_filament_of_another_product_is_fine(self):
|
||||
def test_check2_inheriting_a_real_filament_of_another_product_is_fine(self):
|
||||
# A branded product may inherit the OFL generic (an instantiated
|
||||
# preset) for its settings; it declares its own triple's id.
|
||||
fid = afi.generate_filament_id("BV", "PLA", "Branded PLA")
|
||||
@@ -635,8 +569,6 @@ class TestChecks(OfCleanTreeCase):
|
||||
inherits="Generic PLA @System",
|
||||
filament_vendor="BV",
|
||||
compatible_printers=["P1"]))
|
||||
rc, _out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 0, out)
|
||||
# With a wrong key it is a plain mint mismatch: the parent plays no
|
||||
@@ -670,22 +602,16 @@ class TestChecks(OfCleanTreeCase):
|
||||
self.assertIn(f'filament_id "{fid}"', out)
|
||||
self.assertIn('is not a minted "OF" id', out)
|
||||
|
||||
def test_check3c_unresolvable_instantiated_filament(self):
|
||||
def test_check2c_unresolvable_instantiated_filament(self):
|
||||
self.t.write_preset("VendorA", preset("DNEW @P1", compatible_printers=["P1"]))
|
||||
errors, out = self.t.check()
|
||||
self.assertGreater(errors, 0)
|
||||
self.assertIn("resolves no filament_id", out)
|
||||
self.assertIn("hard load error", out)
|
||||
|
||||
def test_missing_snapshot_is_an_error(self):
|
||||
os.remove(self.t.snapshot)
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 1)
|
||||
self.assertIn("snapshot not found", out)
|
||||
|
||||
|
||||
class TestCheck5(OfCleanTreeCase):
|
||||
def test_5a_empty_vendor_is_hard_error(self):
|
||||
class TestCheck3(OfCleanTreeCase):
|
||||
def test_3a_empty_vendor_is_hard_error(self):
|
||||
fid = afi.generate_filament_id("", "PLA", "NVPLA")
|
||||
self.t.write_preset("VendorA", preset("NVPLA @base", filament_id=fid,
|
||||
instantiation=False,
|
||||
@@ -693,17 +619,11 @@ class TestCheck5(OfCleanTreeCase):
|
||||
self.t.write_preset("VendorA", preset("NVPLA @P1", inherits="NVPLA @base",
|
||||
compatible_printers=["P1"]))
|
||||
errors, out = self.t.check()
|
||||
self.assertGreater(errors, 0)
|
||||
self.assertIn("resolves empty filament_vendor", out)
|
||||
self.assertIn('filament_vendor "Generic"', out)
|
||||
# No grandfathering: sanctioning the tree does not silence check 4a.
|
||||
rc, _out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 1, out)
|
||||
self.assertIn("resolves empty filament_vendor", out)
|
||||
self.assertIn('filament_vendor "Generic"', out)
|
||||
|
||||
def test_5b_divergent_filament_triples(self):
|
||||
def test_3b_divergent_filament_triples(self):
|
||||
id1 = afi.generate_filament_id("MV", "PLA", "MPLA")
|
||||
id2 = afi.generate_filament_id("MV", "PETG", "MPLA")
|
||||
self.t.write_preset("VendorA", preset("MPLA @base1", filament_id=id1,
|
||||
@@ -715,18 +635,12 @@ class TestCheck5(OfCleanTreeCase):
|
||||
filament_vendor="MV",
|
||||
filament_type="PETG"))
|
||||
errors, out = self.t.check()
|
||||
self.assertGreater(errors, 0)
|
||||
self.assertEqual(errors, 1, out)
|
||||
self.assertIn("divergent triples", out)
|
||||
self.assertIn("MV/PLA/MPLA", out)
|
||||
self.assertIn("MV/PETG/MPLA", out)
|
||||
# No grandfathering: sanctioning the tree does not silence check 4b.
|
||||
rc, _out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 1, out)
|
||||
self.assertIn("divergent triples", out)
|
||||
|
||||
def test_5_cross_bundle_divergence_is_warning_only(self):
|
||||
def test_3_cross_bundle_divergence_is_warning_only(self):
|
||||
fid = afi.generate_filament_id("BV", "PETG", "APLA")
|
||||
self.t.add_vendor("VendorB", [
|
||||
preset("APLA @base", filament_id=fid, instantiation=False,
|
||||
@@ -734,8 +648,6 @@ class TestCheck5(OfCleanTreeCase):
|
||||
preset("APLA @PB", inherits="APLA @base",
|
||||
compatible_printers=["PB 0.4 nozzle"]),
|
||||
])
|
||||
rc, _out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 0, out)
|
||||
self.assertIn("[WARNING]", out)
|
||||
@@ -743,7 +655,7 @@ class TestCheck5(OfCleanTreeCase):
|
||||
self.assertIn('"APLA"', out)
|
||||
|
||||
|
||||
class TestCheck6(OfCleanTreeCase):
|
||||
class TestCheck4(OfCleanTreeCase):
|
||||
def _write_map(self, rows):
|
||||
path = os.path.join(self.t.dir, "bambu_filament_ids.json")
|
||||
ubfi.write_map(path, rows, "testcommit", "2026-09-04")
|
||||
@@ -845,104 +757,6 @@ class TestCheck6(OfCleanTreeCase):
|
||||
self.assertIn('declares no "bambu_id"', out)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# --update-snapshot
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestUpdateSnapshot(SyntheticTreeCase):
|
||||
def test_idempotent_and_deterministic(self):
|
||||
with open(self.t.snapshot, "rb") as f:
|
||||
first = f.read()
|
||||
rc, out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertIn("nothing changed", out)
|
||||
with open(self.t.snapshot, "rb") as f:
|
||||
self.assertEqual(f.read(), first)
|
||||
self.assertTrue(first.endswith(b"\n"))
|
||||
self.assertNotIn(b"\r", first)
|
||||
snap = json.loads(first.decode("utf-8"))
|
||||
self.assertEqual(list(snap), ["ids"]) # state only, no exception lists
|
||||
self.assertEqual(list(snap["ids"]), sorted(snap["ids"]))
|
||||
self.assertEqual(snap["ids"]["AX01"], {
|
||||
"filaments": ["VendorA/APLA"], "name": "APLA",
|
||||
"filament_type": "PLA", "filament_vendor": "AVendor"})
|
||||
self.assertEqual(snap["ids"]["OGFL99"], {
|
||||
"filaments": ["OrcaFilamentLibrary/Generic PLA", "VendorA/Generic PLA"],
|
||||
"name": "Generic PLA", "filament_type": "PLA", "filament_vendor": "Generic"})
|
||||
# Key order is part of the on-disk format.
|
||||
self.assertEqual(list(snap["ids"]["AX01"]),
|
||||
["filaments", "name", "filament_type", "filament_vendor"])
|
||||
|
||||
def test_refuses_a_tree_it_could_not_read(self):
|
||||
# A bundle that does not parse contributes no ids, so sanctioning the
|
||||
# rest would record the loss as a deliberate removal.
|
||||
with open(self.t.snapshot, "rb") as f:
|
||||
before = f.read()
|
||||
with open(os.path.join(self.t.profiles, "VendorA",
|
||||
"filament", "APLA @base.json"), "w",
|
||||
encoding="utf-8") as f:
|
||||
f.write("{ not json")
|
||||
rc, out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 1, out)
|
||||
self.assertIn("unreadable filament profile", out)
|
||||
with open(self.t.snapshot, "rb") as f:
|
||||
self.assertEqual(f.read(), before)
|
||||
|
||||
def test_refuses_an_id_declared_under_two_triples(self):
|
||||
# VendorB re-declares APLA's id for a different product: one id, two
|
||||
# triples. No single entry can describe it, and check 3 rejects it anyway.
|
||||
self.t.add_vendor("VendorB", [
|
||||
preset("BPLA @base", filament_id="AX01", instantiation=False,
|
||||
filament_vendor="BVendor", filament_type="PLA"),
|
||||
preset("BPLA @P1", inherits="BPLA @base", compatible_printers=["P1"]),
|
||||
])
|
||||
with open(self.t.snapshot, "rb") as f:
|
||||
before = f.read()
|
||||
rc, out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 1)
|
||||
self.assertIn('refusing to sanction filament_id "AX01": declared under 2 triples '
|
||||
'(AVendor/PLA/APLA; BVendor/PLA/BPLA)', out)
|
||||
with open(self.t.snapshot, "rb") as f:
|
||||
self.assertEqual(f.read(), before) # nothing written on refusal
|
||||
|
||||
def test_records_an_id_it_cannot_defend_and_lets_check_reject_it(self):
|
||||
# update-snapshot records state, it does not judge ids: a foreign id
|
||||
# lands in the diff a maintainer reviews, and fails check 1 straight
|
||||
# after. Sanctioning it does not grandfather it.
|
||||
self.t.write_preset("VendorA", preset("CNEW @base", filament_id="GFX99",
|
||||
instantiation=False,
|
||||
filament_vendor="CV",
|
||||
filament_type="PLA"))
|
||||
self.t.write_preset("VendorA", preset("CNEW @P1", inherits="CNEW @base",
|
||||
compatible_printers=["P1"]))
|
||||
rc, out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0, out)
|
||||
with open(self.t.snapshot, encoding="utf-8") as f:
|
||||
self.assertIn("GFX99", json.load(f)["ids"])
|
||||
errors, out = self.t.check()
|
||||
self.assertGreater(errors, 0)
|
||||
self.assertIn('is not a minted "OF" id', out)
|
||||
|
||||
def test_dry_run_reports_without_writing(self):
|
||||
self.t.write_preset("VendorA", preset("ANEW @P2", inherits="APLA @base",
|
||||
compatible_printers=["P2"]))
|
||||
with open(self.t.snapshot, "rb") as f:
|
||||
before = f.read()
|
||||
rc, out = self.t.update_snapshot(dry_run=True)
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertIn("would be rewritten", out)
|
||||
self.assertIn("claims added : 1", out)
|
||||
with open(self.t.snapshot, "rb") as f:
|
||||
self.assertEqual(f.read(), before)
|
||||
# the real run writes exactly what the dry run reported
|
||||
rc, out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertIn("snapshot written", out)
|
||||
snap = load_json_file(self.t.snapshot)
|
||||
self.assertEqual(snap["ids"]["AX01"]["filaments"],
|
||||
["VendorA/ANEW", "VendorA/APLA"])
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# --generate: one rule for inserts and rewrites alike
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -1026,7 +840,7 @@ class TestAssign(OfCleanTreeCase):
|
||||
def test_parent_of_another_filament_never_receives_the_key(self):
|
||||
# Members whose id-less parent belongs to another filament (here one
|
||||
# parent shared by two filaments) carry the key themselves: the
|
||||
# parent's own triple would mint a different id (check 3).
|
||||
# parent's own triple would mint a different id (check 2).
|
||||
self.t.write_preset("VendorA", preset("shared_base", instantiation=False,
|
||||
filament_vendor="SV",
|
||||
filament_type="PLA"))
|
||||
@@ -1044,8 +858,6 @@ class TestAssign(OfCleanTreeCase):
|
||||
parent = load_json_file(self.t.preset_path("VendorA", "shared_base"))
|
||||
self.assertNotIn("filament_id", parent)
|
||||
# ... and the tree they leave behind passes the identity check.
|
||||
rc, _out = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 0, out)
|
||||
|
||||
@@ -1538,33 +1350,10 @@ class TestCli(unittest.TestCase):
|
||||
rc = afi.main([])
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertIn("usage:", buf.getvalue())
|
||||
for command in ("check", "generate-id", "normalize", "trim", "update-index",
|
||||
"update-snapshot"):
|
||||
for command in ("check", "generate-id", "normalize", "trim", "update-index"):
|
||||
self.assertIn(command, buf.getvalue())
|
||||
self.assertEqual(self.t.bytes_map(), before)
|
||||
|
||||
def test_another_tree_needs_its_own_snapshot(self):
|
||||
# --profiles retargets the tree, but the sanctioned state of that tree
|
||||
# is not the repo snapshot: checking against it is meaningless and
|
||||
# re-recording into it would overwrite the tracked file.
|
||||
with open(afi.SNAPSHOT_PATH, "rb") as f:
|
||||
repo_snapshot = f.read()
|
||||
for command in ("check", "update-snapshot"):
|
||||
with self.assertRaises(SystemExit) as caught:
|
||||
with contextlib.redirect_stderr(io.StringIO()):
|
||||
afi.main([command, "--profiles", self.t.profiles])
|
||||
self.assertEqual(caught.exception.code, 2, command)
|
||||
with open(afi.SNAPSHOT_PATH, "rb") as f:
|
||||
self.assertEqual(f.read(), repo_snapshot)
|
||||
# Named explicitly, both commands run against that tree.
|
||||
rc, out = self.t.cli("update-snapshot")
|
||||
self.assertEqual(rc, 0, out)
|
||||
# generate-id never reads the snapshot, so it keeps working without one.
|
||||
buf = io.StringIO()
|
||||
with contextlib.redirect_stdout(buf):
|
||||
rc = afi.main(["generate-id", "--dry-run", "--profiles", self.t.profiles])
|
||||
self.assertEqual(rc, 0, buf.getvalue())
|
||||
|
||||
def test_filament_id_and_setting_id_together_are_rejected(self):
|
||||
# Each flag's help promises it skips the other kind, so the pair cannot
|
||||
# quietly mean "both".
|
||||
@@ -1667,8 +1456,8 @@ class TestCli(unittest.TestCase):
|
||||
"setting_id", load_json_file(self.t.preset_path("VendorA", name)))
|
||||
|
||||
def test_check_returns_1_on_errors(self):
|
||||
# What CI keys off: check exits nonzero when the tree does not match the
|
||||
# snapshot it is validated against.
|
||||
# What CI keys off: check exits nonzero when the tree breaks a rule, here
|
||||
# the baseline's ids that are not minted.
|
||||
before = self.t.bytes_map()
|
||||
rc, out = self.t.cli("check")
|
||||
self.assertEqual(rc, 1)
|
||||
@@ -1695,12 +1484,12 @@ class TestCli(unittest.TestCase):
|
||||
["--check"], # the pre-subcommand flag
|
||||
["--update-snapshot"], # the pre-subcommand flag
|
||||
["nonsense"], # not a command
|
||||
["update-snapshot"], # removed command
|
||||
["generate-id", "--filament-id", "--setting-id"],
|
||||
["generate-id", "--snapshot", "x"], # check's option
|
||||
["check", "--materials"], # removed flag
|
||||
["check", "--obsolete-keys"], # removed flag
|
||||
["check", "--snapshot", "x"], # removed flag
|
||||
["check", "--filament-id"], # generate-id's option
|
||||
["normalize", "--snapshot", "x"], # not a snapshot command
|
||||
["normalize", "--profile-type", "nozzle"]): # not a profile type
|
||||
with self.subTest(argv=argv):
|
||||
with self.assertRaises(SystemExit) as cm, \
|
||||
@@ -1716,7 +1505,7 @@ class TestCli(unittest.TestCase):
|
||||
|
||||
@unittest.skipUnless(os.path.isdir(REAL_PROFILES), "resources/profiles not present")
|
||||
class TestRealTree(unittest.TestCase):
|
||||
def test_shipped_snapshot_matches_tree(self):
|
||||
def test_shipped_filament_ids_pass(self):
|
||||
buf = io.StringIO()
|
||||
with contextlib.redirect_stdout(buf):
|
||||
errors = afi.check_filament_ids(REAL_PROFILES)
|
||||
@@ -1778,10 +1567,10 @@ class TestReviewFixes(OfCleanTreeCase):
|
||||
self.assertIn(path, str(caught.exception))
|
||||
self.assertIn("test edit", str(caught.exception))
|
||||
|
||||
def test_check3_skips_of_id_inherited_from_other_vendor(self):
|
||||
def test_check2_accepts_an_of_id_inherited_from_another_vendor(self):
|
||||
# An OFL filament carries its own minted OF id and a vendor tunes it
|
||||
# correctly (same base name, non-empty printers). The new claim must
|
||||
# trip only the snapshot gate, never mint conformance.
|
||||
# correctly (same base name, non-empty printers): the id it inherits is
|
||||
# the mint of its own triple.
|
||||
fid = afi.generate_filament_id("Generic", "PLA", "Generic PLA Matte")
|
||||
self.t.write_preset(OFL, preset("Generic PLA Matte @base", filament_id=fid,
|
||||
instantiation=False,
|
||||
@@ -1790,22 +1579,13 @@ class TestReviewFixes(OfCleanTreeCase):
|
||||
self.t.write_preset(OFL, preset("Generic PLA Matte @System",
|
||||
inherits="Generic PLA Matte @base",
|
||||
compatible_printers=[]))
|
||||
rc, _ = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
self.t.write_preset("VendorA", preset("Generic PLA Matte @P1",
|
||||
inherits="Generic PLA Matte @System",
|
||||
compatible_printers=["P1 0.4 nozzle"]))
|
||||
errors, out = self.t.check()
|
||||
self.assertNotIn("does not match the mint", out)
|
||||
self.assertIn("not sanctioned", out)
|
||||
self.assertEqual(errors, 1, out)
|
||||
# After sanctioning the claim the tree is fully green again.
|
||||
rc, _ = self.t.update_snapshot()
|
||||
self.assertEqual(rc, 0)
|
||||
errors, out = self.t.check()
|
||||
self.assertEqual(errors, 0, out)
|
||||
|
||||
def test_check3c_prints_expected_mint(self):
|
||||
def test_check2c_prints_expected_mint(self):
|
||||
self.t.write_preset("VendorA", preset("Orphan PLA @P1",
|
||||
compatible_printers=["P1 0.4 nozzle"],
|
||||
filament_vendor="OV",
|
||||
|
||||
@@ -527,9 +527,7 @@ class TestCheck(TreeCase):
|
||||
# `check`, now that the per-vendor pass no longer skips it.
|
||||
self.t.write(apt.OFL, "filament/Stray.json",
|
||||
{"type": "filament", "name": "Stray"})
|
||||
snapshot = os.path.join(self.t.dir, "snapshot.json")
|
||||
self.run_command("update-snapshot", "--snapshot", snapshot)
|
||||
rc, out = self.run_command("check", "--snapshot", snapshot)
|
||||
rc, out = self.run_command("check")
|
||||
self.assertEqual(rc, 1, out)
|
||||
self.assertIn(f"{apt.OFL}/filament/Stray.json: no {apt.OFL}.json list "
|
||||
f"references it", out)
|
||||
@@ -598,9 +596,7 @@ class TestCheck(TreeCase):
|
||||
self.t.write("V", "filament/A.json", {
|
||||
"type": "filament", "name": "A", "silent_mode": "0"})
|
||||
self.run_command("update-index")
|
||||
snapshot = os.path.join(self.t.dir, "snapshot.json")
|
||||
self.run_command("update-snapshot", "--snapshot", snapshot)
|
||||
rc, out = self.run_command("check", "--snapshot", snapshot)
|
||||
rc, out = self.run_command("check")
|
||||
self.assertEqual(rc, 1, out) # normalization also rejects the obsolete key
|
||||
self.assertIn("Obsolete key: 'silent_mode' found in V/filament/A.json", out)
|
||||
self.assertIn("Files with warnings : 1", out)
|
||||
@@ -623,9 +619,7 @@ class TestCheck(TreeCase):
|
||||
"type": "machine", "name": "M 0.4 nozzle",
|
||||
"default_filament_profile": ["A", "Nope"]})
|
||||
self.t.index("V", "machine", "M 0.4 nozzle", "machine/M.json")
|
||||
snapshot = os.path.join(self.t.dir, "snapshot.json")
|
||||
self.run_command("update-snapshot", "--snapshot", snapshot)
|
||||
rc, out = self.run_command("check", "--snapshot", snapshot)
|
||||
rc, out = self.run_command("check")
|
||||
self.assertEqual(rc, 1, out)
|
||||
self.assertIn("Missing filament profile: 'Nope'", out)
|
||||
|
||||
@@ -635,9 +629,7 @@ class TestCheck(TreeCase):
|
||||
self.bundle()
|
||||
for sub in apt.PROFILE_SUBDIRS:
|
||||
os.makedirs(os.path.join(self.t.profiles, apt.USER_DIR, "default", sub))
|
||||
snapshot = os.path.join(self.t.dir, "snapshot.json")
|
||||
self.run_command("update-snapshot", "--snapshot", snapshot)
|
||||
_rc, out = self.run_command("check", "--snapshot", snapshot)
|
||||
_rc, out = self.run_command("check")
|
||||
self.assertIn("Checked vendors : 1", out)
|
||||
self.assertNotIn("user", out)
|
||||
|
||||
@@ -743,9 +735,7 @@ class TestCheck(TreeCase):
|
||||
self.t.write("V", f"filament/Stray{n}.json",
|
||||
{"type": "filament", "name": f"Stray{n}"})
|
||||
self.t.write("V", "filament/NoType.json", {"name": "NoType"})
|
||||
snapshot = os.path.join(self.t.dir, "snapshot.json")
|
||||
self.run_command("update-snapshot", "--snapshot", snapshot)
|
||||
rc, out = self.run_command("check", "--snapshot", snapshot)
|
||||
rc, out = self.run_command("check")
|
||||
self.assertEqual(rc, 1, out)
|
||||
self.assertEqual(out.count("update-index\" to add them"), 1, out)
|
||||
self.assertEqual(out.count("or delete them"), 1, out)
|
||||
@@ -797,11 +787,6 @@ class TestNormalized(TreeCase):
|
||||
errors, gaps = apt.check_normalized(self.t.profiles, vendor)
|
||||
return errors, gaps, buf.getvalue()
|
||||
|
||||
def snapshot(self):
|
||||
path = os.path.join(self.t.dir, "snapshot.json")
|
||||
self.run_command("update-snapshot", "--snapshot", path)
|
||||
return path
|
||||
|
||||
def test_a_bundle_the_two_commands_just_wrote_reports_nothing(self):
|
||||
self.t.write("V", "filament/A.json", {"type": "filament", "name": "A"})
|
||||
self.t.write("V", "process/B.json", {"type": "process", "name": "B"})
|
||||
@@ -862,7 +847,7 @@ class TestNormalized(TreeCase):
|
||||
# library included.
|
||||
self.t.write(apt.OFL, "filament/A.json",
|
||||
{"type": "filament", "name": "A", "version": "01.00.00.00"})
|
||||
rc, out = self.run_command("check", "--snapshot", self.snapshot())
|
||||
rc, out = self.run_command("check")
|
||||
self.assertEqual(rc, 1, out)
|
||||
self.assertIn(f"{apt.OFL}/filament/A.json: normalize would remove version", out)
|
||||
|
||||
@@ -872,7 +857,7 @@ class TestNormalized(TreeCase):
|
||||
{"type": "filament", "name": f"A{n}",
|
||||
"version": "01.00.00.00"})
|
||||
self.t.write("W", "filament/B.json", {"type": "filament", "name": "B"})
|
||||
rc, out = self.run_command("check", "--snapshot", self.snapshot())
|
||||
rc, out = self.run_command("check")
|
||||
self.assertEqual(rc, 1, out)
|
||||
self.assertIn("3 profile file(s) above are not what", out)
|
||||
self.assertEqual(out.count('normalize" writes: run it and commit'), 1, out)
|
||||
@@ -898,8 +883,7 @@ class TestDispatch(TreeCase):
|
||||
def test_an_option_belongs_to_one_command_only(self):
|
||||
for argv in (["trim", "--force"],
|
||||
["update-index", "--filament-id"],
|
||||
["check", "--profile-type", "filament"],
|
||||
["update-snapshot", "--vendor", "V"]):
|
||||
["check", "--profile-type", "filament"]):
|
||||
with self.subTest(argv=argv):
|
||||
with self.assertRaises(SystemExit) as cm, \
|
||||
contextlib.redirect_stdout(io.StringIO()), \
|
||||
|
||||
Reference in New Issue
Block a user