commit initial

This commit is contained in:
baishi
2026-05-30 07:59:28 +08:00
commit cbefad339f
39 changed files with 7736 additions and 0 deletions

View 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
);

View 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;
}
}

View 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
);
}
}

View 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>