using System.Text; using PrepStep; namespace PrepStep.Tests; public class AsmRecordReaderTests { private static string ScriviFile(byte[] dati) { string percorso = Path.GetTempFileName(); File.WriteAllBytes(percorso, dati); return percorso; } private static byte[] Bytes(params object[] parti) { using var ms = new MemoryStream(); foreach (var p in parti) { switch (p) { case byte b: ms.WriteByte(b); break; case int i: ms.WriteByte((byte)i); break; case string s: var buf = Encoding.Latin1.GetBytes(s); ms.Write(buf); break; case byte[] arr: ms.Write(arr); break; default: throw new ArgumentException($"tipo non gestito: {p.GetType()}"); } } return ms.ToArray(); } // \xe3 id type name\0 family_name\0 - id/type come singolo byte (0x00-0x7f, un byte) private static byte[] Membro(int id, int tipo, string nome, string? nomeFamiglia = null) => Bytes(0xe3, id, tipo, nome, 0, nomeFamiglia ?? nome, 0); // \xe3 ConfigName\0 \xe23 \0 \x82\x7b private static byte[] ConfigName(string istanza, int idMembro) => Bytes(0xe3, "ConfigName", 0, 0xe2, (byte)'3', istanza, 0, idMembro, 0x82, 0x7b); private static byte[] Asm(int conteggioDichiarato, byte[] membri, byte[]? extra = null) => Bytes("header ", extra ?? Array.Empty(), "\0members\0", 0xf1, 0xf8, conteggioDichiarato, membri, "#MdlStatus"); [Fact] public void Legge_membri_semplici_senza_family_table() { byte[] dati = Asm(2, Bytes( Membro(1, 2, "9244206098"), Membro(2, 1, "9244228558_C01"))); var membri = AsmRecordReader.LeggiMembri(dati); Assert.Equal(2, membri.Count); Assert.Equal((1, 2, "9244206098", "9244206098"), (membri[0].Id, membri[0].Tipo, membri[0].Nome, membri[0].Generico)); Assert.Equal((2, 1, "9244228558_C01", "9244228558_C01"), (membri[1].Id, membri[1].Tipo, membri[1].Nome, membri[1].Generico)); } [Fact] public void Risolve_il_nome_istanza_di_una_family_table_da_ConfigName() { // Il membro cita solo il generico (UNI6592); il nome che la BOM mostra // (R8--_-_U6592) sta nel parametro ConfigName, collegato per id membro. byte[] dati = Asm(1, Membro(1, 2, "UNI6592"), extra: ConfigName("R8--_-_U6592", idMembro: 1)); var membri = AsmRecordReader.LeggiMembri(dati); Assert.Single(membri); Assert.Equal("R8--_-_U6592", membri[0].Nome); Assert.Equal("UNI6592", membri[0].Generico); } [Fact] public void Rimuove_il_suffisso_array_feature_dal_nome_istanza() { // Regressione: caso reale 9244250136.asm -> ConfigName con suffisso "#AF" per // componenti creati da un pattern/array feature (id 45 e id 78, entrambi variante // "_C03" con suffisso diverso "#AF0"/"#AF1"): non un'istanza di family table vera, // la BOM di Creo mostra il codice pulito e unisce le occorrenze duplicate. byte[] dati = Asm(2, Bytes( Membro(45, 2, "9244250136_C03"), Membro(78, 2, "9244250136_C03")), extra: Bytes( ConfigName("9244250136_C03#AF0", idMembro: 45), ConfigName("9244250136_C03#AF1", idMembro: 78))); var membri = AsmRecordReader.LeggiMembri(dati); Assert.All(membri, m => Assert.Equal("9244250136_C03", m.Nome)); } [Fact] public void Componente_senza_ConfigName_non_e_toccato_da_istanze_di_altri_membri() { byte[] dati = Asm(2, Bytes( Membro(1, 2, "UNI6592"), Membro(2, 2, "9244206098")), extra: ConfigName("R8--_-_U6592", idMembro: 1)); var membri = AsmRecordReader.LeggiMembri(dati); Assert.Equal("R8--_-_U6592", membri.Single(m => m.Id == 1).Nome); Assert.Equal("9244206098", membri.Single(m => m.Id == 2).Nome); } [Fact] public void Conteggio_dichiarato_diverso_da_quello_letto_lancia_eccezione() { // Dichiara 2 membri ma ne fornisce solo 1: lettura disallineata, va segnalata invece // di restituire un risultato parziale silenzioso. byte[] dati = Asm(2, Membro(1, 2, "9244206098")); Assert.Throws(() => AsmRecordReader.LeggiMembri(dati)); } [Fact] public void Assieme_family_table_con_generic_name_lancia_eccezione() { // Regressione: 2546220101.asm (docs/formato-asm-record.md ยง7) e' esso stesso // un'istanza di family table (model_name "2546220101_C01" + generic_name vuoto); il // suo "members" descrive solo questa rappresentazione, non le 4 istanze reali della // BOM Creo. Va segnalato invece di restituire silenziosamente il contenuto sbagliato. byte[] dati = Asm(1, Membro(1, 2, "9244206098"), extra: Bytes(0xe0, 0x01, "model_t_type", 0, 2, 0xe0, 0x0a, "model_name", 0, "2546220101_C01", 0, 0xe0, 0x0a, "generic_name", 0, 0)); Assert.Throws(() => AsmRecordReader.LeggiMembri(dati)); } [Fact] public void Generic_name_senza_suffisso_istanza_non_lancia_eccezione() { // Regressione: su ~7% dei file reali della share compare un record "model_name" + // "generic_name" (vuoto) che NON e' il sintomo della family table di assiemi - il // valore non ha il suffisso "_C0n" delle istanze (es. e' un codice qualunque non // correlato al nome del file). In quei casi "members" e' letto correttamente e va // usato normalmente, non bloccato per un falso positivo. byte[] dati = Asm(1, Membro(1, 2, "9244206098"), extra: Bytes(0xe0, 0x01, "model_t_type", 0, 2, 0xe0, 0x0a, "model_name", 0, "9244230117", 0, 0xe0, 0x0a, "generic_name", 0, 0)); var membri = AsmRecordReader.LeggiMembri(dati); Assert.Single(membri); } [Fact] public void Sezione_members_assente_lancia_eccezione() { byte[] dati = Encoding.Latin1.GetBytes("un file qualunque senza la sezione attesa"); Assert.Throws(() => AsmRecordReader.LeggiMembri(dati)); } [Fact] public void EstraiCodiciReferenziati_restituisce_i_generici_distinti() { byte[] dati = Asm(3, Bytes( Membro(1, 2, "9144210327"), Membro(2, 2, "9144210327"), // stesso codice montato due volte: un solo codice distinto Membro(3, 1, "9244238563_D100"))); string percorso = ScriviFile(dati); try { var codici = AsmRecordReader.EstraiCodiciReferenziati(percorso); Assert.Equal(new[] { "9144210327", "9244238563_D100" }, codici.OrderBy(c => c)); } finally { File.Delete(percorso); } } [Fact] public void File_inesistente_lancia_eccezione_IO() => Assert.Throws(() => AsmRecordReader.EstraiCodiciReferenziati(@"C:\non\esiste\pippo.asm.1")); }