Files
MycLib/Test/Test.Myc.Ast.Integration.pas
2025-12-26 13:47:10 +01:00

216 lines
6.7 KiB
ObjectPascal

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<string>;
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<string>;
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.