using System; using System.Collections.Generic; using System.Linq; using UnitTestSharp; namespace Ariadne.UnitTests { /// /// Runs scripts next to a twin that is saved and reloaded before every tick, to show a save never changes what a /// script does. /// public class ReloadEquivalenceTests : TestFixture { public const string RestartStep = "restart"; /// /// Starts a step that sets the game time, in seconds, for the ticks after it, eg: "@2.5". /// public const string ClockStep = "@"; public static void ApplyChange(string change, ArcTests.Gates gates) { if (change.StartsWith("+", StringComparison.Ordinal)) { gates.Open.Add(change.Substring(1)); } else if (change.StartsWith("-", StringComparison.Ordinal)) { gates.Open.Remove(change.Substring(1)); } } public static string[] SavedValuesOf(ArcRecord record) { var returnMe = new List(); foreach (KeyValuePair field in record.Fields.OrderBy(pair => pair.Key, StringComparer.Ordinal)) { returnMe.Add("field " + field.Key + "=" + field.Value); } foreach (KeyValuePair local in record.AllLocals().OrderBy(pair => pair.Key, StringComparer.Ordinal)) { returnMe.Add("local " + local.Key + "=" + local.Value); } return returnMe.ToArray(); } public class ReloadedBeforeEveryTickTests : TestFixture { /// /// Ticks a script once per entry of , which opens ("+gate") or closes ("-gate") /// a gate or changes nothing (""), and returns what the script observed. Before each tick a twin is /// loaded from the script's save and given the same tick, and the two must end it identically. /// [IgnoreTest] public string[] TickWithATwinReloadedEachTime(string[] schedule, Func> observe) where ScriptType : Arc, new() { var live = new ScriptType(); var liveGates = new ArcTests.Gates(); TimeSpan gameTime = TimeSpan.Zero; for (int i = 0; i < schedule.Length; ++i) { // Not a tick, so the next step saves the arc just as the restart left it. if (schedule[i] == RestartStep) { live.Restart(); continue; } if (schedule[i].StartsWith(ClockStep, StringComparison.Ordinal)) { gameTime = TimeSpan.FromSeconds(double.Parse(schedule[i].Substring(ClockStep.Length), System.Globalization.CultureInfo.InvariantCulture)); continue; } ArcRecord saved = ArcRecord.Of(live); ScriptType twin = LoadedArc.From(saved); twin.VerifyLoaded(saved); var twinGates = new ArcTests.Gates(); twinGates.Open.UnionWith(liveGates.Open); int observedBefore = observe(live).Count(); ApplyChange(schedule[i], liveGates); ApplyChange(schedule[i], twinGates); live.Tick(liveGates, TestTicks.At(TestTicks.NewSandbox(), TimeSpan.Zero, gameTime)); twin.Tick(twinGates, TestTicks.At(TestTicks.NewSandbox(), TimeSpan.Zero, gameTime)); CheckEqual(observe(live).Skip(observedBefore).ToArray(), observe(twin).ToArray()); CheckEqual(live.Cursor.ToArray(), twin.Cursor.ToArray()); CheckEqual(live.Finished, twin.Finished); CheckTrue(liveGates.Open.SetEquals(twinGates.Open)); CheckEqual(SavedValuesOf(ArcRecord.Of(live)), SavedValuesOf(ArcRecord.Of(twin))); } return observe(live).ToArray(); } public void PassageCalledAgain_BehavesTheSame() { string[] observed = TickWithATwinReloadedEachTime( new[] { "", "", "+A", "", "+B", "" }, script => script.Log); CheckEqual(new[] { "step begins", "A seen", "step ends", "step begins", "A seen", "step ends" }, observed); } public void PassageCalledAgainAndReturningEarly_BehavesTheSame() { string[] observed = TickWithATwinReloadedEachTime( new[] { "", "+skip", "+A", "" }, script => script.Log); CheckEqual(new[] { "step begins", "step ends", "step begins" }, observed); } public void PassageCalledTwiceFromTwoSites_BehavesTheSame() { TickWithATwinReloadedEachTime( new[] { "", "+read", "", "+read", "" }, script => new string[0]); } public void PassageKeepingALocal_BehavesTheSame() { string[] observed = TickWithATwinReloadedEachTime( new[] { "", "", "+go", "" }, script => script.Log); CheckEqual(new[] { "first held 2", "second held 0" }, observed); } public void Wait_BehavesTheSame() { string[] observed = TickWithATwinReloadedEachTime( new[] { "@10", "", "@10.5", "", "@11", "", "@12", "", "@13", "" }, script => script.Log); CheckEqual(new[] { "start", "after one second", "end" }, observed); } public void WaitForTheResultOfAPassage_BehavesTheSame() { string[] observed = TickWithATwinReloadedEachTime( new[] { "@10", "", "@11", "", "@12", "+answer", "@13", "", "@14", "" }, script => script.Log); CheckEqual(new[] { "done" }, observed); } public void WaitForTheSumOfTwoPassages_BehavesTheSame() { string[] observed = TickWithATwinReloadedEachTime( new[] { "@10", "", "@11", "+a", "@12", "+b", "@13", "", "@14.5", "" }, script => script.Log); CheckEqual(new[] { "done" }, observed); } public void WaitInOneBranch_BehavesTheSame() { string[] observed = TickWithATwinReloadedEachTime( new[] { "@10", "+slow", "@10.5", "", "@11", "" }, script => script.Log); CheckEqual(new[] { "start", "slow", "end" }, observed); } public void WaitInALoopAcrossARestart_BehavesTheSame() { string[] observed = TickWithATwinReloadedEachTime( new[] { "@10", "", "@11", "", RestartStep, "@12", "", "@13", "", "@14", "", "@15", "" }, script => script.Log); CheckEqual(new[] { "trip 0", "trip 0", "trip 1", "trip 2" }, observed); } public void PassageKeepingALocal_BehavesTheSameAfterARestart() { string[] observed = TickWithATwinReloadedEachTime( new[] { "", "", RestartStep, "", "+go", "" }, script => script.Log); CheckEqual(new[] { "first held 1", "second held 0" }, observed); } public void RestartOfAFinishedScript_BehavesTheSame() { string[] observed = TickWithATwinReloadedEachTime( new[] { "", "+go", "+crate", "+crate", "", RestartStep, "", "+crate", "+crate", "" }, script => script.Log); CheckEqual(new[] { "hello", "waited 1", "delivered", "hello", "waited 0", "delivered" }, observed); } public void LocalsAndAPassageArgument_BehaveTheSame() { string[] observed = TickWithATwinReloadedEachTime( new[] { "", "", "+go", "+crate", "", "+crate", "" }, script => script.Log); CheckEqual(new[] { "hello", "waited 2", "delivered" }, observed); } public void LocalDeclaredInALoopBody_BehavesTheSame() { string[] observed = TickWithATwinReloadedEachTime( new[] { "", "+a", "+b", "", "", "" }, script => script.Log); CheckTrue(observed.Contains("end")); } public void ParamsPassage_BehavesTheSame() { string[] observed = TickWithATwinReloadedEachTime( new[] { "", "", "+go", "" }, script => script.Log); CheckEqual(new[] { "a,n1" }, observed); } public void ScriptWithSeveralPassages_BehavesTheSame() { string[] observed = TickWithATwinReloadedEachTime( new[] { "", "+intro", "+build", "+connect", "+act3 connect", "" }, script => script.Log); CheckEqual("end", observed.Last()); } } } }