187 lines
5.2 KiB
C#
187 lines
5.2 KiB
C#
using System.Diagnostics;
|
|
using Xunit;
|
|
|
|
namespace DevTool.Tests;
|
|
|
|
public sealed class HeadlessExecutionTests
|
|
{
|
|
[Fact]
|
|
public async Task HeadlessRun_JsonMode_EmitsVersionedEventsAndSummary()
|
|
{
|
|
var root = CreateTempProject("""
|
|
{
|
|
"name": "headless-demo",
|
|
"version": "0.1.0",
|
|
"workflows": [
|
|
{
|
|
"id": "build",
|
|
"label": "Build",
|
|
"description": "Build",
|
|
"group": "Build",
|
|
"dependsOn": [],
|
|
"steps": [
|
|
{
|
|
"id": "s1",
|
|
"label": "dotnet --version",
|
|
"command": "dotnet",
|
|
"args": ["--version"],
|
|
"workingDir": ".",
|
|
"requires": []
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
""");
|
|
|
|
var result = await RunSdtAsync(["run", "build", "--json", "--project-root", root]);
|
|
|
|
Assert.Equal(0, result.ExitCode);
|
|
Assert.Contains("\"run_event_version\":\"1.0\"", result.StdOut, StringComparison.Ordinal);
|
|
Assert.Contains("\"event_type\":\"WorkflowStarted\"", result.StdOut, StringComparison.Ordinal);
|
|
Assert.Contains("\"category\":\"workflow\"", result.StdOut, StringComparison.Ordinal);
|
|
Assert.Contains("\"success\":true", result.StdOut, StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HeadlessRun_NonInteractive_MissingPrereq_UsesDeterministicExitCode()
|
|
{
|
|
var root = CreateTempProject("""
|
|
{
|
|
"name": "headless-demo",
|
|
"version": "0.1.0",
|
|
"workflows": [
|
|
{
|
|
"id": "build",
|
|
"label": "Build",
|
|
"description": "Build",
|
|
"group": "Build",
|
|
"dependsOn": [],
|
|
"steps": [
|
|
{
|
|
"id": "s1",
|
|
"label": "dotnet --version",
|
|
"command": "dotnet",
|
|
"args": ["--version"],
|
|
"workingDir": ".",
|
|
"requires": [
|
|
{ "tool": "definitely-missing-tool", "installPolicy": "Prompt" }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
""");
|
|
|
|
var result = await RunSdtAsync(["run", "build", "--json", "--project-root", root, "--non-interactive"]);
|
|
|
|
Assert.Equal(14, result.ExitCode);
|
|
Assert.Contains("\"stopReason\":\"UserDeclined\"", result.StdOut, StringComparison.Ordinal);
|
|
Assert.Contains("\"exactFixCommand\"", result.StdOut, StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HeadlessDebug_JsonMode_EmitsDebugSummary()
|
|
{
|
|
var root = CreateTempProject("""
|
|
{
|
|
"name": "headless-demo",
|
|
"version": "0.1.0",
|
|
"workflows": [],
|
|
"debug": {
|
|
"profiles": [
|
|
{
|
|
"id": "d1",
|
|
"label": "Dotnet Info",
|
|
"type": "dotnet",
|
|
"command": "dotnet",
|
|
"args": ["--info"],
|
|
"workingDir": ".",
|
|
"env": {},
|
|
"requires": []
|
|
}
|
|
]
|
|
}
|
|
}
|
|
""");
|
|
|
|
var result = await RunSdtAsync(["debug", "d1", "--json", "--project-root", root]);
|
|
|
|
Assert.Equal(0, result.ExitCode);
|
|
Assert.Contains("\"category\":\"debug\"", result.StdOut, StringComparison.Ordinal);
|
|
Assert.Contains("\"event_type\":\"DebugStarted\"", result.StdOut, StringComparison.Ordinal);
|
|
Assert.Contains("\"success\":true", result.StdOut, StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HeadlessRun_CommandFailure_MapsToDeterministicExitCode()
|
|
{
|
|
var root = CreateTempProject("""
|
|
{
|
|
"name": "headless-demo",
|
|
"version": "0.1.0",
|
|
"workflows": [
|
|
{
|
|
"id": "build",
|
|
"label": "Build",
|
|
"description": "Build",
|
|
"group": "Build",
|
|
"dependsOn": [],
|
|
"steps": [
|
|
{
|
|
"id": "s1",
|
|
"label": "dotnet bad command",
|
|
"command": "dotnet",
|
|
"args": ["__definitely_invalid_command__"],
|
|
"workingDir": ".",
|
|
"requires": []
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
""");
|
|
|
|
var result = await RunSdtAsync(["run", "build", "--json", "--project-root", root]);
|
|
|
|
Assert.Equal(12, result.ExitCode);
|
|
Assert.Contains("\"stopReason\":\"CommandFailed\"", result.StdOut, StringComparison.Ordinal);
|
|
Assert.Contains("\"retryInstruction\"", result.StdOut, StringComparison.Ordinal);
|
|
}
|
|
|
|
private static async Task<(int ExitCode, string StdOut, string StdErr)> RunSdtAsync(IReadOnlyList<string> args)
|
|
{
|
|
var exe = Path.Combine(AppContext.BaseDirectory, OperatingSystem.IsWindows() ? "sdt.exe" : "sdt");
|
|
if (!File.Exists(exe))
|
|
throw new InvalidOperationException($"Could not find test runtime executable: {exe}");
|
|
|
|
var psi = new ProcessStartInfo
|
|
{
|
|
FileName = exe,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true,
|
|
};
|
|
|
|
foreach (var arg in args)
|
|
psi.ArgumentList.Add(arg);
|
|
|
|
using var process = new Process { StartInfo = psi };
|
|
process.Start();
|
|
var stdout = await process.StandardOutput.ReadToEndAsync();
|
|
var stderr = await process.StandardError.ReadToEndAsync();
|
|
await process.WaitForExitAsync();
|
|
return (process.ExitCode, stdout, stderr);
|
|
}
|
|
|
|
private static string CreateTempProject(string devtoolJson)
|
|
{
|
|
var root = Path.Combine(Path.GetTempPath(), "sdt-headless-" + Guid.NewGuid().ToString("N"));
|
|
Directory.CreateDirectory(root);
|
|
File.WriteAllText(Path.Combine(root, "devtool.json"), devtoolJson);
|
|
return root;
|
|
}
|
|
}
|