test(gamecli): end-to-end smoke tests exercising the built binary
This commit is contained in:
139
GameCli.Tests/ProgramSmokeTests.cs
Normal file
139
GameCli.Tests/ProgramSmokeTests.cs
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Text.Json;
|
||||||
|
using GameCli;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace GameCli.Tests;
|
||||||
|
|
||||||
|
public class ProgramSmokeTests
|
||||||
|
{
|
||||||
|
private static string GetProjectPath()
|
||||||
|
{
|
||||||
|
// Locate the GameCli binary relative to the test binary's output dir.
|
||||||
|
// Tests run from GameCli.Tests/bin/<Config>/net8.0/, so ../../../.. gets to repo root.
|
||||||
|
var testDir = AppContext.BaseDirectory;
|
||||||
|
var repoRoot = Path.GetFullPath(Path.Combine(testDir, "..", "..", "..", ".."));
|
||||||
|
var projectPath = Path.Combine(repoRoot, "GameCli", "GameCli.csproj");
|
||||||
|
Assert.True(File.Exists(projectPath),
|
||||||
|
$"could not find GameCli.csproj at {projectPath}");
|
||||||
|
return projectPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Process StartGameCli()
|
||||||
|
{
|
||||||
|
var projectPath = GetProjectPath();
|
||||||
|
|
||||||
|
var psi = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "dotnet",
|
||||||
|
ArgumentList = { "run", "--project", projectPath, "-c", "Release", "--no-build" },
|
||||||
|
RedirectStandardInput = true,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
};
|
||||||
|
var p = Process.Start(psi)
|
||||||
|
?? throw new InvalidOperationException("failed to start GameCli");
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FirstOutputLine_IsInitHandshake()
|
||||||
|
{
|
||||||
|
// Ensure release build exists so --no-build works reliably.
|
||||||
|
RunOrFail("dotnet", $"build -c Release \"{GetProjectPath()}\"");
|
||||||
|
|
||||||
|
using var p = StartGameCli();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var line = p.StandardOutput.ReadLine();
|
||||||
|
Assert.NotNull(line);
|
||||||
|
var init = JsonSerializer.Deserialize(line!, ProtocolJsonContext.Default.InitMessage);
|
||||||
|
Assert.NotNull(init?.Init);
|
||||||
|
Assert.Equal(Physics.Dt, init!.Init.Dt);
|
||||||
|
Assert.Equal(Observation.Dim, init.Init.ObsDim);
|
||||||
|
Assert.Equal(4, init.Init.NActions);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
p.StandardInput.Close();
|
||||||
|
p.WaitForExit(3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void StepRoundTrip_ReturnsSevenDimObservation()
|
||||||
|
{
|
||||||
|
RunOrFail("dotnet", $"build -c Release \"{GetProjectPath()}\"");
|
||||||
|
|
||||||
|
using var p = StartGameCli();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_ = p.StandardOutput.ReadLine(); // skip init
|
||||||
|
|
||||||
|
p.StandardInput.WriteLine("""{"action":0,"target":[0.5,0.5]}""");
|
||||||
|
p.StandardInput.Flush();
|
||||||
|
|
||||||
|
var line = p.StandardOutput.ReadLine();
|
||||||
|
Assert.NotNull(line);
|
||||||
|
var output = JsonSerializer.Deserialize(line!, ProtocolJsonContext.Default.StepOutput);
|
||||||
|
Assert.NotNull(output);
|
||||||
|
Assert.Equal(7, output!.Obs.Length);
|
||||||
|
Assert.Equal(1, output.Step);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
p.StandardInput.Close();
|
||||||
|
p.WaitForExit(3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResetCommand_ResetsStepCounter()
|
||||||
|
{
|
||||||
|
RunOrFail("dotnet", $"build -c Release \"{GetProjectPath()}\"");
|
||||||
|
|
||||||
|
using var p = StartGameCli();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_ = p.StandardOutput.ReadLine(); // init
|
||||||
|
|
||||||
|
// Take three steps.
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
p.StandardInput.WriteLine("""{"action":0,"target":[0.5,0.5]}""");
|
||||||
|
}
|
||||||
|
p.StandardInput.Flush();
|
||||||
|
string? last = null;
|
||||||
|
for (int i = 0; i < 3; i++) last = p.StandardOutput.ReadLine();
|
||||||
|
var afterThree = JsonSerializer.Deserialize(last!, ProtocolJsonContext.Default.StepOutput);
|
||||||
|
Assert.Equal(3, afterThree!.Step);
|
||||||
|
|
||||||
|
// Reset. Expect step=0 on the next output line.
|
||||||
|
p.StandardInput.WriteLine("""{"cmd":"reset","seed":42}""");
|
||||||
|
p.StandardInput.Flush();
|
||||||
|
var afterReset = JsonSerializer.Deserialize(
|
||||||
|
p.StandardOutput.ReadLine()!, ProtocolJsonContext.Default.StepOutput);
|
||||||
|
Assert.Equal(0, afterReset!.Step);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
p.StandardInput.Close();
|
||||||
|
p.WaitForExit(3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RunOrFail(string file, string args)
|
||||||
|
{
|
||||||
|
var psi = new ProcessStartInfo(file, args)
|
||||||
|
{
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
};
|
||||||
|
using var p = Process.Start(psi)!;
|
||||||
|
p.WaitForExit(120_000);
|
||||||
|
if (p.ExitCode != 0)
|
||||||
|
throw new Exception($"{file} {args} exited {p.ExitCode}\nstderr:\n{p.StandardError.ReadToEnd()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user