commit initial
This commit is contained in:
238
tests/TinyCC.E2ETests/E2ETestRunner.cs
Normal file
238
tests/TinyCC.E2ETests/E2ETestRunner.cs
Normal file
@@ -0,0 +1,238 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using TinyCC.Core;
|
||||
|
||||
namespace TinyCC.E2ETests;
|
||||
|
||||
/// <summary>
|
||||
/// 端到端测试运行器
|
||||
/// 负责编译 C 源代码并执行生成的 ELF 文件
|
||||
/// </summary>
|
||||
public sealed class E2ETestRunner
|
||||
{
|
||||
private readonly string _tempDirectory;
|
||||
|
||||
public E2ETestRunner(string? tempDirectory = null)
|
||||
{
|
||||
_tempDirectory = tempDirectory ?? "/tmp/tinycc-debug"; // 固定路径便于调试
|
||||
Directory.CreateDirectory(_tempDirectory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 运行单个测试用例
|
||||
/// </summary>
|
||||
public async Task<TestResult> RunTestAsync(TestCase testCase)
|
||||
{
|
||||
var sourceFile = Path.Combine(_tempDirectory, $"{testCase.Name}.c");
|
||||
var outputFile = Path.Combine(_tempDirectory, $"{testCase.Name}.out");
|
||||
|
||||
try
|
||||
{
|
||||
// 写入源代码
|
||||
await File.WriteAllTextAsync(sourceFile, testCase.SourceCode);
|
||||
|
||||
// 编译
|
||||
var compileResult = Compile(sourceFile, outputFile);
|
||||
if (!compileResult.Success)
|
||||
{
|
||||
return new TestResult(
|
||||
testCase.Name,
|
||||
false,
|
||||
-1,
|
||||
null,
|
||||
$"编译失败: {compileResult.Error}"
|
||||
);
|
||||
}
|
||||
|
||||
// 执行
|
||||
var executeResult = await ExecuteAsync(outputFile);
|
||||
|
||||
// 调试:如果失败,打印 objdump
|
||||
if (executeResult.ExitCode != testCase.ExpectedExitCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
var psi = new ProcessStartInfo("objdump", $"-d -M intel \"{outputFile}\"")
|
||||
{
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false
|
||||
};
|
||||
using var p = Process.Start(psi)!;
|
||||
var dump = await p.StandardOutput.ReadToEndAsync();
|
||||
Console.WriteLine($"[DEBUG] objdump:\n{dump}");
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
// 验证结果
|
||||
bool passed = executeResult.ExitCode == testCase.ExpectedExitCode;
|
||||
if (testCase.ExpectedOutput != null && executeResult.Output != testCase.ExpectedOutput)
|
||||
{
|
||||
passed = false;
|
||||
}
|
||||
|
||||
return new TestResult(
|
||||
testCase.Name,
|
||||
passed,
|
||||
executeResult.ExitCode,
|
||||
executeResult.Output,
|
||||
passed ? null : $"期望退出码 {testCase.ExpectedExitCode},实际 {executeResult.ExitCode}"
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new TestResult(
|
||||
testCase.Name,
|
||||
false,
|
||||
-1,
|
||||
null,
|
||||
$"测试执行异常: {ex.Message}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private CompilationResult Compile(string sourceFile, string outputFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
var errorReporter = new ErrorReporter();
|
||||
var driver = new CompilerDriver(errorReporter);
|
||||
|
||||
var options = new CompilationOptions(
|
||||
SourceFile: sourceFile,
|
||||
OutputFile: outputFile,
|
||||
Platform: TargetPlatform.LinuxX64
|
||||
);
|
||||
|
||||
var result = driver.Compile(options);
|
||||
|
||||
if (errorReporter.HasErrors)
|
||||
{
|
||||
var errors = string.Join("\n", errorReporter.GetErrors().Select(e => e.ToString()));
|
||||
return new CompilationResult(false, errors);
|
||||
}
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return new CompilationResult(false, result.Message);
|
||||
}
|
||||
|
||||
return new CompilationResult(true, null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new CompilationResult(false, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<ExecutionResult> ExecuteAsync(string executablePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 设置执行权限
|
||||
var chmod = Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "chmod",
|
||||
Arguments = $"+x \"{executablePath}\"",
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false
|
||||
});
|
||||
|
||||
if (chmod != null)
|
||||
{
|
||||
await chmod.WaitForExitAsync();
|
||||
}
|
||||
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = executablePath,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using var process = Process.Start(psi)
|
||||
?? throw new InvalidOperationException($"无法启动进程: {executablePath}");
|
||||
|
||||
var outputBuilder = new StringBuilder();
|
||||
var errorBuilder = new StringBuilder();
|
||||
|
||||
process.OutputDataReceived += (sender, e) =>
|
||||
{
|
||||
if (e.Data != null)
|
||||
{
|
||||
outputBuilder.AppendLine(e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
process.ErrorDataReceived += (sender, e) =>
|
||||
{
|
||||
if (e.Data != null)
|
||||
{
|
||||
errorBuilder.AppendLine(e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
|
||||
await process.WaitForExitAsync();
|
||||
|
||||
var output = outputBuilder.ToString().TrimEnd();
|
||||
var error = errorBuilder.ToString().TrimEnd();
|
||||
|
||||
return new ExecutionResult(
|
||||
process.ExitCode,
|
||||
string.IsNullOrEmpty(output) ? error : output
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new ExecutionResult(-1, $"执行失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Cleanup()
|
||||
{
|
||||
// 调试期间保留文件
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试用例
|
||||
/// </summary>
|
||||
public record TestCase(
|
||||
string Name,
|
||||
string SourceCode,
|
||||
int ExpectedExitCode,
|
||||
string? ExpectedOutput = null
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// 测试结果
|
||||
/// </summary>
|
||||
public record TestResult(
|
||||
string TestCaseName,
|
||||
bool Passed,
|
||||
int ActualExitCode,
|
||||
string? ActualOutput,
|
||||
string? ErrorMessage
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// 编译结果
|
||||
/// </summary>
|
||||
public record CompilationResult(
|
||||
bool Success,
|
||||
string? Error
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// 执行结果
|
||||
/// </summary>
|
||||
public record ExecutionResult(
|
||||
int ExitCode,
|
||||
string Output
|
||||
);
|
||||
58
tests/TinyCC.E2ETests/E2ETests.cs
Normal file
58
tests/TinyCC.E2ETests/E2ETests.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace TinyCC.E2ETests;
|
||||
|
||||
public class E2ETests : IAsyncLifetime
|
||||
{
|
||||
private readonly ITestOutputHelper _output;
|
||||
private readonly E2ETestRunner _runner;
|
||||
|
||||
public E2ETests(ITestOutputHelper output)
|
||||
{
|
||||
_output = output;
|
||||
_runner = new E2ETestRunner();
|
||||
}
|
||||
|
||||
public Task InitializeAsync() => Task.CompletedTask;
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
_runner.Cleanup();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(GetBasicTestCases))]
|
||||
public async Task BasicFeature_ShouldCompileAndRun(TestCase testCase)
|
||||
{
|
||||
// Act
|
||||
var result = await _runner.RunTestAsync(testCase);
|
||||
|
||||
// Assert
|
||||
_output.WriteLine($"测试: {testCase.Name}");
|
||||
_output.WriteLine($"源代码:\n{testCase.SourceCode}");
|
||||
_output.WriteLine($"结果: {(result.Passed ? "通过" : "失败")}");
|
||||
if (!result.Passed)
|
||||
{
|
||||
_output.WriteLine($"错误: {result.ErrorMessage}");
|
||||
_output.WriteLine($"实际退出码: {result.ActualExitCode}");
|
||||
if (result.ActualOutput != null)
|
||||
{
|
||||
_output.WriteLine($"实际输出: {result.ActualOutput}");
|
||||
}
|
||||
}
|
||||
|
||||
Assert.True(result.Passed, result.ErrorMessage);
|
||||
}
|
||||
|
||||
public static TheoryData<TestCase> GetBasicTestCases()
|
||||
{
|
||||
var data = new TheoryData<TestCase>();
|
||||
foreach (var testCase in TestCases.GetBasicTests())
|
||||
{
|
||||
data.Add(testCase);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
231
tests/TinyCC.E2ETests/TestCases.cs
Normal file
231
tests/TinyCC.E2ETests/TestCases.cs
Normal file
@@ -0,0 +1,231 @@
|
||||
namespace TinyCC.E2ETests;
|
||||
|
||||
/// <summary>
|
||||
/// 端到端测试用例集合
|
||||
/// </summary>
|
||||
public static class TestCases
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取所有基础测试用例
|
||||
/// </summary>
|
||||
public static IEnumerable<TestCase> GetBasicTests()
|
||||
{
|
||||
// 最简单的测试:返回常量
|
||||
yield return new TestCase(
|
||||
Name: "simple_return_zero",
|
||||
SourceCode: """
|
||||
int main() {
|
||||
return 0;
|
||||
}
|
||||
""",
|
||||
ExpectedExitCode: 0
|
||||
);
|
||||
|
||||
// 返回常量 42
|
||||
yield return new TestCase(
|
||||
Name: "simple_return_42",
|
||||
SourceCode: """
|
||||
int main() {
|
||||
return 42;
|
||||
}
|
||||
""",
|
||||
ExpectedExitCode: 42
|
||||
);
|
||||
|
||||
// 算术运算测试 - 使用 if-else 替代三元运算符
|
||||
yield return new TestCase(
|
||||
Name: "arithmetic_add",
|
||||
SourceCode: """
|
||||
int add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
int main() {
|
||||
int result;
|
||||
result = add(3, 4);
|
||||
if (result == 7) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
""",
|
||||
ExpectedExitCode: 0
|
||||
);
|
||||
|
||||
// 控制流测试 - for 循环
|
||||
yield return new TestCase(
|
||||
Name: "control_flow_for_loop",
|
||||
SourceCode: """
|
||||
int main() {
|
||||
int sum;
|
||||
int i;
|
||||
sum = 0;
|
||||
for (i = 1; i <= 10; i = i + 1) {
|
||||
sum = sum + i;
|
||||
}
|
||||
if (sum == 55) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
""",
|
||||
ExpectedExitCode: 0
|
||||
);
|
||||
|
||||
// 控制流测试 - while 循环
|
||||
yield return new TestCase(
|
||||
Name: "control_flow_while_loop",
|
||||
SourceCode: """
|
||||
int main() {
|
||||
int sum;
|
||||
int i;
|
||||
sum = 0;
|
||||
i = 1;
|
||||
while (i <= 10) {
|
||||
sum = sum + i;
|
||||
i = i + 1;
|
||||
}
|
||||
if (sum == 55) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
""",
|
||||
ExpectedExitCode: 0
|
||||
);
|
||||
|
||||
// 函数调用测试
|
||||
yield return new TestCase(
|
||||
Name: "function_call",
|
||||
SourceCode: """
|
||||
int multiply(int a, int b) {
|
||||
return a * b;
|
||||
}
|
||||
int main() {
|
||||
int result;
|
||||
result = multiply(6, 7);
|
||||
if (result == 42) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
""",
|
||||
ExpectedExitCode: 0
|
||||
);
|
||||
|
||||
// 条件分支测试
|
||||
yield return new TestCase(
|
||||
Name: "conditional_branch",
|
||||
SourceCode: """
|
||||
int max(int a, int b) {
|
||||
if (a > b) {
|
||||
return a;
|
||||
} else {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
int main() {
|
||||
int result;
|
||||
result = max(10, 20);
|
||||
if (result == 20) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
""",
|
||||
ExpectedExitCode: 0
|
||||
);
|
||||
|
||||
// 变量赋值测试
|
||||
yield return new TestCase(
|
||||
Name: "variable_assignment",
|
||||
SourceCode: """
|
||||
int main() {
|
||||
int x;
|
||||
x = 42;
|
||||
if (x == 42) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
""",
|
||||
ExpectedExitCode: 0
|
||||
);
|
||||
|
||||
// 递归函数测试
|
||||
yield return new TestCase(
|
||||
Name: "recursive_factorial",
|
||||
SourceCode: """
|
||||
int factorial(int n) {
|
||||
if (n <= 1) {
|
||||
return 1;
|
||||
}
|
||||
return n * factorial(n - 1);
|
||||
}
|
||||
int main() {
|
||||
int result;
|
||||
result = factorial(5);
|
||||
if (result == 120) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
""",
|
||||
ExpectedExitCode: 0
|
||||
);
|
||||
|
||||
// 局部变量作用域测试
|
||||
yield return new TestCase(
|
||||
Name: "local_variable_scope",
|
||||
SourceCode: """
|
||||
int main() {
|
||||
int x;
|
||||
x = 10;
|
||||
if (x > 5) {
|
||||
int y;
|
||||
y = 20;
|
||||
x = y;
|
||||
}
|
||||
if (x == 20) {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
""",
|
||||
ExpectedExitCode: 0
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取预期失败的测试用例(用于记录当前不支持的功能)
|
||||
/// </summary>
|
||||
public static IEnumerable<TestCase> GetKnownFailures()
|
||||
{
|
||||
// 指针测试 - 当前可能不支持
|
||||
yield return new TestCase(
|
||||
Name: "pointers_basic",
|
||||
SourceCode: """
|
||||
int main() {
|
||||
int x = 42;
|
||||
int *p = &x;
|
||||
return *p == 42 ? 0 : 1;
|
||||
}
|
||||
""",
|
||||
ExpectedExitCode: 0
|
||||
);
|
||||
|
||||
// 数组测试 - 当前可能不支持
|
||||
yield return new TestCase(
|
||||
Name: "arrays_basic",
|
||||
SourceCode: """
|
||||
int main() {
|
||||
int arr[3];
|
||||
arr[0] = 1;
|
||||
arr[1] = 2;
|
||||
arr[2] = 3;
|
||||
return arr[1] == 2 ? 0 : 1;
|
||||
}
|
||||
""",
|
||||
ExpectedExitCode: 0
|
||||
);
|
||||
}
|
||||
}
|
||||
27
tests/TinyCC.E2ETests/TinyCC.E2ETests.csproj
Normal file
27
tests/TinyCC.E2ETests/TinyCC.E2ETests.csproj
Normal file
@@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.5.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\TinyCC.Core\TinyCC.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
27
tests/TinyCC.Tests/TinyCC.Tests.csproj
Normal file
27
tests/TinyCC.Tests/TinyCC.Tests.csproj
Normal file
@@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.5.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\TinyCC.Core\TinyCC.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
10
tests/TinyCC.Tests/UnitTest1.cs
Normal file
10
tests/TinyCC.Tests/UnitTest1.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace TinyCC.Tests;
|
||||
|
||||
public class UnitTest1
|
||||
{
|
||||
[Fact]
|
||||
public void Test1()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
86
tests/TinyCC.Tests/UnitTests.cs
Normal file
86
tests/TinyCC.Tests/UnitTests.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using TinyCC.Core;
|
||||
|
||||
namespace TinyCC.Tests;
|
||||
|
||||
public class LexerTests
|
||||
{
|
||||
private readonly IErrorReporter _errorReporter;
|
||||
|
||||
public LexerTests()
|
||||
{
|
||||
_errorReporter = new ErrorReporter();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_SimpleExpression_ReturnsTokens()
|
||||
{
|
||||
var source = "int x = 3 + 4;";
|
||||
var lexer = new Lexer(source, "test.c", _errorReporter);
|
||||
var tokens = lexer.Tokenize().ToList();
|
||||
|
||||
Assert.False(_errorReporter.HasErrors);
|
||||
Assert.Contains(tokens, t => t.Type == TokenType.Int);
|
||||
Assert.Contains(tokens, t => t.Type == TokenType.Identifier && t.Lexeme == "x");
|
||||
Assert.Contains(tokens, t => t.Type == TokenType.Assign);
|
||||
Assert.Contains(tokens, t => t.Type == TokenType.IntLiteral && Convert.ToInt64(t.Value!) == 3);
|
||||
Assert.Contains(tokens, t => t.Type == TokenType.IntLiteral && Convert.ToInt64(t.Value!) == 4);
|
||||
Assert.Contains(tokens, t => t.Type == TokenType.Plus);
|
||||
Assert.Contains(tokens, t => t.Type == TokenType.Semicolon);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_FunctionDefinition_ReturnsTokens()
|
||||
{
|
||||
var source = "int add(int a, int b) { return a + b; }";
|
||||
var lexer = new Lexer(source, "test.c", _errorReporter);
|
||||
var tokens = lexer.Tokenize().ToList();
|
||||
|
||||
Assert.False(_errorReporter.HasErrors);
|
||||
Assert.Contains(tokens, t => t.Type == TokenType.Int);
|
||||
Assert.Contains(tokens, t => t.Type == TokenType.Identifier && t.Lexeme == "add");
|
||||
Assert.Contains(tokens, t => t.Type == TokenType.LeftParen);
|
||||
Assert.Contains(tokens, t => t.Type == TokenType.RightParen);
|
||||
Assert.Contains(tokens, t => t.Type == TokenType.LeftBrace);
|
||||
Assert.Contains(tokens, t => t.Type == TokenType.Return);
|
||||
Assert.Contains(tokens, t => t.Type == TokenType.RightBrace);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tokenize_SkipsComments()
|
||||
{
|
||||
var source = "int x; // this is a comment\nint y;";
|
||||
var lexer = new Lexer(source, "test.c", _errorReporter);
|
||||
var tokens = lexer.Tokenize().ToList();
|
||||
|
||||
Assert.False(_errorReporter.HasErrors);
|
||||
var identifiers = tokens.Where(t => t.Type == TokenType.Identifier).ToList();
|
||||
Assert.Equal(2, identifiers.Count);
|
||||
Assert.Equal("x", identifiers[0].Lexeme);
|
||||
Assert.Equal("y", identifiers[1].Lexeme);
|
||||
}
|
||||
}
|
||||
|
||||
public class ParserTests
|
||||
{
|
||||
private readonly IErrorReporter _errorReporter;
|
||||
|
||||
public ParserTests()
|
||||
{
|
||||
_errorReporter = new ErrorReporter();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_SimpleFunction_ReturnsAst()
|
||||
{
|
||||
var source = "int add(int a, int b) { return a + b; }";
|
||||
var lexer = new Lexer(source, "test.c", _errorReporter);
|
||||
var tokens = lexer.Tokenize().ToList();
|
||||
var parser = new Parser(tokens, _errorReporter);
|
||||
|
||||
var ast = parser.Parse();
|
||||
|
||||
Assert.False(_errorReporter.HasErrors);
|
||||
Assert.NotNull(ast);
|
||||
Assert.IsType<ProgramNode>(ast);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user