216 lines
6.7 KiB
ObjectPascal
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.
|