unit Test.Myc.Ast.Integration; interface uses System.SysUtils, System.Classes, System.IOUtils, System.TypInfo, System.Generics.Collections, DUnitX.TestFramework, // Core Myc.Data.Value, Myc.Ast.Environment, Myc.Ast.Script, Myc.Ast.Nodes; // For Exception handling type [TestFixture] TTestIntegration = class private const TEST_DIR = 'T:\Myc\Test\Scripts'; procedure RunSingleScript(const FileName: string); function ParseKind(const KindStr: string): TDataValueKind; public [Setup] procedure Setup; [Test] [IgnoreMemoryLeaks] procedure RunAllFileBasedTests; end; implementation { TTestIntegration } procedure TTestIntegration.Setup; begin // Ensure the directory exists to avoid immediate crash if not TDirectory.Exists(TEST_DIR) then ForceDirectories(TEST_DIR); end; function TTestIntegration.ParseKind(const KindStr: string): TDataValueKind; var I: Integer; begin // Uses RTTI/TypInfo to convert string 'vkScalar' to enum TDataValueKind.vkScalar I := GetEnumValue(TypeInfo(TDataValueKind), KindStr); if I < 0 then raise Exception.CreateFmt('Unknown TDataValueKind in test header: %s', [KindStr]); Result := TDataValueKind(I); end; procedure TTestIntegration.RunSingleScript(const FileName: string); var Lines: TArray; ScriptSource: TStringBuilder; ExpectedOutput, ExpectedSignature: string; // <-- Neu ExpectedTypeStr: string; ExpectedType: TDataValueKind; HasExpectation: Boolean; Line: string; Env: TAstEnvironment; ResultValue: TDataValue; // AST Variablen für die Analyse RootNode, BoundNode, SpecializedNode: IAstNode; ActualSignature: string; begin Lines := TFile.ReadAllLines(FileName); ScriptSource := TStringBuilder.Create; try ExpectedOutput := ''; ExpectedSignature := ''; // <-- Init ExpectedTypeStr := ''; ExpectedType := TDataValueKind.vkVoid; HasExpectation := False; // 1. Parsing (angepasst für SIGNATURE) for Line in Lines do begin if Line.Trim.StartsWith(';; EXPECT:') then begin ExpectedOutput := Line.Substring(10).Trim; HasExpectation := True; end else if Line.Trim.StartsWith(';; TYPE:') then begin ExpectedTypeStr := Line.Substring(8).Trim; ExpectedType := ParseKind(ExpectedTypeStr); end else if Line.Trim.StartsWith(';; SIGNATURE:') then // <-- Neu begin ExpectedSignature := Line.Substring(13).Trim; end else begin ScriptSource.AppendLine(Line); end; end; if not HasExpectation then Assert.Fail('Test file missing ";; EXPECT:" tag: ' + FileName); // 2. Environment Setup Env := TAstEnvironment.Construct(nil); Env.SetStandardMode; // 3. Parse AST RootNode := TAstScript.Parse(ScriptSource.ToString); // --- SIGNATURE CHECK START --- if ExpectedSignature <> '' then begin try // Wir simulieren die Pipeline bis zum Type-Check // Hinweis: Env.Bind führt intern oft schon Namensauflösung und TypeCheck durch. // Env.Specialize löst generische Aufrufe auf und finalisiert Typen. // A. Bind (Namensauflösung & TypeCheck) // Wir übergeben einen Dummy-Log, oder nil wenn erlaubt BoundNode := Env.Bind(RootNode, [], nil); // B. Specialize (Optimierung & finale Typisierung) SpecializedNode := Env.Specialize(BoundNode); // C. Typ extrahieren if SpecializedNode.IsTyped then begin ActualSignature := SpecializedNode.AsTypedNode.StaticType.ToString; Assert .AreEqual(ExpectedSignature, ActualSignature, Format('File: %s - Signature Mismatch', [ExtractFileName(FileName)])); end else begin Assert.Fail(Format('File: %s - Root node is not typed, cannot check signature.', [ExtractFileName(FileName)])); end; except on E: Exception do begin Assert.Fail(Format('File: %s - Compilation/TypeCheck failed: %s', [ExtractFileName(FileName), E.Message])); end; end; end; // --- SIGNATURE CHECK END --- try // 4. Run Execution (Value Check) // Wir nutzen hier wieder RootNode (oder SpecializedNode, falls Env.Run damit umgehen kann), // um sicherzustellen, dass die Ausführung sauber durchläuft. ResultValue := Env.Run(RootNode); // 5. Verify Type if ExpectedTypeStr <> '' then begin Assert.AreEqual(ExpectedType, ResultValue.Kind, Format('File: %s - Result Type Mismatch', [ExtractFileName(FileName)])); end; // 6. Verify Output Value Assert.AreEqual(ExpectedOutput, ResultValue.ToString, Format('File: %s - Result Value Mismatch', [ExtractFileName(FileName)])); except on E: EAssertionFailed do raise; on E: Exception do Assert.Fail(Format('File: %s - Exception during execution: %s', [ExtractFileName(FileName), E.Message])); end; finally ScriptSource.Free; end; end; procedure TTestIntegration.RunAllFileBasedTests; var Files: TArray; FileName: string; Failures: TStringBuilder; begin Files := TDirectory.GetFiles(TEST_DIR, '*.txt'); if Length(Files) = 0 then Exit; // Silent exit if no files found (success, effectively) Failures := TStringBuilder.Create; try for FileName in Files do begin try RunSingleScript(FileName); except on E: Exception do begin Failures.AppendLine(Format('[FAIL] %s: %s', [ExtractFileName(FileName), E.Message])); end; end; end; // Only fail if errors were collected if Failures.Length > 0 then Assert.Fail('One or more integration scripts failed:' + SLineBreak + Failures.ToString); finally Failures.Free; end; end; end.