using System; using System.Linq; using UnitTestSharp; using Vellum; namespace Ariadne.Vellum.UnitTests { /// /// Runs scripts next to a twin that is saved and reloaded through Vellum before every tick, to show a save never /// changes what a script does when the arcs it calls are saved with it. /// 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, 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 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(ScriptType script, string[] schedule, Func observe) where ScriptType : Arc { var liveGates = new 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) { script.Restart(); continue; } if (schedule[i].StartsWith(ClockStep, StringComparison.Ordinal)) { gameTime = TimeSpan.FromSeconds(double.Parse(schedule[i].Substring(ClockStep.Length), System.Globalization.CultureInfo.InvariantCulture)); continue; } // A session keeps the node it first made for an object, and loads one object per node. SaveSession session = ArcConverterTests.Session(); var twin = (ScriptType)session.FromNode(session.ToNode(script, typeof(ScriptType)), typeof(ScriptType)); var twinGates = new Gates(); twinGates.Open.UnionWith(liveGates.Open); ApplyChange(schedule[i], liveGates); ApplyChange(schedule[i], twinGates); script.Tick(liveGates, TestTicks.At(TimeSpan.Zero, gameTime)); twin.Tick(twinGates, TestTicks.At(TimeSpan.Zero, gameTime)); CheckEqual(observe(script), observe(twin)); CheckEqual(script.Cursor.ToArray(), twin.Cursor.ToArray()); CheckEqual(script.Finished, twin.Finished); CheckTrue(liveGates.Open.SetEquals(twinGates.Open)); } return observe(script); } public void CallToAnotherArc_BehavesTheSame() { string[] observed = TickWithATwinReloadedEachTime( new OperatorScript { Plant = new PlantScript() }, new[] { "", "", "+go", "" }, script => new[] { script.Result, script.Plant.Note, script.Plant.Updates.ToString() }); CheckEqual(new[] { "done 7 2", "updated after 2", "2" }, observed); } public void CallToAnotherArcMadeAgainWhileItsFirstLocalsAreStale_BehavesTheSame() { string[] observed = TickWithATwinReloadedEachTime( new RepeatingOperatorScript { Plant = new PlantScript() }, new[] { "", "", "+go", "", "+go", "" }, script => new[] { script.Result, script.Plant.Note, script.Plant.Updates.ToString() }); CheckEqual(new[] { "done 4", "updated after 2", "4" }, observed); } public void WaitInAPassageOfAnotherArc_BehavesTheSame() { string[] observed = TickWithATwinReloadedEachTime( new WaitOperatorScript { Plant = new WaitPlantScript() }, new[] { "@10", "", "@10.5", "", "@11", "" }, script => new[] { script.Result, script.Plant.Note }); CheckEqual(new[] { "done 7", "rested" }, observed); } public void TwoPassagesOfTwoArcsInOneStatement_BehaveTheSame() { string[] observed = TickWithATwinReloadedEachTime( new GatedSumOperatorScript { First = new GatedPlantScript(), Second = new GatedPlantScript() }, new[] { "@0", "", "", "+a", "", "+b", "" }, script => new[] { script.Result, script.First.Total.ToString(), script.Second.Total.ToString() }); CheckEqual(new[] { "sum 116", "7", "9" }, observed); } public void WaitForAPassageOfAnotherArc_BehavesTheSame() { string[] observed = TickWithATwinReloadedEachTime( new AskWaitOperatorScript { Delay = new DelayPlantScript() }, new[] { "@10", "", "@11", "", "@12", "+answer", "@13", "", "@14", "" }, script => new[] { script.Result }); CheckEqual(new[] { "done" }, observed); } public void RestartInAWaitInAPassageOfAnotherArc_BehavesTheSame() { string[] observed = TickWithATwinReloadedEachTime( new WaitOperatorScript { Plant = new WaitPlantScript() }, new[] { "@10", "", "@10.5", "", RestartStep, "@20", "", "@20.5", "", "@21", "" }, script => new[] { script.Result, script.Plant.Note }); CheckEqual(new[] { "done 7", "rested" }, observed); } public void RestartInsideACallToAnotherArc_BehavesTheSame() { string[] observed = TickWithATwinReloadedEachTime( new RepeatingOperatorScript { Plant = new PlantScript() }, new[] { "", "", "+go", "", RestartStep, "", "+go", "" }, script => new[] { script.Result, script.Plant.Note, script.Plant.Updates.ToString() }); CheckEqual(new[] { null, "updated after 1", "3" }, observed); } } } }