aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/src/Godosa.Core.Tests/Runs/Scripts/ScriptLoaderTests.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Godosa.Core.Tests/Runs/Scripts/ScriptLoaderTests.cs')
-rw-r--r--src/Godosa.Core.Tests/Runs/Scripts/ScriptLoaderTests.cs103
1 files changed, 103 insertions, 0 deletions
diff --git a/src/Godosa.Core.Tests/Runs/Scripts/ScriptLoaderTests.cs b/src/Godosa.Core.Tests/Runs/Scripts/ScriptLoaderTests.cs
new file mode 100644
index 0000000..f28f330
--- /dev/null
+++ b/src/Godosa.Core.Tests/Runs/Scripts/ScriptLoaderTests.cs
@@ -0,0 +1,103 @@
+using Godosa.Core.Runs.Scripts;
+using Xunit;
+
+namespace Godosa.Core.Tests.Runs.Scripts;
+
+public class ScriptLoaderTests
+{
+ private static ScriptProgram Load(Dictionary<string, string> files, Dictionary<string, string>? vars = null) =>
+ ScriptLoader.Load("main.steps", new MemoryScriptSource(files), vars ?? []);
+
+ private static string Errors(ScriptProgram p) => string.Join(" | ", p.Errors.Select(e => e.ToString()));
+
+ private static string Ops(ScriptProgram p) => string.Join(" ", p.Code.Select(i => i.Op switch
+ {
+ Op.Verb => $"{i.A}", Op.ScopeBegin => i.LocalVars ? "{L" : "{", Op.ScopeEnd => "}", Op.LabelBegin => $"[{i.A}",
+ Op.LabelEnd => "]", Op.TryBegin => "try(", Op.TryEnd => ")", Op.SetupBegin => "setup(", Op.SetupEnd => ")",
+ _ => i.Op.ToString().ToLowerInvariant(),
+ }));
+
+ [Fact]
+ public void Flattens_IncludesDefsBlocks()
+ {
+ var p = Load(new()
+ {
+ ["main.steps"] = "format 1\nmode player\nsetup:\n cheat gold\ndef rest(h):\n key R\nstep a:\n include ch/one.steps\n call rest(8)\ncheckpoint done",
+ ["ch/one.steps"] = "format 1\nclick 1,1\ninclude ../common.steps",
+ ["common.steps"] = "format 1\ntry:\n key Q",
+ });
+ Assert.Equal("", Errors(p));
+ Assert.Equal("setup( cheat ) [a { click { try( key ) } } {L key } ] checkpoint", Ops(p));
+ var tryBegin = p.Code.Single(i => i.Op == Op.TryBegin);
+ Assert.Equal(Op.TryEnd, p.Code[tryBegin.Jump].Op);
+ var q = p.Code.Single(i => i.Op == Op.Verb && i.A == "key" && i.B == "Q");
+ Assert.Equal("common.steps:3", q.At.ToString());
+ Assert.Equal(["main.steps:8", "ch/one.steps:3"], q.Chain.Select(c => c.ToString()));
+ var bind = p.Code.Single(i => i.Op == Op.ScopeBegin && i.LocalVars);
+ Assert.Equal([new KeyValuePair<string, string>("h", "8")], bind.Bindings);
+ }
+
+ [Fact]
+ public void VarsFiles_PathExpandedWithCommandLine_FileDoesNotOverrideIt()
+ {
+ var p = Load(new()
+ {
+ ["main.steps"] = "format 1\nmode player\nvars ui/${res}.vars\nclick ${ui.ok}",
+ ["ui/hd.vars"] = "ui.ok = 10,20\nres = sd",
+ }, new() { ["res"] = "hd" });
+ Assert.Equal("", Errors(p));
+ Assert.Equal(("10,20", "hd"), (p.Vars["ui.ok"], p.Vars["res"]));
+ Assert.Contains("res", p.Locked);
+ }
+
+ [Theory]
+ [InlineData("format 1\nclick 1,1", "main.steps:1: top-level file needs 'mode'")]
+ [InlineData("format 1\nmode player\ninclude x.steps", "main.steps:3: include not found: x.steps")]
+ [InlineData("format 1\nmode player\ncall nope()", "main.steps:3: unknown def nope")]
+ [InlineData("format 1\nmode player\ndef d(a):\n key R\ncall d()", "main.steps:5: def d takes 1 arguments, got 0")]
+ [InlineData("format 1\nmode player\ndef d():\n call d()\ncall d()", "main.steps:4: recursive call d")]
+ [InlineData("format 1\nmode player\ndef d():\n key R\ndef d():\n key Q", "main.steps:5: def d already defined")]
+ [InlineData("format 1\nmode player\ncheckpoint a\ncheckpoint a", "main.steps:4: duplicate checkpoint a")]
+ [InlineData("format 1\nmode player\nclick ${nowhere}", "main.steps:3: unresolved variable ${nowhere}")]
+ [InlineData("format 1\nmode player\nkey R\nsetup:\n cheat x", "main.steps:4: setup must be the first block")]
+ [InlineData("format 1\nmode player\ntry:\n checkpoint c", "main.steps:4: checkpoint inside try")]
+ public void Reports(string main, string error) => Assert.Contains(error, Errors(Load(new() { ["main.steps"] = main })));
+
+ [Fact]
+ public void IncludeCycle_NamesThePath() =>
+ Assert.Contains("b.steps:2: include cycle: main.steps → a.steps → b.steps → a.steps", Errors(Load(new()
+ {
+ ["main.steps"] = "format 1\nmode debug\ninclude a.steps",
+ ["a.steps"] = "format 1\ninclude b.steps",
+ ["b.steps"] = "format 1\ninclude a.steps",
+ })));
+
+ [Fact]
+ public void IncludedFile_ModeOrSetup_Rejected()
+ {
+ var p = Load(new()
+ {
+ ["main.steps"] = "format 1\nmode debug\ninclude a.steps",
+ ["a.steps"] = "format 1\nmode player\nsetup:\n cheat x",
+ });
+ Assert.Contains("a.steps:2: mode only in the top-level file", Errors(p));
+ Assert.Contains("a.steps:3: setup only in the top-level file", Errors(p));
+ }
+
+ [Fact]
+ public void LockedVariable_SetIsAnError() =>
+ Assert.Contains("main.steps:3: variable res is set on the command line",
+ Errors(Load(new() { ["main.steps"] = "format 1\nmode debug\nset res = x" }, new() { ["res"] = "hd" })));
+
+ [Fact]
+ public void DefParamsAndEarlierSets_AreKnownVariables() =>
+ Assert.Equal("", Errors(Load(new()
+ {
+ ["main.steps"] = "format 1\nmode debug\nset n = 3\ndef d(a):\n key ${a}\n wait-frames ${n}\ncall d(R)",
+ })));
+
+ [Fact]
+ public void IncludedFileKeepsItsOwnFormat() =>
+ Assert.Equal(1, Load(new() { ["main.steps"] = "format 1\nmode debug\ninclude a.steps", ["a.steps"] = "format 1\nkey R" })
+ .Code.Single(i => i.Op == Op.Verb).Format);
+}