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
|
||||
);
|
||||
Reference in New Issue
Block a user