1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
using Godosa.Core.Runs.Scripts;
using Xunit;
namespace Godosa.Core.Tests.Runs.Scripts;
public class VarsTests
{
private static readonly Dictionary<string, string> Values = new()
{
["res"] = "1920x1080", ["hours"] = "8", ["ui.rest.8"] = "400,300", ["a"] = "b", ["b"] = "deep",
};
private static string Expand(string text) => VarExpander.Expand(text, n => Values.GetValueOrDefault(n),
p => p == "global(gold)" ? RunValue.Of(1250) : throw new ScriptFault($"no probe {p}"));
[Theory]
[InlineData("ui/${res}.vars", "ui/1920x1080.vars")]
[InlineData("click ${ui.rest.${hours}}", "click 400,300")]
[InlineData("${${a}}", "deep")]
[InlineData("gold ${probe:global(gold)}", "gold 1250")]
[InlineData("cost $5 and $", "cost $5 and $")] // review focus 4: lone $ is literal
[InlineData("no vars", "no vars")]
public void Expand_Values(string text, string expected) => Assert.Equal(expected, Expand(text));
[Theory]
[InlineData("${missing}", "unresolved variable ${missing}")]
[InlineData("x ${a", "unclosed ${ in 'x ${a'")]
[InlineData("${}", "empty ${}")]
public void Expand_Errors_AreFaults(string text, string message) =>
Assert.Equal(message, Assert.Throws<ScriptFault>(() => Expand(text)).Message);
[Fact]
public void Names_InnermostPlainOnly() =>
Assert.Equal(["hours", "res"], VarExpander.Names("${ui.${hours}} ${res} ${probe:quest(1)}").Order());
[Fact]
public void VarsFile_ParsesAndReportsBadLines()
{
var (values, errors) = VarsFile.Parse("ui/hd.vars", "# hd\nui.answer1 = 412,520\n\nui.rest.8=400,300\nbroken line\n");
Assert.Equal("412,520", values["ui.answer1"]);
Assert.Equal("400,300", values["ui.rest.8"]);
Assert.Equal(["ui/hd.vars:5: bad vars line (name = value)"], errors.Select(e => e.ToString()));
}
[Fact]
public void Scopes_SetGoesToLocalOrRoot()
{
var s = new VarScopes(OutputLevel.Labels);
s.Set("g", "1");
s.Push(local: false, OutputLevel.Labels, []); // include
s.Set("inc", "2"); // global: root
s.Push(local: true, OutputLevel.Labels, [new("p", "x")]); // def call
s.Set("loc", "3");
Assert.Equal(("1", "2", "3", "x"), (s.Get("g"), s.Get("inc"), s.Get("loc"), s.Get("p")));
s.Pop();
Assert.Null(s.Get("loc"));
Assert.Null(s.Get("p"));
s.Pop();
Assert.Equal("2", s.Get("inc"));
}
[Fact]
public void Scopes_OutputLevelRestoredOnPop()
{
var s = new VarScopes(OutputLevel.Labels);
s.Push(local: false, s.Level, []);
s.Level = OutputLevel.Trace;
s.Pop();
Assert.Equal(OutputLevel.Labels, s.Level);
}
[Fact]
public void Scopes_SnapshotRestore_RoundTrips()
{
var s = new VarScopes(OutputLevel.Labels);
s.Set("g", "1");
s.Push(local: true, OutputLevel.Steps, [new("p", "x")]);
var copy = new VarScopes(OutputLevel.Quiet);
copy.Restore(s.Snapshot());
Assert.Equal(("1", "x", OutputLevel.Steps), (copy.Get("g"), copy.Get("p"), copy.Level));
s.Set("p", "changed");
Assert.Equal("x", copy.Get("p")); // deep copy
}
}
|