aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/src/Godosa.Core.Tests/Runs/Scripts/ReviewFixTests.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Godosa.Core.Tests/Runs/Scripts/ReviewFixTests.cs')
-rw-r--r--src/Godosa.Core.Tests/Runs/Scripts/ReviewFixTests.cs157
1 files changed, 157 insertions, 0 deletions
diff --git a/src/Godosa.Core.Tests/Runs/Scripts/ReviewFixTests.cs b/src/Godosa.Core.Tests/Runs/Scripts/ReviewFixTests.cs
new file mode 100644
index 0000000..1b7257b
--- /dev/null
+++ b/src/Godosa.Core.Tests/Runs/Scripts/ReviewFixTests.cs
@@ -0,0 +1,157 @@
+using Godosa.Core.Runs;
+using Godosa.Core.Runs.Scripts;
+using Xunit;
+using static Godosa.Core.Tests.Runs.Scripts.ScriptRunnerTests;
+
+namespace Godosa.Core.Tests.Runs.Scripts;
+
+/// <summary>Final-review findings (lib core): each test failed before its fix.</summary>
+public sealed class ReviewFixTests : IDisposable
+{
+ private readonly string _dir = Path.Combine(AppContext.BaseDirectory, "rs-tmp", Guid.NewGuid().ToString("N"));
+
+ public void Dispose()
+ {
+ if (Directory.Exists(_dir))
+ Directory.Delete(_dir, recursive: true);
+ }
+
+ private const string Script = "format 1\nmode player\nkey A\ncheckpoint one\nkey B\ncheckpoint two\nkey C";
+
+ [Fact]
+ public void NoArgVerb_AndEmptySet_Run()
+ {
+ var g = new FakeGame();
+ g.ActionTable["close-panel"] = new FakeAction(VerbKind.Player, _ => new ActionStart(Status: StepStatus.Done), min: 0, max: 0);
+ var (r, output) = Run("format 1\nmode player\nset x =\nclose-panel\nlog \"[${x}]\"", g);
+ Assert.Equal(RunOutcome.Passed, r.Outcome);
+ Assert.Contains("[]", output);
+ }
+
+ // A probe value (dialog text) with quotes inside a quoted argument stays text: the line is split into tokens first.
+ [Fact]
+ public void QuotedArgument_ValueWithQuotes_StaysOneToken()
+ {
+ var (r, output) = Run("format 1\nmode player\nset x = \"\\\"Hi,\\\" he said\"\nlog \"R: ${x}\"", new FakeGame());
+ Assert.Equal(RunOutcome.Passed, r.Outcome);
+ Assert.Contains("R: \"Hi,\" he said", output);
+ }
+
+ [Fact]
+ public void SubstitutedValues_AreNotRescanned()
+ {
+ var values = new Dictionary<string, string> { ["d"] = "$", ["e"] = "{x}", ["raw"] = "${y}" };
+ Assert.Equal("${x}", VarExpander.Expand("${d}${e}", n => values.GetValueOrDefault(n), null));
+ Assert.Equal("say ${y}!", VarExpander.Expand("say ${raw}!", n => values.GetValueOrDefault(n), null));
+ }
+
+ [Theory]
+ [InlineData("log Done:", "log", "Done:")]
+ [InlineData("type name:", "type", "name:")]
+ public void LineEndingInColon_IsAVerbUnlessABlockKeyword(string line, string verb, string args)
+ {
+ var f = ScriptParser.Parse("main.steps", $"format 1\nmode debug\n{line}");
+ Assert.Empty(f.Errors);
+ var v = Assert.IsType<VerbNode>(f.Body.Single());
+ Assert.Equal((verb, args), (v.Verb, v.Args));
+ }
+
+ [Fact]
+ public void ThrowingInputRouter_FailsTheRun()
+ {
+ var g = new FakeGame { OnSend = _ => throw new InvalidOperationException("router down") };
+ var (r, output) = Run("format 1\nmode player\nkey R", g);
+ Assert.Equal(RunOutcome.Failed, r.Outcome);
+ Assert.Contains(output, l => l == "FAIL main.steps:3 key R: InvalidOperationException: router down");
+ }
+
+ [Fact]
+ public void CorruptCacheEntry_IsAMiss()
+ {
+ Run(Script, new FakeGame(), new ScriptRunOptions { CacheDir = _dir });
+ foreach (var f in Directory.GetFiles(_dir, "*.snap"))
+ File.WriteAllBytes(f, [.. "GRS1"u8, 3, 0, 0, 0, (byte)'{', (byte)'x', (byte)'!']);
+ var (r, output) = Run(Script, new FakeGame(), new ScriptRunOptions { CacheDir = _dir });
+ Assert.Equal(RunOutcome.Passed, r.Outcome);
+ Assert.DoesNotContain(output, l => l.StartsWith("resumed"));
+ }
+
+ // A game state the snapshot can't hold (an open dialog) → checkpoint not cached, run unharmed.
+ [Fact]
+ public void RefusedSnapshot_CheckpointNotCached()
+ {
+ var g = new FakeGame { Refusal = "dialog open" };
+ var output = new List<string>();
+ var runner = new ScriptRunner(ScriptLoader.Load("main.steps", new MemoryScriptSource(new Dictionary<string, string>
+ {
+ ["main.steps"] = Script,
+ }), new Dictionary<string, string>()), g, new ScriptRunOptions { CacheDir = _dir }, output.Add);
+ Assert.Equal(RunOutcome.Passed, runner.Run(1000).Outcome);
+ Assert.Equal(["checkpoint one not cached: dialog open", "checkpoint two not cached: dialog open"], runner.Report!.Warnings);
+ Assert.False(Directory.Exists(_dir) && Directory.GetFiles(_dir, "*.snap", SearchOption.AllDirectories).Length > 0);
+ }
+
+ [Fact]
+ public void FailingRestore_FailsTheRun()
+ {
+ Run(Script, new FakeGame(), new ScriptRunOptions { CacheDir = _dir });
+ var (r, output) = Run(Script, new FakeGame { RestoreThrows = true }, new ScriptRunOptions { CacheDir = _dir });
+ Assert.Equal((RunOutcome.Failed, 1), (r.Outcome, r.ExitCode));
+ Assert.Contains(output, l => l.Contains("IOException: restore broke"));
+ }
+
+ [Fact]
+ public void UnwritableCacheAndReportDirs_WarnInsteadOfThrowing()
+ {
+ Directory.CreateDirectory(_dir);
+ var file = Path.Combine(_dir, "file");
+ File.WriteAllText(file, "x");
+ var g = new FakeGame();
+ g.ProbeFns["no"] = _ => RunValue.Of(false);
+ var output = new List<string>();
+ var runner = new ScriptRunner(ScriptLoader.Load("main.steps", new MemoryScriptSource(new Dictionary<string, string>
+ {
+ ["main.steps"] = "format 1\nmode player\ncheckpoint one\nexpect no",
+ }), new Dictionary<string, string>()), g, new ScriptRunOptions { CacheDir = Path.Combine(file, "cache"), ReportDir = Path.Combine(file, "report") }, output.Add);
+ var r = runner.Run(1000);
+ Assert.Equal(RunOutcome.Failed, r.Outcome);
+ Assert.Contains(runner.Report!.Warnings, w => w.StartsWith("checkpoint cache not written:"));
+ Assert.Contains(output, l => l.StartsWith("warning: report not written:"));
+ }
+
+ [Fact]
+ public void Until_UnknownName_Invalid()
+ {
+ var (r, output) = Run(Script, new FakeGame(), new ScriptRunOptions { Until = "nowhere" });
+ Assert.Equal((RunOutcome.Invalid, 2), (r.Outcome, r.ExitCode));
+ Assert.Contains("unknown label or checkpoint nowhere", output);
+ }
+
+ [Fact]
+ public void Until_ResumesOnlyBeforeIt()
+ {
+ Run(Script, new FakeGame(), new ScriptRunOptions { CacheDir = _dir }); // caches one and two
+ var output = new List<string>();
+ var r = new ScriptRunner(ScriptLoader.Load("main.steps", new MemoryScriptSource(new Dictionary<string, string> { ["main.steps"] = Script }),
+ new Dictionary<string, string>()), new FakeGame(), new ScriptRunOptions { CacheDir = _dir, Until = "two" }, output.Add).Run(1000);
+ Assert.Contains("resumed at one (cache) f=2", output);
+ Assert.Equal((RunOutcome.Stopped, 4L), (r.Outcome, r.Frames));
+ }
+
+ [Fact]
+ public void ScriptSeedHeader_SeedsTheGame_CommandLineWins()
+ {
+ var g = new FakeGame();
+ var output = new List<string>();
+ var runner = new ScriptRunner(ScriptLoader.Load("main.steps", new MemoryScriptSource(new Dictionary<string, string> { ["main.steps"] = "format 1\nmode player\nseed 7\nkey A" }),
+ new Dictionary<string, string>()), g, new ScriptRunOptions(), output.Add);
+ runner.Run(1000);
+ Assert.Equal(["seed 7"], g.RngImpl!.Calls);
+ Assert.Equal(7UL, runner.Report!.Seed);
+ var g2 = new FakeGame();
+ var runner2 = new ScriptRunner(ScriptLoader.Load("main.steps", new MemoryScriptSource(new Dictionary<string, string> { ["main.steps"] = "format 1\nmode player\nseed 7\nkey A" }),
+ new Dictionary<string, string>()), g2, new ScriptRunOptions { Seed = 9 }, output.Add);
+ runner2.Run(1000);
+ Assert.Equal(["seed 9"], g2.RngImpl!.Calls);
+ }
+}