"""Test di regressione sui file campione presenti nella cartella. Il parser si appoggia alla struttura binaria del formato Creo: qualsiasi modifica alle espressioni di ricerca va verificata contro file reali. Il test si limita ai file effettivamente presenti, quindi resta eseguibile anche su un archivio diverso. python3 -m unittest test_regression -v """ import glob import os import unittest from creoparams.extract import (extract, latest_versions, read_preview, split_filename) HERE = os.path.dirname(os.path.abspath(__file__)) #: Parametri che ci aspettiamo di leggere da ogni .prt aziendale. REQUIRED = ["CODICE", "DENOMINAZIONE", "REVISIONE", "STAMPIGLIATURA", "MATERIALE", "DISEGNATORE", "DATA"] #: Valori verificati manualmente. Vanno estesi man mano che si confrontano #: altri file con Creo: e' questa la rete di sicurezza vera. KNOWN = { "9258400201.prt": { "CODICE": "9258400201", "DENOMINAZIONE": "Carter lato destro", "MATERIALE": "ACC.INOX LAMIERA SP.4", "DISEGNATORE": "Grilli", "DATA": "9/02/2026", }, "9258400205.prt": { "CODICE": "9258400205", "DENOMINAZIONE": "Tirante carter lateriali", "MATERIALE": "AISI 304 TONDO D.12", }, } def _parts(): paths = latest_versions(glob.glob(os.path.join(HERE, "*.prt*"))) return [p for p in paths if split_filename(os.path.basename(p))[1] == "prt"] class TestEstrazione(unittest.TestCase): @classmethod def setUpClass(cls): cls.parts = _parts() if not cls.parts: raise unittest.SkipTest("nessun file .prt nella cartella") cls.records = { os.path.basename(p): extract(p) for p in cls.parts } def test_header_riconosciuto(self): for name, record in self.records.items(): with self.subTest(file=name): self.assertEqual(record["model"]["kind"], "PART") self.assertTrue(record["model"]["creo_version"]) self.assertGreater(record["extraction"]["sections"], 0) def test_parametri_obbligatori_presenti(self): for name, record in self.records.items(): for param in REQUIRED: with self.subTest(file=name, param=param): entry = record["parameters"].get(param) self.assertIsNotNone(entry, f"{param} non trovato") self.assertEqual(entry["status"], "found") def test_confidenza_alta(self): """Ogni parametro obbligatorio deve essere confermato in due rappresentazioni indipendenti del file.""" for name, record in self.records.items(): for param in REQUIRED: with self.subTest(file=name, param=param): self.assertGreaterEqual( record["parameters"][param]["confidence"], 0.95 ) def test_valori_noti(self): for stem, expected in KNOWN.items(): matching = [ r for r in self.records.values() if r["model"]["file_name"] == stem ] if not matching: continue record = matching[0] for param, value in expected.items(): with self.subTest(file=stem, param=param): self.assertEqual(record["parameters"][param]["value"], value) def test_codice_coerente_col_nome_file(self): for name, record in self.records.items(): with self.subTest(file=name): self.assertEqual( record["parameters"]["CODICE"]["value"], record["model"]["name"], ) def test_anteprima_presente_e_valida(self): for path in self.parts: name = os.path.basename(path) with self.subTest(file=name): shot = read_preview(path) self.assertIsNotNone(shot, "anteprima non trovata") self.assertTrue(shot.data.startswith(b"\xff\xd8\xff")) self.assertTrue(shot.data.endswith(b"\xff\xd9")) self.assertGreater(shot.width, 0) self.assertGreater(shot.height, 0) # Le dimensioni dichiarate devono corrispondere al record. declared = self.records[name]["preview"] self.assertTrue(declared["present"]) self.assertEqual(declared["width"], shot.width) def test_una_sola_anteprima_per_file(self): """Se il rilevatore trovasse piu' candidati, starebbe scambiando dati binari qualsiasi per immagini.""" from creoparams import preview for path in self.parts: with self.subTest(file=os.path.basename(path)): with open(path, "rb") as handle: self.assertEqual(len(preview.find_all(handle.read())), 1) def test_parametri_feature_separati(self): """I parametri dei fori non devono finire tra quelli del modello.""" for name, record in self.records.items(): for param, entry in record["parameters"].items(): if param in ("SCREW_SIZE", "DRILL_DIAMETER", "CSINK_ANGLE"): with self.subTest(file=name, param=param): self.assertEqual(entry["owner"], "feature") class TestDisegni(unittest.TestCase): """I .drw non hanno parametri di modello ma contengono la tavola.""" @classmethod def setUpClass(cls): paths = latest_versions(glob.glob(os.path.join(HERE, "*.drw*"))) cls.drawings = [p for p in paths if split_filename(os.path.basename(p))[1] == "drw"] if not cls.drawings: raise unittest.SkipTest("nessun file .drw nella cartella") cls.records = {os.path.basename(p): extract(p) for p in cls.drawings} def test_riconosciuti_come_disegni(self): for name, record in self.records.items(): with self.subTest(file=name): self.assertEqual(record["model"]["kind"], "DRAWING") self.assertFalse(record["extraction"]["holds_model_parameters"]) def test_testi_estratti(self): for name, record in self.records.items(): with self.subTest(file=name): sheet = record["drawing"] self.assertGreater(sheet["decompressed_bytes"], 10_000) self.assertGreater(len(sheet["texts"]), 20) def test_voci_del_cartiglio(self): """Il cartiglio aziendale deve comparire in ogni tavola.""" attese = {"CODICE", "DISEGNATO", "STAMPIGLIATURA", "SCALA"} for name, record in self.records.items(): with self.subTest(file=name): testi = set(record["drawing"]["texts"]) self.assertTrue(attese.issubset(testi), f"mancano {attese - testi}") def test_primitive_grafiche(self): for name, record in self.records.items(): with self.subTest(file=name): prims = record["drawing"]["primitives"] self.assertIn("prim_text", prims) self.assertIn("prim_line", prims) class TestMesh(unittest.TestCase): """La forma arriva da una mesh esportata: il .prt non la espone.""" @classmethod def setUpClass(cls): from creoparams.extract import find_mesh cls.pairs = [(p, find_mesh(p)) for p in _parts()] cls.pairs = [(p, m) for p, m in cls.pairs if m] if not cls.pairs: raise unittest.SkipTest("nessun .stl accanto ai modelli") def test_lettura_stl(self): from creoparams.mesh import read_stl for part, stl in self.pairs: with self.subTest(file=os.path.basename(stl)): mesh = read_stl(stl) self.assertGreater(len(mesh), 0) self.assertTrue(all(len(t) == 3 for t in mesh.triangles)) self.assertTrue(all(len(v) == 3 for t in mesh.triangles for v in t)) def test_ingombro_positivo(self): from creoparams.mesh import read_stl for part, stl in self.pairs: with self.subTest(file=os.path.basename(stl)): mesh = read_stl(stl) self.assertTrue(all(d > 0 for d in mesh.size)) self.assertGreater(mesh.volume(), 0) self.assertGreater(mesh.area(), 0) def test_geometria_nel_record(self): for part, stl in self.pairs: with self.subTest(file=os.path.basename(part)): geometry = extract(part).get("geometry") self.assertIsNotNone(geometry) self.assertEqual(geometry["triangles"], len(read_stl_cached(stl))) def test_pagina_html_autonoma(self): """La pagina deve funzionare offline: nessun riferimento esterno.""" from creoparams import viewer from creoparams.mesh import read_stl mesh = read_stl(self.pairs[0][1]) page = viewer.build(mesh, "prova", viewer.describe(mesh)) self.assertTrue(page.startswith("")) self.assertIn("", page) for marker in ('src="http', "href=\"http", "@import"): self.assertNotIn(marker, page, "la pagina carica risorse esterne") # la mesh deve essere incorporata, non referenziata self.assertIn("const V = [[", page) self.assertIn("], F = [[", page) def test_topologia_spigoli(self): """Gli spigoli servono a rendere leggibile la forma: senza facce adiacenti non si distinguono quelli vivi dalla tassellatura.""" from creoparams.mesh import read_stl for part, stl in self.pairs: with self.subTest(file=os.path.basename(stl)): mesh = read_stl(stl) vertices, faces, edges = mesh.indexed() self.assertLess(len(vertices), len(mesh) * 3, "i vertici ripetuti non sono stati fusi") self.assertGreater(len(edges), len(faces)) # ogni spigolo appartiene ad almeno una faccia valida for a, b, f0, f1 in edges: self.assertNotEqual(a, b) self.assertTrue(0 <= f0 < len(faces)) def read_stl_cached(path): from creoparams.mesh import read_stl return read_stl(path) class TestLzw(unittest.TestCase): def test_round_trip_noto(self): """Verifica il decoder su uno stream prodotto da compress(1), se disponibile nel sistema.""" import shutil import subprocess from creoparams import lzw tool = shutil.which("compress") or shutil.which("gzip") if not tool: self.skipTest("nessun compressore disponibile") if "gzip" in tool: self.skipTest("gzip non produce il formato .Z") payload = b"PGL " * 500 + bytes(range(256)) * 4 proc = subprocess.run([tool, "-c"], input=payload, capture_output=True) out, _ = lzw.decompress(proc.stdout) self.assertEqual(out, payload) if __name__ == "__main__": unittest.main()