From 5e7353e2e19c325bd1b75ffee103045874216540 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Sun, 1 Sep 2024 22:10:45 -0500 Subject: [PATCH 01/20] add first tests. only one of them works, because: - there is no domain reload between tests - the SaveManager stores state in a singleton, so it only resets on domain reload - there is no way to deregister a saveable if it is destroyed --- Tests.meta | 3 + Tests/Runtime.meta | 3 + Tests/Runtime/AsyncToCoroutine.cs | 21 ++++++ Tests/Runtime/AsyncToCoroutine.cs.meta | 3 + Tests/Runtime/AwaitableExtensions.cs | 14 ++++ Tests/Runtime/AwaitableExtensions.cs.meta | 3 + Tests/Runtime/BUCK.SaveAsync.Tests.asmdef | 11 ++++ .../Runtime/BUCK.SaveAsync.Tests.asmdef.meta | 3 + Tests/Runtime/InMemoryFileHandler.cs | 54 +++++++++++++++ Tests/Runtime/InMemoryFileHandler.cs.meta | 3 + .../SaveManagerReflectionExtensions.cs | 35 ++++++++++ .../SaveManagerReflectionExtensions.cs.meta | 3 + Tests/Runtime/TestRoundTripSaveLoad.cs | 65 +++++++++++++++++++ Tests/Runtime/TestRoundTripSaveLoad.cs.meta | 3 + Tests/Runtime/TestSaveableEntity.cs | 24 +++++++ Tests/Runtime/TestSaveableEntity.cs.meta | 3 + 16 files changed, 251 insertions(+) create mode 100644 Tests.meta create mode 100644 Tests/Runtime.meta create mode 100644 Tests/Runtime/AsyncToCoroutine.cs create mode 100644 Tests/Runtime/AsyncToCoroutine.cs.meta create mode 100644 Tests/Runtime/AwaitableExtensions.cs create mode 100644 Tests/Runtime/AwaitableExtensions.cs.meta create mode 100644 Tests/Runtime/BUCK.SaveAsync.Tests.asmdef create mode 100644 Tests/Runtime/BUCK.SaveAsync.Tests.asmdef.meta create mode 100644 Tests/Runtime/InMemoryFileHandler.cs create mode 100644 Tests/Runtime/InMemoryFileHandler.cs.meta create mode 100644 Tests/Runtime/SaveManagerReflectionExtensions.cs create mode 100644 Tests/Runtime/SaveManagerReflectionExtensions.cs.meta create mode 100644 Tests/Runtime/TestRoundTripSaveLoad.cs create mode 100644 Tests/Runtime/TestRoundTripSaveLoad.cs.meta create mode 100644 Tests/Runtime/TestSaveableEntity.cs create mode 100644 Tests/Runtime/TestSaveableEntity.cs.meta diff --git a/Tests.meta b/Tests.meta new file mode 100644 index 0000000..88deba7 --- /dev/null +++ b/Tests.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3220b088ee9d4e60826b78a6544fe1d4 +timeCreated: 1725243685 \ No newline at end of file diff --git a/Tests/Runtime.meta b/Tests/Runtime.meta new file mode 100644 index 0000000..09c2645 --- /dev/null +++ b/Tests/Runtime.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3e8afb4fbe54475f964f2abea3160a68 +timeCreated: 1725243792 \ No newline at end of file diff --git a/Tests/Runtime/AsyncToCoroutine.cs b/Tests/Runtime/AsyncToCoroutine.cs new file mode 100644 index 0000000..c347772 --- /dev/null +++ b/Tests/Runtime/AsyncToCoroutine.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections; +using System.Threading.Tasks; + +namespace Tests.Runtime +{ + /// + /// Functions to allow writing a test as an async function, but run it under UnityTest as a coroutine. + /// + public class AsyncToCoroutine + { + public static IEnumerator AsCoroutine(Func taskFactory) + { + Task task = taskFactory(); + while (!task.IsCompleted) + { + yield return null; + } + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/AsyncToCoroutine.cs.meta b/Tests/Runtime/AsyncToCoroutine.cs.meta new file mode 100644 index 0000000..679e66c --- /dev/null +++ b/Tests/Runtime/AsyncToCoroutine.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 10a6e932f5cf46b7b4b3a3ebf2bbf027 +timeCreated: 1725245967 \ No newline at end of file diff --git a/Tests/Runtime/AwaitableExtensions.cs b/Tests/Runtime/AwaitableExtensions.cs new file mode 100644 index 0000000..c8da778 --- /dev/null +++ b/Tests/Runtime/AwaitableExtensions.cs @@ -0,0 +1,14 @@ +using System; +using System.Threading; +using UnityEngine; + +namespace Tests.Runtime +{ + public class AwaitableExtensions + { + public static Awaitable Delay(TimeSpan time, CancellationToken cancellationToken) + { + return Awaitable.WaitForSecondsAsync((float)time.TotalSeconds, cancellationToken); + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/AwaitableExtensions.cs.meta b/Tests/Runtime/AwaitableExtensions.cs.meta new file mode 100644 index 0000000..36787fa --- /dev/null +++ b/Tests/Runtime/AwaitableExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3b5d10f6523345aea647bd921bba4e0b +timeCreated: 1725244747 \ No newline at end of file diff --git a/Tests/Runtime/BUCK.SaveAsync.Tests.asmdef b/Tests/Runtime/BUCK.SaveAsync.Tests.asmdef new file mode 100644 index 0000000..ac58d1f --- /dev/null +++ b/Tests/Runtime/BUCK.SaveAsync.Tests.asmdef @@ -0,0 +1,11 @@ +{ + "name": "BUCK.SaveAsync.Tests", + "references": [ + "BUCK.SaveAsync.Runtime" + ], + "optionalUnityReferences": [ + "TestAssemblies" + ], + "includePlatforms": [], + "excludePlatforms": [] +} \ No newline at end of file diff --git a/Tests/Runtime/BUCK.SaveAsync.Tests.asmdef.meta b/Tests/Runtime/BUCK.SaveAsync.Tests.asmdef.meta new file mode 100644 index 0000000..c01085b --- /dev/null +++ b/Tests/Runtime/BUCK.SaveAsync.Tests.asmdef.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 06d4c76cc3914fce9036eb37a7ec33ae +timeCreated: 1725243828 \ No newline at end of file diff --git a/Tests/Runtime/InMemoryFileHandler.cs b/Tests/Runtime/InMemoryFileHandler.cs new file mode 100644 index 0000000..9b98a5d --- /dev/null +++ b/Tests/Runtime/InMemoryFileHandler.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Buck.SaveAsync; + +namespace Tests.Runtime +{ + /// + /// A FileHandler used for testing which does not write anything to disk, but instead + /// stores data in memory. Can be configured to simulate slow operations. + /// + public class InMemoryFileHandler : FileHandler + { + public TimeSpan AllOperationDelay { get; set; } = TimeSpan.Zero; + + Dictionary m_files = new(); + + protected override string GetPath(string pathOrFilename) + { + return pathOrFilename; + } + + public override async Task Exists(string pathOrFilename, CancellationToken cancellationToken) + { + await AwaitableExtensions.Delay(AllOperationDelay, cancellationToken); + return m_files.ContainsKey(pathOrFilename); + } + + public override async Task WriteFile(string pathOrFilename, string content, CancellationToken cancellationToken) + { + await AwaitableExtensions.Delay(AllOperationDelay, cancellationToken); + m_files[pathOrFilename] = content; + } + + public override async Task ReadFile(string pathOrFilename, CancellationToken cancellationToken) + { + await AwaitableExtensions.Delay(AllOperationDelay, cancellationToken); + return m_files[pathOrFilename] ?? ""; + } + + public override async Task Erase(string pathOrFilename, CancellationToken cancellationToken) + { + await AwaitableExtensions.Delay(AllOperationDelay, cancellationToken); + m_files[pathOrFilename] = ""; + } + + public override void Delete(string pathOrFilename) + { + // Delete is sync, no delay + m_files.Remove(pathOrFilename); + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/InMemoryFileHandler.cs.meta b/Tests/Runtime/InMemoryFileHandler.cs.meta new file mode 100644 index 0000000..6103d4d --- /dev/null +++ b/Tests/Runtime/InMemoryFileHandler.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 11bcd2d87ab941bc87862dff5eb716d5 +timeCreated: 1725244017 \ No newline at end of file diff --git a/Tests/Runtime/SaveManagerReflectionExtensions.cs b/Tests/Runtime/SaveManagerReflectionExtensions.cs new file mode 100644 index 0000000..63fdd8b --- /dev/null +++ b/Tests/Runtime/SaveManagerReflectionExtensions.cs @@ -0,0 +1,35 @@ +using System; +using Buck.SaveAsync; +using UnityEngine; + +namespace Tests.Runtime +{ + /// + /// These are extensions to configure the SaveManager in potentially nonstandard ways, required in order to test it. + /// Could be placed inside SaveManager if it is appropriate to expose a public API for these functions, rather than a testing-only API. + /// + public static class SaveManagerReflectionExtensions + { + /// + /// Set the static file handler used by the SaveManager singleton. + /// + /// + public static void SetCustomFileHandler(FileHandler newFileHander) + { + if (!newFileHander) + { + throw new System.ArgumentNullException(nameof(newFileHander)); + } + + var fileHandlerField = typeof(SaveManager) + .GetField("m_fileHandler", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + if (fileHandlerField == null) + { + throw new InvalidOperationException("Could not find static private field 'm_fileHandler' on SaveManager."); + } + fileHandlerField.SetValue(null, newFileHander); + } + + + } +} \ No newline at end of file diff --git a/Tests/Runtime/SaveManagerReflectionExtensions.cs.meta b/Tests/Runtime/SaveManagerReflectionExtensions.cs.meta new file mode 100644 index 0000000..6fea56d --- /dev/null +++ b/Tests/Runtime/SaveManagerReflectionExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a4b127ef92c3444e8406b129190e86e8 +timeCreated: 1725245422 \ No newline at end of file diff --git a/Tests/Runtime/TestRoundTripSaveLoad.cs b/Tests/Runtime/TestRoundTripSaveLoad.cs new file mode 100644 index 0000000..15eeb95 --- /dev/null +++ b/Tests/Runtime/TestRoundTripSaveLoad.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Buck.SaveAsync; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Tests.Runtime +{ + public class TestRoundTripSaveLoad + { + [UnityTest] + public IEnumerator TestSaveSystem_WhenSavesState_AndChangesState_RestoresState() + => AsyncToCoroutine.AsCoroutine(async () => + { + // Arrange + var fileHandler = ScriptableObject.CreateInstance(); + fileHandler.AllOperationDelay = TimeSpan.Zero; + + var saveManagerGo = new GameObject(); + var saveManager = saveManagerGo.AddComponent(); + SaveManagerReflectionExtensions.SetCustomFileHandler(fileHandler); + + var saveableEntity = new GameObject(); + var saveable = saveableEntity.AddComponent(); + saveable.Filename = "test.dat"; + saveable.CurrentState = "Hello, World!"; + await SaveManager.Save("test.dat"); + + // Act + saveable.CurrentState = "Goodbye, World!"; + await SaveManager.Load("test.dat"); + + // Assert + Assert.AreEqual("Hello, World!", saveable.CurrentState); + }); + + [UnityTest] + public IEnumerator TestSaveSystem_WhenSavesState_AndChangesState_RestoresState_WithDelay() + => AsyncToCoroutine.AsCoroutine(async () => + { + // Arrange + var fileHandler = ScriptableObject.CreateInstance(); + fileHandler.AllOperationDelay = TimeSpan.FromSeconds(0.3f); + + var saveManagerGo = new GameObject(); + var saveManager = saveManagerGo.AddComponent(); + SaveManagerReflectionExtensions.SetCustomFileHandler(fileHandler); + + var saveableEntity = new GameObject(); + var saveable = saveableEntity.AddComponent(); + saveable.Filename = "test.dat"; + saveable.CurrentState = "Hello, World!"; + await SaveManager.Save("test.dat"); + + // Act + saveable.CurrentState = "Goodbye, World!"; + await SaveManager.Load("test.dat"); + + // Assert + Assert.AreEqual("Hello, World!", saveable.CurrentState); + }); + } +} \ No newline at end of file diff --git a/Tests/Runtime/TestRoundTripSaveLoad.cs.meta b/Tests/Runtime/TestRoundTripSaveLoad.cs.meta new file mode 100644 index 0000000..1972a33 --- /dev/null +++ b/Tests/Runtime/TestRoundTripSaveLoad.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a41fed8bd4e443518725d337830d0661 +timeCreated: 1725243954 \ No newline at end of file diff --git a/Tests/Runtime/TestSaveableEntity.cs b/Tests/Runtime/TestSaveableEntity.cs new file mode 100644 index 0000000..ffeb924 --- /dev/null +++ b/Tests/Runtime/TestSaveableEntity.cs @@ -0,0 +1,24 @@ +using System; +using Buck.SaveAsync; +using UnityEngine; + +namespace Tests.Runtime +{ + /// + /// A test monobehavior that implements saveable. Its internals are exposed, so it can be piloted + /// from a test case. + /// + public class TestSaveableEntity : MonoBehaviour, ISaveable + { + public string Key { get; set; } = nameof(TestSaveableEntity); + public string Filename { get; set; } + public object CurrentState { get; set; } + public object CaptureState() => CurrentState; + public void RestoreState(object state) => this.CurrentState = state; + + void Awake() + { + SaveManager.RegisterSaveable(this); + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/TestSaveableEntity.cs.meta b/Tests/Runtime/TestSaveableEntity.cs.meta new file mode 100644 index 0000000..4ed4ed0 --- /dev/null +++ b/Tests/Runtime/TestSaveableEntity.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 41bd6787a3a447d394ae94b49cc7d3b7 +timeCreated: 1725244973 \ No newline at end of file From 771b2797c96327468ce25ac22a27a5c156211873 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Sun, 1 Sep 2024 22:20:29 -0500 Subject: [PATCH 02/20] got 2nd test working, by forcing per-test unique keys --- Tests/Runtime/SaveManagerReflectionExtensions.cs | 2 -- Tests/Runtime/TestRoundTripSaveLoad.cs | 8 ++++++++ Tests/Runtime/TestSaveableEntity.cs | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Tests/Runtime/SaveManagerReflectionExtensions.cs b/Tests/Runtime/SaveManagerReflectionExtensions.cs index 63fdd8b..32ecf4d 100644 --- a/Tests/Runtime/SaveManagerReflectionExtensions.cs +++ b/Tests/Runtime/SaveManagerReflectionExtensions.cs @@ -29,7 +29,5 @@ public static void SetCustomFileHandler(FileHandler newFileHander) } fileHandlerField.SetValue(null, newFileHander); } - - } } \ No newline at end of file diff --git a/Tests/Runtime/TestRoundTripSaveLoad.cs b/Tests/Runtime/TestRoundTripSaveLoad.cs index 15eeb95..46e0363 100644 --- a/Tests/Runtime/TestRoundTripSaveLoad.cs +++ b/Tests/Runtime/TestRoundTripSaveLoad.cs @@ -15,6 +15,8 @@ public IEnumerator TestSaveSystem_WhenSavesState_AndChangesState_RestoresState() => AsyncToCoroutine.AsCoroutine(async () => { // Arrange + var seed = Guid.NewGuid().ToString(); + var fileHandler = ScriptableObject.CreateInstance(); fileHandler.AllOperationDelay = TimeSpan.Zero; @@ -24,8 +26,10 @@ public IEnumerator TestSaveSystem_WhenSavesState_AndChangesState_RestoresState() var saveableEntity = new GameObject(); var saveable = saveableEntity.AddComponent(); + saveable.Key = "saveable_" + seed; saveable.Filename = "test.dat"; saveable.CurrentState = "Hello, World!"; + saveable.RegisterSelf(); await SaveManager.Save("test.dat"); // Act @@ -41,6 +45,8 @@ public IEnumerator TestSaveSystem_WhenSavesState_AndChangesState_RestoresState_W => AsyncToCoroutine.AsCoroutine(async () => { // Arrange + var seed = Guid.NewGuid().ToString(); + var fileHandler = ScriptableObject.CreateInstance(); fileHandler.AllOperationDelay = TimeSpan.FromSeconds(0.3f); @@ -50,8 +56,10 @@ public IEnumerator TestSaveSystem_WhenSavesState_AndChangesState_RestoresState_W var saveableEntity = new GameObject(); var saveable = saveableEntity.AddComponent(); + saveable.Key = "saveable_" + seed; saveable.Filename = "test.dat"; saveable.CurrentState = "Hello, World!"; + saveable.RegisterSelf(); await SaveManager.Save("test.dat"); // Act diff --git a/Tests/Runtime/TestSaveableEntity.cs b/Tests/Runtime/TestSaveableEntity.cs index ffeb924..edb8f77 100644 --- a/Tests/Runtime/TestSaveableEntity.cs +++ b/Tests/Runtime/TestSaveableEntity.cs @@ -16,7 +16,7 @@ public class TestSaveableEntity : MonoBehaviour, ISaveable public object CaptureState() => CurrentState; public void RestoreState(object state) => this.CurrentState = state; - void Awake() + public void RegisterSelf() { SaveManager.RegisterSaveable(this); } From 274b1a0e3b3cdfa9eb60b3fed8a294430a6c3fad Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Sun, 1 Sep 2024 22:42:41 -0500 Subject: [PATCH 03/20] fix swallowing exceptions in the async to coroutine converter improve async -> coroutine by reading the UniTask library source code --- Tests/Runtime/AsyncToCoroutine.cs | 55 +++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/Tests/Runtime/AsyncToCoroutine.cs b/Tests/Runtime/AsyncToCoroutine.cs index c347772..b4c9e90 100644 --- a/Tests/Runtime/AsyncToCoroutine.cs +++ b/Tests/Runtime/AsyncToCoroutine.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Runtime.ExceptionServices; using System.Threading.Tasks; namespace Tests.Runtime @@ -11,10 +12,58 @@ public class AsyncToCoroutine { public static IEnumerator AsCoroutine(Func taskFactory) { - Task task = taskFactory(); - while (!task.IsCompleted) + return new ToCoroutineEnumerator(taskFactory()); + } + sealed class ToCoroutineEnumerator : IEnumerator + { + bool completed; + Task task; + bool isStarted = false; + ExceptionDispatchInfo exception; + + public ToCoroutineEnumerator(Task task) + { + completed = false; + this.task = task; + } + + async void RunTask(Task task) + { + try + { + await task; + } + catch (Exception ex) + { + this.exception = ExceptionDispatchInfo.Capture(ex); + } + finally + { + completed = true; + } + } + + public object Current => null; + + public bool MoveNext() + { + if (!isStarted) + { + isStarted = true; + RunTask(task); + } + + if (exception != null) + { + exception.Throw(); + return false; + } + + return !completed; + } + + void IEnumerator.Reset() { - yield return null; } } } From f930eefc27095a3395644ff9489fe4d3c0906064 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Sun, 1 Sep 2024 22:48:41 -0500 Subject: [PATCH 04/20] fix hidden exception. the Awaitable WaitForSecondsAsync api is incompatible with running on different threads. instead relying on C# System.Threading.Tasks --- Tests/Runtime/AwaitableExtensions.cs | 4 ---- Tests/Runtime/InMemoryFileHandler.cs | 8 ++++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/Tests/Runtime/AwaitableExtensions.cs b/Tests/Runtime/AwaitableExtensions.cs index c8da778..a032f8c 100644 --- a/Tests/Runtime/AwaitableExtensions.cs +++ b/Tests/Runtime/AwaitableExtensions.cs @@ -6,9 +6,5 @@ namespace Tests.Runtime { public class AwaitableExtensions { - public static Awaitable Delay(TimeSpan time, CancellationToken cancellationToken) - { - return Awaitable.WaitForSecondsAsync((float)time.TotalSeconds, cancellationToken); - } } } \ No newline at end of file diff --git a/Tests/Runtime/InMemoryFileHandler.cs b/Tests/Runtime/InMemoryFileHandler.cs index 9b98a5d..6733817 100644 --- a/Tests/Runtime/InMemoryFileHandler.cs +++ b/Tests/Runtime/InMemoryFileHandler.cs @@ -23,25 +23,25 @@ protected override string GetPath(string pathOrFilename) public override async Task Exists(string pathOrFilename, CancellationToken cancellationToken) { - await AwaitableExtensions.Delay(AllOperationDelay, cancellationToken); + await Task.Delay(AllOperationDelay, cancellationToken); return m_files.ContainsKey(pathOrFilename); } public override async Task WriteFile(string pathOrFilename, string content, CancellationToken cancellationToken) { - await AwaitableExtensions.Delay(AllOperationDelay, cancellationToken); + await Task.Delay(AllOperationDelay, cancellationToken); m_files[pathOrFilename] = content; } public override async Task ReadFile(string pathOrFilename, CancellationToken cancellationToken) { - await AwaitableExtensions.Delay(AllOperationDelay, cancellationToken); + await Task.Delay(AllOperationDelay, cancellationToken); return m_files[pathOrFilename] ?? ""; } public override async Task Erase(string pathOrFilename, CancellationToken cancellationToken) { - await AwaitableExtensions.Delay(AllOperationDelay, cancellationToken); + await Task.Delay(AllOperationDelay, cancellationToken); m_files[pathOrFilename] = ""; } From bbb2cc7e7253140494581ea2777d01aac19b6e02 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Sun, 1 Sep 2024 23:01:09 -0500 Subject: [PATCH 05/20] add presumed best-case save file format tests, failing currently --- Tests/Runtime/StringDiffUtils.cs | 45 +++++++++ Tests/Runtime/StringDiffUtils.cs.meta | 3 + Tests/Runtime/TestSaveFileFormat.cs | 116 +++++++++++++++++++++++ Tests/Runtime/TestSaveFileFormat.cs.meta | 3 + 4 files changed, 167 insertions(+) create mode 100644 Tests/Runtime/StringDiffUtils.cs create mode 100644 Tests/Runtime/StringDiffUtils.cs.meta create mode 100644 Tests/Runtime/TestSaveFileFormat.cs create mode 100644 Tests/Runtime/TestSaveFileFormat.cs.meta diff --git a/Tests/Runtime/StringDiffUtils.cs b/Tests/Runtime/StringDiffUtils.cs new file mode 100644 index 0000000..22ac5ed --- /dev/null +++ b/Tests/Runtime/StringDiffUtils.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; + +namespace Tests.Runtime +{ + public class StringDiffUtils + { + public static void AssertMultilineStringEqual(string expected, string actual) + { + expected = expected.Trim(); + actual = actual.Trim(); + if (expected == actual) return; + Assert.Fail(StringEqualErrorMessage(expected, actual)); + } + + public static string StringEqualErrorMessage(string expected, string actual) + { + var errorMessage = new StringBuilder(); + errorMessage.AppendLine($"#### Expected ####\n{expected}\n#### Actual ####\n{actual}"); + var expectedStrLines = expected.Split('\n'); + var actualStrLines = actual.Split('\n'); + if (expectedStrLines.Length == actualStrLines.Length) + { + foreach (var (index, expectedLine, actualLine) in DiffLines(expectedStrLines, actualStrLines)) + { + var addlMessage = $"##{index}##\n- {expectedLine}\n+ {actualLine}"; + errorMessage.AppendLine(addlMessage); + } + } + return errorMessage.ToString(); + } + + private static IEnumerable<(int index, string expected, string actual)> DiffLines(string[] expected, string[] actual) + { + for (var i = 0; i < expected.Length; i++) + { + var expectedLine = expected[i].Trim(); + var actualLine = actual[i].Trim(); + if(expectedLine == actualLine) continue; + yield return (i, expectedLine, actualLine); + } + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/StringDiffUtils.cs.meta b/Tests/Runtime/StringDiffUtils.cs.meta new file mode 100644 index 0000000..7e4d09f --- /dev/null +++ b/Tests/Runtime/StringDiffUtils.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a7adc162acf04c88b8df54e713265688 +timeCreated: 1725247693 \ No newline at end of file diff --git a/Tests/Runtime/TestSaveFileFormat.cs b/Tests/Runtime/TestSaveFileFormat.cs new file mode 100644 index 0000000..055a3cf --- /dev/null +++ b/Tests/Runtime/TestSaveFileFormat.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Buck.SaveAsync; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Tests.Runtime +{ + public class TestSaveFileFormat + { + async Task GetSerializedFileForObject(string key, object savedObject) + { + var fileName = Guid.NewGuid() + ".dat"; + + var fileHandler = ScriptableObject.CreateInstance(); + fileHandler.AllOperationDelay = TimeSpan.Zero; + + var saveManagerGo = new GameObject(); + var saveManager = saveManagerGo.AddComponent(); + SaveManagerReflectionExtensions.SetCustomFileHandler(fileHandler); + + var saveableEntity = new GameObject(); + var saveable = saveableEntity.AddComponent(); + saveable.Key = key; + saveable.Filename = fileName; + saveable.CurrentState = savedObject; + saveable.RegisterSelf(); + await SaveManager.Save(fileName); + + return await fileHandler.ReadFile(fileName, CancellationToken.None); + } + + [UnityTest] + public IEnumerator TestSaveSystem_WhenSavesNestedDictionary_SavesNestedJson() + => AsyncToCoroutine.AsCoroutine(async () => + { + // Arrange + var nestedObject = new Dictionary + { + { "key1", "value1" }, + { "key2", 2 }, + { + "key3", new Dictionary + { + { "key4", "value4" }, + { "key5", 5 } + } + } + }; + + // Act + var key = Guid.NewGuid().ToString(); + var serializedFile = await GetSerializedFileForObject(key, nestedObject); + + // Assert + var expected = $@" +[ + {{ + ""key"": ""{key}"", + ""data"": {{ + ""key1"": ""value1"", + ""key2"": 2, + ""key3"": {{ + ""key4"": ""value4"", + ""key5"": 5 + }} + }} + }} +] +"; + StringDiffUtils.AssertMultilineStringEqual(expected,serializedFile); + }); + + class TestSaveObject + { + public int IntValue { get; set; } + public string StringValue { get; set; } + public Vector3 Vector3Value { get; set; } + } + [UnityTest] + public IEnumerator TestSaveSystem_WhenSavesObject_SavesJson() + => AsyncToCoroutine.AsCoroutine(async () => + { + // Arrange + var nestedObject = new TestSaveObject + { + IntValue = 1337, + StringValue = "Goodbye, World!", + Vector3Value = new Vector3(1, 2, 3.5f) + }; + + // Act + var key = Guid.NewGuid().ToString(); + var serializedFile = await GetSerializedFileForObject(key, nestedObject); + + // Assert + var expected = $@" +[ + {{ + ""key"": ""{key}"", + ""data"": {{ + ""intValue"": 1337, + ""stringValue"": ""Goodbye, World!"", + ""vector3Value"": {{x: 1, y: 2, z: 3.5}} + }} + }} +] +"; + StringDiffUtils.AssertMultilineStringEqual(expected,serializedFile); + }); + + } +} \ No newline at end of file diff --git a/Tests/Runtime/TestSaveFileFormat.cs.meta b/Tests/Runtime/TestSaveFileFormat.cs.meta new file mode 100644 index 0000000..0c18fc0 --- /dev/null +++ b/Tests/Runtime/TestSaveFileFormat.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6596da966b6e4250a735feb06943a76d +timeCreated: 1725247266 \ No newline at end of file From efde45520eba6d5e0938bd175e0edd59db93f72a Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Sun, 1 Sep 2024 23:27:54 -0500 Subject: [PATCH 06/20] fix the save file format tests to conform w/ current function w/o the unity json converters --- Tests/Runtime/TestConstants.cs | 8 +++ Tests/Runtime/TestConstants.cs.meta | 3 ++ Tests/Runtime/TestSaveFileFormat.cs | 52 +++++++++----------- Tests/Runtime/TestTools.meta | 8 +++ Tests/Runtime/TestTools/TestCaseBase.cs | 49 ++++++++++++++++++ Tests/Runtime/TestTools/TestCaseBase.cs.meta | 2 + 6 files changed, 92 insertions(+), 30 deletions(-) create mode 100644 Tests/Runtime/TestConstants.cs create mode 100644 Tests/Runtime/TestConstants.cs.meta create mode 100644 Tests/Runtime/TestTools.meta create mode 100644 Tests/Runtime/TestTools/TestCaseBase.cs create mode 100644 Tests/Runtime/TestTools/TestCaseBase.cs.meta diff --git a/Tests/Runtime/TestConstants.cs b/Tests/Runtime/TestConstants.cs new file mode 100644 index 0000000..9744410 --- /dev/null +++ b/Tests/Runtime/TestConstants.cs @@ -0,0 +1,8 @@ +namespace Buck.SaveAsync.Tests +{ + public static class TestConstants + { + public static string Namespace => "Buck.SaveAsync.Tests"; + public static string Assembly => "BUCK.SaveAsync.Tests"; + } +} \ No newline at end of file diff --git a/Tests/Runtime/TestConstants.cs.meta b/Tests/Runtime/TestConstants.cs.meta new file mode 100644 index 0000000..590358a --- /dev/null +++ b/Tests/Runtime/TestConstants.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 13c2e224a35d46acad45b370dcd4b1c0 +timeCreated: 1725250898 \ No newline at end of file diff --git a/Tests/Runtime/TestSaveFileFormat.cs b/Tests/Runtime/TestSaveFileFormat.cs index 055a3cf..b5e86b3 100644 --- a/Tests/Runtime/TestSaveFileFormat.cs +++ b/Tests/Runtime/TestSaveFileFormat.cs @@ -7,27 +7,25 @@ using UnityEngine; using UnityEngine.TestTools; -namespace Tests.Runtime +namespace Buck.SaveAsync.Tests { - public class TestSaveFileFormat + internal class TestSaveObject + { + public int IntValue { get; set; } + public string StringValue { get; set; } + } + + public class TestSaveFileFormat : TestCaseBase { async Task GetSerializedFileForObject(string key, object savedObject) { var fileName = Guid.NewGuid() + ".dat"; - - var fileHandler = ScriptableObject.CreateInstance(); - fileHandler.AllOperationDelay = TimeSpan.Zero; - - var saveManagerGo = new GameObject(); - var saveManager = saveManagerGo.AddComponent(); - SaveManagerReflectionExtensions.SetCustomFileHandler(fileHandler); - var saveableEntity = new GameObject(); - var saveable = saveableEntity.AddComponent(); - saveable.Key = key; - saveable.Filename = fileName; + var fileHandler = CreateFileHandler(); + SetupSaveManager(fileHandler); + + var saveable = CreateSaveableEntity(key, fileName); saveable.CurrentState = savedObject; - saveable.RegisterSelf(); await SaveManager.Save(fileName); return await fileHandler.ReadFile(fileName, CancellationToken.None); @@ -59,11 +57,13 @@ public IEnumerator TestSaveSystem_WhenSavesNestedDictionary_SavesNestedJson() var expected = $@" [ {{ - ""key"": ""{key}"", - ""data"": {{ + ""Key"": ""{key}"", + ""Data"": {{ + ""$type"": ""System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Object, mscorlib]], mscorlib"", ""key1"": ""value1"", ""key2"": 2, ""key3"": {{ + ""$type"": ""System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Object, mscorlib]], mscorlib"", ""key4"": ""value4"", ""key5"": 5 }} @@ -74,12 +74,6 @@ public IEnumerator TestSaveSystem_WhenSavesNestedDictionary_SavesNestedJson() StringDiffUtils.AssertMultilineStringEqual(expected,serializedFile); }); - class TestSaveObject - { - public int IntValue { get; set; } - public string StringValue { get; set; } - public Vector3 Vector3Value { get; set; } - } [UnityTest] public IEnumerator TestSaveSystem_WhenSavesObject_SavesJson() => AsyncToCoroutine.AsCoroutine(async () => @@ -88,8 +82,7 @@ public IEnumerator TestSaveSystem_WhenSavesObject_SavesJson() var nestedObject = new TestSaveObject { IntValue = 1337, - StringValue = "Goodbye, World!", - Vector3Value = new Vector3(1, 2, 3.5f) + StringValue = "Goodbye, World!" }; // Act @@ -100,17 +93,16 @@ public IEnumerator TestSaveSystem_WhenSavesObject_SavesJson() var expected = $@" [ {{ - ""key"": ""{key}"", - ""data"": {{ - ""intValue"": 1337, - ""stringValue"": ""Goodbye, World!"", - ""vector3Value"": {{x: 1, y: 2, z: 3.5}} + ""Key"": ""{key}"", + ""Data"": {{ + ""$type"": ""{TestConstants.Namespace}.TestSaveObject, {TestConstants.Assembly}"", + ""IntValue"": 1337, + ""StringValue"": ""Goodbye, World!"" }} }} ] "; StringDiffUtils.AssertMultilineStringEqual(expected,serializedFile); }); - } } \ No newline at end of file diff --git a/Tests/Runtime/TestTools.meta b/Tests/Runtime/TestTools.meta new file mode 100644 index 0000000..32a220c --- /dev/null +++ b/Tests/Runtime/TestTools.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3377ad339e18464ba08c76a0ef386df1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/TestTools/TestCaseBase.cs b/Tests/Runtime/TestTools/TestCaseBase.cs new file mode 100644 index 0000000..97b057b --- /dev/null +++ b/Tests/Runtime/TestTools/TestCaseBase.cs @@ -0,0 +1,49 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Tests.Runtime; +using UnityEngine; + +namespace Buck.SaveAsync.Tests +{ + public class TestCaseBase + { + protected void SetupSaveManager(FileHandler withFileHandler) + { + var saveManagerGo = new GameObject(); + var saveManager = saveManagerGo.AddComponent(); + SaveManagerReflectionExtensions.SetCustomFileHandler(withFileHandler); + } + + protected FileHandler CreateFileHandler(TimeSpan? withEmulatedDelay = null) + { + var fileHandler = ScriptableObject.CreateInstance(); + fileHandler.AllOperationDelay = withEmulatedDelay ?? TimeSpan.Zero; + return fileHandler; + } + + public TestSaveableEntity CreateSaveableEntity(string key, string filename = "test.dat") + { + var saveableEntity = new GameObject(); + var saveable = saveableEntity.AddComponent(); + saveable.Key = key; + saveable.Filename = filename; + saveable.RegisterSelf(); + return saveable; + } + + protected async Task GetSerializedFileForObject(string key, object savedObject) + { + var fileName = Guid.NewGuid() + ".dat"; + + var fileHandler = CreateFileHandler(); + SetupSaveManager(fileHandler); + + var saveable = CreateSaveableEntity(key, fileName); + saveable.CurrentState = savedObject; + await SaveManager.Save(fileName); + + return await fileHandler.ReadFile(fileName, CancellationToken.None); + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/TestTools/TestCaseBase.cs.meta b/Tests/Runtime/TestTools/TestCaseBase.cs.meta new file mode 100644 index 0000000..39a7213 --- /dev/null +++ b/Tests/Runtime/TestTools/TestCaseBase.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b938ab02942849599696dc5ffaf9e899 \ No newline at end of file From e8952d02a472d17d5ec6243606e59bdba250470c Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Sun, 1 Sep 2024 23:08:28 -0500 Subject: [PATCH 07/20] DRY up the test cases organize test files. extract a shared function to a base test class add some documentation to the tests --- Tests/Runtime/AwaitableExtensions.cs | 10 ----- Tests/Runtime/AwaitableExtensions.cs.meta | 3 -- Tests/Runtime/BUCK.SaveAsync.Tests.asmdef | 1 + Tests/Runtime/TestRoundTripSaveLoad.cs | 38 +++++-------------- Tests/Runtime/TestSaveFileFormat.cs | 19 ++-------- Tests/Runtime/TestTools.meta | 7 +--- .../{ => TestTools}/AsyncToCoroutine.cs | 2 +- .../{ => TestTools}/AsyncToCoroutine.cs.meta | 0 .../{ => TestTools}/InMemoryFileHandler.cs | 3 +- .../InMemoryFileHandler.cs.meta | 0 .../SaveManagerReflectionExtensions.cs | 4 +- .../SaveManagerReflectionExtensions.cs.meta | 0 .../{ => TestTools}/StringDiffUtils.cs | 2 +- .../{ => TestTools}/StringDiffUtils.cs.meta | 0 Tests/Runtime/TestTools/TestCaseBase.cs | 1 - Tests/Runtime/TestTools/TestCaseBase.cs.meta | 3 +- .../{ => TestTools}/TestSaveableEntity.cs | 4 +- .../TestSaveableEntity.cs.meta | 0 18 files changed, 23 insertions(+), 74 deletions(-) delete mode 100644 Tests/Runtime/AwaitableExtensions.cs delete mode 100644 Tests/Runtime/AwaitableExtensions.cs.meta rename Tests/Runtime/{ => TestTools}/AsyncToCoroutine.cs (98%) rename Tests/Runtime/{ => TestTools}/AsyncToCoroutine.cs.meta (100%) rename Tests/Runtime/{ => TestTools}/InMemoryFileHandler.cs (97%) rename Tests/Runtime/{ => TestTools}/InMemoryFileHandler.cs.meta (100%) rename Tests/Runtime/{ => TestTools}/SaveManagerReflectionExtensions.cs (95%) rename Tests/Runtime/{ => TestTools}/SaveManagerReflectionExtensions.cs.meta (100%) rename Tests/Runtime/{ => TestTools}/StringDiffUtils.cs (98%) rename Tests/Runtime/{ => TestTools}/StringDiffUtils.cs.meta (100%) rename Tests/Runtime/{ => TestTools}/TestSaveableEntity.cs (91%) rename Tests/Runtime/{ => TestTools}/TestSaveableEntity.cs.meta (100%) diff --git a/Tests/Runtime/AwaitableExtensions.cs b/Tests/Runtime/AwaitableExtensions.cs deleted file mode 100644 index a032f8c..0000000 --- a/Tests/Runtime/AwaitableExtensions.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Threading; -using UnityEngine; - -namespace Tests.Runtime -{ - public class AwaitableExtensions - { - } -} \ No newline at end of file diff --git a/Tests/Runtime/AwaitableExtensions.cs.meta b/Tests/Runtime/AwaitableExtensions.cs.meta deleted file mode 100644 index 36787fa..0000000 --- a/Tests/Runtime/AwaitableExtensions.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 3b5d10f6523345aea647bd921bba4e0b -timeCreated: 1725244747 \ No newline at end of file diff --git a/Tests/Runtime/BUCK.SaveAsync.Tests.asmdef b/Tests/Runtime/BUCK.SaveAsync.Tests.asmdef index ac58d1f..92ddfc5 100644 --- a/Tests/Runtime/BUCK.SaveAsync.Tests.asmdef +++ b/Tests/Runtime/BUCK.SaveAsync.Tests.asmdef @@ -1,5 +1,6 @@ { "name": "BUCK.SaveAsync.Tests", + "rootNamespace": "Buck.SaveAsync.Tests", "references": [ "BUCK.SaveAsync.Runtime" ], diff --git a/Tests/Runtime/TestRoundTripSaveLoad.cs b/Tests/Runtime/TestRoundTripSaveLoad.cs index 46e0363..1598f14 100644 --- a/Tests/Runtime/TestRoundTripSaveLoad.cs +++ b/Tests/Runtime/TestRoundTripSaveLoad.cs @@ -1,14 +1,12 @@ using System; using System.Collections; -using System.Collections.Generic; -using Buck.SaveAsync; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; -namespace Tests.Runtime +namespace Buck.SaveAsync.Tests { - public class TestRoundTripSaveLoad + public class TestRoundTripSaveLoad : TestCaseBase { [UnityTest] public IEnumerator TestSaveSystem_WhenSavesState_AndChangesState_RestoresState() @@ -17,19 +15,11 @@ public IEnumerator TestSaveSystem_WhenSavesState_AndChangesState_RestoresState() // Arrange var seed = Guid.NewGuid().ToString(); - var fileHandler = ScriptableObject.CreateInstance(); - fileHandler.AllOperationDelay = TimeSpan.Zero; - - var saveManagerGo = new GameObject(); - var saveManager = saveManagerGo.AddComponent(); - SaveManagerReflectionExtensions.SetCustomFileHandler(fileHandler); - - var saveableEntity = new GameObject(); - var saveable = saveableEntity.AddComponent(); - saveable.Key = "saveable_" + seed; - saveable.Filename = "test.dat"; + var fileHandler = CreateFileHandler(); + SetupSaveManager(fileHandler); + var saveable = CreateSaveableEntity("saveable_" + seed, "test.dat"); + saveable.CurrentState = "Hello, World!"; - saveable.RegisterSelf(); await SaveManager.Save("test.dat"); // Act @@ -47,19 +37,11 @@ public IEnumerator TestSaveSystem_WhenSavesState_AndChangesState_RestoresState_W // Arrange var seed = Guid.NewGuid().ToString(); - var fileHandler = ScriptableObject.CreateInstance(); - fileHandler.AllOperationDelay = TimeSpan.FromSeconds(0.3f); - - var saveManagerGo = new GameObject(); - var saveManager = saveManagerGo.AddComponent(); - SaveManagerReflectionExtensions.SetCustomFileHandler(fileHandler); - - var saveableEntity = new GameObject(); - var saveable = saveableEntity.AddComponent(); - saveable.Key = "saveable_" + seed; - saveable.Filename = "test.dat"; + var fileHandler = CreateFileHandler(TimeSpan.FromSeconds(0.3f)); + SetupSaveManager(fileHandler); + var saveable = CreateSaveableEntity("saveable_" + seed, "test.dat"); + saveable.CurrentState = "Hello, World!"; - saveable.RegisterSelf(); await SaveManager.Save("test.dat"); // Act diff --git a/Tests/Runtime/TestSaveFileFormat.cs b/Tests/Runtime/TestSaveFileFormat.cs index b5e86b3..f27321d 100644 --- a/Tests/Runtime/TestSaveFileFormat.cs +++ b/Tests/Runtime/TestSaveFileFormat.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Buck.SaveAsync; using UnityEngine; using UnityEngine.TestTools; @@ -15,22 +14,12 @@ internal class TestSaveObject public string StringValue { get; set; } } + /// + /// These tests verify the json format of save files generated by the save system. Useful to detect when a change + /// is introduced which may break existing saves. + /// public class TestSaveFileFormat : TestCaseBase { - async Task GetSerializedFileForObject(string key, object savedObject) - { - var fileName = Guid.NewGuid() + ".dat"; - - var fileHandler = CreateFileHandler(); - SetupSaveManager(fileHandler); - - var saveable = CreateSaveableEntity(key, fileName); - saveable.CurrentState = savedObject; - await SaveManager.Save(fileName); - - return await fileHandler.ReadFile(fileName, CancellationToken.None); - } - [UnityTest] public IEnumerator TestSaveSystem_WhenSavesNestedDictionary_SavesNestedJson() => AsyncToCoroutine.AsCoroutine(async () => diff --git a/Tests/Runtime/TestTools.meta b/Tests/Runtime/TestTools.meta index 32a220c..f1d1119 100644 --- a/Tests/Runtime/TestTools.meta +++ b/Tests/Runtime/TestTools.meta @@ -1,8 +1,3 @@ fileFormatVersion: 2 guid: 3377ad339e18464ba08c76a0ef386df1 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: +timeCreated: 1725249891 \ No newline at end of file diff --git a/Tests/Runtime/AsyncToCoroutine.cs b/Tests/Runtime/TestTools/AsyncToCoroutine.cs similarity index 98% rename from Tests/Runtime/AsyncToCoroutine.cs rename to Tests/Runtime/TestTools/AsyncToCoroutine.cs index b4c9e90..a427f3e 100644 --- a/Tests/Runtime/AsyncToCoroutine.cs +++ b/Tests/Runtime/TestTools/AsyncToCoroutine.cs @@ -3,7 +3,7 @@ using System.Runtime.ExceptionServices; using System.Threading.Tasks; -namespace Tests.Runtime +namespace Buck.SaveAsync.Tests { /// /// Functions to allow writing a test as an async function, but run it under UnityTest as a coroutine. diff --git a/Tests/Runtime/AsyncToCoroutine.cs.meta b/Tests/Runtime/TestTools/AsyncToCoroutine.cs.meta similarity index 100% rename from Tests/Runtime/AsyncToCoroutine.cs.meta rename to Tests/Runtime/TestTools/AsyncToCoroutine.cs.meta diff --git a/Tests/Runtime/InMemoryFileHandler.cs b/Tests/Runtime/TestTools/InMemoryFileHandler.cs similarity index 97% rename from Tests/Runtime/InMemoryFileHandler.cs rename to Tests/Runtime/TestTools/InMemoryFileHandler.cs index 6733817..4688bbb 100644 --- a/Tests/Runtime/InMemoryFileHandler.cs +++ b/Tests/Runtime/TestTools/InMemoryFileHandler.cs @@ -2,9 +2,8 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Buck.SaveAsync; -namespace Tests.Runtime +namespace Buck.SaveAsync.Tests { /// /// A FileHandler used for testing which does not write anything to disk, but instead diff --git a/Tests/Runtime/InMemoryFileHandler.cs.meta b/Tests/Runtime/TestTools/InMemoryFileHandler.cs.meta similarity index 100% rename from Tests/Runtime/InMemoryFileHandler.cs.meta rename to Tests/Runtime/TestTools/InMemoryFileHandler.cs.meta diff --git a/Tests/Runtime/SaveManagerReflectionExtensions.cs b/Tests/Runtime/TestTools/SaveManagerReflectionExtensions.cs similarity index 95% rename from Tests/Runtime/SaveManagerReflectionExtensions.cs rename to Tests/Runtime/TestTools/SaveManagerReflectionExtensions.cs index 32ecf4d..1e5a95e 100644 --- a/Tests/Runtime/SaveManagerReflectionExtensions.cs +++ b/Tests/Runtime/TestTools/SaveManagerReflectionExtensions.cs @@ -1,8 +1,6 @@ using System; -using Buck.SaveAsync; -using UnityEngine; -namespace Tests.Runtime +namespace Buck.SaveAsync.Tests { /// /// These are extensions to configure the SaveManager in potentially nonstandard ways, required in order to test it. diff --git a/Tests/Runtime/SaveManagerReflectionExtensions.cs.meta b/Tests/Runtime/TestTools/SaveManagerReflectionExtensions.cs.meta similarity index 100% rename from Tests/Runtime/SaveManagerReflectionExtensions.cs.meta rename to Tests/Runtime/TestTools/SaveManagerReflectionExtensions.cs.meta diff --git a/Tests/Runtime/StringDiffUtils.cs b/Tests/Runtime/TestTools/StringDiffUtils.cs similarity index 98% rename from Tests/Runtime/StringDiffUtils.cs rename to Tests/Runtime/TestTools/StringDiffUtils.cs index 22ac5ed..c0aadde 100644 --- a/Tests/Runtime/StringDiffUtils.cs +++ b/Tests/Runtime/TestTools/StringDiffUtils.cs @@ -2,7 +2,7 @@ using System.Text; using NUnit.Framework; -namespace Tests.Runtime +namespace Buck.SaveAsync.Tests { public class StringDiffUtils { diff --git a/Tests/Runtime/StringDiffUtils.cs.meta b/Tests/Runtime/TestTools/StringDiffUtils.cs.meta similarity index 100% rename from Tests/Runtime/StringDiffUtils.cs.meta rename to Tests/Runtime/TestTools/StringDiffUtils.cs.meta diff --git a/Tests/Runtime/TestTools/TestCaseBase.cs b/Tests/Runtime/TestTools/TestCaseBase.cs index 97b057b..1091bc2 100644 --- a/Tests/Runtime/TestTools/TestCaseBase.cs +++ b/Tests/Runtime/TestTools/TestCaseBase.cs @@ -1,7 +1,6 @@ using System; using System.Threading; using System.Threading.Tasks; -using Tests.Runtime; using UnityEngine; namespace Buck.SaveAsync.Tests diff --git a/Tests/Runtime/TestTools/TestCaseBase.cs.meta b/Tests/Runtime/TestTools/TestCaseBase.cs.meta index 39a7213..684386f 100644 --- a/Tests/Runtime/TestTools/TestCaseBase.cs.meta +++ b/Tests/Runtime/TestTools/TestCaseBase.cs.meta @@ -1,2 +1,3 @@ fileFormatVersion: 2 -guid: b938ab02942849599696dc5ffaf9e899 \ No newline at end of file +guid: b938ab02942849599696dc5ffaf9e899 +timeCreated: 1725249929 \ No newline at end of file diff --git a/Tests/Runtime/TestSaveableEntity.cs b/Tests/Runtime/TestTools/TestSaveableEntity.cs similarity index 91% rename from Tests/Runtime/TestSaveableEntity.cs rename to Tests/Runtime/TestTools/TestSaveableEntity.cs index edb8f77..89eff65 100644 --- a/Tests/Runtime/TestSaveableEntity.cs +++ b/Tests/Runtime/TestTools/TestSaveableEntity.cs @@ -1,8 +1,6 @@ -using System; -using Buck.SaveAsync; using UnityEngine; -namespace Tests.Runtime +namespace Buck.SaveAsync.Tests { /// /// A test monobehavior that implements saveable. Its internals are exposed, so it can be piloted diff --git a/Tests/Runtime/TestSaveableEntity.cs.meta b/Tests/Runtime/TestTools/TestSaveableEntity.cs.meta similarity index 100% rename from Tests/Runtime/TestSaveableEntity.cs.meta rename to Tests/Runtime/TestTools/TestSaveableEntity.cs.meta From 9a3ef020f189ab57225a72c21299a345caa99114 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Sun, 1 Sep 2024 23:16:39 -0500 Subject: [PATCH 08/20] add test for Vector3 serialization. seems to work --- Tests/Runtime/TestRoundTripSaveLoad.cs | 28 ++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/Tests/Runtime/TestRoundTripSaveLoad.cs b/Tests/Runtime/TestRoundTripSaveLoad.cs index 1598f14..53cbdbc 100644 --- a/Tests/Runtime/TestRoundTripSaveLoad.cs +++ b/Tests/Runtime/TestRoundTripSaveLoad.cs @@ -9,7 +9,7 @@ namespace Buck.SaveAsync.Tests public class TestRoundTripSaveLoad : TestCaseBase { [UnityTest] - public IEnumerator TestSaveSystem_WhenSavesState_AndChangesState_RestoresState() + public IEnumerator TestSaveSystem_WhenSavesStringState_AndChangesState_RestoresState() => AsyncToCoroutine.AsCoroutine(async () => { // Arrange @@ -31,7 +31,7 @@ public IEnumerator TestSaveSystem_WhenSavesState_AndChangesState_RestoresState() }); [UnityTest] - public IEnumerator TestSaveSystem_WhenSavesState_AndChangesState_RestoresState_WithDelay() + public IEnumerator TestSaveSystem_WhenSavesStringState_AndChangesState_RestoresState_WithDelay() => AsyncToCoroutine.AsCoroutine(async () => { // Arrange @@ -51,5 +51,29 @@ public IEnumerator TestSaveSystem_WhenSavesState_AndChangesState_RestoresState_W // Assert Assert.AreEqual("Hello, World!", saveable.CurrentState); }); + + [UnityTest] + public IEnumerator TestSaveSystem_WhenSavesVector3State_AndChangesState_RestoresState() + => AsyncToCoroutine.AsCoroutine(async () => + { + // Arrange + var seed = Guid.NewGuid().ToString(); + + var fileHandler = CreateFileHandler(); + SetupSaveManager(fileHandler); + var saveable = CreateSaveableEntity("saveable_" + seed, "test.dat"); + + var expected = new Vector3(1, 2.3f, 10000.2f); + saveable.CurrentState = expected; + await SaveManager.Save("test.dat"); + + // Act + saveable.CurrentState = Vector3.zero; + await SaveManager.Load("test.dat"); + + // Assert + var actual = (Vector3)saveable.CurrentState; + Assert.AreEqual(0, (expected - actual).magnitude, 0.0001f); + }); } } \ No newline at end of file From 5572e9a164abfa3389c782ae41c598f6cc80d14c Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Sun, 1 Sep 2024 23:28:11 -0500 Subject: [PATCH 09/20] add test to test the unity json converterrs fixup the unity converter test based on real functionality --- .../TestSaveFileFormatWithUnityConverters.cs | 103 ++++++++++++++++++ ...tSaveFileFormatWithUnityConverters.cs.meta | 3 + 2 files changed, 106 insertions(+) create mode 100644 Tests/Runtime/TestSaveFileFormatWithUnityConverters.cs create mode 100644 Tests/Runtime/TestSaveFileFormatWithUnityConverters.cs.meta diff --git a/Tests/Runtime/TestSaveFileFormatWithUnityConverters.cs b/Tests/Runtime/TestSaveFileFormatWithUnityConverters.cs new file mode 100644 index 0000000..93aaad6 --- /dev/null +++ b/Tests/Runtime/TestSaveFileFormatWithUnityConverters.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Buck.SaveAsync.Tests +{ + internal class TestUnitySaveObject + { + public Vector3 Vector3Value { get; set; } + public Quaternion QuaternionValue { get; set; } + public Color ColorValue { get; set; } + public AnimationCurve AnimationCurveValue { get; set; } + } + + /// + /// These tests require that Newtonsoft.Json-for-Unity.Converters are installed. + /// They verify that the save system correctly integrates with Json-for-Unity. + /// + /// + /// install from https://github.com/applejag/Newtonsoft.Json-for-Unity.Converters + /// + public class TestSaveFileFormatWithUnityConverters : TestCaseBase + { + [UnityTest] + public IEnumerator TestSaveSystem_WhenSavesUnityObjects_SavesJson() + => AsyncToCoroutine.AsCoroutine(async () => + { + // Arrange + var nestedObject = new TestUnitySaveObject + { + Vector3Value = new Vector3(1, 2, 3.5f), + QuaternionValue = new Quaternion(0.1f, 0.2f, 0.3f, 0.4f), + ColorValue = new Color(0.1f, 0.2f, 0.3f, 0.4f), + AnimationCurveValue = AnimationCurve.EaseInOut(0, 0, 1, 1) + }; + + // Act + var key = Guid.NewGuid().ToString(); + var serializedFile = await GetSerializedFileForObject(key, nestedObject); + + // Assert + var expected = $@" +[ + {{ + ""Key"": ""{key}"", + ""Data"": {{ + ""$type"": ""{TestConstants.Namespace}.TestUnitySaveObject, {TestConstants.Assembly}"", + ""Vector3Value"": {{ + ""x"": 1.0, + ""y"": 2.0, + ""z"": 3.5 + }}, + ""QuaternionValue"": {{ + ""x"": 0.1, + ""y"": 0.2, + ""z"": 0.3, + ""w"": 0.4 + }}, + ""ColorValue"": {{ + ""r"": 0.1, + ""g"": 0.2, + ""b"": 0.3, + ""a"": 0.4 + }}, + ""AnimationCurveValue"": {{ + ""keys"": [ + {{ + ""time"": 0.0, + ""value"": 0.0, + ""inTangent"": 0.0, + ""outTangent"": 0.0, + ""inWeight"": 0.0, + ""outWeight"": 0.0, + ""weightedMode"": ""None"", + ""tangentMode"": 0 + }}, + {{ + ""time"": 1.0, + ""value"": 1.0, + ""inTangent"": 0.0, + ""outTangent"": 0.0, + ""inWeight"": 0.0, + ""outWeight"": 0.0, + ""weightedMode"": ""None"", + ""tangentMode"": 0 + }} + ], + ""length"": 2, + ""preWrapMode"": ""ClampForever"", + ""postWrapMode"": ""ClampForever"" + }} + }} + }} +] +"; + StringDiffUtils.AssertMultilineStringEqual(expected,serializedFile); + }); + } +} \ No newline at end of file diff --git a/Tests/Runtime/TestSaveFileFormatWithUnityConverters.cs.meta b/Tests/Runtime/TestSaveFileFormatWithUnityConverters.cs.meta new file mode 100644 index 0000000..5c04108 --- /dev/null +++ b/Tests/Runtime/TestSaveFileFormatWithUnityConverters.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 79383e7b47f44a7fb8cca613aec3dd6c +timeCreated: 1725250709 \ No newline at end of file From 3c0b8ca2f143f8da392f56340f1e145150f56cd9 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Sun, 1 Sep 2024 23:41:11 -0500 Subject: [PATCH 10/20] flesh out the test ASMDEF --- Tests/Runtime/BUCK.SaveAsync.Tests.asmdef | 30 +++++++++++++++-------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/Tests/Runtime/BUCK.SaveAsync.Tests.asmdef b/Tests/Runtime/BUCK.SaveAsync.Tests.asmdef index 92ddfc5..ab052b9 100644 --- a/Tests/Runtime/BUCK.SaveAsync.Tests.asmdef +++ b/Tests/Runtime/BUCK.SaveAsync.Tests.asmdef @@ -1,12 +1,22 @@ { - "name": "BUCK.SaveAsync.Tests", - "rootNamespace": "Buck.SaveAsync.Tests", - "references": [ - "BUCK.SaveAsync.Runtime" - ], - "optionalUnityReferences": [ - "TestAssemblies" - ], - "includePlatforms": [], - "excludePlatforms": [] + "name": "BUCK.SaveAsync.Tests", + "rootNamespace": "Buck.SaveAsync.Tests", + "references": [ + "GUID:27619889b8ba8c24980f49ee34dbb44a", + "GUID:0acc523941302664db1f4e527237feb3", + "GUID:ad4bea86cb6093347bcf3482d1635cc9" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [ + "nunit.framework.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false } \ No newline at end of file From 9ee4ef07215e71f01924713a36078eab75afe66a Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 2 Sep 2024 00:01:27 -0500 Subject: [PATCH 11/20] add TODO and variant test around a failing, potential bugged, test --- Tests/Runtime/TestRoundTripSaveLoad.cs | 34 ++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Tests/Runtime/TestRoundTripSaveLoad.cs b/Tests/Runtime/TestRoundTripSaveLoad.cs index 53cbdbc..85b56b7 100644 --- a/Tests/Runtime/TestRoundTripSaveLoad.cs +++ b/Tests/Runtime/TestRoundTripSaveLoad.cs @@ -52,6 +52,40 @@ public IEnumerator TestSaveSystem_WhenSavesStringState_AndChangesState_RestoresS Assert.AreEqual("Hello, World!", saveable.CurrentState); }); + class SaveObjectWithNestedVector3 + { + public Vector3 NestedVector3 { get; set; } + } + + [UnityTest] + public IEnumerator TestSaveSystem_WhenSavesVector3StateInNestedProperty_AndChangesState_RestoresState() + => AsyncToCoroutine.AsCoroutine(async () => + { + // Arrange + var seed = Guid.NewGuid().ToString(); + + var fileHandler = CreateFileHandler(); + SetupSaveManager(fileHandler); + var saveable = CreateSaveableEntity("saveable_" + seed, "test.dat"); + + var expected = new Vector3(1, 2.3f, 10000.2f); + saveable.CurrentState = new SaveObjectWithNestedVector3 + { + NestedVector3 = expected + }; + await SaveManager.Save("test.dat"); + + // Act + saveable.CurrentState = Vector3.zero; + await SaveManager.Load("test.dat"); + + // Assert + var actual = (SaveObjectWithNestedVector3)saveable.CurrentState; + Assert.AreEqual(0, (expected - actual.NestedVector3).magnitude, 0.0001f); + }); + + + // TODO: this test passes only when -not- using the Json-for-Unity Vector3 converter [UnityTest] public IEnumerator TestSaveSystem_WhenSavesVector3State_AndChangesState_RestoresState() => AsyncToCoroutine.AsCoroutine(async () => From b8179ecadb2fbee3ff1d62e65bded00285f5c315 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 2 Sep 2024 00:04:23 -0500 Subject: [PATCH 12/20] more docs, and cleanup --- Tests/Runtime/TestRoundTripSaveLoad.cs | 4 +++ Tests/Runtime/TestSaveFileFormat.cs | 3 -- .../TestSaveFileFormatWithUnityConverters.cs | 3 -- Tests/Runtime/TestTools/AsyncToCoroutine.cs | 32 +++++++++---------- .../Runtime/TestTools/InMemoryFileHandler.cs | 2 +- .../SaveManagerReflectionExtensions.cs | 2 +- Tests/Runtime/TestTools/StringDiffUtils.cs | 2 +- Tests/Runtime/TestTools/TestSaveableEntity.cs | 4 +-- 8 files changed, 24 insertions(+), 28 deletions(-) diff --git a/Tests/Runtime/TestRoundTripSaveLoad.cs b/Tests/Runtime/TestRoundTripSaveLoad.cs index 85b56b7..7f1ac7f 100644 --- a/Tests/Runtime/TestRoundTripSaveLoad.cs +++ b/Tests/Runtime/TestRoundTripSaveLoad.cs @@ -6,6 +6,10 @@ namespace Buck.SaveAsync.Tests { + /// + /// These tests verify round-trip save to load by saving a state, changing the state, + /// and then loading the saved state. + /// public class TestRoundTripSaveLoad : TestCaseBase { [UnityTest] diff --git a/Tests/Runtime/TestSaveFileFormat.cs b/Tests/Runtime/TestSaveFileFormat.cs index f27321d..e261d77 100644 --- a/Tests/Runtime/TestSaveFileFormat.cs +++ b/Tests/Runtime/TestSaveFileFormat.cs @@ -1,9 +1,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using UnityEngine; using UnityEngine.TestTools; namespace Buck.SaveAsync.Tests diff --git a/Tests/Runtime/TestSaveFileFormatWithUnityConverters.cs b/Tests/Runtime/TestSaveFileFormatWithUnityConverters.cs index 93aaad6..55c50f1 100644 --- a/Tests/Runtime/TestSaveFileFormatWithUnityConverters.cs +++ b/Tests/Runtime/TestSaveFileFormatWithUnityConverters.cs @@ -1,8 +1,5 @@ using System; using System.Collections; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; using UnityEngine; using UnityEngine.TestTools; diff --git a/Tests/Runtime/TestTools/AsyncToCoroutine.cs b/Tests/Runtime/TestTools/AsyncToCoroutine.cs index a427f3e..c95318a 100644 --- a/Tests/Runtime/TestTools/AsyncToCoroutine.cs +++ b/Tests/Runtime/TestTools/AsyncToCoroutine.cs @@ -16,15 +16,15 @@ public static IEnumerator AsCoroutine(Func taskFactory) } sealed class ToCoroutineEnumerator : IEnumerator { - bool completed; - Task task; - bool isStarted = false; - ExceptionDispatchInfo exception; + readonly Task m_task; + bool m_completed; + bool m_isStarted = false; + ExceptionDispatchInfo m_exception; public ToCoroutineEnumerator(Task task) { - completed = false; - this.task = task; + m_completed = false; + this.m_task = task; } async void RunTask(Task task) @@ -35,11 +35,11 @@ async void RunTask(Task task) } catch (Exception ex) { - this.exception = ExceptionDispatchInfo.Capture(ex); + this.m_exception = ExceptionDispatchInfo.Capture(ex); } finally { - completed = true; + m_completed = true; } } @@ -47,24 +47,22 @@ async void RunTask(Task task) public bool MoveNext() { - if (!isStarted) + if (!m_isStarted) { - isStarted = true; - RunTask(task); + m_isStarted = true; + RunTask(m_task); } - if (exception != null) + if (m_exception != null) { - exception.Throw(); + m_exception.Throw(); return false; } - return !completed; + return !m_completed; } - void IEnumerator.Reset() - { - } + void IEnumerator.Reset() { } } } } \ No newline at end of file diff --git a/Tests/Runtime/TestTools/InMemoryFileHandler.cs b/Tests/Runtime/TestTools/InMemoryFileHandler.cs index 4688bbb..0cf9699 100644 --- a/Tests/Runtime/TestTools/InMemoryFileHandler.cs +++ b/Tests/Runtime/TestTools/InMemoryFileHandler.cs @@ -13,7 +13,7 @@ public class InMemoryFileHandler : FileHandler { public TimeSpan AllOperationDelay { get; set; } = TimeSpan.Zero; - Dictionary m_files = new(); + readonly Dictionary m_files = new(); protected override string GetPath(string pathOrFilename) { diff --git a/Tests/Runtime/TestTools/SaveManagerReflectionExtensions.cs b/Tests/Runtime/TestTools/SaveManagerReflectionExtensions.cs index 1e5a95e..cfdd28f 100644 --- a/Tests/Runtime/TestTools/SaveManagerReflectionExtensions.cs +++ b/Tests/Runtime/TestTools/SaveManagerReflectionExtensions.cs @@ -16,7 +16,7 @@ public static void SetCustomFileHandler(FileHandler newFileHander) { if (!newFileHander) { - throw new System.ArgumentNullException(nameof(newFileHander)); + throw new ArgumentNullException(nameof(newFileHander)); } var fileHandlerField = typeof(SaveManager) diff --git a/Tests/Runtime/TestTools/StringDiffUtils.cs b/Tests/Runtime/TestTools/StringDiffUtils.cs index c0aadde..8847f73 100644 --- a/Tests/Runtime/TestTools/StringDiffUtils.cs +++ b/Tests/Runtime/TestTools/StringDiffUtils.cs @@ -31,7 +31,7 @@ public static string StringEqualErrorMessage(string expected, string actual) return errorMessage.ToString(); } - private static IEnumerable<(int index, string expected, string actual)> DiffLines(string[] expected, string[] actual) + static IEnumerable<(int index, string expected, string actual)> DiffLines(string[] expected, string[] actual) { for (var i = 0; i < expected.Length; i++) { diff --git a/Tests/Runtime/TestTools/TestSaveableEntity.cs b/Tests/Runtime/TestTools/TestSaveableEntity.cs index 89eff65..ef49eea 100644 --- a/Tests/Runtime/TestTools/TestSaveableEntity.cs +++ b/Tests/Runtime/TestTools/TestSaveableEntity.cs @@ -3,8 +3,8 @@ namespace Buck.SaveAsync.Tests { /// - /// A test monobehavior that implements saveable. Its internals are exposed, so it can be piloted - /// from a test case. + /// A test MonoBehavior that implements saveable. Its internals are exposed since it is piloted + /// from test cases. /// public class TestSaveableEntity : MonoBehaviour, ISaveable { From d8ebdad4b68df8fe6e2c3f48718e3e824df6aed3 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 2 Sep 2024 09:33:51 -0500 Subject: [PATCH 13/20] remove creation of a saveManager game object from the tests - not necessary, Singleton will auto-create --- Tests/Runtime/TestTools/TestCaseBase.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/Runtime/TestTools/TestCaseBase.cs b/Tests/Runtime/TestTools/TestCaseBase.cs index 1091bc2..3c369f7 100644 --- a/Tests/Runtime/TestTools/TestCaseBase.cs +++ b/Tests/Runtime/TestTools/TestCaseBase.cs @@ -9,8 +9,6 @@ public class TestCaseBase { protected void SetupSaveManager(FileHandler withFileHandler) { - var saveManagerGo = new GameObject(); - var saveManager = saveManagerGo.AddComponent(); SaveManagerReflectionExtensions.SetCustomFileHandler(withFileHandler); } From 51eca0e16f77004ede956f677fc1c0b38e0a86cd Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 2 Sep 2024 09:48:58 -0500 Subject: [PATCH 14/20] add documentation for SaveManager internal state --- Runtime/SaveManager.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Runtime/SaveManager.cs b/Runtime/SaveManager.cs index 9823028..9815fbd 100644 --- a/Runtime/SaveManager.cs +++ b/Runtime/SaveManager.cs @@ -52,9 +52,20 @@ public FileOperation(FileOperationType operationType, string[] filenames) } static FileHandler m_fileHandler; + /// + /// Saveables which have registered themselves inside the manager. + /// static Dictionary m_saveables = new(); + /// + /// Temporary working memory used during a operation, to store + /// data which was loaded from a save file and will be restored to the ISaveables after the load operation completes. + /// static List m_loadedSaveables = new(); static Queue m_fileOperationQueue = new(); + /// + /// A set of all files associated with ISaveables which have registered themselves inside the manager. + /// Currently unused. + /// static HashSet m_files = new(); static bool m_isInitialized; From 649fdf9eb800c2d1a334abacbd58ebe61bbca7df Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 2 Sep 2024 09:55:22 -0500 Subject: [PATCH 15/20] create conditionally-compiling test assembly to test integration with UnityConverters --- Tests/Runtime/TestConstants.cs | 6 +++ Tests/Runtime/TestRoundTripSaveLoad.cs | 4 +- ...nExtensions.cs => ReflectionExtensions.cs} | 3 +- ...s.cs.meta => ReflectionExtensions.cs.meta} | 0 Tests/Runtime/TestTools/TestCaseBase.cs | 8 +++- Tests/Runtime/UnityConverters.meta | 8 ++++ ...UCK.SaveAsync.UnityConverters.Tests.asmdef | 31 ++++++++++++++ ...aveAsync.UnityConverters.Tests.asmdef.meta | 3 ++ ...estRoundTripSaveLoadWithUnityConverters.cs | 42 +++++++++++++++++++ ...undTripSaveLoadWithUnityConverters.cs.meta | 3 ++ .../TestSaveFileFormatWithUnityConverters.cs | 4 +- ...tSaveFileFormatWithUnityConverters.cs.meta | 0 .../UnityConverterTestCaseBase.cs | 16 +++++++ .../UnityConverterTestCaseBase.cs.meta | 3 ++ 14 files changed, 123 insertions(+), 8 deletions(-) rename Tests/Runtime/TestTools/{SaveManagerReflectionExtensions.cs => ReflectionExtensions.cs} (94%) rename Tests/Runtime/TestTools/{SaveManagerReflectionExtensions.cs.meta => ReflectionExtensions.cs.meta} (100%) create mode 100644 Tests/Runtime/UnityConverters.meta create mode 100644 Tests/Runtime/UnityConverters/BUCK.SaveAsync.UnityConverters.Tests.asmdef create mode 100644 Tests/Runtime/UnityConverters/BUCK.SaveAsync.UnityConverters.Tests.asmdef.meta create mode 100644 Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs create mode 100644 Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs.meta rename Tests/Runtime/{ => UnityConverters}/TestSaveFileFormatWithUnityConverters.cs (95%) rename Tests/Runtime/{ => UnityConverters}/TestSaveFileFormatWithUnityConverters.cs.meta (100%) create mode 100644 Tests/Runtime/UnityConverters/UnityConverterTestCaseBase.cs create mode 100644 Tests/Runtime/UnityConverters/UnityConverterTestCaseBase.cs.meta diff --git a/Tests/Runtime/TestConstants.cs b/Tests/Runtime/TestConstants.cs index 9744410..af80603 100644 --- a/Tests/Runtime/TestConstants.cs +++ b/Tests/Runtime/TestConstants.cs @@ -2,7 +2,13 @@ namespace Buck.SaveAsync.Tests { public static class TestConstants { + /// + /// Tested against when the namespace is included in the saved json + /// public static string Namespace => "Buck.SaveAsync.Tests"; + /// + /// Tested against when the assembly is included in the saved json + /// public static string Assembly => "BUCK.SaveAsync.Tests"; } } \ No newline at end of file diff --git a/Tests/Runtime/TestRoundTripSaveLoad.cs b/Tests/Runtime/TestRoundTripSaveLoad.cs index 7f1ac7f..0d50d97 100644 --- a/Tests/Runtime/TestRoundTripSaveLoad.cs +++ b/Tests/Runtime/TestRoundTripSaveLoad.cs @@ -7,7 +7,7 @@ namespace Buck.SaveAsync.Tests { /// - /// These tests verify round-trip save to load by saving a state, changing the state, + /// These tests verify round-trip save and load by saving a state, changing the state, /// and then loading the saved state. /// public class TestRoundTripSaveLoad : TestCaseBase @@ -88,8 +88,6 @@ public IEnumerator TestSaveSystem_WhenSavesVector3StateInNestedProperty_AndChang Assert.AreEqual(0, (expected - actual.NestedVector3).magnitude, 0.0001f); }); - - // TODO: this test passes only when -not- using the Json-for-Unity Vector3 converter [UnityTest] public IEnumerator TestSaveSystem_WhenSavesVector3State_AndChangesState_RestoresState() => AsyncToCoroutine.AsCoroutine(async () => diff --git a/Tests/Runtime/TestTools/SaveManagerReflectionExtensions.cs b/Tests/Runtime/TestTools/ReflectionExtensions.cs similarity index 94% rename from Tests/Runtime/TestTools/SaveManagerReflectionExtensions.cs rename to Tests/Runtime/TestTools/ReflectionExtensions.cs index cfdd28f..af5699d 100644 --- a/Tests/Runtime/TestTools/SaveManagerReflectionExtensions.cs +++ b/Tests/Runtime/TestTools/ReflectionExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; namespace Buck.SaveAsync.Tests { @@ -6,7 +7,7 @@ namespace Buck.SaveAsync.Tests /// These are extensions to configure the SaveManager in potentially nonstandard ways, required in order to test it. /// Could be placed inside SaveManager if it is appropriate to expose a public API for these functions, rather than a testing-only API. /// - public static class SaveManagerReflectionExtensions + public static class ReflectionExtensions { /// /// Set the static file handler used by the SaveManager singleton. diff --git a/Tests/Runtime/TestTools/SaveManagerReflectionExtensions.cs.meta b/Tests/Runtime/TestTools/ReflectionExtensions.cs.meta similarity index 100% rename from Tests/Runtime/TestTools/SaveManagerReflectionExtensions.cs.meta rename to Tests/Runtime/TestTools/ReflectionExtensions.cs.meta diff --git a/Tests/Runtime/TestTools/TestCaseBase.cs b/Tests/Runtime/TestTools/TestCaseBase.cs index 3c369f7..4e38706 100644 --- a/Tests/Runtime/TestTools/TestCaseBase.cs +++ b/Tests/Runtime/TestTools/TestCaseBase.cs @@ -1,15 +1,19 @@ using System; using System.Threading; using System.Threading.Tasks; +using Newtonsoft.Json; +using NUnit.Framework; using UnityEngine; namespace Buck.SaveAsync.Tests { public class TestCaseBase { - protected void SetupSaveManager(FileHandler withFileHandler) + protected virtual void SetupSaveManager(FileHandler withFileHandler) { - SaveManagerReflectionExtensions.SetCustomFileHandler(withFileHandler); + ReflectionExtensions.SetCustomFileHandler(withFileHandler); + // ensure that the default settings are not overriden, for test consistency. + JsonConvert.DefaultSettings = null; } protected FileHandler CreateFileHandler(TimeSpan? withEmulatedDelay = null) diff --git a/Tests/Runtime/UnityConverters.meta b/Tests/Runtime/UnityConverters.meta new file mode 100644 index 0000000..07c5c83 --- /dev/null +++ b/Tests/Runtime/UnityConverters.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ed79852fba21f6b4a964e75637ceb74a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/UnityConverters/BUCK.SaveAsync.UnityConverters.Tests.asmdef b/Tests/Runtime/UnityConverters/BUCK.SaveAsync.UnityConverters.Tests.asmdef new file mode 100644 index 0000000..ecf110a --- /dev/null +++ b/Tests/Runtime/UnityConverters/BUCK.SaveAsync.UnityConverters.Tests.asmdef @@ -0,0 +1,31 @@ +{ + "name": "BUCK.SaveAsync.Tests.UnityConverters", + "rootNamespace": "Buck.SaveAsync.Tests", + "references": [ + "GUID:27619889b8ba8c24980f49ee34dbb44a", + "GUID:0acc523941302664db1f4e527237feb3", + "GUID:ad4bea86cb6093347bcf3482d1635cc9", + "GUID:06d4c76cc3914fce9036eb37a7ec33ae", + "GUID:c55d28459d9c4444ebb4788be912e1f1" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [ + "nunit.framework.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS", + "HAVE_JSON_FOR_UNITY_CONVERTERS" + ], + "versionDefines": [ + { + "name": "jillejr.newtonsoft.json-for-unity.converters", + "expression": "", + "define": "HAVE_JSON_FOR_UNITY_CONVERTERS" + } + ], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Tests/Runtime/UnityConverters/BUCK.SaveAsync.UnityConverters.Tests.asmdef.meta b/Tests/Runtime/UnityConverters/BUCK.SaveAsync.UnityConverters.Tests.asmdef.meta new file mode 100644 index 0000000..1a92595 --- /dev/null +++ b/Tests/Runtime/UnityConverters/BUCK.SaveAsync.UnityConverters.Tests.asmdef.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 82e0ee47d15b6954ab150e78908f6c69 +timeCreated: 1725243828 diff --git a/Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs b/Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs new file mode 100644 index 0000000..2615069 --- /dev/null +++ b/Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Buck.SaveAsync.Tests +{ + /// + /// These tests verify round-trip save and load by saving a state, changing the state, + /// and then loading the saved state. + /// + /// + /// specifically meant for testing integration with Json-for-Unity converters + /// + public class TestRoundTripSaveLoadWithUnityConverters : UnityConverterTestCaseBase + { + [UnityTest] + public IEnumerator TestSaveSystem_WhenSavesVector3State_AndChangesState_RestoresState() + => AsyncToCoroutine.AsCoroutine(async () => + { + // Arrange + var seed = Guid.NewGuid().ToString(); + + var fileHandler = CreateFileHandler(); + SetupSaveManager(fileHandler); + var saveable = CreateSaveableEntity("saveable_" + seed, "test.dat"); + + var expected = new Vector3(1, 2.3f, 10000.2f); + saveable.CurrentState = expected; + await SaveManager.Save("test.dat"); + + // Act + saveable.CurrentState = Vector3.zero; + await SaveManager.Load("test.dat"); + + // Assert + var actual = (Vector3)saveable.CurrentState; + Assert.AreEqual(0, (expected - actual).magnitude, 0.0001f); + }); + } +} \ No newline at end of file diff --git a/Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs.meta b/Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs.meta new file mode 100644 index 0000000..a9ce314 --- /dev/null +++ b/Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 73732682696c4e2ab8c5cbcb1a8455ae +timeCreated: 1725288339 \ No newline at end of file diff --git a/Tests/Runtime/TestSaveFileFormatWithUnityConverters.cs b/Tests/Runtime/UnityConverters/TestSaveFileFormatWithUnityConverters.cs similarity index 95% rename from Tests/Runtime/TestSaveFileFormatWithUnityConverters.cs rename to Tests/Runtime/UnityConverters/TestSaveFileFormatWithUnityConverters.cs index 55c50f1..17481c0 100644 --- a/Tests/Runtime/TestSaveFileFormatWithUnityConverters.cs +++ b/Tests/Runtime/UnityConverters/TestSaveFileFormatWithUnityConverters.cs @@ -20,7 +20,7 @@ internal class TestUnitySaveObject /// /// install from https://github.com/applejag/Newtonsoft.Json-for-Unity.Converters /// - public class TestSaveFileFormatWithUnityConverters : TestCaseBase + public class TestSaveFileFormatWithUnityConverters : UnityConverterTestCaseBase { [UnityTest] public IEnumerator TestSaveSystem_WhenSavesUnityObjects_SavesJson() @@ -45,7 +45,7 @@ public IEnumerator TestSaveSystem_WhenSavesUnityObjects_SavesJson() {{ ""Key"": ""{key}"", ""Data"": {{ - ""$type"": ""{TestConstants.Namespace}.TestUnitySaveObject, {TestConstants.Assembly}"", + ""$type"": ""{TestConstants.Namespace}.TestUnitySaveObject, {TestConstants.Assembly}.UnityConverters"", ""Vector3Value"": {{ ""x"": 1.0, ""y"": 2.0, diff --git a/Tests/Runtime/TestSaveFileFormatWithUnityConverters.cs.meta b/Tests/Runtime/UnityConverters/TestSaveFileFormatWithUnityConverters.cs.meta similarity index 100% rename from Tests/Runtime/TestSaveFileFormatWithUnityConverters.cs.meta rename to Tests/Runtime/UnityConverters/TestSaveFileFormatWithUnityConverters.cs.meta diff --git a/Tests/Runtime/UnityConverters/UnityConverterTestCaseBase.cs b/Tests/Runtime/UnityConverters/UnityConverterTestCaseBase.cs new file mode 100644 index 0000000..703b763 --- /dev/null +++ b/Tests/Runtime/UnityConverters/UnityConverterTestCaseBase.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.UnityConverters; + +namespace Buck.SaveAsync.Tests +{ + public class UnityConverterTestCaseBase : TestCaseBase + { + protected override void SetupSaveManager(FileHandler withFileHandler) + { + base.SetupSaveManager(withFileHandler); + // ensure we are using the default unity converters + var jsonSettings = UnityConverterInitializer.defaultUnityConvertersSettings; + JsonConvert.DefaultSettings = () => jsonSettings; + } + } +} \ No newline at end of file diff --git a/Tests/Runtime/UnityConverters/UnityConverterTestCaseBase.cs.meta b/Tests/Runtime/UnityConverters/UnityConverterTestCaseBase.cs.meta new file mode 100644 index 0000000..e86c6e2 --- /dev/null +++ b/Tests/Runtime/UnityConverters/UnityConverterTestCaseBase.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 762b7f18faf04283b3893f320ac36b81 +timeCreated: 1725291217 \ No newline at end of file From 7503cabe6e0e0df3cd5556fa65e254b2c28b6cc2 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 2 Sep 2024 11:22:39 -0500 Subject: [PATCH 16/20] more DRY - functional helper for round-trip testing --- Tests/Runtime/TestRoundTripSaveLoad.cs | 77 ++++--------------- Tests/Runtime/TestTools/StringDiffUtils.cs | 3 + Tests/Runtime/TestTools/TestCaseBase.cs | 27 ++++++- ...estRoundTripSaveLoadWithUnityConverters.cs | 40 ++++++---- 4 files changed, 65 insertions(+), 82 deletions(-) diff --git a/Tests/Runtime/TestRoundTripSaveLoad.cs b/Tests/Runtime/TestRoundTripSaveLoad.cs index 0d50d97..5472a4a 100644 --- a/Tests/Runtime/TestRoundTripSaveLoad.cs +++ b/Tests/Runtime/TestRoundTripSaveLoad.cs @@ -16,44 +16,19 @@ public class TestRoundTripSaveLoad : TestCaseBase public IEnumerator TestSaveSystem_WhenSavesStringState_AndChangesState_RestoresState() => AsyncToCoroutine.AsCoroutine(async () => { - // Arrange - var seed = Guid.NewGuid().ToString(); + var expected = "Hello, World!"; + var actual = await GetRoundTrip(expected, "Goodbye, World!"); - var fileHandler = CreateFileHandler(); - SetupSaveManager(fileHandler); - var saveable = CreateSaveableEntity("saveable_" + seed, "test.dat"); - - saveable.CurrentState = "Hello, World!"; - await SaveManager.Save("test.dat"); - - // Act - saveable.CurrentState = "Goodbye, World!"; - await SaveManager.Load("test.dat"); - - // Assert - Assert.AreEqual("Hello, World!", saveable.CurrentState); + Assert.AreEqual(expected, actual); }); [UnityTest] public IEnumerator TestSaveSystem_WhenSavesStringState_AndChangesState_RestoresState_WithDelay() => AsyncToCoroutine.AsCoroutine(async () => { - // Arrange - var seed = Guid.NewGuid().ToString(); - - var fileHandler = CreateFileHandler(TimeSpan.FromSeconds(0.3f)); - SetupSaveManager(fileHandler); - var saveable = CreateSaveableEntity("saveable_" + seed, "test.dat"); - - saveable.CurrentState = "Hello, World!"; - await SaveManager.Save("test.dat"); - - // Act - saveable.CurrentState = "Goodbye, World!"; - await SaveManager.Load("test.dat"); - - // Assert - Assert.AreEqual("Hello, World!", saveable.CurrentState); + var expected = "Hello, World!"; + var actual = await GetRoundTrip(expected, "Goodbye, World!", TimeSpan.FromSeconds(0.3f)); + Assert.AreEqual(expected, actual); }); class SaveObjectWithNestedVector3 @@ -65,26 +40,15 @@ class SaveObjectWithNestedVector3 public IEnumerator TestSaveSystem_WhenSavesVector3StateInNestedProperty_AndChangesState_RestoresState() => AsyncToCoroutine.AsCoroutine(async () => { - // Arrange - var seed = Guid.NewGuid().ToString(); - - var fileHandler = CreateFileHandler(); - SetupSaveManager(fileHandler); - var saveable = CreateSaveableEntity("saveable_" + seed, "test.dat"); - var expected = new Vector3(1, 2.3f, 10000.2f); - saveable.CurrentState = new SaveObjectWithNestedVector3 + var actual = await GetRoundTrip(new SaveObjectWithNestedVector3 { NestedVector3 = expected - }; - await SaveManager.Save("test.dat"); - - // Act - saveable.CurrentState = Vector3.zero; - await SaveManager.Load("test.dat"); - - // Assert - var actual = (SaveObjectWithNestedVector3)saveable.CurrentState; + }, + new SaveObjectWithNestedVector3 + { + NestedVector3 = Vector3.zero + }); Assert.AreEqual(0, (expected - actual.NestedVector3).magnitude, 0.0001f); }); @@ -92,23 +56,8 @@ public IEnumerator TestSaveSystem_WhenSavesVector3StateInNestedProperty_AndChang public IEnumerator TestSaveSystem_WhenSavesVector3State_AndChangesState_RestoresState() => AsyncToCoroutine.AsCoroutine(async () => { - // Arrange - var seed = Guid.NewGuid().ToString(); - - var fileHandler = CreateFileHandler(); - SetupSaveManager(fileHandler); - var saveable = CreateSaveableEntity("saveable_" + seed, "test.dat"); - var expected = new Vector3(1, 2.3f, 10000.2f); - saveable.CurrentState = expected; - await SaveManager.Save("test.dat"); - - // Act - saveable.CurrentState = Vector3.zero; - await SaveManager.Load("test.dat"); - - // Assert - var actual = (Vector3)saveable.CurrentState; + var actual = await GetRoundTrip(expected, Vector3.zero); Assert.AreEqual(0, (expected - actual).magnitude, 0.0001f); }); } diff --git a/Tests/Runtime/TestTools/StringDiffUtils.cs b/Tests/Runtime/TestTools/StringDiffUtils.cs index 8847f73..1bbf5f7 100644 --- a/Tests/Runtime/TestTools/StringDiffUtils.cs +++ b/Tests/Runtime/TestTools/StringDiffUtils.cs @@ -4,6 +4,9 @@ namespace Buck.SaveAsync.Tests { + /// + /// Helpers to make assertion messages on multiline strings more readable + /// public class StringDiffUtils { public static void AssertMultilineStringEqual(string expected, string actual) diff --git a/Tests/Runtime/TestTools/TestCaseBase.cs b/Tests/Runtime/TestTools/TestCaseBase.cs index 4e38706..78f149e 100644 --- a/Tests/Runtime/TestTools/TestCaseBase.cs +++ b/Tests/Runtime/TestTools/TestCaseBase.cs @@ -7,6 +7,10 @@ namespace Buck.SaveAsync.Tests { + /// + /// A set of base methods used by most test cases. Overridable to provide different save manager setup methods, + /// useful when we want to configure JsonConvert's default settings for example. + /// public class TestCaseBase { protected virtual void SetupSaveManager(FileHandler withFileHandler) @@ -23,7 +27,7 @@ protected FileHandler CreateFileHandler(TimeSpan? withEmulatedDelay = null) return fileHandler; } - public TestSaveableEntity CreateSaveableEntity(string key, string filename = "test.dat") + protected TestSaveableEntity CreateSaveableEntity(string key, string filename = "test.dat") { var saveableEntity = new GameObject(); var saveable = saveableEntity.AddComponent(); @@ -46,5 +50,26 @@ protected async Task GetSerializedFileForObject(string key, object saved return await fileHandler.ReadFile(fileName, CancellationToken.None); } + + protected async Task GetRoundTrip(T initial, T resetTo, TimeSpan? fileHandlerDelay = null) + { + // Arrange + var seed = Guid.NewGuid().ToString(); + + var fileHandler = CreateFileHandler(fileHandlerDelay); + SetupSaveManager(fileHandler); + var saveable = CreateSaveableEntity("saveable_" + seed, "test.dat"); + + saveable.CurrentState = initial; + await SaveManager.Save("test.dat"); + + // Act + saveable.CurrentState = resetTo; + await SaveManager.Load("test.dat"); + + // Assert + Assert.IsAssignableFrom(typeof(T), saveable.CurrentState); + return (T)saveable.CurrentState; + } } } \ No newline at end of file diff --git a/Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs b/Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs index 2615069..d72f1b7 100644 --- a/Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs +++ b/Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs @@ -14,28 +14,34 @@ namespace Buck.SaveAsync.Tests /// specifically meant for testing integration with Json-for-Unity converters /// public class TestRoundTripSaveLoadWithUnityConverters : UnityConverterTestCaseBase - { + { + class SaveObjectWithNestedVector3 + { + public Vector3 NestedVector3 { get; set; } + } + + [UnityTest] + public IEnumerator TestSaveSystem_WhenSavesVector3StateInNestedProperty_AndChangesState_RestoresState() + => AsyncToCoroutine.AsCoroutine(async () => + { + var expected = new Vector3(1, 2.3f, 10000.2f); + var actual = await GetRoundTrip(new SaveObjectWithNestedVector3 + { + NestedVector3 = expected + }, + new SaveObjectWithNestedVector3 + { + NestedVector3 = Vector3.zero + }); + Assert.AreEqual(0, (expected - actual.NestedVector3).magnitude, 0.0001f); + }); + [UnityTest] public IEnumerator TestSaveSystem_WhenSavesVector3State_AndChangesState_RestoresState() => AsyncToCoroutine.AsCoroutine(async () => { - // Arrange - var seed = Guid.NewGuid().ToString(); - - var fileHandler = CreateFileHandler(); - SetupSaveManager(fileHandler); - var saveable = CreateSaveableEntity("saveable_" + seed, "test.dat"); - var expected = new Vector3(1, 2.3f, 10000.2f); - saveable.CurrentState = expected; - await SaveManager.Save("test.dat"); - - // Act - saveable.CurrentState = Vector3.zero; - await SaveManager.Load("test.dat"); - - // Assert - var actual = (Vector3)saveable.CurrentState; + var actual = await GetRoundTrip(expected, Vector3.zero); Assert.AreEqual(0, (expected - actual).magnitude, 0.0001f); }); } From ac30d3390f8d907c65133b8bef8cb2153d9cd27c Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 2 Sep 2024 11:39:17 -0500 Subject: [PATCH 17/20] rename tests. add a new test to make evident the Vector3 save format when using unity converters --- Tests/Runtime/TestRoundTripSaveLoad.cs | 8 ++--- Tests/Runtime/TestSaveFileFormat.cs | 4 +-- ...estRoundTripSaveLoadWithUnityConverters.cs | 4 +-- .../TestSaveFileFormatWithUnityConverters.cs | 29 ++++++++++++++++++- 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/Tests/Runtime/TestRoundTripSaveLoad.cs b/Tests/Runtime/TestRoundTripSaveLoad.cs index 5472a4a..a316cfd 100644 --- a/Tests/Runtime/TestRoundTripSaveLoad.cs +++ b/Tests/Runtime/TestRoundTripSaveLoad.cs @@ -13,7 +13,7 @@ namespace Buck.SaveAsync.Tests public class TestRoundTripSaveLoad : TestCaseBase { [UnityTest] - public IEnumerator TestSaveSystem_WhenSavesStringState_AndChangesState_RestoresState() + public IEnumerator Test_RoundTrip_String() => AsyncToCoroutine.AsCoroutine(async () => { var expected = "Hello, World!"; @@ -23,7 +23,7 @@ public IEnumerator TestSaveSystem_WhenSavesStringState_AndChangesState_RestoresS }); [UnityTest] - public IEnumerator TestSaveSystem_WhenSavesStringState_AndChangesState_RestoresState_WithDelay() + public IEnumerator Test_RoundTrip_String_WithDelay() => AsyncToCoroutine.AsCoroutine(async () => { var expected = "Hello, World!"; @@ -37,7 +37,7 @@ class SaveObjectWithNestedVector3 } [UnityTest] - public IEnumerator TestSaveSystem_WhenSavesVector3StateInNestedProperty_AndChangesState_RestoresState() + public IEnumerator Test_RoundTrip_Vector3Nested() => AsyncToCoroutine.AsCoroutine(async () => { var expected = new Vector3(1, 2.3f, 10000.2f); @@ -53,7 +53,7 @@ public IEnumerator TestSaveSystem_WhenSavesVector3StateInNestedProperty_AndChang }); [UnityTest] - public IEnumerator TestSaveSystem_WhenSavesVector3State_AndChangesState_RestoresState() + public IEnumerator Test_RoundTrip_Vector3Raw() => AsyncToCoroutine.AsCoroutine(async () => { var expected = new Vector3(1, 2.3f, 10000.2f); diff --git a/Tests/Runtime/TestSaveFileFormat.cs b/Tests/Runtime/TestSaveFileFormat.cs index e261d77..0b10ebf 100644 --- a/Tests/Runtime/TestSaveFileFormat.cs +++ b/Tests/Runtime/TestSaveFileFormat.cs @@ -18,7 +18,7 @@ internal class TestSaveObject public class TestSaveFileFormat : TestCaseBase { [UnityTest] - public IEnumerator TestSaveSystem_WhenSavesNestedDictionary_SavesNestedJson() + public IEnumerator Test_SaveFormat_NestedDictionary() => AsyncToCoroutine.AsCoroutine(async () => { // Arrange @@ -61,7 +61,7 @@ public IEnumerator TestSaveSystem_WhenSavesNestedDictionary_SavesNestedJson() }); [UnityTest] - public IEnumerator TestSaveSystem_WhenSavesObject_SavesJson() + public IEnumerator Test_SaveFormat_BasicObject() => AsyncToCoroutine.AsCoroutine(async () => { // Arrange diff --git a/Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs b/Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs index d72f1b7..f4b0e38 100644 --- a/Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs +++ b/Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs @@ -21,7 +21,7 @@ class SaveObjectWithNestedVector3 } [UnityTest] - public IEnumerator TestSaveSystem_WhenSavesVector3StateInNestedProperty_AndChangesState_RestoresState() + public IEnumerator TestUnityConverter_RoundTrip_Vector3Nested() => AsyncToCoroutine.AsCoroutine(async () => { var expected = new Vector3(1, 2.3f, 10000.2f); @@ -37,7 +37,7 @@ public IEnumerator TestSaveSystem_WhenSavesVector3StateInNestedProperty_AndChang }); [UnityTest] - public IEnumerator TestSaveSystem_WhenSavesVector3State_AndChangesState_RestoresState() + public IEnumerator TestUnityConverter_RoundTrip_Vector3Raw() => AsyncToCoroutine.AsCoroutine(async () => { var expected = new Vector3(1, 2.3f, 10000.2f); diff --git a/Tests/Runtime/UnityConverters/TestSaveFileFormatWithUnityConverters.cs b/Tests/Runtime/UnityConverters/TestSaveFileFormatWithUnityConverters.cs index 17481c0..43b9050 100644 --- a/Tests/Runtime/UnityConverters/TestSaveFileFormatWithUnityConverters.cs +++ b/Tests/Runtime/UnityConverters/TestSaveFileFormatWithUnityConverters.cs @@ -23,7 +23,7 @@ internal class TestUnitySaveObject public class TestSaveFileFormatWithUnityConverters : UnityConverterTestCaseBase { [UnityTest] - public IEnumerator TestSaveSystem_WhenSavesUnityObjects_SavesJson() + public IEnumerator TestUnityConverter_SaveFormat_UnityPrimitiveObjects() => AsyncToCoroutine.AsCoroutine(async () => { // Arrange @@ -93,6 +93,33 @@ public IEnumerator TestSaveSystem_WhenSavesUnityObjects_SavesJson() }} }} ] +"; + StringDiffUtils.AssertMultilineStringEqual(expected,serializedFile); + }); + + [UnityTest] + public IEnumerator TestUnityConverter_SaveFormat_Vector3Raw() + => AsyncToCoroutine.AsCoroutine(async () => + { + // Arrange + var nestedObject = new Vector3(1, 2, 3.5f); + + // Act + var key = Guid.NewGuid().ToString(); + var serializedFile = await GetSerializedFileForObject(key, nestedObject); + + // Assert + var expected = $@" +[ + {{ + ""Key"": ""{key}"", + ""Data"": {{ + ""x"": 1.0, + ""y"": 2.0, + ""z"": 3.5 + }} + }} +] "; StringDiffUtils.AssertMultilineStringEqual(expected,serializedFile); }); From 723fad008ebcfcf15adbb6c419c58e726fbd48d3 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 2 Sep 2024 14:55:55 -0500 Subject: [PATCH 18/20] add comments indicating the thread context of ISaveable methods Add doc comments to some test methods --- Runtime/ISaveable.cs | 12 ++++++++++++ Tests/Runtime/TestTools/StringDiffUtils.cs | 2 +- Tests/Runtime/TestTools/TestCaseBase.cs | 16 ++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Runtime/ISaveable.cs b/Runtime/ISaveable.cs index 35d4e8c..62548d0 100644 --- a/Runtime/ISaveable.cs +++ b/Runtime/ISaveable.cs @@ -12,12 +12,18 @@ public interface ISaveable /// If you choose to use a Guid, it is recommended that it is backed by a /// serialized byte array that does not change. /// + /// + /// This is invoked off Unity's main thread, cannot use thread unsafe APIs. + /// public string Key { get; } /// /// This is the file name where this object's data will be saved. /// It is recommended to use a static class to store file paths as strings to avoid typos. /// + /// + /// This is invoked on Unity's main thread, save to use thread unsafe APIs. + /// public string Filename { get; } /// @@ -25,6 +31,9 @@ public interface ISaveable /// Typically this is a struct defined by the ISaveable implementing class. /// The contents of the struct could be created at the time of saving, or cached in a variable. /// + /// + /// This is invoked off Unity's main thread, cannot use thread unsafe APIs. + /// object CaptureState(); /// @@ -32,6 +41,9 @@ public interface ISaveable /// This will be called any time the game is loaded, so you may want to consider /// also using this method to initialize any fields that are not saved (i.e. "resetting the object"). /// + /// + /// This is invoked on Unity's main thread, save to use thread unsafe APIs. + /// void RestoreState(object state); } } diff --git a/Tests/Runtime/TestTools/StringDiffUtils.cs b/Tests/Runtime/TestTools/StringDiffUtils.cs index 1bbf5f7..6d5df52 100644 --- a/Tests/Runtime/TestTools/StringDiffUtils.cs +++ b/Tests/Runtime/TestTools/StringDiffUtils.cs @@ -5,7 +5,7 @@ namespace Buck.SaveAsync.Tests { /// - /// Helpers to make assertion messages on multiline strings more readable + /// Helpers which assert equality of multiline strings in a readable way /// public class StringDiffUtils { diff --git a/Tests/Runtime/TestTools/TestCaseBase.cs b/Tests/Runtime/TestTools/TestCaseBase.cs index 78f149e..add2644 100644 --- a/Tests/Runtime/TestTools/TestCaseBase.cs +++ b/Tests/Runtime/TestTools/TestCaseBase.cs @@ -37,6 +37,13 @@ protected TestSaveableEntity CreateSaveableEntity(string key, string filename = return saveable; } + /// + /// Creates a saveable object, saves it to an emulated file handler, and returns + /// the serialized string from the emulated file handler. + /// + /// The key of the saveable object + /// The data to place inside the saveable object + /// The string value which is saved to file when the temporary saveable object is saved protected async Task GetSerializedFileForObject(string key, object savedObject) { var fileName = Guid.NewGuid() + ".dat"; @@ -51,6 +58,15 @@ protected async Task GetSerializedFileForObject(string key, object saved return await fileHandler.ReadFile(fileName, CancellationToken.None); } + /// + /// Tests round-trip serialization of a given value. + /// is round-tripped through the save system backed by a temporary file handler. + /// + /// The value to be round-tripped + /// A value used to clear the saveable's internal state, to ensure the round-trip isn't a result of a cached value. + /// Optional artificial delay applied to the file handler + /// + /// The value in the saveable after it has been Loaded protected async Task GetRoundTrip(T initial, T resetTo, TimeSpan? fileHandlerDelay = null) { // Arrange From 494db755ec7d9d1629743310df5e145cfbdf9b85 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 2 Sep 2024 15:41:03 -0500 Subject: [PATCH 19/20] pre-review touchup --- Runtime/ISaveable.cs | 4 +- Runtime/SaveManager.cs | 6 +- Tests/Runtime/TestConstants.cs | 4 +- Tests/Runtime/TestRoundTripSaveLoad.cs | 67 ++++++++--------- Tests/Runtime/TestSaveFileFormat.cs | 75 +++++++++---------- .../Runtime/TestTools/InMemoryFileHandler.cs | 5 +- ...ringDiffUtils.cs => MultilineDiffUtils.cs} | 2 +- ...ils.cs.meta => MultilineDiffUtils.cs.meta} | 0 ...Extensions.cs => SaveManagerExtensions.cs} | 2 +- ....cs.meta => SaveManagerExtensions.cs.meta} | 0 Tests/Runtime/TestTools/TestCaseBase.cs | 2 +- ...estRoundTripSaveLoadWithUnityConverters.cs | 46 ++++++------ .../TestSaveFileFormatWithUnityConverters.cs | 72 +++++++++--------- .../UnityConverterTestCaseBase.cs | 2 +- 14 files changed, 140 insertions(+), 147 deletions(-) rename Tests/Runtime/TestTools/{StringDiffUtils.cs => MultilineDiffUtils.cs} (98%) rename Tests/Runtime/TestTools/{StringDiffUtils.cs.meta => MultilineDiffUtils.cs.meta} (100%) rename Tests/Runtime/TestTools/{ReflectionExtensions.cs => SaveManagerExtensions.cs} (96%) rename Tests/Runtime/TestTools/{ReflectionExtensions.cs.meta => SaveManagerExtensions.cs.meta} (100%) diff --git a/Runtime/ISaveable.cs b/Runtime/ISaveable.cs index 62548d0..31773fc 100644 --- a/Runtime/ISaveable.cs +++ b/Runtime/ISaveable.cs @@ -22,7 +22,7 @@ public interface ISaveable /// It is recommended to use a static class to store file paths as strings to avoid typos. /// /// - /// This is invoked on Unity's main thread, save to use thread unsafe APIs. + /// This is invoked on Unity's main thread, safe to use thread unsafe APIs. /// public string Filename { get; } @@ -42,7 +42,7 @@ public interface ISaveable /// also using this method to initialize any fields that are not saved (i.e. "resetting the object"). /// /// - /// This is invoked on Unity's main thread, save to use thread unsafe APIs. + /// This is invoked on Unity's main thread, safe to use thread unsafe APIs. /// void RestoreState(object state); } diff --git a/Runtime/SaveManager.cs b/Runtime/SaveManager.cs index 9815fbd..7b71402 100644 --- a/Runtime/SaveManager.cs +++ b/Runtime/SaveManager.cs @@ -57,13 +57,13 @@ public FileOperation(FileOperationType operationType, string[] filenames) /// static Dictionary m_saveables = new(); /// - /// Temporary working memory used during a operation, to store - /// data which was loaded from a save file and will be restored to the ISaveables after the load operation completes. + /// Temporary working memory used during a operation. Stores + /// data which was loaded from a save file and will be restored to ISaveables after the load operation completes. /// static List m_loadedSaveables = new(); static Queue m_fileOperationQueue = new(); /// - /// A set of all files associated with ISaveables which have registered themselves inside the manager. + /// A set of all files associated with currently registered ISaveables. /// Currently unused. /// static HashSet m_files = new(); diff --git a/Tests/Runtime/TestConstants.cs b/Tests/Runtime/TestConstants.cs index af80603..1f4d762 100644 --- a/Tests/Runtime/TestConstants.cs +++ b/Tests/Runtime/TestConstants.cs @@ -3,11 +3,11 @@ namespace Buck.SaveAsync.Tests public static class TestConstants { /// - /// Tested against when the namespace is included in the saved json + /// Asserted against when the namespace is included in json serialization output /// public static string Namespace => "Buck.SaveAsync.Tests"; /// - /// Tested against when the assembly is included in the saved json + /// Asserted against when the assembly is included in json serialization output /// public static string Assembly => "BUCK.SaveAsync.Tests"; } diff --git a/Tests/Runtime/TestRoundTripSaveLoad.cs b/Tests/Runtime/TestRoundTripSaveLoad.cs index a316cfd..cb9fe62 100644 --- a/Tests/Runtime/TestRoundTripSaveLoad.cs +++ b/Tests/Runtime/TestRoundTripSaveLoad.cs @@ -3,33 +3,32 @@ using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; +using static Buck.SaveAsync.Tests.AsyncToCoroutine; namespace Buck.SaveAsync.Tests { /// /// These tests verify round-trip save and load by saving a state, changing the state, - /// and then loading the saved state. + /// and then loading the previously saved state. /// public class TestRoundTripSaveLoad : TestCaseBase { [UnityTest] - public IEnumerator Test_RoundTrip_String() - => AsyncToCoroutine.AsCoroutine(async () => - { - var expected = "Hello, World!"; - var actual = await GetRoundTrip(expected, "Goodbye, World!"); - - Assert.AreEqual(expected, actual); - }); + public IEnumerator Test_RoundTrip_String() => AsCoroutine(async () => + { + var expected = "Hello, World!"; + var actual = await GetRoundTrip(expected, "Goodbye, World!"); + + Assert.AreEqual(expected, actual); + }); [UnityTest] - public IEnumerator Test_RoundTrip_String_WithDelay() - => AsyncToCoroutine.AsCoroutine(async () => - { - var expected = "Hello, World!"; - var actual = await GetRoundTrip(expected, "Goodbye, World!", TimeSpan.FromSeconds(0.3f)); - Assert.AreEqual(expected, actual); - }); + public IEnumerator Test_RoundTrip_String_WithDelay() => AsCoroutine(async () => + { + var expected = "Hello, World!"; + var actual = await GetRoundTrip(expected, "Goodbye, World!", TimeSpan.FromSeconds(0.3f)); + Assert.AreEqual(expected, actual); + }); class SaveObjectWithNestedVector3 { @@ -37,28 +36,26 @@ class SaveObjectWithNestedVector3 } [UnityTest] - public IEnumerator Test_RoundTrip_Vector3Nested() - => AsyncToCoroutine.AsCoroutine(async () => + public IEnumerator Test_RoundTrip_Vector3Nested() => AsCoroutine(async () => + { + var expected = new Vector3(1, 2.3f, 10000.2f); + var actual = await GetRoundTrip(new SaveObjectWithNestedVector3 + { + NestedVector3 = expected + }, + new SaveObjectWithNestedVector3 { - var expected = new Vector3(1, 2.3f, 10000.2f); - var actual = await GetRoundTrip(new SaveObjectWithNestedVector3 - { - NestedVector3 = expected - }, - new SaveObjectWithNestedVector3 - { - NestedVector3 = Vector3.zero - }); - Assert.AreEqual(0, (expected - actual.NestedVector3).magnitude, 0.0001f); + NestedVector3 = Vector3.zero }); + Assert.AreEqual(0, (expected - actual.NestedVector3).magnitude, 0.0001f); + }); [UnityTest] - public IEnumerator Test_RoundTrip_Vector3Raw() - => AsyncToCoroutine.AsCoroutine(async () => - { - var expected = new Vector3(1, 2.3f, 10000.2f); - var actual = await GetRoundTrip(expected, Vector3.zero); - Assert.AreEqual(0, (expected - actual).magnitude, 0.0001f); - }); + public IEnumerator Test_RoundTrip_Vector3Raw() => AsCoroutine(async () => + { + var expected = new Vector3(1, 2.3f, 10000.2f); + var actual = await GetRoundTrip(expected, Vector3.zero); + Assert.AreEqual(0, (expected - actual).magnitude, 0.0001f); + }); } } \ No newline at end of file diff --git a/Tests/Runtime/TestSaveFileFormat.cs b/Tests/Runtime/TestSaveFileFormat.cs index 0b10ebf..e3c1a24 100644 --- a/Tests/Runtime/TestSaveFileFormat.cs +++ b/Tests/Runtime/TestSaveFileFormat.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using UnityEngine.TestTools; +using static Buck.SaveAsync.Tests.AsyncToCoroutine; namespace Buck.SaveAsync.Tests { @@ -18,29 +19,28 @@ internal class TestSaveObject public class TestSaveFileFormat : TestCaseBase { [UnityTest] - public IEnumerator Test_SaveFormat_NestedDictionary() - => AsyncToCoroutine.AsCoroutine(async () => + public IEnumerator Test_SaveFormat_NestedDictionary() => AsCoroutine(async () => + { + // Arrange + var nestedObject = new Dictionary { - // Arrange - var nestedObject = new Dictionary + { "key1", "value1" }, + { "key2", 2 }, { - { "key1", "value1" }, - { "key2", 2 }, + "key3", new Dictionary { - "key3", new Dictionary - { - { "key4", "value4" }, - { "key5", 5 } - } + { "key4", "value4" }, + { "key5", 5 } } - }; - - // Act - var key = Guid.NewGuid().ToString(); - var serializedFile = await GetSerializedFileForObject(key, nestedObject); - - // Assert - var expected = $@" + } + }; + + // Act + var key = Guid.NewGuid().ToString(); + var serializedFile = await GetSerializedFileForObject(key, nestedObject); + + // Assert + var expected = $@" [ {{ ""Key"": ""{key}"", @@ -57,26 +57,25 @@ public IEnumerator Test_SaveFormat_NestedDictionary() }} ] "; - StringDiffUtils.AssertMultilineStringEqual(expected,serializedFile); - }); + MultilineDiffUtils.AssertMultilineStringEqual(expected,serializedFile); + }); [UnityTest] - public IEnumerator Test_SaveFormat_BasicObject() - => AsyncToCoroutine.AsCoroutine(async () => + public IEnumerator Test_SaveFormat_BasicObject() => AsCoroutine(async () => + { + // Arrange + var nestedObject = new TestSaveObject { - // Arrange - var nestedObject = new TestSaveObject - { - IntValue = 1337, - StringValue = "Goodbye, World!" - }; - - // Act - var key = Guid.NewGuid().ToString(); - var serializedFile = await GetSerializedFileForObject(key, nestedObject); - - // Assert - var expected = $@" + IntValue = 1337, + StringValue = "Goodbye, World!" + }; + + // Act + var key = Guid.NewGuid().ToString(); + var serializedFile = await GetSerializedFileForObject(key, nestedObject); + + // Assert + var expected = $@" [ {{ ""Key"": ""{key}"", @@ -88,7 +87,7 @@ public IEnumerator Test_SaveFormat_BasicObject() }} ] "; - StringDiffUtils.AssertMultilineStringEqual(expected,serializedFile); - }); + MultilineDiffUtils.AssertMultilineStringEqual(expected,serializedFile); + }); } } \ No newline at end of file diff --git a/Tests/Runtime/TestTools/InMemoryFileHandler.cs b/Tests/Runtime/TestTools/InMemoryFileHandler.cs index 0cf9699..e49264f 100644 --- a/Tests/Runtime/TestTools/InMemoryFileHandler.cs +++ b/Tests/Runtime/TestTools/InMemoryFileHandler.cs @@ -15,10 +15,7 @@ public class InMemoryFileHandler : FileHandler readonly Dictionary m_files = new(); - protected override string GetPath(string pathOrFilename) - { - return pathOrFilename; - } + protected override string GetPath(string pathOrFilename) => pathOrFilename; public override async Task Exists(string pathOrFilename, CancellationToken cancellationToken) { diff --git a/Tests/Runtime/TestTools/StringDiffUtils.cs b/Tests/Runtime/TestTools/MultilineDiffUtils.cs similarity index 98% rename from Tests/Runtime/TestTools/StringDiffUtils.cs rename to Tests/Runtime/TestTools/MultilineDiffUtils.cs index 6d5df52..519e394 100644 --- a/Tests/Runtime/TestTools/StringDiffUtils.cs +++ b/Tests/Runtime/TestTools/MultilineDiffUtils.cs @@ -7,7 +7,7 @@ namespace Buck.SaveAsync.Tests /// /// Helpers which assert equality of multiline strings in a readable way /// - public class StringDiffUtils + public class MultilineDiffUtils { public static void AssertMultilineStringEqual(string expected, string actual) { diff --git a/Tests/Runtime/TestTools/StringDiffUtils.cs.meta b/Tests/Runtime/TestTools/MultilineDiffUtils.cs.meta similarity index 100% rename from Tests/Runtime/TestTools/StringDiffUtils.cs.meta rename to Tests/Runtime/TestTools/MultilineDiffUtils.cs.meta diff --git a/Tests/Runtime/TestTools/ReflectionExtensions.cs b/Tests/Runtime/TestTools/SaveManagerExtensions.cs similarity index 96% rename from Tests/Runtime/TestTools/ReflectionExtensions.cs rename to Tests/Runtime/TestTools/SaveManagerExtensions.cs index af5699d..23cc0e2 100644 --- a/Tests/Runtime/TestTools/ReflectionExtensions.cs +++ b/Tests/Runtime/TestTools/SaveManagerExtensions.cs @@ -7,7 +7,7 @@ namespace Buck.SaveAsync.Tests /// These are extensions to configure the SaveManager in potentially nonstandard ways, required in order to test it. /// Could be placed inside SaveManager if it is appropriate to expose a public API for these functions, rather than a testing-only API. /// - public static class ReflectionExtensions + public static class SaveManagerExtensions { /// /// Set the static file handler used by the SaveManager singleton. diff --git a/Tests/Runtime/TestTools/ReflectionExtensions.cs.meta b/Tests/Runtime/TestTools/SaveManagerExtensions.cs.meta similarity index 100% rename from Tests/Runtime/TestTools/ReflectionExtensions.cs.meta rename to Tests/Runtime/TestTools/SaveManagerExtensions.cs.meta diff --git a/Tests/Runtime/TestTools/TestCaseBase.cs b/Tests/Runtime/TestTools/TestCaseBase.cs index add2644..bbbbbe1 100644 --- a/Tests/Runtime/TestTools/TestCaseBase.cs +++ b/Tests/Runtime/TestTools/TestCaseBase.cs @@ -15,7 +15,7 @@ public class TestCaseBase { protected virtual void SetupSaveManager(FileHandler withFileHandler) { - ReflectionExtensions.SetCustomFileHandler(withFileHandler); + SaveManagerExtensions.SetCustomFileHandler(withFileHandler); // ensure that the default settings are not overriden, for test consistency. JsonConvert.DefaultSettings = null; } diff --git a/Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs b/Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs index f4b0e38..fa58fe0 100644 --- a/Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs +++ b/Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs @@ -3,15 +3,17 @@ using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; +using static Buck.SaveAsync.Tests.AsyncToCoroutine; namespace Buck.SaveAsync.Tests { /// /// These tests verify round-trip save and load by saving a state, changing the state, - /// and then loading the saved state. + /// and then loading the previously saved state. /// /// - /// specifically meant for testing integration with Json-for-Unity converters + /// specifically meant for testing integration with Json-for-Unity converters, + /// see https://github.com/applejag/Newtonsoft.Json-for-Unity.Converters /// public class TestRoundTripSaveLoadWithUnityConverters : UnityConverterTestCaseBase { @@ -21,28 +23,26 @@ class SaveObjectWithNestedVector3 } [UnityTest] - public IEnumerator TestUnityConverter_RoundTrip_Vector3Nested() - => AsyncToCoroutine.AsCoroutine(async () => - { - var expected = new Vector3(1, 2.3f, 10000.2f); - var actual = await GetRoundTrip(new SaveObjectWithNestedVector3 - { - NestedVector3 = expected - }, - new SaveObjectWithNestedVector3 - { - NestedVector3 = Vector3.zero - }); - Assert.AreEqual(0, (expected - actual.NestedVector3).magnitude, 0.0001f); - }); + public IEnumerator TestUnityConverter_RoundTrip_Vector3Nested() => AsCoroutine(async () => + { + var expected = new Vector3(1, 2.3f, 10000.2f); + var actual = await GetRoundTrip(new SaveObjectWithNestedVector3 + { + NestedVector3 = expected + }, + new SaveObjectWithNestedVector3 + { + NestedVector3 = Vector3.zero + }); + Assert.AreEqual(0, (expected - actual.NestedVector3).magnitude, 0.0001f); + }); [UnityTest] - public IEnumerator TestUnityConverter_RoundTrip_Vector3Raw() - => AsyncToCoroutine.AsCoroutine(async () => - { - var expected = new Vector3(1, 2.3f, 10000.2f); - var actual = await GetRoundTrip(expected, Vector3.zero); - Assert.AreEqual(0, (expected - actual).magnitude, 0.0001f); - }); + public IEnumerator TestUnityConverter_RoundTrip_Vector3Raw() => AsCoroutine(async () => + { + var expected = new Vector3(1, 2.3f, 10000.2f); + var actual = await GetRoundTrip(expected, Vector3.zero); + Assert.AreEqual(0, (expected - actual).magnitude, 0.0001f); + }); } } \ No newline at end of file diff --git a/Tests/Runtime/UnityConverters/TestSaveFileFormatWithUnityConverters.cs b/Tests/Runtime/UnityConverters/TestSaveFileFormatWithUnityConverters.cs index 43b9050..9dc2449 100644 --- a/Tests/Runtime/UnityConverters/TestSaveFileFormatWithUnityConverters.cs +++ b/Tests/Runtime/UnityConverters/TestSaveFileFormatWithUnityConverters.cs @@ -2,6 +2,7 @@ using System.Collections; using UnityEngine; using UnityEngine.TestTools; +using static Buck.SaveAsync.Tests.AsyncToCoroutine; namespace Buck.SaveAsync.Tests { @@ -14,33 +15,33 @@ internal class TestUnitySaveObject } /// - /// These tests require that Newtonsoft.Json-for-Unity.Converters are installed. - /// They verify that the save system correctly integrates with Json-for-Unity. + /// These tests verify the json format of save files generated by the save system. Useful to detect when a change + /// is introduced which may break existing saves. /// /// - /// install from https://github.com/applejag/Newtonsoft.Json-for-Unity.Converters + /// specifically meant for testing integration with Json-for-Unity converters, + /// see https://github.com/applejag/Newtonsoft.Json-for-Unity.Converters /// public class TestSaveFileFormatWithUnityConverters : UnityConverterTestCaseBase { [UnityTest] - public IEnumerator TestUnityConverter_SaveFormat_UnityPrimitiveObjects() - => AsyncToCoroutine.AsCoroutine(async () => + public IEnumerator TestUnityConverter_SaveFormat_UnityPrimitiveObjects() => AsCoroutine(async () => + { + // Arrange + var nestedObject = new TestUnitySaveObject { - // Arrange - var nestedObject = new TestUnitySaveObject - { - Vector3Value = new Vector3(1, 2, 3.5f), - QuaternionValue = new Quaternion(0.1f, 0.2f, 0.3f, 0.4f), - ColorValue = new Color(0.1f, 0.2f, 0.3f, 0.4f), - AnimationCurveValue = AnimationCurve.EaseInOut(0, 0, 1, 1) - }; - - // Act - var key = Guid.NewGuid().ToString(); - var serializedFile = await GetSerializedFileForObject(key, nestedObject); - - // Assert - var expected = $@" + Vector3Value = new Vector3(1, 2, 3.5f), + QuaternionValue = new Quaternion(0.1f, 0.2f, 0.3f, 0.4f), + ColorValue = new Color(0.1f, 0.2f, 0.3f, 0.4f), + AnimationCurveValue = AnimationCurve.EaseInOut(0, 0, 1, 1) + }; + + // Act + var key = Guid.NewGuid().ToString(); + var serializedFile = await GetSerializedFileForObject(key, nestedObject); + + // Assert + var expected = $@" [ {{ ""Key"": ""{key}"", @@ -94,22 +95,21 @@ public IEnumerator TestUnityConverter_SaveFormat_UnityPrimitiveObjects() }} ] "; - StringDiffUtils.AssertMultilineStringEqual(expected,serializedFile); - }); + MultilineDiffUtils.AssertMultilineStringEqual(expected,serializedFile); + }); [UnityTest] - public IEnumerator TestUnityConverter_SaveFormat_Vector3Raw() - => AsyncToCoroutine.AsCoroutine(async () => - { - // Arrange - var nestedObject = new Vector3(1, 2, 3.5f); - - // Act - var key = Guid.NewGuid().ToString(); - var serializedFile = await GetSerializedFileForObject(key, nestedObject); - - // Assert - var expected = $@" + public IEnumerator TestUnityConverter_SaveFormat_Vector3Raw() => AsCoroutine(async () => + { + // Arrange + var nestedObject = new Vector3(1, 2, 3.5f); + + // Act + var key = Guid.NewGuid().ToString(); + var serializedFile = await GetSerializedFileForObject(key, nestedObject); + + // Assert + var expected = $@" [ {{ ""Key"": ""{key}"", @@ -121,7 +121,7 @@ public IEnumerator TestUnityConverter_SaveFormat_Vector3Raw() }} ] "; - StringDiffUtils.AssertMultilineStringEqual(expected,serializedFile); - }); + MultilineDiffUtils.AssertMultilineStringEqual(expected,serializedFile); + }); } } \ No newline at end of file diff --git a/Tests/Runtime/UnityConverters/UnityConverterTestCaseBase.cs b/Tests/Runtime/UnityConverters/UnityConverterTestCaseBase.cs index 703b763..654b603 100644 --- a/Tests/Runtime/UnityConverters/UnityConverterTestCaseBase.cs +++ b/Tests/Runtime/UnityConverters/UnityConverterTestCaseBase.cs @@ -8,7 +8,7 @@ public class UnityConverterTestCaseBase : TestCaseBase protected override void SetupSaveManager(FileHandler withFileHandler) { base.SetupSaveManager(withFileHandler); - // ensure we are using the default unity converters + // ensure we are using the default Json-for-Unity converters var jsonSettings = UnityConverterInitializer.defaultUnityConvertersSettings; JsonConvert.DefaultSettings = () => jsonSettings; } From 09bf45607c75f6377e7833eb7bc9dc2c296f891c Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Mon, 2 Sep 2024 16:17:52 -0500 Subject: [PATCH 20/20] use recommendation from unity to test Awaitable async functions. don't need a custom to-coroutine converter: https://docs.unity3d.com/6000.0/Documentation/Manual/AwaitSupport.html --- Tests/Runtime/TestRoundTripSaveLoad.cs | 75 ++++++++++------- Tests/Runtime/TestSaveFileFormat.cs | 80 +++++++++++-------- Tests/Runtime/TestTools/AsyncToCoroutine.cs | 68 ---------------- .../TestTools/AsyncToCoroutine.cs.meta | 3 - .../TestTools/SaveManagerExtensions.cs | 1 - ...estRoundTripSaveLoadWithUnityConverters.cs | 49 +++++++----- .../TestSaveFileFormatWithUnityConverters.cs | 74 +++++++++-------- 7 files changed, 160 insertions(+), 190 deletions(-) delete mode 100644 Tests/Runtime/TestTools/AsyncToCoroutine.cs delete mode 100644 Tests/Runtime/TestTools/AsyncToCoroutine.cs.meta diff --git a/Tests/Runtime/TestRoundTripSaveLoad.cs b/Tests/Runtime/TestRoundTripSaveLoad.cs index cb9fe62..8e40a9f 100644 --- a/Tests/Runtime/TestRoundTripSaveLoad.cs +++ b/Tests/Runtime/TestRoundTripSaveLoad.cs @@ -3,7 +3,6 @@ using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; -using static Buck.SaveAsync.Tests.AsyncToCoroutine; namespace Buck.SaveAsync.Tests { @@ -14,21 +13,31 @@ namespace Buck.SaveAsync.Tests public class TestRoundTripSaveLoad : TestCaseBase { [UnityTest] - public IEnumerator Test_RoundTrip_String() => AsCoroutine(async () => + public IEnumerator Test_RoundTrip_String() { - var expected = "Hello, World!"; - var actual = await GetRoundTrip(expected, "Goodbye, World!"); - - Assert.AreEqual(expected, actual); - }); + async Awaitable Impl() + { + var expected = "Hello, World!"; + var actual = await GetRoundTrip(expected, "Goodbye, World!"); + + Assert.AreEqual(expected, actual); + } + + return Impl(); + } [UnityTest] - public IEnumerator Test_RoundTrip_String_WithDelay() => AsCoroutine(async () => + public IEnumerator Test_RoundTrip_String_WithDelay() { - var expected = "Hello, World!"; - var actual = await GetRoundTrip(expected, "Goodbye, World!", TimeSpan.FromSeconds(0.3f)); - Assert.AreEqual(expected, actual); - }); + async Awaitable Impl() + { + var expected = "Hello, World!"; + var actual = await GetRoundTrip(expected, "Goodbye, World!", TimeSpan.FromSeconds(0.3f)); + Assert.AreEqual(expected, actual); + } + + return Impl(); + } class SaveObjectWithNestedVector3 { @@ -36,26 +45,36 @@ class SaveObjectWithNestedVector3 } [UnityTest] - public IEnumerator Test_RoundTrip_Vector3Nested() => AsCoroutine(async () => + public IEnumerator Test_RoundTrip_Vector3Nested() { - var expected = new Vector3(1, 2.3f, 10000.2f); - var actual = await GetRoundTrip(new SaveObjectWithNestedVector3 + async Awaitable Impl() { - NestedVector3 = expected - }, - new SaveObjectWithNestedVector3 - { - NestedVector3 = Vector3.zero - }); - Assert.AreEqual(0, (expected - actual.NestedVector3).magnitude, 0.0001f); - }); + var expected = new Vector3(1, 2.3f, 10000.2f); + var actual = await GetRoundTrip(new SaveObjectWithNestedVector3 + { + NestedVector3 = expected + }, + new SaveObjectWithNestedVector3 + { + NestedVector3 = Vector3.zero + }); + Assert.AreEqual(0, (expected - actual.NestedVector3).magnitude, 0.0001f); + } + + return Impl(); + } [UnityTest] - public IEnumerator Test_RoundTrip_Vector3Raw() => AsCoroutine(async () => + public IEnumerator Test_RoundTrip_Vector3Raw() { - var expected = new Vector3(1, 2.3f, 10000.2f); - var actual = await GetRoundTrip(expected, Vector3.zero); - Assert.AreEqual(0, (expected - actual).magnitude, 0.0001f); - }); + async Awaitable Impl() + { + var expected = new Vector3(1, 2.3f, 10000.2f); + var actual = await GetRoundTrip(expected, Vector3.zero); + Assert.AreEqual(0, (expected - actual).magnitude, 0.0001f); + } + + return Impl(); + } } } \ No newline at end of file diff --git a/Tests/Runtime/TestSaveFileFormat.cs b/Tests/Runtime/TestSaveFileFormat.cs index e3c1a24..6b33a22 100644 --- a/Tests/Runtime/TestSaveFileFormat.cs +++ b/Tests/Runtime/TestSaveFileFormat.cs @@ -1,8 +1,8 @@ using System; using System.Collections; using System.Collections.Generic; +using UnityEngine; using UnityEngine.TestTools; -using static Buck.SaveAsync.Tests.AsyncToCoroutine; namespace Buck.SaveAsync.Tests { @@ -19,28 +19,30 @@ internal class TestSaveObject public class TestSaveFileFormat : TestCaseBase { [UnityTest] - public IEnumerator Test_SaveFormat_NestedDictionary() => AsCoroutine(async () => + public IEnumerator Test_SaveFormat_NestedDictionary() { - // Arrange - var nestedObject = new Dictionary + async Awaitable Impl() { - { "key1", "value1" }, - { "key2", 2 }, + // Arrange + var nestedObject = new Dictionary { - "key3", new Dictionary + { "key1", "value1" }, + { "key2", 2 }, { - { "key4", "value4" }, - { "key5", 5 } + "key3", new Dictionary + { + { "key4", "value4" }, + { "key5", 5 } + } } - } - }; - - // Act - var key = Guid.NewGuid().ToString(); - var serializedFile = await GetSerializedFileForObject(key, nestedObject); - - // Assert - var expected = $@" + }; + + // Act + var key = Guid.NewGuid().ToString(); + var serializedFile = await GetSerializedFileForObject(key, nestedObject); + + // Assert + var expected = $@" [ {{ ""Key"": ""{key}"", @@ -57,25 +59,30 @@ public IEnumerator Test_SaveFormat_NestedDictionary() => AsCoroutine(async () => }} ] "; - MultilineDiffUtils.AssertMultilineStringEqual(expected,serializedFile); - }); + MultilineDiffUtils.AssertMultilineStringEqual(expected,serializedFile); + } + + return Impl(); + } [UnityTest] - public IEnumerator Test_SaveFormat_BasicObject() => AsCoroutine(async () => + public IEnumerator Test_SaveFormat_BasicObject() { - // Arrange - var nestedObject = new TestSaveObject + async Awaitable Impl() { - IntValue = 1337, - StringValue = "Goodbye, World!" - }; - - // Act - var key = Guid.NewGuid().ToString(); - var serializedFile = await GetSerializedFileForObject(key, nestedObject); - - // Assert - var expected = $@" + // Arrange + var nestedObject = new TestSaveObject + { + IntValue = 1337, + StringValue = "Goodbye, World!" + }; + + // Act + var key = Guid.NewGuid().ToString(); + var serializedFile = await GetSerializedFileForObject(key, nestedObject); + + // Assert + var expected = $@" [ {{ ""Key"": ""{key}"", @@ -87,7 +94,10 @@ public IEnumerator Test_SaveFormat_BasicObject() => AsCoroutine(async () => }} ] "; - MultilineDiffUtils.AssertMultilineStringEqual(expected,serializedFile); - }); + MultilineDiffUtils.AssertMultilineStringEqual(expected,serializedFile); + } + + return Impl(); + } } } \ No newline at end of file diff --git a/Tests/Runtime/TestTools/AsyncToCoroutine.cs b/Tests/Runtime/TestTools/AsyncToCoroutine.cs deleted file mode 100644 index c95318a..0000000 --- a/Tests/Runtime/TestTools/AsyncToCoroutine.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Collections; -using System.Runtime.ExceptionServices; -using System.Threading.Tasks; - -namespace Buck.SaveAsync.Tests -{ - /// - /// Functions to allow writing a test as an async function, but run it under UnityTest as a coroutine. - /// - public class AsyncToCoroutine - { - public static IEnumerator AsCoroutine(Func taskFactory) - { - return new ToCoroutineEnumerator(taskFactory()); - } - sealed class ToCoroutineEnumerator : IEnumerator - { - readonly Task m_task; - bool m_completed; - bool m_isStarted = false; - ExceptionDispatchInfo m_exception; - - public ToCoroutineEnumerator(Task task) - { - m_completed = false; - this.m_task = task; - } - - async void RunTask(Task task) - { - try - { - await task; - } - catch (Exception ex) - { - this.m_exception = ExceptionDispatchInfo.Capture(ex); - } - finally - { - m_completed = true; - } - } - - public object Current => null; - - public bool MoveNext() - { - if (!m_isStarted) - { - m_isStarted = true; - RunTask(m_task); - } - - if (m_exception != null) - { - m_exception.Throw(); - return false; - } - - return !m_completed; - } - - void IEnumerator.Reset() { } - } - } -} \ No newline at end of file diff --git a/Tests/Runtime/TestTools/AsyncToCoroutine.cs.meta b/Tests/Runtime/TestTools/AsyncToCoroutine.cs.meta deleted file mode 100644 index 679e66c..0000000 --- a/Tests/Runtime/TestTools/AsyncToCoroutine.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 10a6e932f5cf46b7b4b3a3ebf2bbf027 -timeCreated: 1725245967 \ No newline at end of file diff --git a/Tests/Runtime/TestTools/SaveManagerExtensions.cs b/Tests/Runtime/TestTools/SaveManagerExtensions.cs index 23cc0e2..c2c6d2f 100644 --- a/Tests/Runtime/TestTools/SaveManagerExtensions.cs +++ b/Tests/Runtime/TestTools/SaveManagerExtensions.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; namespace Buck.SaveAsync.Tests { diff --git a/Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs b/Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs index fa58fe0..b74cb27 100644 --- a/Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs +++ b/Tests/Runtime/UnityConverters/TestRoundTripSaveLoadWithUnityConverters.cs @@ -1,9 +1,7 @@ -using System; using System.Collections; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; -using static Buck.SaveAsync.Tests.AsyncToCoroutine; namespace Buck.SaveAsync.Tests { @@ -23,26 +21,35 @@ class SaveObjectWithNestedVector3 } [UnityTest] - public IEnumerator TestUnityConverter_RoundTrip_Vector3Nested() => AsCoroutine(async () => - { - var expected = new Vector3(1, 2.3f, 10000.2f); - var actual = await GetRoundTrip(new SaveObjectWithNestedVector3 - { - NestedVector3 = expected - }, - new SaveObjectWithNestedVector3 - { - NestedVector3 = Vector3.zero - }); - Assert.AreEqual(0, (expected - actual.NestedVector3).magnitude, 0.0001f); - }); - + public IEnumerator TestUnityConverter_RoundTrip_Vector3Nested() { + async Awaitable Impl() + { + var expected = new Vector3(1, 2.3f, 10000.2f); + var actual = await GetRoundTrip(new SaveObjectWithNestedVector3 + { + NestedVector3 = expected + }, + new SaveObjectWithNestedVector3 + { + NestedVector3 = Vector3.zero + }); + Assert.AreEqual(0, (expected - actual.NestedVector3).magnitude, 0.0001f); + } + + return Impl(); + } + [UnityTest] - public IEnumerator TestUnityConverter_RoundTrip_Vector3Raw() => AsCoroutine(async () => + public IEnumerator TestUnityConverter_RoundTrip_Vector3Raw() { - var expected = new Vector3(1, 2.3f, 10000.2f); - var actual = await GetRoundTrip(expected, Vector3.zero); - Assert.AreEqual(0, (expected - actual).magnitude, 0.0001f); - }); + async Awaitable Impl() + { + var expected = new Vector3(1, 2.3f, 10000.2f); + var actual = await GetRoundTrip(expected, Vector3.zero); + Assert.AreEqual(0, (expected - actual).magnitude, 0.0001f); + } + + return Impl(); + } } } \ No newline at end of file diff --git a/Tests/Runtime/UnityConverters/TestSaveFileFormatWithUnityConverters.cs b/Tests/Runtime/UnityConverters/TestSaveFileFormatWithUnityConverters.cs index 9dc2449..1b78a95 100644 --- a/Tests/Runtime/UnityConverters/TestSaveFileFormatWithUnityConverters.cs +++ b/Tests/Runtime/UnityConverters/TestSaveFileFormatWithUnityConverters.cs @@ -2,7 +2,6 @@ using System.Collections; using UnityEngine; using UnityEngine.TestTools; -using static Buck.SaveAsync.Tests.AsyncToCoroutine; namespace Buck.SaveAsync.Tests { @@ -24,24 +23,23 @@ internal class TestUnitySaveObject /// public class TestSaveFileFormatWithUnityConverters : UnityConverterTestCaseBase { - [UnityTest] - public IEnumerator TestUnityConverter_SaveFormat_UnityPrimitiveObjects() => AsCoroutine(async () => - { - // Arrange - var nestedObject = new TestUnitySaveObject - { - Vector3Value = new Vector3(1, 2, 3.5f), - QuaternionValue = new Quaternion(0.1f, 0.2f, 0.3f, 0.4f), - ColorValue = new Color(0.1f, 0.2f, 0.3f, 0.4f), - AnimationCurveValue = AnimationCurve.EaseInOut(0, 0, 1, 1) - }; - - // Act - var key = Guid.NewGuid().ToString(); - var serializedFile = await GetSerializedFileForObject(key, nestedObject); - - // Assert - var expected = $@" + [UnityTest] + public IEnumerator TestUnityConverter_SaveFormat_UnityPrimitiveObjects() { + async Awaitable Impl() { + // Arrange + var nestedObject = new TestUnitySaveObject + { + Vector3Value = new Vector3(1, 2, 3.5f), + QuaternionValue = new Quaternion(0.1f, 0.2f, 0.3f, 0.4f), + ColorValue = new Color(0.1f, 0.2f, 0.3f, 0.4f), + AnimationCurveValue = AnimationCurve.EaseInOut(0, 0, 1, 1) + }; + // Act + var key = Guid.NewGuid().ToString(); + var serializedFile = await GetSerializedFileForObject(key, nestedObject); + + // Assert + var expected = $@" [ {{ ""Key"": ""{key}"", @@ -95,21 +93,26 @@ public IEnumerator TestUnityConverter_SaveFormat_UnityPrimitiveObjects() => AsCo }} ] "; - MultilineDiffUtils.AssertMultilineStringEqual(expected,serializedFile); - }); - + MultilineDiffUtils.AssertMultilineStringEqual(expected, serializedFile); + } + + return Impl(); + } + [UnityTest] - public IEnumerator TestUnityConverter_SaveFormat_Vector3Raw() => AsCoroutine(async () => + public IEnumerator TestUnityConverter_SaveFormat_Vector3Raw() { - // Arrange - var nestedObject = new Vector3(1, 2, 3.5f); - - // Act - var key = Guid.NewGuid().ToString(); - var serializedFile = await GetSerializedFileForObject(key, nestedObject); - - // Assert - var expected = $@" + async Awaitable Impl() + { + // Arrange + var nestedObject = new Vector3(1, 2, 3.5f); + + // Act + var key = Guid.NewGuid().ToString(); + var serializedFile = await GetSerializedFileForObject(key, nestedObject); + + // Assert + var expected = $@" [ {{ ""Key"": ""{key}"", @@ -121,7 +124,10 @@ public IEnumerator TestUnityConverter_SaveFormat_Vector3Raw() => AsCoroutine(asy }} ] "; - MultilineDiffUtils.AssertMultilineStringEqual(expected,serializedFile); - }); + MultilineDiffUtils.AssertMultilineStringEqual(expected,serializedFile); + } + + return Impl(); + } } } \ No newline at end of file