From a393f7ca8ba44032283a72a3a03f97c31acdedc2 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Thu, 5 Dec 2024 11:29:44 +0000 Subject: [PATCH 01/13] add explicit interface tests (#186) --- .../ExplicitInterfaceTests.cs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 tests/Speckle.Sdk.Serialization.Tests/ExplicitInterfaceTests.cs diff --git a/tests/Speckle.Sdk.Serialization.Tests/ExplicitInterfaceTests.cs b/tests/Speckle.Sdk.Serialization.Tests/ExplicitInterfaceTests.cs new file mode 100644 index 00000000..acebdf92 --- /dev/null +++ b/tests/Speckle.Sdk.Serialization.Tests/ExplicitInterfaceTests.cs @@ -0,0 +1,63 @@ +using NUnit.Framework; +using Shouldly; +using Speckle.Sdk.Host; +using Speckle.Sdk.Models; +using Speckle.Sdk.Serialisation.V2.Send; + +namespace Speckle.Sdk.Serialization.Tests; + +public class ExplicitInterfaceTests +{ + [SetUp] + public void Setup() + { + TypeLoader.Reset(); + TypeLoader.Initialize(typeof(Base).Assembly, typeof(TestClass).Assembly); + } + + [Test] + public async Task Test_Json() + { + var testClass = new TestClass() { RegularProperty = "Hello" }; + + var objects = new Dictionary(); + var process2 = new SerializeProcess( + null, + new DummySendCacheManager(objects), + new DummyServerObjectManager(), + new BaseChildFinder(new BasePropertyGatherer()), + new ObjectSerializerFactory(new BasePropertyGatherer()), + new SerializeProcessOptions(false, false, true) + ); + await process2.Serialize(testClass, default).ConfigureAwait(false); + objects.Count.ShouldBe(1); + objects["daaa67cfd73a957247cf2d631b7ca4f3"] + .ShouldBe( + "{\"RegularProperty\":\"Hello\",\"applicationId\":null,\"speckle_type\":\"Speckle.Core.Serialisation.TestClass\",\"id\":\"daaa67cfd73a957247cf2d631b7ca4f3\"}" + ); + } + + [Test] + public void Test_ExtractAllProperties() + { + var testClass = new TestClass() { RegularProperty = "Hello" }; + + var gatherer = new BasePropertyGatherer(); + var properties = gatherer.ExtractAllProperties(testClass).ToList(); + properties.Count.ShouldBe(3); + properties.Select(x => x.Name).ShouldContain("RegularProperty"); + properties.Select(x => x.Name).ShouldNotContain("TestProperty"); + } +} + +[SpeckleType("Speckle.Core.Serialisation.TestClass")] +public sealed class TestClass : Base, ITestInterface +{ + public string RegularProperty { get; set; } + string ITestInterface.TestProperty => RegularProperty; +} + +public interface ITestInterface +{ + string TestProperty { get; } +} From 431bb459fc144a18567f22f2d724580a0fe4100a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Steinhagen?= Date: Thu, 5 Dec 2024 20:20:45 +0000 Subject: [PATCH 02/13] etabs versions (#187) - support for etabs v21 and v22 - sap 2000 is already covered with v25 and v26 --- src/Speckle.Sdk/Host/HostAppVersion.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Speckle.Sdk/Host/HostAppVersion.cs b/src/Speckle.Sdk/Host/HostAppVersion.cs index e9e26d33..2dc6c540 100644 --- a/src/Speckle.Sdk/Host/HostAppVersion.cs +++ b/src/Speckle.Sdk/Host/HostAppVersion.cs @@ -13,6 +13,8 @@ public enum HostAppVersion v2023, v2024, v2025, + v21, + v22, v25, v26, v715, From 722df50d346bcfc61cae9b350d0c7f8c06c08565 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Tue, 10 Dec 2024 11:21:12 +0000 Subject: [PATCH 03/13] Fix sending caching (#188) * add list pool creation * add back base cache for closures * use bounded channels and tell them to wait * format and add exception * adding back json cache? * nevermind * format * add option to skip total finding * add some comments --- src/Speckle.Sdk.Dependencies/Pools.cs | 2 + .../Serialization/ChannelSaver.cs | 27 ++++- .../Serialisation/SerializationResult.cs | 12 ++ .../Serialisation/V2/Send/ObjectSerializer.cs | 34 ++++-- .../V2/Send/ObjectSerializerFactory.cs | 5 +- .../Serialisation/V2/Send/SerializeProcess.cs | 105 +++++++++++------- .../Program.cs | 2 +- .../DetachedTests.cs | 6 +- .../DummySendServerObjectManager.cs | 12 +- .../ExplicitInterfaceTests.cs | 2 +- .../ExternalIdTests.cs | 8 +- .../SerializationTests.cs | 2 +- 12 files changed, 144 insertions(+), 73 deletions(-) diff --git a/src/Speckle.Sdk.Dependencies/Pools.cs b/src/Speckle.Sdk.Dependencies/Pools.cs index 7977e681..d4721910 100644 --- a/src/Speckle.Sdk.Dependencies/Pools.cs +++ b/src/Speckle.Sdk.Dependencies/Pools.cs @@ -20,4 +20,6 @@ public bool Return(Dictionary obj) public static Pool StringBuilders { get; } = new(new StringBuilderPooledObjectPolicy() { MaximumRetainedCapacity = 100 * 1024 * 1024 }); + + public static Pool> CreateListPool() => new(new DefaultPooledObjectPolicy>()); } diff --git a/src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs b/src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs index fa161355..64d8bfb4 100644 --- a/src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs +++ b/src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs @@ -8,17 +8,40 @@ namespace Speckle.Sdk.Dependencies.Serialization; public readonly record struct BaseItem(string Id, string Json, bool NeedsStorage) { public int Size { get; } = Encoding.UTF8.GetByteCount(Json); + + public bool Equals(BaseItem? other) + { + if (other is null) + { + return false; + } + return string.Equals(Id, other.Value.Id, StringComparison.OrdinalIgnoreCase); + } + + public override int GetHashCode() => Id.GetHashCode(); } public abstract class ChannelSaver { + private const int SEND_CAPACITY = 50; private const int HTTP_SEND_CHUNK_SIZE = 25_000_000; //bytes private static readonly TimeSpan HTTP_BATCH_TIMEOUT = TimeSpan.FromSeconds(2); private const int MAX_PARALLELISM_HTTP = 4; + private const int HTTP_CAPACITY = 50; private const int MAX_CACHE_WRITE_PARALLELISM = 1; private const int MAX_CACHE_BATCH = 200; - private readonly Channel _checkCacheChannel = Channel.CreateUnbounded(); + private readonly Channel _checkCacheChannel = Channel.CreateBounded( + new BoundedChannelOptions(SEND_CAPACITY) + { + AllowSynchronousContinuations = true, + Capacity = SEND_CAPACITY, + SingleWriter = false, + SingleReader = false, + FullMode = BoundedChannelFullMode.Wait, + }, + _ => throw new NotImplementedException("Dropping items not supported.") + ); public Task Start(CancellationToken cancellationToken = default) { @@ -28,7 +51,7 @@ public Task Start(CancellationToken cancellationToken = default) .PipeAsync( MAX_PARALLELISM_HTTP, async x => await SendToServer(x, cancellationToken).ConfigureAwait(false), - -1, + HTTP_CAPACITY, false, cancellationToken ) diff --git a/src/Speckle.Sdk/Serialisation/SerializationResult.cs b/src/Speckle.Sdk/Serialisation/SerializationResult.cs index ca080da0..570e73bc 100644 --- a/src/Speckle.Sdk/Serialisation/SerializationResult.cs +++ b/src/Speckle.Sdk/Serialisation/SerializationResult.cs @@ -10,4 +10,16 @@ public readonly record struct Json(string Value) public readonly record struct Id(string Value) { public override string ToString() => Value; + + public bool Equals(Id? other) + { + if (other is null) + { + return false; + } + + return string.Equals(Value, other.Value.Value, StringComparison.OrdinalIgnoreCase); + } + + public override int GetHashCode() => Value.GetHashCode(); } diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/ObjectSerializer.cs b/src/Speckle.Sdk/Serialisation/V2/Send/ObjectSerializer.cs index c5f1a377..7df0d7ac 100644 --- a/src/Speckle.Sdk/Serialisation/V2/Send/ObjectSerializer.cs +++ b/src/Speckle.Sdk/Serialisation/V2/Send/ObjectSerializer.cs @@ -21,6 +21,8 @@ public class ObjectSerializer : IObjectSerializer private HashSet _parentObjects = new(); private readonly Dictionary _currentClosures = new(); + private readonly IDictionary _baseCache; + private readonly bool _trackDetachedChildren; private readonly IBasePropertyGatherer _propertyGatherer; private readonly CancellationToken _cancellationToken; @@ -40,11 +42,13 @@ public class ObjectSerializer : IObjectSerializer /// public ObjectSerializer( IBasePropertyGatherer propertyGatherer, + IDictionary baseCache, bool trackDetachedChildren = false, CancellationToken cancellationToken = default ) { _propertyGatherer = propertyGatherer; + _baseCache = baseCache; _cancellationToken = cancellationToken; _trackDetachedChildren = trackDetachedChildren; } @@ -66,6 +70,7 @@ public ObjectSerializer( { throw new SpeckleSerializeException($"Failed to extract (pre-serialize) properties from the {baseObj}", ex); } + _baseCache[baseObj] = new(item.Item2, _currentClosures); yield return (item.Item1, item.Item2); foreach (var chunk in _chunks) { @@ -227,14 +232,27 @@ private void SerializeProperty(object? obj, JsonWriter writer, PropertyAttribute { return null; } - - var childClosures = isRoot || inheritedDetachInfo.IsDetachable ? _currentClosures : []; - var sb = Pools.StringBuilders.Get(); - using var writer = new StringWriter(sb); - using var jsonWriter = SpeckleObjectSerializerPool.Instance.GetJsonTextWriter(writer); - var id = SerializeBaseObject(baseObj, jsonWriter, childClosures); - var json = new Json(writer.ToString()); - Pools.StringBuilders.Return(sb); + Closures childClosures; + Id id; + Json json; + //avoid multiple serialization to get closures + if (_baseCache.TryGetValue(baseObj, out var info)) + { + id = new Id(baseObj.id.NotNull()); + childClosures = info.Closures; + json = info.Json; + MergeClosures(_currentClosures, childClosures); + } + else + { + childClosures = isRoot || inheritedDetachInfo.IsDetachable ? _currentClosures : []; + var sb = Pools.StringBuilders.Get(); + using var writer = new StringWriter(sb); + using var jsonWriter = SpeckleObjectSerializerPool.Instance.GetJsonTextWriter(writer); + id = SerializeBaseObject(baseObj, jsonWriter, childClosures); + json = new Json(writer.ToString()); + Pools.StringBuilders.Return(sb); + } _parentObjects.Remove(baseObj); diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/ObjectSerializerFactory.cs b/src/Speckle.Sdk/Serialisation/V2/Send/ObjectSerializerFactory.cs index 77f3b6f9..2fa75d05 100644 --- a/src/Speckle.Sdk/Serialisation/V2/Send/ObjectSerializerFactory.cs +++ b/src/Speckle.Sdk/Serialisation/V2/Send/ObjectSerializerFactory.cs @@ -1,10 +1,11 @@ using Speckle.InterfaceGenerator; +using Speckle.Sdk.Models; namespace Speckle.Sdk.Serialisation.V2.Send; [GenerateAutoInterface] public class ObjectSerializerFactory(IBasePropertyGatherer propertyGatherer) : IObjectSerializerFactory { - public IObjectSerializer Create(CancellationToken cancellationToken) => - new ObjectSerializer(propertyGatherer, true, cancellationToken); + public IObjectSerializer Create(IDictionary baseCache, CancellationToken cancellationToken) => + new ObjectSerializer(propertyGatherer, baseCache, true, cancellationToken); } diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs b/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs index 534fbc1e..42873c7a 100644 --- a/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs +++ b/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs @@ -9,7 +9,12 @@ namespace Speckle.Sdk.Serialisation.V2.Send; -public record SerializeProcessOptions(bool SkipCacheRead, bool SkipCacheWrite, bool SkipServer); +public record SerializeProcessOptions( + bool SkipCacheRead, + bool SkipCacheWrite, + bool SkipServer, + bool SkipFindTotalObjects +); public readonly record struct SerializeProcessResults( string RootId, @@ -26,31 +31,56 @@ public class SerializeProcess( SerializeProcessOptions? options = null ) : ChannelSaver, ISerializeProcess { - private readonly SerializeProcessOptions _options = options ?? new(false, false, false); - private readonly ConcurrentDictionary _jsonCache = new(); + private readonly SerializeProcessOptions _options = options ?? new(false, false, false, false); + + //cache bases and closure info to avoid reserialization + private readonly IDictionary _baseCache = new ConcurrentDictionary(); private readonly ConcurrentDictionary _objectReferences = new(); + private readonly Pool> _pool = Pools.CreateListPool<(Id, Json)>(); + + private long _objectCount; + private long _objectsFound; + + private long _objectsSerialized; - private long _totalFound; - private long _totalToUpload; private long _uploaded; private long _cached; - private long _serialized; public async Task Serialize(Base root, CancellationToken cancellationToken) { var channelTask = Start(cancellationToken); + var findTotalObjectsTask = Task.CompletedTask; + if (!_options.SkipFindTotalObjects) + { + findTotalObjectsTask = Task.Factory.StartNew( + () => TraverseTotal(root), + default, + TaskCreationOptions.LongRunning, + TaskScheduler.Default + ); + } + await Traverse(root, true, cancellationToken).ConfigureAwait(false); await channelTask.ConfigureAwait(false); + await findTotalObjectsTask.ConfigureAwait(false); return new(root.id.NotNull(), _objectReferences.Freeze()); } + private void TraverseTotal(Base obj) + { + foreach (var child in baseChildFinder.GetChildren(obj)) + { + _objectsFound++; + progress?.Report(new(ProgressEvent.FindingChildren, _objectsFound, null)); + TraverseTotal(child); + } + } + private async Task Traverse(Base obj, bool isEnd, CancellationToken cancellationToken) { var tasks = new List(); foreach (var child in baseChildFinder.GetChildren(obj)) { - Interlocked.Increment(ref _totalFound); - progress?.Report(new(ProgressEvent.FindingChildren, _totalFound, null)); // tmp is necessary because of the way closures close over loop variables var tmp = child; var t = Task @@ -70,13 +100,12 @@ private async Task Traverse(Base obj, bool isEnd, CancellationToken cancellation } var items = Serialise(obj, cancellationToken); + Interlocked.Increment(ref _objectCount); + progress?.Report(new(ProgressEvent.FromCacheOrSerialized, _objectCount, _objectsFound)); foreach (var item in items) { - Interlocked.Increment(ref _serialized); - progress?.Report(new(ProgressEvent.FromCacheOrSerialized, _serialized, _totalFound)); if (item.NeedsStorage) { - Interlocked.Increment(ref _totalToUpload); await Save(item, cancellationToken).ConfigureAwait(false); } } @@ -90,12 +119,6 @@ private async Task Traverse(Base obj, bool isEnd, CancellationToken cancellation //leave this sync private IEnumerable Serialise(Base obj, CancellationToken cancellationToken) { - Id? id = obj.id != null ? new Id(obj.id) : null; - if (id != null && _jsonCache.ContainsKey(id.Value)) - { - yield break; - } - if (!_options.SkipCacheRead && obj.id != null) { var cachedJson = sqLiteJsonCacheManager.GetObject(obj.id); @@ -105,37 +128,28 @@ private IEnumerable Serialise(Base obj, CancellationToken cancellation yield break; } } - if (id is null || !_jsonCache.TryGetValue(id.Value, out var json)) + + var serializer2 = objectSerializerFactory.Create(_baseCache, cancellationToken); + var items = _pool.Get(); + try { - var serializer2 = objectSerializerFactory.Create(cancellationToken); - var items = serializer2.Serialize(obj).ToList(); + items.AddRange(serializer2.Serialize(obj)); + Interlocked.Add(ref _objectsSerialized, items.Count); foreach (var kvp in serializer2.ObjectReferences) { _objectReferences.TryAdd(kvp.Key, kvp.Value); } - var newId = new Id(obj.id.NotNull()); - var (_, j) = items.First(); - json = j; - _jsonCache.TryAdd(newId, j); - yield return CheckCache(newId, j); - if (id is not null && id != newId) - { - //in case the ids changes which is due to id hash algorithm changing - _jsonCache.TryAdd(id.Value, json); - } + var (id, json) = items.First(); + yield return CheckCache(id, json); foreach (var (cid, cJson) in items.Skip(1)) { - if (_jsonCache.TryAdd(cid, cJson)) - { - Interlocked.Increment(ref _totalFound); - yield return CheckCache(cid, cJson); - } + yield return CheckCache(cid, cJson); } } - else + finally { - yield return new BaseItem(id.NotNull().Value, json.Value, true); + _pool.Return(items); } } @@ -156,9 +170,18 @@ public override async Task> SendToServer(List batch, Ca { if (!_options.SkipServer && batch.Count != 0) { - await serverObjectManager.UploadObjects(batch, true, progress, cancellationToken).ConfigureAwait(false); - Interlocked.Exchange(ref _uploaded, _uploaded + batch.Count); - progress?.Report(new(ProgressEvent.UploadedObjects, _uploaded, _totalToUpload)); + var objectBatch = batch.Distinct().ToList(); + var hasObjects = await serverObjectManager + .HasObjects(objectBatch.Select(x => x.Id).ToList(), cancellationToken) + .ConfigureAwait(false); + objectBatch = batch.Where(x => !hasObjects[x.Id]).ToList(); + if (objectBatch.Count != 0) + { + await serverObjectManager.UploadObjects(objectBatch, true, progress, cancellationToken).ConfigureAwait(false); + Interlocked.Exchange(ref _uploaded, _uploaded + batch.Count); + } + + progress?.Report(new(ProgressEvent.UploadedObjects, _uploaded, null)); } return batch; } @@ -169,7 +192,7 @@ public override void SaveToCache(List batch) { sqLiteJsonCacheManager.SaveObjects(batch.Select(x => (x.Id, x.Json))); Interlocked.Exchange(ref _cached, _cached + batch.Count); - progress?.Report(new(ProgressEvent.CachedToLocal, _cached, null)); + progress?.Report(new(ProgressEvent.CachedToLocal, _cached, _objectsSerialized)); } } } diff --git a/tests/Speckle.Sdk.Serialization.Testing/Program.cs b/tests/Speckle.Sdk.Serialization.Testing/Program.cs index 5df2d34d..b807f795 100644 --- a/tests/Speckle.Sdk.Serialization.Testing/Program.cs +++ b/tests/Speckle.Sdk.Serialization.Testing/Program.cs @@ -61,7 +61,7 @@ streamId, token, progress, - new SerializeProcessOptions(skipCacheSendCheck, skipCacheSendSave, true) + new SerializeProcessOptions(skipCacheSendCheck, skipCacheSendSave, true, true) ); await process2.Serialize(@base, default).ConfigureAwait(false); Console.WriteLine("Detach"); diff --git a/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs b/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs index 54316cd2..9a9cf5db 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs @@ -74,7 +74,7 @@ public async Task CanSerialize_New_Detached() new DummyServerObjectManager(), new BaseChildFinder(new BasePropertyGatherer()), new ObjectSerializerFactory(new BasePropertyGatherer()), - new SerializeProcessOptions(false, false, true) + new SerializeProcessOptions(false, false, true, true) ); await process2.Serialize(@base, default).ConfigureAwait(false); @@ -266,7 +266,7 @@ public async Task CanSerialize_New_Detached2() new DummyServerObjectManager(), new BaseChildFinder(new BasePropertyGatherer()), new ObjectSerializerFactory(new BasePropertyGatherer()), - new SerializeProcessOptions(false, false, true) + new SerializeProcessOptions(false, false, true, true) ); var results = await process2.Serialize(@base, default).ConfigureAwait(false); @@ -386,7 +386,7 @@ public void SaveObjects(IEnumerable<(string id, string json)> items) { foreach (var (id, json) in items) { - objects.Add(id, json); + objects[id] = json; } } } diff --git a/tests/Speckle.Sdk.Serialization.Tests/DummySendServerObjectManager.cs b/tests/Speckle.Sdk.Serialization.Tests/DummySendServerObjectManager.cs index e2434282..1dbaefcd 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/DummySendServerObjectManager.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/DummySendServerObjectManager.cs @@ -24,7 +24,7 @@ CancellationToken cancellationToken public Task> HasObjects(IReadOnlyList objectIds, CancellationToken cancellationToken) { - return Task.FromResult(objectIds.ToDictionary(x => x, x => false)); + return Task.FromResult(objectIds.Distinct().ToDictionary(x => x, savedObjects.ContainsKey)); } public Task UploadObjects( @@ -36,15 +36,7 @@ CancellationToken cancellationToken { foreach (var obj in objects) { - obj.Id.ShouldBe(JObject.Parse(obj.Json)["id"].NotNull().Value()); - if (savedObjects.TryGetValue(obj.Id, out var j)) - { - j.ShouldBe(obj.Json); - } - else - { - savedObjects.TryAdd(obj.Id, obj.Json); - } + savedObjects.TryAdd(obj.Id, obj.Json); } return Task.CompletedTask; } diff --git a/tests/Speckle.Sdk.Serialization.Tests/ExplicitInterfaceTests.cs b/tests/Speckle.Sdk.Serialization.Tests/ExplicitInterfaceTests.cs index acebdf92..d82eef21 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/ExplicitInterfaceTests.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/ExplicitInterfaceTests.cs @@ -27,7 +27,7 @@ public async Task Test_Json() new DummyServerObjectManager(), new BaseChildFinder(new BasePropertyGatherer()), new ObjectSerializerFactory(new BasePropertyGatherer()), - new SerializeProcessOptions(false, false, true) + new SerializeProcessOptions(false, false, true, true) ); await process2.Serialize(testClass, default).ConfigureAwait(false); objects.Count.ShouldBe(1); diff --git a/tests/Speckle.Sdk.Serialization.Tests/ExternalIdTests.cs b/tests/Speckle.Sdk.Serialization.Tests/ExternalIdTests.cs index cde6aac0..2c0c5014 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/ExternalIdTests.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/ExternalIdTests.cs @@ -26,7 +26,7 @@ public void Setup() public void ExternalIdTest_Detached(string lineId, string valueId) { var p = new Polyline() { units = "cm", value = [1, 2] }; - var serializer = new ObjectSerializer(new BasePropertyGatherer(), true); + var serializer = new ObjectSerializer(new BasePropertyGatherer(), new Dictionary(), true); var list = serializer.Serialize(p).ToDictionary(x => x.Item1, x => x.Item2); list.ContainsKey(new Id(lineId)).ShouldBeTrue(); var json = list[new Id(lineId)]; @@ -53,7 +53,7 @@ public void ExternalIdTest_Detached_Nested(string lineId, string valueId) knots = [], weights = [], }; - var serializer = new ObjectSerializer(new BasePropertyGatherer(), true); + var serializer = new ObjectSerializer(new BasePropertyGatherer(), new Dictionary(), true); var list = serializer.Serialize(curve).ToDictionary(x => x.Item1, x => x.Item2); list.ContainsKey(new Id(lineId)).ShouldBeTrue(); var json = list[new Id(lineId)]; @@ -81,7 +81,7 @@ public void ExternalIdTest_Detached_Nested_More(string lineId, string valueId) weights = [], }; var polycurve = new Polycurve() { segments = [curve], units = "cm" }; - var serializer = new ObjectSerializer(new BasePropertyGatherer(), true); + var serializer = new ObjectSerializer(new BasePropertyGatherer(), new Dictionary(), true); var list = serializer.Serialize(polycurve).ToDictionary(x => x.Item1, x => x.Item2); list.ContainsKey(new Id(lineId)).ShouldBeTrue(); var json = list[new Id(lineId)]; @@ -111,7 +111,7 @@ public void ExternalIdTest_Detached_Nested_More_Too(string lineId, string valueI var polycurve = new Polycurve() { segments = [curve], units = "cm" }; var @base = new Base(); @base.SetDetachedProp("profile", polycurve); - var serializer = new ObjectSerializer(new BasePropertyGatherer(), true); + var serializer = new ObjectSerializer(new BasePropertyGatherer(), new Dictionary(), true); var list = serializer.Serialize(@base).ToDictionary(x => x.Item1, x => x.Item2); list.ContainsKey(new Id(lineId)).ShouldBeTrue(); var json = list[new Id(lineId)]; diff --git a/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs b/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs index 386e9b51..49648b4a 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs @@ -263,7 +263,7 @@ public async Task Roundtrip_Test_New(string fileName, string rootId, int oldCoun new DummySendServerObjectManager(newIdToJson), new BaseChildFinder(new BasePropertyGatherer()), new ObjectSerializerFactory(new BasePropertyGatherer()), - new SerializeProcessOptions(true, true, false) + new SerializeProcessOptions(true, true, false, true) ); var (rootId2, _) = await serializeProcess.Serialize(root, default); From defcee165aeae92ca3b844545c39ec678eb994d1 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Fri, 13 Dec 2024 11:00:21 +0000 Subject: [PATCH 04/13] Closures are kept for children instead of global (#189) * disable channels when skipping things * pass child closures to current. Current closures out to parent. * fix build * adjust options * use a dictionary pool and pool correctly * add pools for data chunks * format --- src/Speckle.Sdk.Dependencies/Pools.cs | 28 +++- .../Serialization/ChannelExtensions.cs | 10 +- .../Serialization/ChannelLoader.cs | 6 +- .../Serialization/ChannelSaver.cs | 93 +++++++------ .../SizeBatchingChannelReader.cs | 23 ++-- .../V2/DummySendServerObjectManager.cs | 8 +- .../Serialisation/V2/Receive/ObjectLoader.cs | 9 +- .../Serialisation/V2/Send/ObjectSerializer.cs | 124 +++++++++++++----- .../V2/Send/ObjectSerializerFactory.cs | 10 +- .../Serialisation/V2/Send/SerializeProcess.cs | 92 +++++++++---- .../Serialisation/V2/ServerObjectManager.cs | 6 +- .../DetachedTests.cs | 6 +- .../DummyReceiveServerObjectManager.cs | 7 +- .../DummySendServerObjectManager.cs | 10 +- .../ExternalIdTests.cs | 20 ++- 15 files changed, 306 insertions(+), 146 deletions(-) diff --git a/src/Speckle.Sdk.Dependencies/Pools.cs b/src/Speckle.Sdk.Dependencies/Pools.cs index d4721910..86bfe7c1 100644 --- a/src/Speckle.Sdk.Dependencies/Pools.cs +++ b/src/Speckle.Sdk.Dependencies/Pools.cs @@ -21,5 +21,31 @@ public bool Return(Dictionary obj) public static Pool StringBuilders { get; } = new(new StringBuilderPooledObjectPolicy() { MaximumRetainedCapacity = 100 * 1024 * 1024 }); - public static Pool> CreateListPool() => new(new DefaultPooledObjectPolicy>()); + private sealed class ObjectDictionaryPolicy : IPooledObjectPolicy> + where TKey : notnull + { + public Dictionary Create() => new(50); + + public bool Return(Dictionary obj) + { + obj.Clear(); + return true; + } + } + + private sealed class ObjectListPolicy : IPooledObjectPolicy> + { + public List Create() => new(50); + + public bool Return(List obj) + { + obj.Clear(); + return true; + } + } + + public static Pool> CreateListPool() => new(new ObjectListPolicy()); + + public static Pool> CreateDictionaryPool() + where TKey : notnull => new(new ObjectDictionaryPolicy()); } diff --git a/src/Speckle.Sdk.Dependencies/Serialization/ChannelExtensions.cs b/src/Speckle.Sdk.Dependencies/Serialization/ChannelExtensions.cs index 2154b26c..cb56850c 100644 --- a/src/Speckle.Sdk.Dependencies/Serialization/ChannelExtensions.cs +++ b/src/Speckle.Sdk.Dependencies/Serialization/ChannelExtensions.cs @@ -1,18 +1,18 @@ using System.Threading.Channels; using Open.ChannelExtensions; -using Speckle.Sdk.Dependencies.Serialization; namespace Speckle.Sdk.Serialisation.V2.Send; public static class ChannelExtensions { - public static BatchingChannelReader> BatchBySize( - this ChannelReader source, + public static BatchingChannelReader> BatchBySize( + this ChannelReader source, int batchSize, bool singleReader = false, bool allowSynchronousContinuations = false - ) => - new SizeBatchingChannelReader( + ) + where T : IHasSize => + new SizeBatchingChannelReader( source ?? throw new ArgumentNullException(nameof(source)), batchSize, singleReader, diff --git a/src/Speckle.Sdk.Dependencies/Serialization/ChannelLoader.cs b/src/Speckle.Sdk.Dependencies/Serialization/ChannelLoader.cs index 447440fd..e9d6e729 100644 --- a/src/Speckle.Sdk.Dependencies/Serialization/ChannelLoader.cs +++ b/src/Speckle.Sdk.Dependencies/Serialization/ChannelLoader.cs @@ -2,7 +2,7 @@ namespace Speckle.Sdk.Dependencies.Serialization; -public abstract class ChannelLoader +public abstract class ChannelLoader { private const int HTTP_GET_CHUNK_SIZE = 500; private const int MAX_PARALLELISM_HTTP = 4; @@ -27,7 +27,7 @@ await allChildrenIds public abstract string? CheckCache(string id); - public abstract Task> Download(List ids); + public abstract Task> Download(List ids); - public abstract void SaveToCache(List x); + public abstract void SaveToCache(List x); } diff --git a/src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs b/src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs index 64d8bfb4..4f974eda 100644 --- a/src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs +++ b/src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs @@ -1,27 +1,11 @@ -using System.Text; using System.Threading.Channels; using Open.ChannelExtensions; using Speckle.Sdk.Serialisation.V2.Send; namespace Speckle.Sdk.Dependencies.Serialization; -public readonly record struct BaseItem(string Id, string Json, bool NeedsStorage) -{ - public int Size { get; } = Encoding.UTF8.GetByteCount(Json); - - public bool Equals(BaseItem? other) - { - if (other is null) - { - return false; - } - return string.Equals(Id, other.Value.Id, StringComparison.OrdinalIgnoreCase); - } - - public override int GetHashCode() => Id.GetHashCode(); -} - -public abstract class ChannelSaver +public abstract class ChannelSaver + where T : IHasSize { private const int SEND_CAPACITY = 50; private const int HTTP_SEND_CHUNK_SIZE = 25_000_000; //bytes @@ -31,7 +15,9 @@ public abstract class ChannelSaver private const int MAX_CACHE_WRITE_PARALLELISM = 1; private const int MAX_CACHE_BATCH = 200; - private readonly Channel _checkCacheChannel = Channel.CreateBounded( + private bool _enabled; + + private readonly Channel _checkCacheChannel = Channel.CreateBounded( new BoundedChannelOptions(SEND_CAPACITY) { AllowSynchronousContinuations = true, @@ -43,35 +29,60 @@ public abstract class ChannelSaver _ => throw new NotImplementedException("Dropping items not supported.") ); - public Task Start(CancellationToken cancellationToken = default) + public Task Start( + bool enableServerSending = true, + bool enableCacheSaving = true, + CancellationToken cancellationToken = default + ) { - var t = _checkCacheChannel - .Reader.BatchBySize(HTTP_SEND_CHUNK_SIZE) - .WithTimeout(HTTP_BATCH_TIMEOUT) - .PipeAsync( - MAX_PARALLELISM_HTTP, - async x => await SendToServer(x, cancellationToken).ConfigureAwait(false), - HTTP_CAPACITY, - false, - cancellationToken - ) - .Join() - .Batch(MAX_CACHE_BATCH) - .WithTimeout(HTTP_BATCH_TIMEOUT) - .ReadAllConcurrently(MAX_CACHE_WRITE_PARALLELISM, SaveToCache, cancellationToken); - return t; + ValueTask t = new(Task.FromResult(0L)); + if (enableServerSending) + { + _enabled = true; + var tChannelReader = _checkCacheChannel + .Reader.BatchBySize(HTTP_SEND_CHUNK_SIZE) + .WithTimeout(HTTP_BATCH_TIMEOUT) + .PipeAsync( + MAX_PARALLELISM_HTTP, + async x => await SendToServer(x, cancellationToken).ConfigureAwait(false), + HTTP_CAPACITY, + false, + cancellationToken + ); + if (enableCacheSaving) + { + t = new( + tChannelReader + .Join() + .Batch(MAX_CACHE_BATCH) + .WithTimeout(HTTP_BATCH_TIMEOUT) + .ReadAllConcurrently(MAX_CACHE_WRITE_PARALLELISM, SaveToCache, cancellationToken) + ); + } + else + { + t = tChannelReader.ReadUntilCancelledAsync(cancellationToken, (list, l) => new ValueTask()); + } + } + + return t.AsTask(); } - public async Task Save(BaseItem item, CancellationToken cancellationToken = default) => - await _checkCacheChannel.Writer.WriteAsync(item, cancellationToken).ConfigureAwait(false); + public async ValueTask Save(T item, CancellationToken cancellationToken = default) + { + if (_enabled) + { + await _checkCacheChannel.Writer.WriteAsync(item, cancellationToken).ConfigureAwait(false); + } + } - public abstract Task> SendToServer(List batch, CancellationToken cancellationToken); + public abstract Task> SendToServer(List batch, CancellationToken cancellationToken); - public Task Done() + public ValueTask Done() { _checkCacheChannel.Writer.Complete(); - return Task.CompletedTask; + return new(Task.CompletedTask); } - public abstract void SaveToCache(List item); + public abstract void SaveToCache(List item); } diff --git a/src/Speckle.Sdk.Dependencies/Serialization/SizeBatchingChannelReader.cs b/src/Speckle.Sdk.Dependencies/Serialization/SizeBatchingChannelReader.cs index c824e9fb..9c668547 100644 --- a/src/Speckle.Sdk.Dependencies/Serialization/SizeBatchingChannelReader.cs +++ b/src/Speckle.Sdk.Dependencies/Serialization/SizeBatchingChannelReader.cs @@ -1,28 +1,33 @@ using System.Threading.Channels; using Open.ChannelExtensions; -using Speckle.Sdk.Dependencies.Serialization; namespace Speckle.Sdk.Serialisation.V2.Send; -public class SizeBatchingChannelReader( - ChannelReader source, +public interface IHasSize +{ + int Size { get; } +} + +public class SizeBatchingChannelReader( + ChannelReader source, int batchSize, bool singleReader, bool syncCont = false -) : BatchingChannelReader>(source, batchSize, singleReader, syncCont) +) : BatchingChannelReader>(source, batchSize, singleReader, syncCont) + where T : IHasSize { private readonly int _batchSize = batchSize; - protected override List CreateBatch(int capacity) => new(); + protected override List CreateBatch(int capacity) => new(); - protected override void TrimBatch(List batch) => batch.TrimExcess(); + protected override void TrimBatch(List batch) => batch.TrimExcess(); - protected override void AddBatchItem(List batch, BaseItem item) => batch.Add(item); + protected override void AddBatchItem(List batch, T item) => batch.Add(item); - protected override int GetBatchSize(List batch) + protected override int GetBatchSize(List batch) { int size = 0; - foreach (BaseItem item in batch) + foreach (T item in batch) { size += item.Size; } diff --git a/src/Speckle.Sdk/Serialisation/V2/DummySendServerObjectManager.cs b/src/Speckle.Sdk/Serialisation/V2/DummySendServerObjectManager.cs index e50ef75e..950a8667 100644 --- a/src/Speckle.Sdk/Serialisation/V2/DummySendServerObjectManager.cs +++ b/src/Speckle.Sdk/Serialisation/V2/DummySendServerObjectManager.cs @@ -1,5 +1,5 @@ using System.Text; -using Speckle.Sdk.Dependencies.Serialization; +using Speckle.Sdk.Serialisation.V2.Send; using Speckle.Sdk.SQLite; using Speckle.Sdk.Transports; @@ -23,7 +23,7 @@ public class DummySqLiteJsonCacheManager : ISqLiteJsonCacheManager public class DummySendServerObjectManager : IServerObjectManager { public IAsyncEnumerable<(string, string)> DownloadObjects( - IReadOnlyList objectIds, + IReadOnlyCollection objectIds, IProgress? progress, CancellationToken cancellationToken ) => throw new NotImplementedException(); @@ -35,7 +35,7 @@ CancellationToken cancellationToken ) => throw new NotImplementedException(); public Task> HasObjects( - IReadOnlyList objectIds, + IReadOnlyCollection objectIds, CancellationToken cancellationToken ) => throw new NotImplementedException(); @@ -49,7 +49,7 @@ CancellationToken cancellationToken long totalBytes = 0; foreach (var item in objects) { - totalBytes += Encoding.Default.GetByteCount(item.Json); + totalBytes += Encoding.Default.GetByteCount(item.Json.Value); } progress?.Report(new(ProgressEvent.UploadBytes, totalBytes, totalBytes)); diff --git a/src/Speckle.Sdk/Serialisation/V2/Receive/ObjectLoader.cs b/src/Speckle.Sdk/Serialisation/V2/Receive/ObjectLoader.cs index b55e30ba..7201c4d0 100644 --- a/src/Speckle.Sdk/Serialisation/V2/Receive/ObjectLoader.cs +++ b/src/Speckle.Sdk/Serialisation/V2/Receive/ObjectLoader.cs @@ -3,6 +3,7 @@ using Speckle.Sdk.Dependencies; using Speckle.Sdk.Dependencies.Serialization; using Speckle.Sdk.Serialisation.Utilities; +using Speckle.Sdk.Serialisation.V2.Send; using Speckle.Sdk.SQLite; using Speckle.Sdk.Transports; @@ -13,7 +14,7 @@ public sealed class ObjectLoader( ISqLiteJsonCacheManager sqLiteJsonCacheManager, IServerObjectManager serverObjectManager, IProgress? progress -) : ChannelLoader, IObjectLoader +) : ChannelLoader, IObjectLoader { private int? _allChildrenCount; private long _checkCache; @@ -80,13 +81,13 @@ public override async Task> Download(List ids) var (id, json) in serverObjectManager.DownloadObjects(ids.Select(x => x.NotNull()).ToList(), progress, default) ) { - toCache.Add(new(id, json, true)); + toCache.Add(new(new(id), new(json), true, null)); } if (toCache.Count != ids.Count) { throw new SpeckleException( - $"Objects in batch missing: {string.Join(",", ids.Except(toCache.Select(y => y.Id)).Take(10))}" + $"Objects in batch missing: {string.Join(",", ids.Except(toCache.Select(y => y.Id.Value)).Take(10))}" ); } return toCache; @@ -97,7 +98,7 @@ public override void SaveToCache(List batch) { if (!_options.SkipCache) { - sqLiteJsonCacheManager.SaveObjects(batch.Select(x => (x.Id, x.Json))); + sqLiteJsonCacheManager.SaveObjects(batch.Select(x => (x.Id.Value, x.Json.Value))); Interlocked.Exchange(ref _cached, _cached + batch.Count); progress?.Report(new(ProgressEvent.CachedToLocal, _cached, _allChildrenCount)); } diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/ObjectSerializer.cs b/src/Speckle.Sdk/Serialisation/V2/Send/ObjectSerializer.cs index 7df0d7ac..2777fdfc 100644 --- a/src/Speckle.Sdk/Serialisation/V2/Send/ObjectSerializer.cs +++ b/src/Speckle.Sdk/Serialisation/V2/Send/ObjectSerializer.cs @@ -9,19 +9,25 @@ using Speckle.Sdk.Helpers; using Speckle.Sdk.Models; using Speckle.Sdk.Serialisation.Utilities; -using Closures = System.Collections.Generic.IReadOnlyDictionary; +using Closures = System.Collections.Generic.Dictionary; namespace Speckle.Sdk.Serialisation.V2.Send; -public readonly record struct CacheInfo(Json Json, Closures Closures); +public readonly record struct NodeInfo(Json Json, Closures? C) +{ + public Closures GetClosures() => + C ?? ClosureParser.GetClosures(Json.Value).ToDictionary(x => new Id(x.Item1), x => x.Item2); +} + +public partial interface IObjectSerializer : IDisposable; [GenerateAutoInterface] -public class ObjectSerializer : IObjectSerializer +public sealed class ObjectSerializer : IObjectSerializer { private HashSet _parentObjects = new(); private readonly Dictionary _currentClosures = new(); - private readonly IDictionary _baseCache; + private readonly IReadOnlyDictionary _childCache; private readonly bool _trackDetachedChildren; private readonly IBasePropertyGatherer _propertyGatherer; @@ -33,7 +39,14 @@ public class ObjectSerializer : IObjectSerializer /// public Dictionary ObjectReferences { get; } = new(); - private readonly List<(Id, Json)> _chunks = new(); + private readonly List<(Id, Json, Closures)> _chunks; + private readonly Pool> _chunksPool; + + private readonly List> _chunks2 = new(); + private readonly Pool> _chunks2Pool; + + private readonly List> _chunks3 = new(); + private readonly Pool> _chunks3Pool; /// /// Creates a new Serializer instance. @@ -42,22 +55,43 @@ public class ObjectSerializer : IObjectSerializer /// public ObjectSerializer( IBasePropertyGatherer propertyGatherer, - IDictionary baseCache, + IReadOnlyDictionary childCache, + Pool> chunksPool, + Pool> chunks2Pool, + Pool> chunks3Pool, bool trackDetachedChildren = false, CancellationToken cancellationToken = default ) { _propertyGatherer = propertyGatherer; - _baseCache = baseCache; + _childCache = childCache; + _chunksPool = chunksPool; + _chunks2Pool = chunks2Pool; + _chunks3Pool = chunks3Pool; _cancellationToken = cancellationToken; _trackDetachedChildren = trackDetachedChildren; + _chunks = chunksPool.Get(); + } + + [AutoInterfaceIgnore] + public void Dispose() + { + _chunksPool.Return(_chunks); + foreach (var c2 in _chunks2) + { + _chunks2Pool.Return(c2); + } + foreach (var c3 in _chunks3) + { + _chunks3Pool.Return(c3); + } } /// The object to serialize /// The serialized JSON /// The serializer is busy (already serializing an object) /// Failed to extract (pre-serialize) properties from the - public IEnumerable<(Id, Json)> Serialize(Base baseObj) + public IEnumerable<(Id, Json, Closures)> Serialize(Base baseObj) { try { @@ -70,8 +104,7 @@ public ObjectSerializer( { throw new SpeckleSerializeException($"Failed to extract (pre-serialize) properties from the {baseObj}", ex); } - _baseCache[baseObj] = new(item.Item2, _currentClosures); - yield return (item.Item1, item.Item2); + yield return (item.Item1, item.Item2, _currentClosures); foreach (var chunk in _chunks) { yield return chunk; @@ -232,27 +265,6 @@ private void SerializeProperty(object? obj, JsonWriter writer, PropertyAttribute { return null; } - Closures childClosures; - Id id; - Json json; - //avoid multiple serialization to get closures - if (_baseCache.TryGetValue(baseObj, out var info)) - { - id = new Id(baseObj.id.NotNull()); - childClosures = info.Closures; - json = info.Json; - MergeClosures(_currentClosures, childClosures); - } - else - { - childClosures = isRoot || inheritedDetachInfo.IsDetachable ? _currentClosures : []; - var sb = Pools.StringBuilders.Get(); - using var writer = new StringWriter(sb); - using var jsonWriter = SpeckleObjectSerializerPool.Instance.GetJsonTextWriter(writer); - id = SerializeBaseObject(baseObj, jsonWriter, childClosures); - json = new Json(writer.ToString()); - Pools.StringBuilders.Return(sb); - } _parentObjects.Remove(baseObj); @@ -266,6 +278,27 @@ private void SerializeProperty(object? obj, JsonWriter writer, PropertyAttribute if (inheritedDetachInfo.IsDetachable) { + Closures childClosures; + Id id; + Json json; + //avoid multiple serialization to get closures + if (baseObj.id != null && _childCache.TryGetValue(new(baseObj.id), out var info)) + { + id = new Id(baseObj.id); + childClosures = info.GetClosures(); + json = info.Json; + MergeClosures(_currentClosures, childClosures); + } + else + { + childClosures = isRoot || inheritedDetachInfo.IsDetachable ? _currentClosures : []; + var sb = Pools.StringBuilders.Get(); + using var writer = new StringWriter(sb); + using var jsonWriter = SpeckleObjectSerializerPool.Instance.GetJsonTextWriter(writer); + id = SerializeBaseObject(baseObj, jsonWriter, childClosures); + json = new Json(writer.ToString()); + Pools.StringBuilders.Return(sb); + } var json2 = ReferenceGenerator.CreateReference(id); AddClosure(id); // add to obj refs to return @@ -278,10 +311,20 @@ private void SerializeProperty(object? obj, JsonWriter writer, PropertyAttribute closure = childClosures.ToDictionary(x => x.Key.Value, x => x.Value), }; } - _chunks.Add(new(id, json)); + _chunks.Add(new(id, json, [])); return new(id, json2); } - return new(id, json); + else + { + var childClosures = isRoot || inheritedDetachInfo.IsDetachable ? _currentClosures : []; + var sb = Pools.StringBuilders.Get(); + using var writer = new StringWriter(sb); + using var jsonWriter = SpeckleObjectSerializerPool.Instance.GetJsonTextWriter(writer); + var id = SerializeBaseObject(baseObj, jsonWriter, childClosures); + var json = new Json(writer.ToString()); + Pools.StringBuilders.Return(sb); + return new(id, json); + } } private Id SerializeBaseObject(Base baseObj, JsonWriter writer, Closures closure) @@ -334,12 +377,21 @@ private Id SerializeBaseObject(Base baseObj, JsonWriter writer, Closures closure return id; } + private List GetChunk() + { + var chunk = _chunks3Pool.Get(); + _chunks3.Add(chunk); + return chunk; + } + private void SerializeOrChunkProperty(object? baseValue, JsonWriter jsonWriter, PropertyAttributeInfo detachInfo) { if (baseValue is IEnumerable chunkableCollection && detachInfo.IsChunkable) { - List chunks = new(); - DataChunk crtChunk = new() { data = new List(detachInfo.ChunkSize) }; + List chunks = _chunks2Pool.Get(); + _chunks2.Add(chunks); + + DataChunk crtChunk = new() { data = GetChunk() }; foreach (object element in chunkableCollection) { @@ -347,7 +399,7 @@ private void SerializeOrChunkProperty(object? baseValue, JsonWriter jsonWriter, if (crtChunk.data.Count >= detachInfo.ChunkSize) { chunks.Add(crtChunk); - crtChunk = new DataChunk { data = new List(detachInfo.ChunkSize) }; + crtChunk = new DataChunk { data = GetChunk() }; } } diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/ObjectSerializerFactory.cs b/src/Speckle.Sdk/Serialisation/V2/Send/ObjectSerializerFactory.cs index 2fa75d05..9c5bbc8a 100644 --- a/src/Speckle.Sdk/Serialisation/V2/Send/ObjectSerializerFactory.cs +++ b/src/Speckle.Sdk/Serialisation/V2/Send/ObjectSerializerFactory.cs @@ -1,11 +1,17 @@ using Speckle.InterfaceGenerator; +using Speckle.Sdk.Dependencies; using Speckle.Sdk.Models; +using Closures = System.Collections.Generic.Dictionary; namespace Speckle.Sdk.Serialisation.V2.Send; [GenerateAutoInterface] public class ObjectSerializerFactory(IBasePropertyGatherer propertyGatherer) : IObjectSerializerFactory { - public IObjectSerializer Create(IDictionary baseCache, CancellationToken cancellationToken) => - new ObjectSerializer(propertyGatherer, baseCache, true, cancellationToken); + private readonly Pool> _chunkPool = Pools.CreateListPool<(Id, Json, Closures)>(); + private readonly Pool> _chunk2Pool = Pools.CreateListPool(); + private readonly Pool> _chunk3Pool = Pools.CreateListPool(); + + public IObjectSerializer Create(IReadOnlyDictionary baseCache, CancellationToken cancellationToken) => + new ObjectSerializer(propertyGatherer, baseCache, _chunkPool, _chunk2Pool, _chunk3Pool, true, cancellationToken); } diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs b/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs index 42873c7a..564e47f7 100644 --- a/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs +++ b/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using System.Text; using Speckle.InterfaceGenerator; using Speckle.Sdk.Common; using Speckle.Sdk.Dependencies; @@ -6,14 +7,17 @@ using Speckle.Sdk.Models; using Speckle.Sdk.SQLite; using Speckle.Sdk.Transports; +using Closures = System.Collections.Generic.Dictionary; namespace Speckle.Sdk.Serialisation.V2.Send; public record SerializeProcessOptions( - bool SkipCacheRead, - bool SkipCacheWrite, - bool SkipServer, - bool SkipFindTotalObjects + bool SkipCacheRead = false, + bool SkipCacheWrite = false, + bool SkipServer = false, + bool SkipFindTotalObjects = false, + bool EnableServerSending = true, + bool EnableCacheSaving = true ); public readonly record struct SerializeProcessResults( @@ -21,6 +25,22 @@ public readonly record struct SerializeProcessResults( IReadOnlyDictionary ConvertedReferences ); +public readonly record struct BaseItem(Id Id, Json Json, bool NeedsStorage, Closures? Closures) : IHasSize +{ + public int Size { get; } = Encoding.UTF8.GetByteCount(Json.Value); + + public bool Equals(BaseItem? other) + { + if (other is null) + { + return false; + } + return string.Equals(Id.Value, other.Value.Id.Value, StringComparison.OrdinalIgnoreCase); + } + + public override int GetHashCode() => Id.GetHashCode(); +} + [GenerateAutoInterface] public class SerializeProcess( IProgress? progress, @@ -29,14 +49,13 @@ public class SerializeProcess( IBaseChildFinder baseChildFinder, IObjectSerializerFactory objectSerializerFactory, SerializeProcessOptions? options = null -) : ChannelSaver, ISerializeProcess +) : ChannelSaver, ISerializeProcess { private readonly SerializeProcessOptions _options = options ?? new(false, false, false, false); - //cache bases and closure info to avoid reserialization - private readonly IDictionary _baseCache = new ConcurrentDictionary(); private readonly ConcurrentDictionary _objectReferences = new(); - private readonly Pool> _pool = Pools.CreateListPool<(Id, Json)>(); + private readonly Pool> _pool = Pools.CreateListPool<(Id, Json, Closures)>(); + private readonly Pool> _childClosurePool = Pools.CreateDictionaryPool(); private long _objectCount; private long _objectsFound; @@ -48,7 +67,7 @@ public class SerializeProcess( public async Task Serialize(Base root, CancellationToken cancellationToken) { - var channelTask = Start(cancellationToken); + var channelTask = Start(_options.EnableServerSending, _options.EnableCacheSaving, cancellationToken); var findTotalObjectsTask = Task.CompletedTask; if (!_options.SkipFindTotalObjects) { @@ -76,9 +95,9 @@ private void TraverseTotal(Base obj) } } - private async Task Traverse(Base obj, bool isEnd, CancellationToken cancellationToken) + private async Task> Traverse(Base obj, bool isEnd, CancellationToken cancellationToken) { - var tasks = new List(); + var tasks = new List>>(); foreach (var child in baseChildFinder.GetChildren(obj)) { // tmp is necessary because of the way closures close over loop variables @@ -98,8 +117,19 @@ private async Task Traverse(Base obj, bool isEnd, CancellationToken cancellation { await Task.WhenAll(tasks).ConfigureAwait(false); } + var childClosures = _childClosurePool.Get(); + foreach (var t in tasks) + { + var childClosure = t.Result; + foreach (var kvp in childClosure) + { + childClosures[kvp.Key] = kvp.Value; + } + } + + var items = Serialise(obj, childClosures, cancellationToken); - var items = Serialise(obj, cancellationToken); + var currentClosures = new Dictionary(); Interlocked.Increment(ref _objectCount); progress?.Report(new(ProgressEvent.FromCacheOrSerialized, _objectCount, _objectsFound)); foreach (var item in items) @@ -108,28 +138,40 @@ private async Task Traverse(Base obj, bool isEnd, CancellationToken cancellation { await Save(item, cancellationToken).ConfigureAwait(false); } + + if (!currentClosures.ContainsKey(item.Id)) + { + currentClosures.Add(item.Id, new NodeInfo(item.Json, item.Closures)); + } } + _childClosurePool.Return(childClosures); if (isEnd) { await Done().ConfigureAwait(false); } + + return currentClosures; } //leave this sync - private IEnumerable Serialise(Base obj, CancellationToken cancellationToken) + private IEnumerable Serialise( + Base obj, + IReadOnlyDictionary childInfo, + CancellationToken cancellationToken + ) { if (!_options.SkipCacheRead && obj.id != null) { var cachedJson = sqLiteJsonCacheManager.GetObject(obj.id); if (cachedJson != null) { - yield return new BaseItem(obj.id.NotNull(), cachedJson, false); + yield return new BaseItem(new(obj.id.NotNull()), new(cachedJson), false, null); yield break; } } - var serializer2 = objectSerializerFactory.Create(_baseCache, cancellationToken); + using var serializer2 = objectSerializerFactory.Create(childInfo, cancellationToken); var items = _pool.Get(); try { @@ -140,11 +182,11 @@ private IEnumerable Serialise(Base obj, CancellationToken cancellation _objectReferences.TryAdd(kvp.Key, kvp.Value); } - var (id, json) = items.First(); - yield return CheckCache(id, json); - foreach (var (cid, cJson) in items.Skip(1)) + var (id, json, closures) = items.First(); + yield return CheckCache(id, json, closures); + foreach (var (cid, cJson, cClosures) in items.Skip(1)) { - yield return CheckCache(cid, cJson); + yield return CheckCache(cid, cJson, cClosures); } } finally @@ -153,17 +195,17 @@ private IEnumerable Serialise(Base obj, CancellationToken cancellation } } - private BaseItem CheckCache(Id id, Json json) + private BaseItem CheckCache(Id id, Json json, Dictionary closures) { if (!_options.SkipCacheRead) { var cachedJson = sqLiteJsonCacheManager.GetObject(id.Value); if (cachedJson != null) { - return new BaseItem(id.Value, cachedJson, false); + return new BaseItem(id, new(cachedJson), false, null); } } - return new BaseItem(id.Value, json.Value, true); + return new BaseItem(id, json, true, closures); } public override async Task> SendToServer(List batch, CancellationToken cancellationToken) @@ -172,9 +214,9 @@ public override async Task> SendToServer(List batch, Ca { var objectBatch = batch.Distinct().ToList(); var hasObjects = await serverObjectManager - .HasObjects(objectBatch.Select(x => x.Id).ToList(), cancellationToken) + .HasObjects(objectBatch.Select(x => x.Id.Value).Freeze(), cancellationToken) .ConfigureAwait(false); - objectBatch = batch.Where(x => !hasObjects[x.Id]).ToList(); + objectBatch = batch.Where(x => !hasObjects[x.Id.Value]).ToList(); if (objectBatch.Count != 0) { await serverObjectManager.UploadObjects(objectBatch, true, progress, cancellationToken).ConfigureAwait(false); @@ -190,7 +232,7 @@ public override void SaveToCache(List batch) { if (!_options.SkipCacheWrite && batch.Count != 0) { - sqLiteJsonCacheManager.SaveObjects(batch.Select(x => (x.Id, x.Json))); + sqLiteJsonCacheManager.SaveObjects(batch.Select(x => (x.Id.Value, x.Json.Value))); Interlocked.Exchange(ref _cached, _cached + batch.Count); progress?.Report(new(ProgressEvent.CachedToLocal, _cached, _objectsSerialized)); } diff --git a/src/Speckle.Sdk/Serialisation/V2/ServerObjectManager.cs b/src/Speckle.Sdk/Serialisation/V2/ServerObjectManager.cs index d5833868..c957087f 100644 --- a/src/Speckle.Sdk/Serialisation/V2/ServerObjectManager.cs +++ b/src/Speckle.Sdk/Serialisation/V2/ServerObjectManager.cs @@ -5,9 +5,9 @@ using Speckle.InterfaceGenerator; using Speckle.Newtonsoft.Json; using Speckle.Sdk.Common; -using Speckle.Sdk.Dependencies.Serialization; using Speckle.Sdk.Helpers; using Speckle.Sdk.Logging; +using Speckle.Sdk.Serialisation.V2.Send; using Speckle.Sdk.Transports; using Speckle.Sdk.Transports.ServerUtils; @@ -42,7 +42,7 @@ public ServerObjectManager( } public async IAsyncEnumerable<(string, string)> DownloadObjects( - IReadOnlyList objectIds, + IReadOnlyCollection objectIds, IProgress? progress, [EnumeratorCancellation] CancellationToken cancellationToken ) @@ -139,7 +139,7 @@ [EnumeratorCancellation] CancellationToken cancellationToken } public async Task> HasObjects( - IReadOnlyList objectIds, + IReadOnlyCollection objectIds, CancellationToken cancellationToken ) { diff --git a/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs b/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs index 9a9cf5db..c48021f1 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs @@ -336,7 +336,7 @@ public class SamplePropBase2 : Base public class DummyServerObjectManager : IServerObjectManager { public IAsyncEnumerable<(string, string)> DownloadObjects( - IReadOnlyList objectIds, + IReadOnlyCollection objectIds, IProgress? progress, CancellationToken cancellationToken ) => throw new NotImplementedException(); @@ -348,7 +348,7 @@ CancellationToken cancellationToken ) => throw new NotImplementedException(); public Task> HasObjects( - IReadOnlyList objectIds, + IReadOnlyCollection objectIds, CancellationToken cancellationToken ) => throw new NotImplementedException(); @@ -362,7 +362,7 @@ CancellationToken cancellationToken long totalBytes = 0; foreach (var item in objects) { - totalBytes += Encoding.Default.GetByteCount(item.Json); + totalBytes += Encoding.Default.GetByteCount(item.Json.Value); } progress?.Report(new(ProgressEvent.UploadBytes, totalBytes, totalBytes)); diff --git a/tests/Speckle.Sdk.Serialization.Tests/DummyReceiveServerObjectManager.cs b/tests/Speckle.Sdk.Serialization.Tests/DummyReceiveServerObjectManager.cs index 213bc391..797d7b67 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/DummyReceiveServerObjectManager.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/DummyReceiveServerObjectManager.cs @@ -2,6 +2,7 @@ using System.Text; using Speckle.Sdk.Dependencies.Serialization; using Speckle.Sdk.Serialisation.V2; +using Speckle.Sdk.Serialisation.V2.Send; using Speckle.Sdk.Transports; namespace Speckle.Sdk.Serialization.Tests; @@ -9,7 +10,7 @@ namespace Speckle.Sdk.Serialization.Tests; public class DummyReceiveServerObjectManager(Dictionary objects) : IServerObjectManager { public async IAsyncEnumerable<(string, string)> DownloadObjects( - IReadOnlyList objectIds, + IReadOnlyCollection objectIds, IProgress? progress, [EnumeratorCancellation] CancellationToken cancellationToken ) @@ -32,7 +33,7 @@ CancellationToken cancellationToken } public Task> HasObjects( - IReadOnlyList objectIds, + IReadOnlyCollection objectIds, CancellationToken cancellationToken ) => throw new NotImplementedException(); @@ -46,7 +47,7 @@ CancellationToken cancellationToken long totalBytes = 0; foreach (var item in objects) { - totalBytes += Encoding.Default.GetByteCount(item.Json); + totalBytes += Encoding.Default.GetByteCount(item.Json.Value); } progress?.Report(new(ProgressEvent.UploadBytes, totalBytes, totalBytes)); diff --git a/tests/Speckle.Sdk.Serialization.Tests/DummySendServerObjectManager.cs b/tests/Speckle.Sdk.Serialization.Tests/DummySendServerObjectManager.cs index 1dbaefcd..0d8433fc 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/DummySendServerObjectManager.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/DummySendServerObjectManager.cs @@ -4,6 +4,7 @@ using Speckle.Sdk.Common; using Speckle.Sdk.Dependencies.Serialization; using Speckle.Sdk.Serialisation.V2; +using Speckle.Sdk.Serialisation.V2.Send; using Speckle.Sdk.Transports; namespace Speckle.Sdk.Serialization.Tests; @@ -11,7 +12,7 @@ namespace Speckle.Sdk.Serialization.Tests; public class DummySendServerObjectManager(ConcurrentDictionary savedObjects) : IServerObjectManager { public IAsyncEnumerable<(string, string)> DownloadObjects( - IReadOnlyList objectIds, + IReadOnlyCollection objectIds, IProgress? progress, CancellationToken cancellationToken ) => throw new NotImplementedException(); @@ -22,7 +23,10 @@ CancellationToken cancellationToken CancellationToken cancellationToken ) => throw new NotImplementedException(); - public Task> HasObjects(IReadOnlyList objectIds, CancellationToken cancellationToken) + public Task> HasObjects( + IReadOnlyCollection objectIds, + CancellationToken cancellationToken + ) { return Task.FromResult(objectIds.Distinct().ToDictionary(x => x, savedObjects.ContainsKey)); } @@ -36,7 +40,7 @@ CancellationToken cancellationToken { foreach (var obj in objects) { - savedObjects.TryAdd(obj.Id, obj.Json); + savedObjects.TryAdd(obj.Id.Value, obj.Json.Value); } return Task.CompletedTask; } diff --git a/tests/Speckle.Sdk.Serialization.Tests/ExternalIdTests.cs b/tests/Speckle.Sdk.Serialization.Tests/ExternalIdTests.cs index 2c0c5014..925d73f8 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/ExternalIdTests.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/ExternalIdTests.cs @@ -26,7 +26,10 @@ public void Setup() public void ExternalIdTest_Detached(string lineId, string valueId) { var p = new Polyline() { units = "cm", value = [1, 2] }; - var serializer = new ObjectSerializer(new BasePropertyGatherer(), new Dictionary(), true); + using var serializer = new ObjectSerializerFactory(new BasePropertyGatherer()).Create( + new Dictionary(), + default + ); var list = serializer.Serialize(p).ToDictionary(x => x.Item1, x => x.Item2); list.ContainsKey(new Id(lineId)).ShouldBeTrue(); var json = list[new Id(lineId)]; @@ -53,7 +56,10 @@ public void ExternalIdTest_Detached_Nested(string lineId, string valueId) knots = [], weights = [], }; - var serializer = new ObjectSerializer(new BasePropertyGatherer(), new Dictionary(), true); + using var serializer = new ObjectSerializerFactory(new BasePropertyGatherer()).Create( + new Dictionary(), + default + ); var list = serializer.Serialize(curve).ToDictionary(x => x.Item1, x => x.Item2); list.ContainsKey(new Id(lineId)).ShouldBeTrue(); var json = list[new Id(lineId)]; @@ -81,7 +87,10 @@ public void ExternalIdTest_Detached_Nested_More(string lineId, string valueId) weights = [], }; var polycurve = new Polycurve() { segments = [curve], units = "cm" }; - var serializer = new ObjectSerializer(new BasePropertyGatherer(), new Dictionary(), true); + using var serializer = new ObjectSerializerFactory(new BasePropertyGatherer()).Create( + new Dictionary(), + default + ); var list = serializer.Serialize(polycurve).ToDictionary(x => x.Item1, x => x.Item2); list.ContainsKey(new Id(lineId)).ShouldBeTrue(); var json = list[new Id(lineId)]; @@ -111,7 +120,10 @@ public void ExternalIdTest_Detached_Nested_More_Too(string lineId, string valueI var polycurve = new Polycurve() { segments = [curve], units = "cm" }; var @base = new Base(); @base.SetDetachedProp("profile", polycurve); - var serializer = new ObjectSerializer(new BasePropertyGatherer(), new Dictionary(), true); + using var serializer = new ObjectSerializerFactory(new BasePropertyGatherer()).Create( + new Dictionary(), + default + ); var list = serializer.Serialize(@base).ToDictionary(x => x.Item1, x => x.Item2); list.ContainsKey(new Id(lineId)).ShouldBeTrue(); var json = list[new Id(lineId)]; From 675d896e0d2a654ebc2fb0a9e20d00a169b29b96 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Mon, 16 Dec 2024 12:01:53 +0000 Subject: [PATCH 05/13] Add method to replace in sqlite (#190) --- src/Speckle.Sdk/SQLite/SQLiteJsonCacheManager.cs | 13 +++++++++++++ .../V2/DummySendServerObjectManager.cs | 2 ++ .../DetachedTests.cs | 2 ++ .../DummySqLiteReceiveManager.cs | 2 ++ .../DummySqLiteSendManager.cs | 2 ++ 5 files changed, 21 insertions(+) diff --git a/src/Speckle.Sdk/SQLite/SQLiteJsonCacheManager.cs b/src/Speckle.Sdk/SQLite/SQLiteJsonCacheManager.cs index 9cd7a98c..8ffffddd 100644 --- a/src/Speckle.Sdk/SQLite/SQLiteJsonCacheManager.cs +++ b/src/Speckle.Sdk/SQLite/SQLiteJsonCacheManager.cs @@ -96,6 +96,7 @@ public void DeleteObject(string id) return null; // pass on the duty of null checks to consumers } + //This does an insert or ignores if already exists public void SaveObject(string id, string json) { using var c = new SqliteConnection(_connectionString); @@ -108,6 +109,18 @@ public void SaveObject(string id, string json) command.ExecuteNonQuery(); } + //This does an insert or replaces if already exists + public void UpdateObject(string id, string json) + { + using var c = new SqliteConnection(_connectionString); + c.Open(); + const string COMMAND_TEXT = "REPLACE INTO objects(hash, content) VALUES(@hash, @content)"; + using var command = new SqliteCommand(COMMAND_TEXT, c); + command.Parameters.AddWithValue("@hash", id); + command.Parameters.AddWithValue("@content", json); + command.ExecuteNonQuery(); + } + public void SaveObjects(IEnumerable<(string id, string json)> items) { using var c = new SqliteConnection(_connectionString); diff --git a/src/Speckle.Sdk/Serialisation/V2/DummySendServerObjectManager.cs b/src/Speckle.Sdk/Serialisation/V2/DummySendServerObjectManager.cs index 950a8667..827ed34a 100644 --- a/src/Speckle.Sdk/Serialisation/V2/DummySendServerObjectManager.cs +++ b/src/Speckle.Sdk/Serialisation/V2/DummySendServerObjectManager.cs @@ -15,6 +15,8 @@ public class DummySqLiteJsonCacheManager : ISqLiteJsonCacheManager public void SaveObject(string id, string json) => throw new NotImplementedException(); + public void UpdateObject(string id, string json) => throw new NotImplementedException(); + public void SaveObjects(IEnumerable<(string id, string json)> items) => throw new NotImplementedException(); public bool HasObject(string objectId) => throw new NotImplementedException(); diff --git a/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs b/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs index c48021f1..a226c956 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs @@ -380,6 +380,8 @@ public class DummySendCacheManager(Dictionary objects) : ISqLite public void SaveObject(string id, string json) => throw new NotImplementedException(); + public void UpdateObject(string id, string json) => throw new NotImplementedException(); + public bool HasObject(string objectId) => false; public void SaveObjects(IEnumerable<(string id, string json)> items) diff --git a/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteReceiveManager.cs b/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteReceiveManager.cs index cdf25af7..42961d6b 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteReceiveManager.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteReceiveManager.cs @@ -12,6 +12,8 @@ public class DummySqLiteReceiveManager(Dictionary savedObjects) public void SaveObject(string id, string json) => throw new NotImplementedException(); + public void UpdateObject(string id, string json) => throw new NotImplementedException(); + public void SaveObjects(IEnumerable<(string id, string json)> items) => throw new NotImplementedException(); public bool HasObject(string objectId) => throw new NotImplementedException(); diff --git a/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteSendManager.cs b/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteSendManager.cs index eb8780c0..ce1dae8c 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteSendManager.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteSendManager.cs @@ -8,6 +8,8 @@ public class DummySqLiteSendManager : ISqLiteJsonCacheManager public void SaveObject(string id, string json) => throw new NotImplementedException(); + public void UpdateObject(string id, string json) => throw new NotImplementedException(); + public void SaveObjects(IEnumerable<(string id, string json)> items) => throw new NotImplementedException(); public bool HasObject(string objectId) => throw new NotImplementedException(); From 991b31265f0ee41b774d7d4e6acedfd3efd9a775 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Tue, 17 Dec 2024 13:49:17 +0000 Subject: [PATCH 06/13] Fix data chunks getting written with closure tables (#191) * Never write closures for datachunks * add tests and format * add another test with array and reuses reference * Remove a writeline in tests --- .../Serialisation/V2/Send/ObjectSerializer.cs | 39 ++- .../DetachedTests.cs | 243 ++++++++++++++---- 2 files changed, 226 insertions(+), 56 deletions(-) diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/ObjectSerializer.cs b/src/Speckle.Sdk/Serialisation/V2/Send/ObjectSerializer.cs index 2777fdfc..1d72177a 100644 --- a/src/Speckle.Sdk/Serialisation/V2/Send/ObjectSerializer.cs +++ b/src/Speckle.Sdk/Serialisation/V2/Send/ObjectSerializer.cs @@ -98,7 +98,7 @@ public void Dispose() (Id, Json) item; try { - item = SerializeBase(baseObj, true).NotNull(); + item = SerializeBase(baseObj, true, default).NotNull(); } catch (Exception ex) when (!ex.IsFatal() && ex is not OperationCanceledException) { @@ -118,7 +118,7 @@ public void Dispose() // `Preserialize` means transforming all objects into the final form that will appear in json, with basic .net objects // (primitives, lists and dictionaries with string keys) - private void SerializeProperty(object? obj, JsonWriter writer, PropertyAttributeInfo inheritedDetachInfo = default) + private void SerializeProperty(object? obj, JsonWriter writer, PropertyAttributeInfo propertyAttributeInfo) { _cancellationToken.ThrowIfCancellationRequested(); @@ -172,10 +172,10 @@ private void SerializeProperty(object? obj, JsonWriter writer, PropertyAttribute AddClosure(new(kvp.Key)); } AddClosure(new(r.referencedId)); - SerializeProperty(ret, writer); + SerializeProperty(ret, writer, default); break; case Base b: - var result = SerializeBase(b, false, inheritedDetachInfo); + var result = SerializeBase(b, false, propertyAttributeInfo); if (result is not null) { writer.WriteRawValue(result.Value.Item2.Value); @@ -200,7 +200,7 @@ private void SerializeProperty(object? obj, JsonWriter writer, PropertyAttribute } writer.WritePropertyName(key); - SerializeProperty(kvp.Value, writer, inheritedDetachInfo: inheritedDetachInfo); + SerializeProperty(kvp.Value, writer, propertyAttributeInfo); } writer.WriteEndObject(); } @@ -210,7 +210,7 @@ private void SerializeProperty(object? obj, JsonWriter writer, PropertyAttribute writer.WriteStartArray(); foreach (object? element in e) { - SerializeProperty(element, writer, inheritedDetachInfo: inheritedDetachInfo); + SerializeProperty(element, writer, propertyAttributeInfo); } writer.WriteEndArray(); } @@ -257,7 +257,7 @@ private void SerializeProperty(object? obj, JsonWriter writer, PropertyAttribute } } - private (Id, Json)? SerializeBase(Base baseObj, bool isRoot, PropertyAttributeInfo inheritedDetachInfo = default) + private (Id, Json)? SerializeBase(Base baseObj, bool isRoot, PropertyAttributeInfo inheritedDetachInfo) { // handle circular references bool alreadySerialized = !_parentObjects.Add(baseObj); @@ -276,6 +276,8 @@ private void SerializeProperty(object? obj, JsonWriter writer, PropertyAttribute return new(json, id);*/ } + var isDataChunk = baseObj is DataChunk; + if (inheritedDetachInfo.IsDetachable) { Closures childClosures; @@ -291,7 +293,14 @@ private void SerializeProperty(object? obj, JsonWriter writer, PropertyAttribute } else { - childClosures = isRoot || inheritedDetachInfo.IsDetachable ? _currentClosures : []; + if (isDataChunk) //datachunks never have child closures + { + childClosures = []; + } + else + { + childClosures = isRoot || inheritedDetachInfo.IsDetachable ? _currentClosures : []; + } var sb = Pools.StringBuilders.Get(); using var writer = new StringWriter(sb); using var jsonWriter = SpeckleObjectSerializerPool.Instance.GetJsonTextWriter(writer); @@ -384,9 +393,13 @@ private Id SerializeBaseObject(Base baseObj, JsonWriter writer, Closures closure return chunk; } - private void SerializeOrChunkProperty(object? baseValue, JsonWriter jsonWriter, PropertyAttributeInfo detachInfo) + private void SerializeOrChunkProperty( + object? baseValue, + JsonWriter jsonWriter, + PropertyAttributeInfo propertyAttributeInfo + ) { - if (baseValue is IEnumerable chunkableCollection && detachInfo.IsChunkable) + if (baseValue is IEnumerable chunkableCollection && propertyAttributeInfo.IsChunkable) { List chunks = _chunks2Pool.Get(); _chunks2.Add(chunks); @@ -396,7 +409,7 @@ private void SerializeOrChunkProperty(object? baseValue, JsonWriter jsonWriter, foreach (object element in chunkableCollection) { crtChunk.data.Add(element); - if (crtChunk.data.Count >= detachInfo.ChunkSize) + if (crtChunk.data.Count >= propertyAttributeInfo.ChunkSize) { chunks.Add(crtChunk); crtChunk = new DataChunk { data = GetChunk() }; @@ -408,11 +421,11 @@ private void SerializeOrChunkProperty(object? baseValue, JsonWriter jsonWriter, chunks.Add(crtChunk); } - SerializeProperty(chunks, jsonWriter, inheritedDetachInfo: new PropertyAttributeInfo(true, false, 0, null)); + SerializeProperty(chunks, jsonWriter, new PropertyAttributeInfo(true, false, 0, null)); return; } - SerializeProperty(baseValue, jsonWriter, inheritedDetachInfo: detachInfo); + SerializeProperty(baseValue, jsonWriter, propertyAttributeInfo); } private static void MergeClosures(Dictionary current, Closures child) diff --git a/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs b/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs index a226c956..07d02914 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs @@ -1,11 +1,10 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Text; using NUnit.Framework; using Shouldly; using Speckle.Newtonsoft.Json; using Speckle.Newtonsoft.Json.Linq; using Speckle.Objects.Geometry; -using Speckle.Sdk.Dependencies.Serialization; using Speckle.Sdk.Host; using Speckle.Sdk.Models; using Speckle.Sdk.Serialisation; @@ -194,46 +193,46 @@ public void GetPropertiesExpected_All() public async Task CanSerialize_New_Detached2() { var root = """ - { - "list": [], - "arr": null, - "detachedProp": { - "speckle_type": "reference", - "referencedId": "32a385e7ddeda810e037b21ab26381b7", - "__closure": null - }, - "detachedProp2": { - "speckle_type": "reference", - "referencedId": "c3858f47dd3e7a308a1b465375f1645f", - "__closure": null - }, - "attachedProp": { - "name": "attachedProp", - "line": { - "speckle_type": "reference", - "referencedId": "027a7c5ffcf8d8efe432899c729a954c", - "__closure": null - }, - "applicationId": "4", - "speckle_type": "Speckle.Core.Tests.Unit.Models.BaseTests+SamplePropBase2", - "id": "c5dd540ee1299c0349829d045c04ef2d" + "list" : [ ], + "list2" : null, + "arr" : null, + "detachedProp" : { + "speckle_type" : "reference", + "referencedId" : "32a385e7ddeda810e037b21ab26381b7", + "__closure" : null + }, + "detachedProp2" : { + "speckle_type" : "reference", + "referencedId" : "c3858f47dd3e7a308a1b465375f1645f", + "__closure" : null + }, + "attachedProp" : { + "name" : "attachedProp", + "line" : { + "speckle_type" : "reference", + "referencedId" : "027a7c5ffcf8d8efe432899c729a954c", + "__closure" : null }, - "crazyProp": null, - "applicationId": "1", - "speckle_type": "Speckle.Core.Tests.Unit.Models.BaseTests+SampleObjectBase2", - "dynamicProp": 123, - "id": "fd4efeb8a036838c53ad1cf9e82b8992", - "__closure": { - "8d27f5c7fac36d985d89bb6d6d8acddc": 100, - "4ba53b5e84e956fb076bc8b0a03ca879": 100, - "32a385e7ddeda810e037b21ab26381b7": 100, - "1afc694774efa5913d0077302cd37888": 100, - "045cbee36837d589b17f9d8483c90763": 100, - "c3858f47dd3e7a308a1b465375f1645f": 100, - "5b86b66b61c556ead500915b05852875": 100, - "027a7c5ffcf8d8efe432899c729a954c": 100 - } + "applicationId" : "4", + "speckle_type" : "Speckle.Core.Tests.Unit.Models.BaseTests+SamplePropBase2", + "id" : "c5dd540ee1299c0349829d045c04ef2d" + }, + "crazyProp" : null, + "applicationId" : "1", + "speckle_type" : "Speckle.Core.Tests.Unit.Models.BaseTests+SampleObjectBase2", + "dynamicProp" : 123, + "id" : "2ebfd4f317754fce14cadd001151441e", + "__closure" : { + "8d27f5c7fac36d985d89bb6d6d8acddc" : 100, + "4ba53b5e84e956fb076bc8b0a03ca879" : 100, + "32a385e7ddeda810e037b21ab26381b7" : 100, + "1afc694774efa5913d0077302cd37888" : 100, + "045cbee36837d589b17f9d8483c90763" : 100, + "c3858f47dd3e7a308a1b465375f1645f" : 100, + "5b86b66b61c556ead500915b05852875" : 100, + "027a7c5ffcf8d8efe432899c729a954c" : 100 + } } """; var @base = new SampleObjectBase2(); @@ -271,14 +270,169 @@ public async Task CanSerialize_New_Detached2() var results = await process2.Serialize(@base, default).ConfigureAwait(false); objects.Count.ShouldBe(9); - var x = JObject.Parse(objects["fd4efeb8a036838c53ad1cf9e82b8992"]); - var y = x.ToString(Formatting.Indented); - Console.WriteLine(y); + var x = JObject.Parse(objects["2ebfd4f317754fce14cadd001151441e"]); JToken.DeepEquals(JObject.Parse(root), x).ShouldBeTrue(); results.RootId.ShouldBe(@base.id); results.ConvertedReferences.Count.ShouldBe(2); } + + [Test(Description = "Checks that all typed properties (including obsolete ones) are returned")] + public async Task CanSerialize_New_Detached_With_DataChunks() + { + var root = """ + { + "list" : [ { + "speckle_type" : "reference", + "referencedId" : "0e61e61edee00404ec6e0f9f594bce24", + "__closure" : null + } ], + "list2" : [ { + "speckle_type" : "reference", + "referencedId" : "f70738e3e3e593ac11099a6ed6b71154", + "__closure" : null + } ], + "arr" : null, + "detachedProp" : null, + "detachedProp2" : null, + "attachedProp" : null, + "crazyProp" : null, + "applicationId" : "1", + "speckle_type" : "Speckle.Core.Tests.Unit.Models.BaseTests+SampleObjectBase2", + "dynamicProp" : 123, + "id" : "efeadaca70a85ae6d3acfc93a8b380db", + "__closure" : { + "0e61e61edee00404ec6e0f9f594bce24" : 100, + "f70738e3e3e593ac11099a6ed6b71154" : 100 + } + } + """; + + var list1 = """ + { + "data" : [ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0 ], + "applicationId" : null, + "speckle_type" : "Speckle.Core.Models.DataChunk", + "id" : "0e61e61edee00404ec6e0f9f594bce24" + } + """; + var list2 = """ + { + "data" : [ 1.0, 10.0 ], + "applicationId" : null, + "speckle_type" : "Speckle.Core.Models.DataChunk", + "id" : "f70738e3e3e593ac11099a6ed6b71154" + } + """; + var @base = new SampleObjectBase2(); + @base["dynamicProp"] = 123; + @base.applicationId = "1"; + @base.list = new List() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + @base.list2 = new List() { 1, 10 }; + + var objects = new Dictionary(); + + var process2 = new SerializeProcess( + null, + new DummySendCacheManager(objects), + new DummyServerObjectManager(), + new BaseChildFinder(new BasePropertyGatherer()), + new ObjectSerializerFactory(new BasePropertyGatherer()), + new SerializeProcessOptions(false, false, true, true) + ); + var results = await process2.Serialize(@base, default).ConfigureAwait(false); + + objects.Count.ShouldBe(3); + var x = JObject.Parse(objects["efeadaca70a85ae6d3acfc93a8b380db"]); + JToken.DeepEquals(JObject.Parse(root), x).ShouldBeTrue(); + + x = JObject.Parse(objects["0e61e61edee00404ec6e0f9f594bce24"]); + JToken.DeepEquals(JObject.Parse(list1), x).ShouldBeTrue(); + + x = JObject.Parse(objects["f70738e3e3e593ac11099a6ed6b71154"]); + JToken.DeepEquals(JObject.Parse(list2), x).ShouldBeTrue(); + } + + [Test(Description = "Checks that all typed properties (including obsolete ones) are returned")] + public async Task CanSerialize_New_Detached_With_DataChunks2() + { + var root = """ + { + "list" : [ { + "speckle_type" : "reference", + "referencedId" : "0e61e61edee00404ec6e0f9f594bce24", + "__closure" : null + } ], + "list2" : [ { + "speckle_type" : "reference", + "referencedId" : "f70738e3e3e593ac11099a6ed6b71154", + "__closure" : null + } ], + "arr" : [ { + "speckle_type" : "reference", + "referencedId" : "f70738e3e3e593ac11099a6ed6b71154", + "__closure" : null + } ], + "detachedProp" : null, + "detachedProp2" : null, + "attachedProp" : null, + "crazyProp" : null, + "applicationId" : "1", + "speckle_type" : "Speckle.Core.Tests.Unit.Models.BaseTests+SampleObjectBase2", + "dynamicProp" : 123, + "id" : "525b1e9eef4d07165abb4ffc518395fc", + "__closure" : { + "0e61e61edee00404ec6e0f9f594bce24" : 100, + "f70738e3e3e593ac11099a6ed6b71154" : 100 + } + } + """; + + var list1 = """ + { + "data" : [ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0 ], + "applicationId" : null, + "speckle_type" : "Speckle.Core.Models.DataChunk", + "id" : "0e61e61edee00404ec6e0f9f594bce24" + } + """; + var list2 = """ + { + "data" : [ 1.0, 10.0 ], + "applicationId" : null, + "speckle_type" : "Speckle.Core.Models.DataChunk", + "id" : "f70738e3e3e593ac11099a6ed6b71154" + } + """; + var @base = new SampleObjectBase2(); + @base["dynamicProp"] = 123; + @base.applicationId = "1"; + @base.list = new List() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + @base.list2 = new List() { 1, 10 }; + @base.arr = [1, 10]; + + var objects = new Dictionary(); + + var process2 = new SerializeProcess( + null, + new DummySendCacheManager(objects), + new DummyServerObjectManager(), + new BaseChildFinder(new BasePropertyGatherer()), + new ObjectSerializerFactory(new BasePropertyGatherer()), + new SerializeProcessOptions(false, false, true, true) + ); + var results = await process2.Serialize(@base, default).ConfigureAwait(false); + + objects.Count.ShouldBe(3); + var x = JObject.Parse(objects["525b1e9eef4d07165abb4ffc518395fc"]); + JToken.DeepEquals(JObject.Parse(root), x).ShouldBeTrue(); + + x = JObject.Parse(objects["0e61e61edee00404ec6e0f9f594bce24"]); + JToken.DeepEquals(JObject.Parse(list1), x).ShouldBeTrue(); + + x = JObject.Parse(objects["f70738e3e3e593ac11099a6ed6b71154"]); + JToken.DeepEquals(JObject.Parse(list2), x).ShouldBeTrue(); + } } [SpeckleType("Speckle.Core.Tests.Unit.Models.BaseTests+SampleObjectBase")] @@ -310,6 +464,9 @@ public class SampleObjectBase2 : Base [Chunkable, DetachProperty] public List list { get; set; } = new(); + [Chunkable, DetachProperty] + public List list2 { get; set; } = null!; + [Chunkable(300), DetachProperty] public double[] arr { get; set; } From eec400d0cf600405b823a53fd2837b790d88d7ef Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Thu, 19 Dec 2024 14:34:15 +0000 Subject: [PATCH 07/13] Fix DataObject inheritance (#192) --- src/Speckle.Objects/Data/ArcgisObject.cs | 8 +------- src/Speckle.Objects/Data/Civil3dObject.cs | 10 +--------- src/Speckle.Objects/Data/EtabsObject.cs | 8 +------- src/Speckle.Objects/Data/NavisworksObject.cs | 9 +-------- src/Speckle.Objects/Data/RevitObject.cs | 11 +---------- src/Speckle.Objects/Data/TeklaObject.cs | 8 +------- src/Speckle.Objects/Interfaces.cs | 2 +- 7 files changed, 7 insertions(+), 49 deletions(-) diff --git a/src/Speckle.Objects/Data/ArcgisObject.cs b/src/Speckle.Objects/Data/ArcgisObject.cs index abe87aea..701a7871 100644 --- a/src/Speckle.Objects/Data/ArcgisObject.cs +++ b/src/Speckle.Objects/Data/ArcgisObject.cs @@ -6,16 +6,10 @@ namespace Speckle.Objects.Data; /// Represents a ArcGIS.Core.CoreObjectsBase object in ArcGIS /// [SpeckleType("Objects.Data.ArcgisObject")] -public class ArcgisObject : Base, IGisObject +public class ArcgisObject : DataObject, IGisObject { - public required string name { get; set; } public required string type { get; set; } - [DetachProperty] - public required List displayValue { get; set; } - - public required Dictionary properties { get; set; } - public required string units { get; set; } IReadOnlyList IDisplayValue>.displayValue => displayValue; diff --git a/src/Speckle.Objects/Data/Civil3dObject.cs b/src/Speckle.Objects/Data/Civil3dObject.cs index cad3e2fe..995a3c1c 100644 --- a/src/Speckle.Objects/Data/Civil3dObject.cs +++ b/src/Speckle.Objects/Data/Civil3dObject.cs @@ -6,9 +6,8 @@ namespace Speckle.Objects.Data; /// Represents an Autodesk.Civil.DatabaseServices.Entity object in Civil3d /// [SpeckleType("Objects.Data.Civil3dObject")] -public class Civil3dObject : Base, ICivilObject +public class Civil3dObject : DataObject, ICivilObject { - public required string name { get; set; } public required string type { get; set; } /// @@ -22,14 +21,7 @@ public class Civil3dObject : Base, ICivilObject [DetachProperty] public required List elements { get; set; } - [DetachProperty] - public required List displayValue { get; set; } - - public required Dictionary properties { get; set; } - public required string units { get; set; } IReadOnlyList ICivilObject.elements => elements; - - IReadOnlyList IDisplayValue>.displayValue => displayValue; } diff --git a/src/Speckle.Objects/Data/EtabsObject.cs b/src/Speckle.Objects/Data/EtabsObject.cs index fa11940f..3f0aeafe 100644 --- a/src/Speckle.Objects/Data/EtabsObject.cs +++ b/src/Speckle.Objects/Data/EtabsObject.cs @@ -6,9 +6,8 @@ namespace Speckle.Objects.Data; /// Represents a wrapper object in ETABS /// [SpeckleType("Objects.Data.EtabsObject")] -public class EtabsObject : Base, ICsiObject +public class EtabsObject : DataObject, ICsiObject { - public required string name { get; set; } public required string type { get; set; } /// @@ -17,11 +16,6 @@ public class EtabsObject : Base, ICsiObject [DetachProperty] public required List elements { get; set; } - [DetachProperty] - public required List displayValue { get; set; } - - public required Dictionary properties { get; set; } - public required string units { get; set; } IReadOnlyList ICsiObject.elements => elements; diff --git a/src/Speckle.Objects/Data/NavisworksObject.cs b/src/Speckle.Objects/Data/NavisworksObject.cs index 571cc286..1b321000 100644 --- a/src/Speckle.Objects/Data/NavisworksObject.cs +++ b/src/Speckle.Objects/Data/NavisworksObject.cs @@ -6,15 +6,8 @@ namespace Speckle.Objects.Data; /// Represents a "first selectable ancestor" Navisworks.ModelItem object in Navisworks /// [SpeckleType("Objects.Data.NavisworksObject")] -public class NavisworksObject : Base, INavisworksObject +public class NavisworksObject : DataObject, INavisworksObject { - public required string name { get; set; } - - [DetachProperty] - public required List displayValue { get; set; } - - public required Dictionary properties { get; set; } - public required string units { get; set; } IReadOnlyList IDisplayValue>.displayValue => displayValue; diff --git a/src/Speckle.Objects/Data/RevitObject.cs b/src/Speckle.Objects/Data/RevitObject.cs index 20238bee..bdbd51cc 100644 --- a/src/Speckle.Objects/Data/RevitObject.cs +++ b/src/Speckle.Objects/Data/RevitObject.cs @@ -1,4 +1,3 @@ -using Speckle.Objects.Geometry; using Speckle.Sdk.Models; namespace Speckle.Objects.Data; @@ -7,9 +6,8 @@ namespace Speckle.Objects.Data; /// Represents an Autodesk.Revit.DB.Element object in Revit /// [SpeckleType("Objects.Data.RevitObject")] -public class RevitObject : Base, IRevitObject +public class RevitObject : DataObject, IRevitObject { - public required string name { get; set; } public required string type { get; set; } public required string family { get; set; } public required string category { get; set; } @@ -25,14 +23,7 @@ public class RevitObject : Base, IRevitObject [DetachProperty] public required List elements { get; set; } - [DetachProperty] - public required List displayValue { get; set; } - - public required Dictionary properties { get; set; } - public required string units { get; set; } IReadOnlyList IRevitObject.elements => elements; - - IReadOnlyList IDisplayValue>.displayValue => displayValue; } diff --git a/src/Speckle.Objects/Data/TeklaObject.cs b/src/Speckle.Objects/Data/TeklaObject.cs index 737dbb55..c8de0a27 100644 --- a/src/Speckle.Objects/Data/TeklaObject.cs +++ b/src/Speckle.Objects/Data/TeklaObject.cs @@ -6,9 +6,8 @@ namespace Speckle.Objects.Data; /// Represents an Tekla.Structures.Model.ModelObject object in Tekla Structures /// [SpeckleType("Objects.Data.TeklaObject")] -public class TeklaObject : Base, ITeklaObject +public class TeklaObject : DataObject, ITeklaObject { - public required string name { get; set; } public required string type { get; set; } /// @@ -17,11 +16,6 @@ public class TeklaObject : Base, ITeklaObject [DetachProperty] public required List elements { get; set; } - [DetachProperty] - public required List displayValue { get; set; } - - public required Dictionary properties { get; set; } - public required string units { get; set; } IReadOnlyList ITeklaObject.elements => elements; diff --git a/src/Speckle.Objects/Interfaces.cs b/src/Speckle.Objects/Interfaces.cs index 2e9ac929..93237bc9 100644 --- a/src/Speckle.Objects/Interfaces.cs +++ b/src/Speckle.Objects/Interfaces.cs @@ -126,7 +126,7 @@ public interface IProperties : ISpeckleObject Dictionary properties { get; } } -public interface IDataObject : ISpeckleObject, IProperties, IDisplayValue> +public interface IDataObject : IProperties, IDisplayValue> { /// /// The name of the object, primarily used to decorate the object for consumption in frontend and other apps From a1b9030dbad84958a4b5c22267f0f8f938492db6 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Thu, 2 Jan 2025 10:12:46 +0000 Subject: [PATCH 08/13] Fixes Markdown to be non HTML based for github and nuget (#195) --- README.md | 20 ++++++++------------ logo.png | Bin 5866 -> 6131 bytes 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 013fcff7..5a437272 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,14 @@ -

-
- Speckle | Sharp | SDK -

+![Speckle Box](/logo.png) +Speckle | Sharp | SDK +================================================================================================================================= -

Twitter Follow Community forum users website docs

+[![Twitter Follow](https://img.shields.io/twitter/follow/SpeckleSystems?style=social)](https://twitter.com/SpeckleSystems) [![Community forum users](https://img.shields.io/discourse/users?server=https%3A%2F%2Fspeckle.community&style=flat-square&logo=discourse&logoColor=white)](https://speckle.community) [![website](https://img.shields.io/badge/https://-speckle.systems-royalblue?style=flat-square)](https://speckle.systems) [![docs](https://img.shields.io/badge/docs-speckle.guide-orange?style=flat-square&logo=read-the-docs&logoColor=white)](https://speckle.guide/dev/) -> Speckle is the first AEC data hub that connects with your favorite AEC tools. Speckle exists to overcome the challenges of working in a fragmented industry where communication, creative workflows, and the exchange of data are often hindered by siloed software and processes. It is here to make the industry better. + > Speckle is the first AEC data hub that connects with your favorite AEC tools. Speckle exists to overcome the challenges of working in a fragmented industry where communication, creative workflows, and the exchange of data are often hindered by siloed software and processes. It is here to make the industry better. -

- .NET SDK, Tests, and Objects -

+### .NET SDK, Tests, and Objects -

Codecov

- -
+[![Codecov](https://codecov.io/gh/specklesystems/speckle-sharp-sdk/graph/badge.svg?token=TTM5OGr38m)](https://codecov.io/gh/specklesystems/speckle-sharp-sdk) > [!WARNING] > This is an early beta release, not meant for use in production! We're working to stabilise the 3.0 API, and until then there will be breaking changes. You have been warned! @@ -24,6 +19,7 @@ This repo is the home of our next-generation Speckle .NET SDK. It uses .NET Stan - **SDK** - [`Speckle.Sdk`](https://github.com/specklesystems/speckle-sharp-sdk/tree/dev/src/Speckle.Sdk): Transports, serialization, API wrappers, and logging. + - [`Speckle.Sdk.Dependencies`](https://github.com/specklesystems/speckle-sharp-sdk/tree/dev/src/Speckle.Sdk.Dependencies): Dependencies and code that shouldn't cause conflicts in Host Apps. This uses [IL Repack](https://github.com/gluck/il-repack) to merge together and interalized only to be used by Speckle. - **Speckle Objects** - [`Speckle.Objects`](https://github.com/specklesystems/speckle-sharp-sdk/tree/dev/src/Speckle.Objects): The Speckle Objects classes used for conversions. - **Tests** diff --git a/logo.png b/logo.png index 2c86ad54500475af2c0760dc0af20259c33da04d..d56f2522586d2f660c1e67b025cd2617591b2f5a 100644 GIT binary patch literal 6131 zcmeHLXIB$Uw+%5sXo_@@-fO@{5d%a(niK^eq&Mj$^cEoWu5?fYf=I6lQWCm|fIz6C zw8TOQ9qBFM&AaZp_uKsk&xcuaX4aWC`90jWsPxu1dV5yu*4G|W>bk1zGuB=-tQAlynVbGHK?yI z7Tl9)a|y=fnal~|29}A61%7DDo*PD-Qb$1XwcLzHpG%vkF-wS;gd*c=r^d<>X<8Do zePe&(5NDWZrr#dOIBRH`hlS(?76FZsM5}M8p?oePej4_IlkI0lQjTW|4Nipjri;Yy z>Yp9M1JP3ulyXtd^MaGyXOpyU@ohTN70L%;)OpE{b6Q-|ww~VRw-Irz?tIA8bBFbn zr#Du1ZtlSq#lp8)-{$9nK^$BFuk}ZFO|;hE9~GaV;=eSjp{1_LM(Tmc}mML}*{ z(I)iM>#7>G z!0v&G#3;O#B9uNR*Sye&sSP;{MXcHc1O$jQCs$pkp151dw66%XI^Oi46V=yi4ZM>F zdSPj@&iU?1ngrB&2{A6rqibls4s}FU9kamF4yz#rY_YKQww{OEZ9UL7v4{=2Ae$g- zthO$n?v)AE*jS#T$OEIt-Xv52sIG7=x24?wD+4`T^NU95LEpmUt~?h9E;qhiJ-+qU z^cEk9%2;^e6|yL4)6fZV?zMh4ved_hf;n^n*xtIYm@pkEbQgHJyXg(s-7Iu7C@VZD ze#?hTF6jE6E#_Foo^$^UF&_2H>oV-)oy!C0y71`z0hdkFXvMC;wE!Ntg|e!K!jp{p zWefA@qo&Eawd91de`|$p!F-27Cjz|3hlDV)K}iff-){AB*pgW!L_^m*MLjtSnx#qwiQL zHC~n%jwDH>Xl6Ou7zb4Cx~PM$w%hwodFsufK4wm}u@IYv^C}>(yq`Kh%`?@om-sDg z*88HCCF?%@b#E9%J6cw{v;Gi9XIA*6YhK#@KCHcspim z`ytBJN~58FnboYVPe*x?wo1(g2hi4Pv4iFkA5`o0Sv$MUnSUsV5QveVp0Q$6R zxKaVXc-!%~+F=lve{rvb&_x-dId;&j`UC1Wu%RVCQ>q!Q1_1UkgZnn-&C5x(?v74# zyVOrfd0ENUw479hhER<2_R8v!XJrQsF9ikI%a#3p`%Iq3H#X#WxR}%JE@H)5 z>oMV;c$sgpyg7uf-t9+ry~#Ts_}mRC-fB?_UOEbJLAIKoWGdt>} zPG_k{m@c7;PkzhZ;8YsmUe{5=kuW^GD^oVLvRaF;R20Rm;&2$#?z9qZvtzVl-@Z6kC{ zQtW|@=J8gu$HFeI#F-E29Io`jj~>J?7Yf|lZl%k&JQ(@@4^QXr^T%#7n2vf;aI2?M zNAOl(J1QNm@ooPys}O)p=m{3(?>o8uCf}k66CQxi5%Q@-6JlNr-LR{__!X{PB*SJE z@jn>jdR(i-&4UK=7n~DP>P0bdIX}hA53I*t_{=ep*T0x-RYCklSActkh+Fv)wzK)0 zHU;AO)#P-CwJ5?*q4P&a+%nUL#@u1cL>LX`-Shs5gIck4!EV9!>e;d(C`u=65_axx@qJrqKuXAmv?P@l(z1@b( zqd*44U#!7TtAfOi(3xJ2rXA&UIlmWeNUafmdR?V`_s?zlu`X}lMV0lI%nL;}phIfU zN}Jg6d;t5RdbYdQP~3jG$1CL8nrZ6I%7Uw)^#^L)U#TGX^M!R9{EIsG3??lLKxlg$ z>+-Q8DoCe39TZnfca2;8{$jeUYWCA;my|F&jTNH^qFBAEyN}i2I2c6F$Ovn92(HO@*Os9ny2J4z-AL>w6BhX;6n5P5ga;@l$PX>QOwW0@ch<125jrLu zII9?lw(n(K3J}!HxXmWf{}1~SUH+Y<+EH|Y`{-&`;OSaA-Y{$6+l4xxVfGI7aT4KJ z!O|aPVxcwl}XAENS^$`$fq4g<{0_h0I zD*HlMZW;?#v40fFOPi-f7t7da-}9RdZuGMocJX_^t9ZKv!BJK2vN4ME-aa^bS-!^h zGhMw8e8O&UJf!<_2Z~s_GJK}HZ^(&ba}bNhJhP5me$FA)H?I-j3K4-i`9a=%91PEx zYG(JXHzujfs@8D@vc+@Bi(pm-cWUwBe|Ex!6+PYNAE_5;mvI9jiB^Pue<%cu$SmQ* ziK0tVU6waGla_F_s!=7n?~E!o?O*H}#JsB(;7?K4MAJ{Ib{x`a_&njlu_fQSlx{1LW)cZez0Ce* zu_p7*12t%t&i5Lr+b|1J$6w`KF-XU?_;ef zlOp#jBG$E7VZ&V=zY|(#!xp3)TLuS^%}J$O5hSxgu{B{Di@12cl`}pshwMnr31W@E zvcmlqXWWEPT*V6HRp2Ryz3kLjaRpVLsVGfWqih5HSlacyU5_s_HXg~_oaeHs!4df7 z*Fxw+)3Ei_eun<-Rg^EWDSue1F>$E@S?)+uf?@kFy<^lC!9Q5T0lfyNpTKLgsE(r` zCu>O*+-gG7O$O31C>InRW)|1Ixr%svd3*hhHkq+82-yC<7pml}a&z(G;Hg5mMtwte zP)M0ks@T;L)nS^kn~WSypjyb6LdM_~ZMzx68J^moT*`~9G_XcLOQy>Cd6NMVD+4az zLoh!k<3TzV=nf4(M%2n)y$4J|COY8B$VgENjqjy;9$*fENWw!}7#HKXa3mq$KZ$`t z2*kLkEueh`m=9vK%7T0lu#E+j+>ijds;OlGO57(!%c^>QpQpW3VmQ=y-wZ_M!5%oi5D!+4L9Y#839WzV=WeZlGv6$r`{MR8r>-nPpk^{Vs&#<=0?qWo;?0vtY*86lVYPf*FRSYyDq4hVaj z?2zHYt7#WjAY&zKl%3`=LkGqe*J@c1W*&NsGrz0NO{VQ6&_kBRiv79u6jMr_3!2xh zI}OOtk6;S$baao7=J5*;E9fOVgoe1>TG;H8%|S*LPLnNyFtzb?>fMqmkm!bE|K6uny#OoiTD05{PJ}5WCUI&y+|=ioZ_yWEm@1 z&Grtc`g-Ry&b?-Z)J~TK3 zhq@KdzB7L$bxptUF2a|CicT{8g68FpXW{rnNHlRXnygnucN=xnND*(Wk}M<68|nY# zeeJMhWsFu-a^1+-G1El zKSZ;E)9;09iyFtyN9#iD#8QrSsgqBMnD4Kt29%;O$=hb#A3|{}r35yeG18f$^Vpht z@e1fUR9e<+D{t%StXJ+<7KBDV{w};~B(kaDtbh7_r=ufBe9G65MCp~SH^FDqYs=ij zHyIUkz}aloeZ__hG8vR$=x1q`wcI^zx3y}oks>*ph z`=vZ>NyAkw;6#GZ?UzT&V)jXi5#VzQ9lbtWW@%F5Eb@_@c*Q}n&a)8~KfCgP`Q8}W zLF$TBA-6K)yF!>TCu}Brx47TYW%gygKA$wG1ercq}~Ci40R~h zq0bU&6W{EM*A#T$Tt)RivP`_?X=k&7-XW#Y!`n|HvmWOzW)n!Kh5+=tl7wPW#`?$OpgSLp9GBO3J zkxCQcpPK6N7Zz1LP{GdgkG;%l2QVffc-iU*--b-WQT~;_#lhoB5drE@qi$d2HZ@^r z<*?T4Ait_Uc!OdMd@1*2qe`Qvl)(G5U_+r$ERSR6YgwG%1@Qv}b5Pu=ju@Y?v`(_V z=No(EuM3R7ll-L* O0O)8MXw*Njjs6c;<=vzJ literal 5866 zcmds52~bn_)(=pnPeoo^5yBF*5fLLGkxfXTxBww+1q2L&)Wl*$f=Ji`h{{)O;UW#2 z0z#-{5g{NNL>7b8k{~Ex0wR$`M8KdCfgn44H*u-czIncm@0&N@yEAw0l%pZQD4^wN-Cfh#SxX+l5~fXE>3w z7?FwOoE39G*d@M74yUY}bloXvChoh;Ol1{B37Lz#AO-O$K$T!hu!5M!*)1}V(9SyR zGs6Y@g@)q#c71eu08!`j3IlCuNS6cAC!rd$HNt@>CBEbw!kMshuRB|_l!V`k>10wh zswbzqVzD?aNLVbs>1BMs@lbQplE2P^i^2(G8zeV-2$RgvWlnmWc|!Y)p6J0MAG0#{ zr;@O$EWV)4baP55A702tEI!H-&)-uj_`;sIirCAE#u>0_*PUt@`kQ zezys=rM!FE$G?UgYNndtb8HQ>y_CT4`$Pleb^3~} zA!mWT!X4r{;v^?bWG=~EjpZ|JnaRQkaeg~o#ThQlW!E7`c6fEg7HSBNk0@}tR1-g( zrD6I5$6UHZ&@1i~SBe2LrrM?_m%_9Y{~*nx{<6GexVq%3UVs1MEof+U9%1XjI%nDpKFYXLxebEMU;4O{!q+PQasL@LlVu?qz zPzFXt5?HEr)1BoR-v!Pa_Ze>Q51uCdL_ZdJoj-8G01A~zYV^{Yx+Y<%i9Ai{{-GK@ zRkOAz?1s#1PqcG{2CD~kq5GfK=vB^vMv>lQL#;bVpKU6txUO-vBOs`@qxCzzqIyK{ zYMh7-(c_FzlqxYDI;lDxLb*J^GvM|;$e=btS3Sb=S;NcK2#iX=SXo8oQR{Coe5d<7 zYt_-1?wt{JrHG>Yx~Y$ZDhhf!h~3Xglg@Q`-AgkRHr+k}^-`nqvkY>{8g`ZValJ#I zC-nKPwD!k&&&Mn`iG2-l@r8Eqvo|811i$glF3s-NaR0Sj&^@cP-}*()6fMdZbNCf( zELuAwqJ(4m`c^U@J$ME5pyIr;W_!T}JmcBzo307Mfv_~qo9Y52Np;h(ufb)>(Yu#m zGMBx%Vh7USK^c+MtQ7Dn`U2fPbjQf!wI|cgW?oE*)E*kM#H0l1NhBNPk_ao*!ozaa zcAKMtVA$##E(LTvV`l7zOTOkYDLOMs+dECOcBMc{Qrpz;OFWJu-L`|Lg77z;$)qnI z9WuR^yUfYb8>gEZyUV3Q zMCZA3q*&tK%2gTRC8oBpN`ICQXnkiOEH$n+D_5qO^XNbw)kUs2M?_SrfSht{V4cO- zN=m~?u#8{NM`b`7AL~)N&^;xZFa_U`^Z~P>q9wY|s3KjBY zs&hfOmXOcDZZS;d>r5u`2R2kxj_}d@bo&PTXP@WkUQT@>4O|f49xbyCYwR0|SG`f( z8{GQnu#0uKZ?I@YgcI_7+9~Yr3ei2#jSk}80)a;D(IKTdxf{XCNq* zb~22gU#2-~ekv_B^P)SRuAJ-lz7;>@E`|H{AZ<(^{5y08K(~ng&kKs)O@3vs_X5E7;1h&FfR6xOGPwDDfi&;cy8z(@kgW_!Iz8GrD9`^;=hF(Aq`(U_(_hHSEAdyKb zai~IN`OSx8L#q2D*E@#v(gC1y8#2Jio=1g3>$sJ@OXs3GV@K1e<-sP%(oiT=i$Ewj z9#+w(+ETW9-~5ZFYfz~1K?0CH_*;`Q?}u_l6ae5AI`m|r23OeBl?D_0RRz@tifCxVa-qqnf%7(M7rdP{c%WC%lKsK-nifG%4 zXI$cj@O^Uj70l{VY~gsaJuf*Ab_51>gUwb_>Xb&C8gH(z|u!{q-C!RKe1x(+UkMzFfJL3_;o%)eE04weEZ7FKecvlaj7QwB!zhDZJYOrgN8i>U001(py_Mr^;R7{6-ubjqPSd3>x6;~Rf+KGQ zr?^}&AKkrn=W;T)bs)^s5^3`sGsbO5e-hg(0#}n8+MqXzv9eex%Z6n|SV3EJj!^Oq z9>{#aAIO)Ghv^nt^O&*qXO_d>wN&-Sv4C2 zo7bLwhBKA=4N-NBnHoJ8zX1Q>S!wzcY<|ytPn!A=5lr}0qHkKcVhYL|r&-rGgQrac zqgip|^k{qgB>?E9#Z^9?#ZBFDOHzm9XR=1KP>*JNzI02%%4$J;76m zG(yRV-`&5Jy#*jMHu2sdL)dUqU;g;rS-Ij{;{)$O{$&kWoHy=2^!s7HGC=>F0~yG$ zXP6JM`cerc5Rj}7G6X^XRSh|7fAV{x{P%q2MUfNuyX+4f$THNXysPM22vEewDF3*I zEYAB*dGGgqMc)DXryTs_3_qPYLdlJke^1-}Im1t9?*EQk$VV!I^!{`W`8@a!RrO0fb{Ll*Q^-tFL7tiXSr_rZRxsOlEf7cuYhxH$4-hZy+KCGae{naS& z!D%hOO4h{7hx$R?E$|ds=?m^A9{fd0m+zsBm@9X)Vdk>Ea1yDS1r<)pC%OAjJ<{Kb zFW(bQHaIIr^*iks)QwQt8v7<&xp|0K`%YYvZ?E-E>_PmWcj7JQm&+?E)7CD^eJ5U% z%J&a?0}8gKVIVB%jms{FW~%4hm?e0y%N6G<>#zFFHok#?@-@efzK?rZtRg@An5_~dtW1GCY3tkiszA6LX805h?9f4hg#nGr1 z`7#UH?rV-2)qI!FW^u&(a3~cE?+s?|x(pNIE(+3&k=ygR`^$P{0_CnR)oa|wxllME z{;3_^lUIuI7ZSw}$&R9J3tK`myj`%2WrDk|@#iCzv18HQ@w_B@S%IoROI&5TZUNqt zUFxdN^z3YI>;zoTl_f=U?K_;mEpx3R8)h`~CI#$E|GmRWsbiBY= zNFo+<>~Px+sYBRpR{kf;4sb4fU*aeve+7P{@uh4*Um&^pFeik%;3pqGB2at3-{cbu4?(fGtKAr#LBjgbCj=PrC32uhFT(LTLrr**NP;UJ z;?!UD%sEuzxD-LC%?-e`(z!)`)n@+rEf{?!T&O@C<{ZT7vw!BE<7P6WMj&xj_2UEJ z-^6dNOR{*3b3$Q}8|BKh+5_@lE9<GEysod;D%qYK@ z=H-GH+6sSex9*{loDwiWi~&zRXTNBDNd5ySTgPPj_LNm8UR(IC#oh+gT_WF*<9Q;C5i)Wxh)w{!Guf-vzF zX|i;JgTcY$Z4EQNpR9zPAckF$bu_6Yj@@TirSE^v$ce==61Q=DMCKtEys=o;GU1j* zreYwc#NyQ&d_xo#Qt%>i@aH?Je3#q!!KH-Cr=wgyMTjK$$_sZC%tDRV#H#ZXEMem+ z3B9I?wsw$1K>gM4cuhYVPxqw#veaLrr$0yf8>DS0OS1LIOzSl1PPNSb|Mex|FDb-_ Ww&g!}_Y=qx!R&V3Rk|ba)c*p8ssttg From 1fe1a54dcf57eda066b48a71b87e62ae019c6377 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Fri, 3 Jan 2025 12:26:19 +0000 Subject: [PATCH 09/13] Custom task scheduler for serialization, fix batch size calc (#194) * Add a custom task scheduler * Better usage, don't wait to enqueue to save to channels * Completely pre-cal batch size to avoid spinning issues * Try to fix cache counting * properly dispose things * format * clean up * adjust count and save on current thread * move batch it's own file * update a few packages * fix build and add batch tests --- Directory.Packages.props | 7 +- build/packages.lock.json | 6 +- src/Speckle.Objects/packages.lock.json | 12 +-- src/Speckle.Sdk.Dependencies/Pools.cs | 19 ++++- .../Serialization/Batch.cs | 25 ++++++ .../Serialization/ChannelExtensions.cs | 2 +- .../Serialization/ChannelSaver.cs | 82 ++++++------------- .../SizeBatchingChannelReader.cs | 27 ++---- .../packages.lock.json | 36 ++++---- .../Api/Operations/Operations.Send.cs | 7 +- .../V2/Send/PriorityScheduler.cs | 43 ++++++++++ .../Serialisation/V2/Send/SerializeProcess.cs | 65 +++++++++------ src/Speckle.Sdk/packages.lock.json | 12 +-- .../packages.lock.json | 6 +- .../Program.cs | 2 +- .../packages.lock.json | 6 +- .../DetachedTests.cs | 8 +- .../ExplicitInterfaceTests.cs | 2 +- .../SerializationTests.cs | 2 +- .../packages.lock.json | 6 +- .../packages.lock.json | 6 +- .../packages.lock.json | 6 +- .../Serialisation/BatchTests.cs | 44 ++++++++++ .../Serialisation/SimpleRoundTripTests.cs | 1 + .../Speckle.Sdk.Tests.Unit/packages.lock.json | 6 +- 25 files changed, 274 insertions(+), 164 deletions(-) create mode 100644 src/Speckle.Sdk.Dependencies/Serialization/Batch.cs create mode 100644 src/Speckle.Sdk/Serialisation/V2/Send/PriorityScheduler.cs create mode 100644 tests/Speckle.Sdk.Tests.Unit/Serialisation/BatchTests.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index d86ca569..6f1923c7 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -9,7 +9,7 @@ - + @@ -19,7 +19,7 @@ - + @@ -27,9 +27,8 @@ - - + diff --git a/build/packages.lock.json b/build/packages.lock.json index 0e580e7a..775329d9 100644 --- a/build/packages.lock.json +++ b/build/packages.lock.json @@ -32,9 +32,9 @@ }, "PolySharp": { "type": "Direct", - "requested": "[1.14.1, )", - "resolved": "1.14.1", - "contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ==" + "requested": "[1.15.0, )", + "resolved": "1.15.0", + "contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g==" }, "SimpleExec": { "type": "Direct", diff --git a/src/Speckle.Objects/packages.lock.json b/src/Speckle.Objects/packages.lock.json index e0eab833..74ea8aa1 100644 --- a/src/Speckle.Objects/packages.lock.json +++ b/src/Speckle.Objects/packages.lock.json @@ -29,9 +29,9 @@ }, "PolySharp": { "type": "Direct", - "requested": "[1.14.1, )", - "resolved": "1.14.1", - "contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ==" + "requested": "[1.15.0, )", + "resolved": "1.15.0", + "contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g==" }, "Speckle.InterfaceGenerator": { "type": "Direct", @@ -333,9 +333,9 @@ }, "PolySharp": { "type": "Direct", - "requested": "[1.14.1, )", - "resolved": "1.14.1", - "contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ==" + "requested": "[1.15.0, )", + "resolved": "1.15.0", + "contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g==" }, "Speckle.InterfaceGenerator": { "type": "Direct", diff --git a/src/Speckle.Sdk.Dependencies/Pools.cs b/src/Speckle.Sdk.Dependencies/Pools.cs index 86bfe7c1..5f3451fb 100644 --- a/src/Speckle.Sdk.Dependencies/Pools.cs +++ b/src/Speckle.Sdk.Dependencies/Pools.cs @@ -1,4 +1,5 @@ -using System.Text; +using System.Collections.Concurrent; +using System.Text; using Microsoft.Extensions.ObjectPool; namespace Speckle.Sdk.Dependencies; @@ -33,6 +34,19 @@ public bool Return(Dictionary obj) } } + private sealed class ObjectConcurrentDictionaryPolicy + : IPooledObjectPolicy> + where TKey : notnull + { + public ConcurrentDictionary Create() => new(Environment.ProcessorCount, 50); + + public bool Return(ConcurrentDictionary obj) + { + obj.Clear(); + return true; + } + } + private sealed class ObjectListPolicy : IPooledObjectPolicy> { public List Create() => new(50); @@ -48,4 +62,7 @@ public bool Return(List obj) public static Pool> CreateDictionaryPool() where TKey : notnull => new(new ObjectDictionaryPolicy()); + + public static Pool> CreateConcurrentDictionaryPool() + where TKey : notnull => new(new ObjectConcurrentDictionaryPolicy()); } diff --git a/src/Speckle.Sdk.Dependencies/Serialization/Batch.cs b/src/Speckle.Sdk.Dependencies/Serialization/Batch.cs new file mode 100644 index 00000000..368cfd5c --- /dev/null +++ b/src/Speckle.Sdk.Dependencies/Serialization/Batch.cs @@ -0,0 +1,25 @@ +namespace Speckle.Sdk.Serialisation.V2.Send; + +public class Batch(int capacity) : IHasSize + where T : IHasSize +{ +#pragma warning disable IDE0032 + private readonly List _items = new(capacity); + private int _batchSize; +#pragma warning restore IDE0032 + + public void Add(T item) + { + _items.Add(item); + _batchSize += item.Size; + } + + public void TrimExcess() + { + _items.TrimExcess(); + _batchSize = _items.Sum(x => x.Size); + } + + public int Size => _batchSize; + public List Items => _items; +} diff --git a/src/Speckle.Sdk.Dependencies/Serialization/ChannelExtensions.cs b/src/Speckle.Sdk.Dependencies/Serialization/ChannelExtensions.cs index cb56850c..a03a7337 100644 --- a/src/Speckle.Sdk.Dependencies/Serialization/ChannelExtensions.cs +++ b/src/Speckle.Sdk.Dependencies/Serialization/ChannelExtensions.cs @@ -5,7 +5,7 @@ namespace Speckle.Sdk.Serialisation.V2.Send; public static class ChannelExtensions { - public static BatchingChannelReader> BatchBySize( + public static BatchingChannelReader> BatchBySize( this ChannelReader source, int batchSize, bool singleReader = false, diff --git a/src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs b/src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs index 4f974eda..97c00f10 100644 --- a/src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs +++ b/src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs @@ -7,16 +7,14 @@ namespace Speckle.Sdk.Dependencies.Serialization; public abstract class ChannelSaver where T : IHasSize { - private const int SEND_CAPACITY = 50; + private const int SEND_CAPACITY = 500; private const int HTTP_SEND_CHUNK_SIZE = 25_000_000; //bytes private static readonly TimeSpan HTTP_BATCH_TIMEOUT = TimeSpan.FromSeconds(2); private const int MAX_PARALLELISM_HTTP = 4; - private const int HTTP_CAPACITY = 50; - private const int MAX_CACHE_WRITE_PARALLELISM = 1; + private const int HTTP_CAPACITY = 500; + private const int MAX_CACHE_WRITE_PARALLELISM = 4; private const int MAX_CACHE_BATCH = 200; - private bool _enabled; - private readonly Channel _checkCacheChannel = Channel.CreateBounded( new BoundedChannelOptions(SEND_CAPACITY) { @@ -29,59 +27,31 @@ public abstract class ChannelSaver _ => throw new NotImplementedException("Dropping items not supported.") ); - public Task Start( - bool enableServerSending = true, - bool enableCacheSaving = true, - CancellationToken cancellationToken = default - ) - { - ValueTask t = new(Task.FromResult(0L)); - if (enableServerSending) - { - _enabled = true; - var tChannelReader = _checkCacheChannel - .Reader.BatchBySize(HTTP_SEND_CHUNK_SIZE) - .WithTimeout(HTTP_BATCH_TIMEOUT) - .PipeAsync( - MAX_PARALLELISM_HTTP, - async x => await SendToServer(x, cancellationToken).ConfigureAwait(false), - HTTP_CAPACITY, - false, - cancellationToken - ); - if (enableCacheSaving) - { - t = new( - tChannelReader - .Join() - .Batch(MAX_CACHE_BATCH) - .WithTimeout(HTTP_BATCH_TIMEOUT) - .ReadAllConcurrently(MAX_CACHE_WRITE_PARALLELISM, SaveToCache, cancellationToken) - ); - } - else - { - t = tChannelReader.ReadUntilCancelledAsync(cancellationToken, (list, l) => new ValueTask()); - } - } - - return t.AsTask(); - } - - public async ValueTask Save(T item, CancellationToken cancellationToken = default) - { - if (_enabled) - { - await _checkCacheChannel.Writer.WriteAsync(item, cancellationToken).ConfigureAwait(false); - } - } - - public abstract Task> SendToServer(List batch, CancellationToken cancellationToken); - - public ValueTask Done() + public Task Start(CancellationToken cancellationToken = default) => + _checkCacheChannel + .Reader.BatchBySize(HTTP_SEND_CHUNK_SIZE) + .WithTimeout(HTTP_BATCH_TIMEOUT) + .PipeAsync( + MAX_PARALLELISM_HTTP, + async x => await SendToServer(x, cancellationToken).ConfigureAwait(false), + HTTP_CAPACITY, + false, + cancellationToken + ) + .Join() + .Batch(MAX_CACHE_BATCH) + .WithTimeout(HTTP_BATCH_TIMEOUT) + .ReadAllConcurrently(MAX_CACHE_WRITE_PARALLELISM, SaveToCache, cancellationToken); + + public ValueTask Save(T item, CancellationToken cancellationToken = default) => + _checkCacheChannel.Writer.WriteAsync(item, cancellationToken); + + public abstract Task> SendToServer(Batch batch, CancellationToken cancellationToken); + + public Task Done() { _checkCacheChannel.Writer.Complete(); - return new(Task.CompletedTask); + return Task.CompletedTask; } public abstract void SaveToCache(List item); diff --git a/src/Speckle.Sdk.Dependencies/Serialization/SizeBatchingChannelReader.cs b/src/Speckle.Sdk.Dependencies/Serialization/SizeBatchingChannelReader.cs index 9c668547..0f66b49b 100644 --- a/src/Speckle.Sdk.Dependencies/Serialization/SizeBatchingChannelReader.cs +++ b/src/Speckle.Sdk.Dependencies/Serialization/SizeBatchingChannelReader.cs @@ -13,29 +13,20 @@ public class SizeBatchingChannelReader( int batchSize, bool singleReader, bool syncCont = false -) : BatchingChannelReader>(source, batchSize, singleReader, syncCont) +) : BatchingChannelReader>(x => new(x), source, batchSize, singleReader, syncCont) where T : IHasSize { - private readonly int _batchSize = batchSize; + protected override Batch CreateBatch(int capacity) => new(capacity); - protected override List CreateBatch(int capacity) => new(); - - protected override void TrimBatch(List batch) => batch.TrimExcess(); - - protected override void AddBatchItem(List batch, T item) => batch.Add(item); - - protected override int GetBatchSize(List batch) + protected override void TrimBatch(ref Batch batch, bool isVerifiedFull) { - int size = 0; - foreach (T item in batch) - { - size += item.Size; - } - - if (size >= _batchSize) + if (!isVerifiedFull) { - return _batchSize; + batch.TrimExcess(); } - return size; } + + protected override void AddBatchItem(Batch batch, T item) => batch.Add(item); + + protected override int GetBatchSize(Batch batch) => batch.Size; } diff --git a/src/Speckle.Sdk.Dependencies/packages.lock.json b/src/Speckle.Sdk.Dependencies/packages.lock.json index 80f29e97..38bc5fac 100644 --- a/src/Speckle.Sdk.Dependencies/packages.lock.json +++ b/src/Speckle.Sdk.Dependencies/packages.lock.json @@ -19,9 +19,9 @@ }, "Microsoft.Extensions.ObjectPool": { "type": "Direct", - "requested": "[8.0.10, )", - "resolved": "8.0.10", - "contentHash": "u7gAG7JgxF8VSJUGPSudAcPxOt+ymJKQCSxNRxiuKV+klCQbHljQR75SilpedCTfhPWDhtUwIJpnDVtspr9nMg==" + "requested": "[8.0.11, )", + "resolved": "8.0.11", + "contentHash": "6ApKcHNJigXBfZa6XlDQ8feJpq7SG1ogZXg6M4FiNzgd6irs3LUAzo0Pfn4F2ZI9liGnH1XIBR/OtSbZmJAV5w==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", @@ -44,9 +44,9 @@ }, "Open.ChannelExtensions": { "type": "Direct", - "requested": "[8.5.0, )", - "resolved": "8.5.0", - "contentHash": "dKD2iNfUYw+aOvwM2vCnD+q6JCtHiabkufKM1GateedRzcgv0RrtA4MoJI+7Y8N21R5A+wUA+j6P88g6mXPavA==", + "requested": "[8.6.0, )", + "resolved": "8.6.0", + "contentHash": "g5axz417bA6FXifJaBlB0l62gV7dYmknXx0n8lT/LSA3+7isaGMsOjJp5J+H/yXDRe4r+KZrE+bzQcs4Ets2kA==", "dependencies": { "Microsoft.Bcl.AsyncInterfaces": "8.0.0", "System.Collections.Immutable": "8.0.0", @@ -76,9 +76,9 @@ }, "PolySharp": { "type": "Direct", - "requested": "[1.14.1, )", - "resolved": "1.14.1", - "contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ==" + "requested": "[1.15.0, )", + "resolved": "1.15.0", + "contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g==" }, "Speckle.InterfaceGenerator": { "type": "Direct", @@ -185,9 +185,9 @@ }, "Microsoft.Extensions.ObjectPool": { "type": "Direct", - "requested": "[8.0.10, )", - "resolved": "8.0.10", - "contentHash": "u7gAG7JgxF8VSJUGPSudAcPxOt+ymJKQCSxNRxiuKV+klCQbHljQR75SilpedCTfhPWDhtUwIJpnDVtspr9nMg==" + "requested": "[8.0.11, )", + "resolved": "8.0.11", + "contentHash": "6ApKcHNJigXBfZa6XlDQ8feJpq7SG1ogZXg6M4FiNzgd6irs3LUAzo0Pfn4F2ZI9liGnH1XIBR/OtSbZmJAV5w==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", @@ -201,9 +201,9 @@ }, "Open.ChannelExtensions": { "type": "Direct", - "requested": "[8.5.0, )", - "resolved": "8.5.0", - "contentHash": "dKD2iNfUYw+aOvwM2vCnD+q6JCtHiabkufKM1GateedRzcgv0RrtA4MoJI+7Y8N21R5A+wUA+j6P88g6mXPavA==" + "requested": "[8.6.0, )", + "resolved": "8.6.0", + "contentHash": "g5axz417bA6FXifJaBlB0l62gV7dYmknXx0n8lT/LSA3+7isaGMsOjJp5J+H/yXDRe4r+KZrE+bzQcs4Ets2kA==" }, "Polly": { "type": "Direct", @@ -228,9 +228,9 @@ }, "PolySharp": { "type": "Direct", - "requested": "[1.14.1, )", - "resolved": "1.14.1", - "contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ==" + "requested": "[1.15.0, )", + "resolved": "1.15.0", + "contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g==" }, "Speckle.InterfaceGenerator": { "type": "Direct", diff --git a/src/Speckle.Sdk/Api/Operations/Operations.Send.cs b/src/Speckle.Sdk/Api/Operations/Operations.Send.cs index 94d6e270..16636e70 100644 --- a/src/Speckle.Sdk/Api/Operations/Operations.Send.cs +++ b/src/Speckle.Sdk/Api/Operations/Operations.Send.cs @@ -25,7 +25,12 @@ public async Task Send2( try { - var process = serializeProcessFactory.CreateSerializeProcess(url, streamId, authorizationToken, onProgressAction); + using var process = serializeProcessFactory.CreateSerializeProcess( + url, + streamId, + authorizationToken, + onProgressAction + ); var results = await process.Serialize(value, cancellationToken).ConfigureAwait(false); receiveActivity?.SetStatus(SdkActivityStatusCode.Ok); diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/PriorityScheduler.cs b/src/Speckle.Sdk/Serialisation/V2/Send/PriorityScheduler.cs new file mode 100644 index 00000000..3e254d1e --- /dev/null +++ b/src/Speckle.Sdk/Serialisation/V2/Send/PriorityScheduler.cs @@ -0,0 +1,43 @@ +using System.Collections.Concurrent; + +namespace Speckle.Sdk.Serialisation.V2.Send; + +public sealed class PriorityScheduler(ThreadPriority priority, int maximumConcurrencyLevel) : TaskScheduler, IDisposable +{ + private readonly BlockingCollection _tasks = new(); + private Thread[]? _threads; + + public void Dispose() => _tasks.Dispose(); + + public override int MaximumConcurrencyLevel => maximumConcurrencyLevel; + + protected override IEnumerable GetScheduledTasks() => _tasks; + + protected override void QueueTask(Task task) + { + _tasks.Add(task); + + if (_threads == null) + { + _threads = new Thread[maximumConcurrencyLevel]; + for (int i = 0; i < _threads.Length; i++) + { + _threads[i] = new Thread(() => + { + foreach (Task t in _tasks.GetConsumingEnumerable()) + { + TryExecuteTask(t); + } + }) + { + Name = $"{priority}: {i}", + Priority = priority, + IsBackground = true, + }; + _threads[i].Start(); + } + } + } + + protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) => false; // we might not want to execute task that should schedule as high or low priority inline +} diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs b/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs index 564e47f7..8f08173a 100644 --- a/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs +++ b/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs @@ -15,9 +15,7 @@ public record SerializeProcessOptions( bool SkipCacheRead = false, bool SkipCacheWrite = false, bool SkipServer = false, - bool SkipFindTotalObjects = false, - bool EnableServerSending = true, - bool EnableCacheSaving = true + bool SkipFindTotalObjects = false ); public readonly record struct SerializeProcessResults( @@ -41,8 +39,10 @@ public bool Equals(BaseItem? other) public override int GetHashCode() => Id.GetHashCode(); } +public partial interface ISerializeProcess : IDisposable; + [GenerateAutoInterface] -public class SerializeProcess( +public sealed class SerializeProcess( IProgress? progress, ISqLiteJsonCacheManager sqLiteJsonCacheManager, IServerObjectManager serverObjectManager, @@ -51,11 +51,18 @@ public class SerializeProcess( SerializeProcessOptions? options = null ) : ChannelSaver, ISerializeProcess { + private readonly PriorityScheduler _highest = new(ThreadPriority.Highest, 2); + private readonly PriorityScheduler _belowNormal = new(ThreadPriority.BelowNormal, Environment.ProcessorCount * 2); + private readonly SerializeProcessOptions _options = options ?? new(false, false, false, false); private readonly ConcurrentDictionary _objectReferences = new(); private readonly Pool> _pool = Pools.CreateListPool<(Id, Json, Closures)>(); - private readonly Pool> _childClosurePool = Pools.CreateDictionaryPool(); + private readonly Pool> _currentClosurePool = Pools.CreateDictionaryPool(); + private readonly Pool> _childClosurePool = Pools.CreateConcurrentDictionaryPool< + Id, + NodeInfo + >(); private long _objectCount; private long _objectsFound; @@ -65,17 +72,24 @@ public class SerializeProcess( private long _uploaded; private long _cached; + [AutoInterfaceIgnore] + public void Dispose() + { + _highest.Dispose(); + _belowNormal.Dispose(); + } + public async Task Serialize(Base root, CancellationToken cancellationToken) { - var channelTask = Start(_options.EnableServerSending, _options.EnableCacheSaving, cancellationToken); + var channelTask = Start(cancellationToken); var findTotalObjectsTask = Task.CompletedTask; if (!_options.SkipFindTotalObjects) { findTotalObjectsTask = Task.Factory.StartNew( () => TraverseTotal(root), default, - TaskCreationOptions.LongRunning, - TaskScheduler.Default + TaskCreationOptions.AttachedToParent | TaskCreationOptions.PreferFairness, + _highest ); } @@ -106,37 +120,39 @@ private async Task> Traverse(Base obj, bool isEnd, Canc .Factory.StartNew( () => Traverse(tmp, false, cancellationToken), cancellationToken, - TaskCreationOptions.AttachedToParent, - TaskScheduler.Default + TaskCreationOptions.AttachedToParent | TaskCreationOptions.PreferFairness, + _belowNormal ) .Unwrap(); tasks.Add(t); } + Dictionary[] taskClosures = []; if (tasks.Count > 0) { - await Task.WhenAll(tasks).ConfigureAwait(false); + taskClosures = await Task.WhenAll(tasks).ConfigureAwait(true); } var childClosures = _childClosurePool.Get(); - foreach (var t in tasks) + foreach (var childClosure in taskClosures) { - var childClosure = t.Result; foreach (var kvp in childClosure) { childClosures[kvp.Key] = kvp.Value; } + _currentClosurePool.Return(childClosure); } var items = Serialise(obj, childClosures, cancellationToken); - var currentClosures = new Dictionary(); + var currentClosures = _currentClosurePool.Get(); Interlocked.Increment(ref _objectCount); - progress?.Report(new(ProgressEvent.FromCacheOrSerialized, _objectCount, _objectsFound)); + progress?.Report(new(ProgressEvent.FromCacheOrSerialized, _objectCount, Math.Max(_objectCount, _objectsFound))); foreach (var item in items) { if (item.NeedsStorage) { - await Save(item, cancellationToken).ConfigureAwait(false); + Interlocked.Increment(ref _objectsSerialized); + await Save(item, cancellationToken).ConfigureAwait(true); } if (!currentClosures.ContainsKey(item.Id)) @@ -148,7 +164,7 @@ private async Task> Traverse(Base obj, bool isEnd, Canc if (isEnd) { - await Done().ConfigureAwait(false); + await Done().ConfigureAwait(true); } return currentClosures; @@ -176,7 +192,6 @@ CancellationToken cancellationToken try { items.AddRange(serializer2.Serialize(obj)); - Interlocked.Add(ref _objectsSerialized, items.Count); foreach (var kvp in serializer2.ObjectReferences) { _objectReferences.TryAdd(kvp.Key, kvp.Value); @@ -208,24 +223,24 @@ private BaseItem CheckCache(Id id, Json json, Dictionary closures) return new BaseItem(id, json, true, closures); } - public override async Task> SendToServer(List batch, CancellationToken cancellationToken) + public override async Task> SendToServer(Batch batch, CancellationToken cancellationToken) { - if (!_options.SkipServer && batch.Count != 0) + if (!_options.SkipServer && batch.Items.Count != 0) { - var objectBatch = batch.Distinct().ToList(); + var objectBatch = batch.Items.Distinct().ToList(); var hasObjects = await serverObjectManager .HasObjects(objectBatch.Select(x => x.Id.Value).Freeze(), cancellationToken) .ConfigureAwait(false); - objectBatch = batch.Where(x => !hasObjects[x.Id.Value]).ToList(); + objectBatch = batch.Items.Where(x => !hasObjects[x.Id.Value]).ToList(); if (objectBatch.Count != 0) { await serverObjectManager.UploadObjects(objectBatch, true, progress, cancellationToken).ConfigureAwait(false); - Interlocked.Exchange(ref _uploaded, _uploaded + batch.Count); + Interlocked.Exchange(ref _uploaded, _uploaded + batch.Items.Count); } - progress?.Report(new(ProgressEvent.UploadedObjects, _uploaded, null)); + return objectBatch; } - return batch; + return batch.Items; } public override void SaveToCache(List batch) diff --git a/src/Speckle.Sdk/packages.lock.json b/src/Speckle.Sdk/packages.lock.json index 03e98959..bd0ad6f2 100644 --- a/src/Speckle.Sdk/packages.lock.json +++ b/src/Speckle.Sdk/packages.lock.json @@ -83,9 +83,9 @@ }, "PolySharp": { "type": "Direct", - "requested": "[1.14.1, )", - "resolved": "1.14.1", - "contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ==" + "requested": "[1.15.0, )", + "resolved": "1.15.0", + "contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g==" }, "Speckle.DoubleNumerics": { "type": "Direct", @@ -364,9 +364,9 @@ }, "PolySharp": { "type": "Direct", - "requested": "[1.14.1, )", - "resolved": "1.14.1", - "contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ==" + "requested": "[1.15.0, )", + "resolved": "1.15.0", + "contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g==" }, "Speckle.DoubleNumerics": { "type": "Direct", diff --git a/tests/Speckle.Objects.Tests.Unit/packages.lock.json b/tests/Speckle.Objects.Tests.Unit/packages.lock.json index 973a8574..fcd06d85 100644 --- a/tests/Speckle.Objects.Tests.Unit/packages.lock.json +++ b/tests/Speckle.Objects.Tests.Unit/packages.lock.json @@ -48,9 +48,9 @@ }, "PolySharp": { "type": "Direct", - "requested": "[1.14.1, )", - "resolved": "1.14.1", - "contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ==" + "requested": "[1.15.0, )", + "resolved": "1.15.0", + "contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g==" }, "Shouldly": { "type": "Direct", diff --git a/tests/Speckle.Sdk.Serialization.Testing/Program.cs b/tests/Speckle.Sdk.Serialization.Testing/Program.cs index b807f795..b5a324f4 100644 --- a/tests/Speckle.Sdk.Serialization.Testing/Program.cs +++ b/tests/Speckle.Sdk.Serialization.Testing/Program.cs @@ -56,7 +56,7 @@ Console.ReadLine(); Console.WriteLine("Executing"); -var process2 = factory.CreateSerializeProcess( +using var process2 = factory.CreateSerializeProcess( new Uri(url), streamId, token, diff --git a/tests/Speckle.Sdk.Serialization.Testing/packages.lock.json b/tests/Speckle.Sdk.Serialization.Testing/packages.lock.json index 6f507f12..544b5e42 100644 --- a/tests/Speckle.Sdk.Serialization.Testing/packages.lock.json +++ b/tests/Speckle.Sdk.Serialization.Testing/packages.lock.json @@ -29,9 +29,9 @@ }, "PolySharp": { "type": "Direct", - "requested": "[1.14.1, )", - "resolved": "1.14.1", - "contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ==" + "requested": "[1.15.0, )", + "resolved": "1.15.0", + "contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g==" }, "Speckle.InterfaceGenerator": { "type": "Direct", diff --git a/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs b/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs index 07d02914..94a396e2 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs @@ -67,7 +67,7 @@ public async Task CanSerialize_New_Detached() var objects = new Dictionary(); - var process2 = new SerializeProcess( + using var process2 = new SerializeProcess( null, new DummySendCacheManager(objects), new DummyServerObjectManager(), @@ -259,7 +259,7 @@ public async Task CanSerialize_New_Detached2() var objects = new Dictionary(); - var process2 = new SerializeProcess( + using var process2 = new SerializeProcess( null, new DummySendCacheManager(objects), new DummyServerObjectManager(), @@ -332,7 +332,7 @@ public async Task CanSerialize_New_Detached_With_DataChunks() var objects = new Dictionary(); - var process2 = new SerializeProcess( + using var process2 = new SerializeProcess( null, new DummySendCacheManager(objects), new DummyServerObjectManager(), @@ -413,7 +413,7 @@ public async Task CanSerialize_New_Detached_With_DataChunks2() var objects = new Dictionary(); - var process2 = new SerializeProcess( + using var process2 = new SerializeProcess( null, new DummySendCacheManager(objects), new DummyServerObjectManager(), diff --git a/tests/Speckle.Sdk.Serialization.Tests/ExplicitInterfaceTests.cs b/tests/Speckle.Sdk.Serialization.Tests/ExplicitInterfaceTests.cs index d82eef21..e90293de 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/ExplicitInterfaceTests.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/ExplicitInterfaceTests.cs @@ -21,7 +21,7 @@ public async Task Test_Json() var testClass = new TestClass() { RegularProperty = "Hello" }; var objects = new Dictionary(); - var process2 = new SerializeProcess( + using var process2 = new SerializeProcess( null, new DummySendCacheManager(objects), new DummyServerObjectManager(), diff --git a/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs b/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs index 49648b4a..aaf98d30 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs @@ -257,7 +257,7 @@ public async Task Roundtrip_Test_New(string fileName, string rootId, int oldCoun process.Total.ShouldBe(oldCount); var newIdToJson = new ConcurrentDictionary(); - var serializeProcess = new SerializeProcess( + using var serializeProcess = new SerializeProcess( null, new DummySqLiteSendManager(), new DummySendServerObjectManager(newIdToJson), diff --git a/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json b/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json index 973a8574..fcd06d85 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json +++ b/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json @@ -48,9 +48,9 @@ }, "PolySharp": { "type": "Direct", - "requested": "[1.14.1, )", - "resolved": "1.14.1", - "contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ==" + "requested": "[1.15.0, )", + "resolved": "1.15.0", + "contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g==" }, "Shouldly": { "type": "Direct", diff --git a/tests/Speckle.Sdk.Tests.Integration/packages.lock.json b/tests/Speckle.Sdk.Tests.Integration/packages.lock.json index 8fc495de..160e2842 100644 --- a/tests/Speckle.Sdk.Tests.Integration/packages.lock.json +++ b/tests/Speckle.Sdk.Tests.Integration/packages.lock.json @@ -48,9 +48,9 @@ }, "PolySharp": { "type": "Direct", - "requested": "[1.14.1, )", - "resolved": "1.14.1", - "contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ==" + "requested": "[1.15.0, )", + "resolved": "1.15.0", + "contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g==" }, "Shouldly": { "type": "Direct", diff --git a/tests/Speckle.Sdk.Tests.Performance/packages.lock.json b/tests/Speckle.Sdk.Tests.Performance/packages.lock.json index ee12cf74..d02dfe45 100644 --- a/tests/Speckle.Sdk.Tests.Performance/packages.lock.json +++ b/tests/Speckle.Sdk.Tests.Performance/packages.lock.json @@ -47,9 +47,9 @@ }, "PolySharp": { "type": "Direct", - "requested": "[1.14.1, )", - "resolved": "1.14.1", - "contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ==" + "requested": "[1.15.0, )", + "resolved": "1.15.0", + "contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g==" }, "Speckle.InterfaceGenerator": { "type": "Direct", diff --git a/tests/Speckle.Sdk.Tests.Unit/Serialisation/BatchTests.cs b/tests/Speckle.Sdk.Tests.Unit/Serialisation/BatchTests.cs new file mode 100644 index 00000000..e994288d --- /dev/null +++ b/tests/Speckle.Sdk.Tests.Unit/Serialisation/BatchTests.cs @@ -0,0 +1,44 @@ +using NUnit.Framework; +using Shouldly; +using Speckle.Sdk.Serialisation.V2.Send; + +namespace Speckle.Sdk.Tests.Unit.Serialisation; + +[TestFixture] +public class BatchTests +{ + private class BatchItem : IHasSize + { + public BatchItem(int size) + { + Size = size; + } + + public int Size { get; } + } + + [Test] + public void TestBatchSize_Calc() + { + var batch = new Batch(4); + batch.Add(new BatchItem(1)); + batch.Size.ShouldBe(1); + batch.Add(new BatchItem(2)); + batch.Size.ShouldBe(3); + } + + [Test] + public void TestBatchSize_Trim() + { + var batch = new Batch(4); + batch.Add(new BatchItem(1)); + batch.Add(new BatchItem(2)); + batch.Size.ShouldBe(3); + + batch.Items.Capacity.ShouldBe(4); + batch.TrimExcess(); + + batch.Items.Capacity.ShouldBe(2); + batch.Size.ShouldBe(3); + } +} diff --git a/tests/Speckle.Sdk.Tests.Unit/Serialisation/SimpleRoundTripTests.cs b/tests/Speckle.Sdk.Tests.Unit/Serialisation/SimpleRoundTripTests.cs index 3efc054f..b9cd0183 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Serialisation/SimpleRoundTripTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Serialisation/SimpleRoundTripTests.cs @@ -8,6 +8,7 @@ namespace Speckle.Sdk.Tests.Unit.Serialisation; +[TestFixture] public class SimpleRoundTripTests { private IOperations _operations; diff --git a/tests/Speckle.Sdk.Tests.Unit/packages.lock.json b/tests/Speckle.Sdk.Tests.Unit/packages.lock.json index 5cb51559..7c77fd93 100644 --- a/tests/Speckle.Sdk.Tests.Unit/packages.lock.json +++ b/tests/Speckle.Sdk.Tests.Unit/packages.lock.json @@ -57,9 +57,9 @@ }, "PolySharp": { "type": "Direct", - "requested": "[1.14.1, )", - "resolved": "1.14.1", - "contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ==" + "requested": "[1.15.0, )", + "resolved": "1.15.0", + "contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g==" }, "Shouldly": { "type": "Direct", From 11fe8e8cce33e7c54943b258c58d6e124a96e184 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Fri, 3 Jan 2025 14:25:29 +0000 Subject: [PATCH 10/13] Use CancellationSource to properly dispose of threads in PriorityScheduler (#197) * Use CancellationSource to properly dispose of threads in PriorityScheduler * formatting --- .../V2/Send/PriorityScheduler.cs | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/PriorityScheduler.cs b/src/Speckle.Sdk/Serialisation/V2/Send/PriorityScheduler.cs index 3e254d1e..c38a3df3 100644 --- a/src/Speckle.Sdk/Serialisation/V2/Send/PriorityScheduler.cs +++ b/src/Speckle.Sdk/Serialisation/V2/Send/PriorityScheduler.cs @@ -4,10 +4,17 @@ namespace Speckle.Sdk.Serialisation.V2.Send; public sealed class PriorityScheduler(ThreadPriority priority, int maximumConcurrencyLevel) : TaskScheduler, IDisposable { + private readonly CancellationTokenSource _cancellationTokenSource = new(); private readonly BlockingCollection _tasks = new(); private Thread[]? _threads; - public void Dispose() => _tasks.Dispose(); + public void Dispose() + { + _tasks.CompleteAdding(); + _cancellationTokenSource.Cancel(); + _tasks.Dispose(); + _cancellationTokenSource.Dispose(); + } public override int MaximumConcurrencyLevel => maximumConcurrencyLevel; @@ -24,9 +31,26 @@ protected override void QueueTask(Task task) { _threads[i] = new Thread(() => { - foreach (Task t in _tasks.GetConsumingEnumerable()) + try + { + foreach (Task t in _tasks.GetConsumingEnumerable(_cancellationTokenSource.Token)) + { + if (_cancellationTokenSource.IsCancellationRequested) + { + break; + } + TryExecuteTask(t); + if (_cancellationTokenSource.IsCancellationRequested) + { + break; + } + } + } +#pragma warning disable CA1031 + catch (Exception) +#pragma warning restore CA1031 { - TryExecuteTask(t); + // ignored } }) { From ed5bdc91edf03d306ae3aafe7a23787bdccb3b48 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Wed, 8 Jan 2025 11:04:32 +0000 Subject: [PATCH 11/13] Sqlite pooling for connections and commands (#193) * add ServerObjectManagerFactory * add usage of a command pool * add more disposal * save saving increase * fix tests * fixes * push out concurrency and disposablity * Add a custom task scheduler * Better usage, don't wait to enqueue to save to channels * Completely pre-cal batch size to avoid spinning issues * Try to fix cache counting * properly dispose things * format * clean up * adjust count and save on current thread * move batch it's own file * update a few packages * fix build and add batch tests * revert and format * Revert "save saving increase" This reverts commit 3b50c857fb9a9da3fbf8193d36b149d2e0d2cb72. * revert change * adjust and add tests * Dispose sqlite manager properly * Make Batch a IMemoryOwner to allow for pooling * Fix tests * Upgrade some deps * try to make tests more explicit * remove return value * Use named tuple for all objects --- Directory.Packages.props | 6 +- src/Speckle.Sdk.Dependencies/Pools.cs | 10 +- .../Serialization/Batch.cs | 14 +- .../Serialization/ChannelExtensions.cs | 5 +- .../Serialization/ChannelSaver.cs | 11 +- .../SizeBatchingChannelReader.cs | 13 +- .../packages.lock.json | 51 ++--- src/Speckle.Sdk/Credentials/AccountManager.cs | 17 +- src/Speckle.Sdk/SQLite/CacheDbCommandPool.cs | 96 +++++++++ src/Speckle.Sdk/SQLite/CacheDbCommands.cs | 35 ++++ .../SQLite/SQLiteJsonCacheManager.cs | 190 ++++++++++-------- .../SQLite/SqLiteJsonCacheManagerFactory.cs | 10 +- .../V2/DummySendServerObjectManager.cs | 6 +- .../V2/Receive/DeserializeProcess.cs | 5 + .../Serialisation/V2/Receive/ObjectLoader.cs | 5 + .../Serialisation/V2/Send/BaseItem.cs | 19 ++ .../V2/Send/PriorityScheduler.cs | 2 +- .../Serialisation/V2/Send/SerializeProcess.cs | 22 +- .../V2/SerializeProcessFactory.cs | 36 +--- .../V2/ServerObjectManagerFactory.cs | 13 ++ .../Program.cs | 7 +- .../DetachedTests.cs | 4 +- .../DummySqLiteReceiveManager.cs | 4 +- .../DummySqLiteSendManager.cs | 4 +- .../SerializationTests.cs | 8 +- .../Benchmarks/GeneralDeserializerTest.cs | 2 +- .../SQLite/SQLiteJsonCacheManagerTests.cs | 91 +++++++++ .../Serialisation/BatchTests.cs | 9 +- 28 files changed, 489 insertions(+), 206 deletions(-) create mode 100644 src/Speckle.Sdk/SQLite/CacheDbCommandPool.cs create mode 100644 src/Speckle.Sdk/SQLite/CacheDbCommands.cs create mode 100644 src/Speckle.Sdk/Serialisation/V2/Send/BaseItem.cs create mode 100644 src/Speckle.Sdk/Serialisation/V2/ServerObjectManagerFactory.cs create mode 100644 tests/Speckle.Sdk.Tests.Unit/SQLite/SQLiteJsonCacheManagerTests.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 6f1923c7..f2acca28 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -9,7 +9,7 @@ - + @@ -19,7 +19,7 @@ - + @@ -27,7 +27,7 @@ - + diff --git a/src/Speckle.Sdk.Dependencies/Pools.cs b/src/Speckle.Sdk.Dependencies/Pools.cs index 5f3451fb..734b7432 100644 --- a/src/Speckle.Sdk.Dependencies/Pools.cs +++ b/src/Speckle.Sdk.Dependencies/Pools.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Text; using Microsoft.Extensions.ObjectPool; @@ -6,6 +6,8 @@ namespace Speckle.Sdk.Dependencies; public static class Pools { + public const int DefaultCapacity = 50; + public static Pool> ObjectDictionaries { get; } = new(new ObjectDictionaryPolicy()); private sealed class ObjectDictionaryPolicy : IPooledObjectPolicy> @@ -25,7 +27,7 @@ public bool Return(Dictionary obj) private sealed class ObjectDictionaryPolicy : IPooledObjectPolicy> where TKey : notnull { - public Dictionary Create() => new(50); + public Dictionary Create() => new(DefaultCapacity); public bool Return(Dictionary obj) { @@ -38,7 +40,7 @@ private sealed class ObjectConcurrentDictionaryPolicy : IPooledObjectPolicy> where TKey : notnull { - public ConcurrentDictionary Create() => new(Environment.ProcessorCount, 50); + public ConcurrentDictionary Create() => new(Environment.ProcessorCount, DefaultCapacity); public bool Return(ConcurrentDictionary obj) { @@ -49,7 +51,7 @@ public bool Return(ConcurrentDictionary obj) private sealed class ObjectListPolicy : IPooledObjectPolicy> { - public List Create() => new(50); + public List Create() => new(DefaultCapacity); public bool Return(List obj) { diff --git a/src/Speckle.Sdk.Dependencies/Serialization/Batch.cs b/src/Speckle.Sdk.Dependencies/Serialization/Batch.cs index 368cfd5c..fb6bb14c 100644 --- a/src/Speckle.Sdk.Dependencies/Serialization/Batch.cs +++ b/src/Speckle.Sdk.Dependencies/Serialization/Batch.cs @@ -1,10 +1,14 @@ -namespace Speckle.Sdk.Serialisation.V2.Send; +using System.Buffers; +using Speckle.Sdk.Dependencies; -public class Batch(int capacity) : IHasSize +namespace Speckle.Sdk.Serialisation.V2.Send; + +public sealed class Batch : IHasSize, IMemoryOwner where T : IHasSize { + private static readonly Pool> _pool = Pools.CreateListPool(); #pragma warning disable IDE0032 - private readonly List _items = new(capacity); + private readonly List _items = _pool.Get(); private int _batchSize; #pragma warning restore IDE0032 @@ -22,4 +26,8 @@ public void TrimExcess() public int Size => _batchSize; public List Items => _items; + + public void Dispose() => _pool.Return(_items); + + public Memory Memory => new(_items.ToArray()); } diff --git a/src/Speckle.Sdk.Dependencies/Serialization/ChannelExtensions.cs b/src/Speckle.Sdk.Dependencies/Serialization/ChannelExtensions.cs index a03a7337..e420a235 100644 --- a/src/Speckle.Sdk.Dependencies/Serialization/ChannelExtensions.cs +++ b/src/Speckle.Sdk.Dependencies/Serialization/ChannelExtensions.cs @@ -1,11 +1,12 @@ -using System.Threading.Channels; +using System.Buffers; +using System.Threading.Channels; using Open.ChannelExtensions; namespace Speckle.Sdk.Serialisation.V2.Send; public static class ChannelExtensions { - public static BatchingChannelReader> BatchBySize( + public static BatchingChannelReader> BatchBySize( this ChannelReader source, int batchSize, bool singleReader = false, diff --git a/src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs b/src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs index 97c00f10..99190998 100644 --- a/src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs +++ b/src/Speckle.Sdk.Dependencies/Serialization/ChannelSaver.cs @@ -1,3 +1,4 @@ +using System.Buffers; using System.Threading.Channels; using Open.ChannelExtensions; using Speckle.Sdk.Serialisation.V2.Send; @@ -13,7 +14,7 @@ public abstract class ChannelSaver private const int MAX_PARALLELISM_HTTP = 4; private const int HTTP_CAPACITY = 500; private const int MAX_CACHE_WRITE_PARALLELISM = 4; - private const int MAX_CACHE_BATCH = 200; + private const int MAX_CACHE_BATCH = 500; private readonly Channel _checkCacheChannel = Channel.CreateBounded( new BoundedChannelOptions(SEND_CAPACITY) @@ -46,7 +47,13 @@ public Task Start(CancellationToken cancellationToken = default) => public ValueTask Save(T item, CancellationToken cancellationToken = default) => _checkCacheChannel.Writer.WriteAsync(item, cancellationToken); - public abstract Task> SendToServer(Batch batch, CancellationToken cancellationToken); + public async Task> SendToServer(IMemoryOwner batch, CancellationToken cancellationToken) + { + await SendToServer((Batch)batch, cancellationToken).ConfigureAwait(false); + return batch; + } + + public abstract Task SendToServer(Batch batch, CancellationToken cancellationToken); public Task Done() { diff --git a/src/Speckle.Sdk.Dependencies/Serialization/SizeBatchingChannelReader.cs b/src/Speckle.Sdk.Dependencies/Serialization/SizeBatchingChannelReader.cs index 0f66b49b..e979aba9 100644 --- a/src/Speckle.Sdk.Dependencies/Serialization/SizeBatchingChannelReader.cs +++ b/src/Speckle.Sdk.Dependencies/Serialization/SizeBatchingChannelReader.cs @@ -1,3 +1,4 @@ +using System.Buffers; using System.Threading.Channels; using Open.ChannelExtensions; @@ -13,20 +14,20 @@ public class SizeBatchingChannelReader( int batchSize, bool singleReader, bool syncCont = false -) : BatchingChannelReader>(x => new(x), source, batchSize, singleReader, syncCont) +) : BatchingChannelReader>(x => new Batch(), source, batchSize, singleReader, syncCont) where T : IHasSize { - protected override Batch CreateBatch(int capacity) => new(capacity); + protected override IMemoryOwner CreateBatch(int capacity) => new Batch(); - protected override void TrimBatch(ref Batch batch, bool isVerifiedFull) + protected override void TrimBatch(ref IMemoryOwner batch, bool isVerifiedFull) { if (!isVerifiedFull) { - batch.TrimExcess(); + ((Batch)batch).TrimExcess(); } } - protected override void AddBatchItem(Batch batch, T item) => batch.Add(item); + protected override void AddBatchItem(IMemoryOwner batch, T item) => ((Batch)batch).Add(item); - protected override int GetBatchSize(Batch batch) => batch.Size; + protected override int GetBatchSize(IMemoryOwner batch) => ((Batch)batch).Size; } diff --git a/src/Speckle.Sdk.Dependencies/packages.lock.json b/src/Speckle.Sdk.Dependencies/packages.lock.json index 38bc5fac..123ae1af 100644 --- a/src/Speckle.Sdk.Dependencies/packages.lock.json +++ b/src/Speckle.Sdk.Dependencies/packages.lock.json @@ -19,9 +19,9 @@ }, "Microsoft.Extensions.ObjectPool": { "type": "Direct", - "requested": "[8.0.11, )", - "resolved": "8.0.11", - "contentHash": "6ApKcHNJigXBfZa6XlDQ8feJpq7SG1ogZXg6M4FiNzgd6irs3LUAzo0Pfn4F2ZI9liGnH1XIBR/OtSbZmJAV5w==" + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "UbsU/gYe4nv1DeqMXIVzDfNNek7Sk2kKuAOXL/Y+sLcAR0HwFUqzg1EPiU88jeHNe0g81aPvvHbvHarQr3r9IA==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", @@ -44,13 +44,13 @@ }, "Open.ChannelExtensions": { "type": "Direct", - "requested": "[8.6.0, )", - "resolved": "8.6.0", - "contentHash": "g5axz417bA6FXifJaBlB0l62gV7dYmknXx0n8lT/LSA3+7isaGMsOjJp5J+H/yXDRe4r+KZrE+bzQcs4Ets2kA==", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "DP+l5S6G46wcuY4I4kNXE+RDOmJr0DKuMienOdt0mMBN9z7vmLSC8YQbqCyb9i9LNjXj1tgCx5LyitJiRr/v7g==", "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "8.0.0", - "System.Collections.Immutable": "8.0.0", - "System.Threading.Channels": "8.0.0" + "Microsoft.Bcl.AsyncInterfaces": "9.0.0", + "System.Collections.Immutable": "9.0.0", + "System.Threading.Channels": "9.0.0" } }, "Polly": { @@ -88,10 +88,11 @@ }, "System.Threading.Channels": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "CMaFr7v+57RW7uZfZkPExsPB6ljwzhjACWW1gfU35Y56rk72B/Wu+sTqxVmGSk4SFUlPc3cjeKND0zktziyjBA==", + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "hzACdIf1C+4Dqos5ijV404b94+LqfIC8nfS3mNpCDFWowb1N3PNfJPopneq32ahWlDeyaPZJqjBk76YFR69Rpg==", "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "9.0.0", "System.Threading.Tasks.Extensions": "4.5.4" } }, @@ -122,8 +123,8 @@ }, "System.Collections.Immutable": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==", + "resolved": "9.0.0", + "contentHash": "QhkXUl2gNrQtvPmtBTQHb0YsUrDiDQ2QS09YbtTTiSjGcf7NBqtYbrG/BE06zcBPCKEwQGzIv13IVdXNOSub2w==", "dependencies": { "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "6.0.0" @@ -160,8 +161,8 @@ "Microsoft.Bcl.AsyncInterfaces": { "type": "CentralTransitive", "requested": "[5.0.0, )", - "resolved": "8.0.0", - "contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==", + "resolved": "9.0.0", + "contentHash": "owmu2Cr3IQ8yQiBleBHlGk8dSQ12oaF2e7TpzwJKEl4m84kkZJjEY1n33L67Y3zM5jPOjmmbdHjbfiL0RqcMRQ==", "dependencies": { "System.Threading.Tasks.Extensions": "4.5.4" } @@ -185,9 +186,9 @@ }, "Microsoft.Extensions.ObjectPool": { "type": "Direct", - "requested": "[8.0.11, )", - "resolved": "8.0.11", - "contentHash": "6ApKcHNJigXBfZa6XlDQ8feJpq7SG1ogZXg6M4FiNzgd6irs3LUAzo0Pfn4F2ZI9liGnH1XIBR/OtSbZmJAV5w==" + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "UbsU/gYe4nv1DeqMXIVzDfNNek7Sk2kKuAOXL/Y+sLcAR0HwFUqzg1EPiU88jeHNe0g81aPvvHbvHarQr3r9IA==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", @@ -201,9 +202,9 @@ }, "Open.ChannelExtensions": { "type": "Direct", - "requested": "[8.6.0, )", - "resolved": "8.6.0", - "contentHash": "g5axz417bA6FXifJaBlB0l62gV7dYmknXx0n8lT/LSA3+7isaGMsOjJp5J+H/yXDRe4r+KZrE+bzQcs4Ets2kA==" + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "DP+l5S6G46wcuY4I4kNXE+RDOmJr0DKuMienOdt0mMBN9z7vmLSC8YQbqCyb9i9LNjXj1tgCx5LyitJiRr/v7g==" }, "Polly": { "type": "Direct", @@ -240,9 +241,9 @@ }, "System.Threading.Channels": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "CMaFr7v+57RW7uZfZkPExsPB6ljwzhjACWW1gfU35Y56rk72B/Wu+sTqxVmGSk4SFUlPc3cjeKND0zktziyjBA==" + "requested": "[9.0.0, )", + "resolved": "9.0.0", + "contentHash": "hzACdIf1C+4Dqos5ijV404b94+LqfIC8nfS3mNpCDFWowb1N3PNfJPopneq32ahWlDeyaPZJqjBk76YFR69Rpg==" }, "ILRepack": { "type": "Transitive", diff --git a/src/Speckle.Sdk/Credentials/AccountManager.cs b/src/Speckle.Sdk/Credentials/AccountManager.cs index e8784ee1..2009ebbf 100644 --- a/src/Speckle.Sdk/Credentials/AccountManager.cs +++ b/src/Speckle.Sdk/Credentials/AccountManager.cs @@ -21,11 +21,13 @@ namespace Speckle.Sdk.Credentials; +public partial interface IAccountManager : IDisposable; + /// /// Manage accounts locally for desktop applications. /// [GenerateAutoInterface] -public class AccountManager( +public sealed class AccountManager( ISpeckleApplication application, ILogger logger, ISpeckleHttp speckleHttp, @@ -40,6 +42,13 @@ ISqLiteJsonCacheManagerFactory sqLiteJsonCacheManagerFactory "AccountAddFlow" ); + [AutoInterfaceIgnore] + public void Dispose() + { + _accountStorage.Dispose(); + _accountAddLockStorage.Dispose(); + } + /// /// Gets the basic information about a server. /// @@ -321,7 +330,7 @@ public IEnumerable GetAccounts() { static bool IsInvalid(Account ac) => ac.userInfo == null || ac.serverInfo == null; - var sqlAccounts = _accountStorage.GetAllObjects().Select(x => JsonConvert.DeserializeObject(x)); + var sqlAccounts = _accountStorage.GetAllObjects().Select(x => JsonConvert.DeserializeObject(x.Json)); var localAccounts = GetLocalAccounts(); foreach (var acc in sqlAccounts) @@ -642,7 +651,7 @@ private void TryLockAccountAddFlow(TimeSpan timespan) } // this uses the SQLite transport to store locks - var lockIds = _accountAddLockStorage.GetAllObjects().OrderByDescending(d => d).ToList(); + var lockIds = _accountAddLockStorage.GetAllObjects().Select(x => x.Id).OrderByDescending(d => d).ToList(); var now = DateTime.Now; foreach (var l in lockIds) { @@ -674,7 +683,7 @@ private void UnlockAccountAddFlow() { s_isAddingAccount = false; // make sure all old locks are removed - foreach (var id in _accountAddLockStorage.GetAllObjects()) + foreach (var (id, _) in _accountAddLockStorage.GetAllObjects()) { _accountAddLockStorage.DeleteObject(id); } diff --git a/src/Speckle.Sdk/SQLite/CacheDbCommandPool.cs b/src/Speckle.Sdk/SQLite/CacheDbCommandPool.cs new file mode 100644 index 00000000..500d2230 --- /dev/null +++ b/src/Speckle.Sdk/SQLite/CacheDbCommandPool.cs @@ -0,0 +1,96 @@ +using System.Collections.Concurrent; +using Microsoft.Data.Sqlite; + +namespace Speckle.Sdk.SQLite; + +//inspired by https://github.com/neosmart/SqliteCache/blob/master/SqliteCache/DbCommandPool.cs +public sealed class CacheDbCommandPool : IDisposable +{ + private readonly ConcurrentBag[] _commands = new ConcurrentBag[CacheDbCommands.Count]; + private readonly ConcurrentBag _connections = new(); + private readonly string _connectionString; + + public CacheDbCommandPool(string connectionString, int concurrency) + { + _connectionString = connectionString; + for (int i = 0; i < _commands.Length; ++i) + { + _commands[i] = new ConcurrentBag(); + } + for (int i = 0; i < concurrency; ++i) + { + var connection = new SqliteConnection(_connectionString); + connection.Open(); + _connections.Add(connection); + } + } + + public void Use(CacheOperation type, Action handler) => + Use( + type, + cmd => + { + handler(cmd); + return true; + } + ); + + private T Use(Func handler) + { + if (!_connections.TryTake(out var db)) + { + db = new SqliteConnection(_connectionString); + db.Open(); + } + + try + { + return handler(db); + } + finally + { + _connections.Add(db); + } + } + + public T Use(CacheOperation type, Func handler) => + Use(conn => + { + var pool = _commands[(int)type]; + if (!pool.TryTake(out var command)) + { +#pragma warning disable CA2100 + command = new SqliteCommand(CacheDbCommands.Commands[(int)type], conn); +#pragma warning restore CA2100 + } + + try + { + command.Connection = conn; + return handler(command); + } + finally + { + command.Connection = null; + command.Parameters.Clear(); + pool.Add(command); + } + }); + + public void Dispose() + { + foreach (var pool in _commands) + { + while (pool.TryTake(out var cmd)) + { + cmd.Dispose(); + } + } + + foreach (var conn in _connections) + { + conn.Close(); + conn.Dispose(); + } + } +} diff --git a/src/Speckle.Sdk/SQLite/CacheDbCommands.cs b/src/Speckle.Sdk/SQLite/CacheDbCommands.cs new file mode 100644 index 00000000..33a28771 --- /dev/null +++ b/src/Speckle.Sdk/SQLite/CacheDbCommands.cs @@ -0,0 +1,35 @@ +namespace Speckle.Sdk.SQLite; + +public enum CacheOperation +{ + InsertOrIgnore, + InsertOrReplace, + Has, + Get, + Delete, + GetAll, + BulkInsertOrIgnore, +} + +public static class CacheDbCommands +{ + public static readonly string[] Commands; + public static readonly int Count = Enum.GetValues(typeof(CacheOperation)).Length; + +#pragma warning disable CA1810 + static CacheDbCommands() +#pragma warning restore CA1810 + { + Commands = new string[Count]; + + Commands[(int)CacheOperation.InsertOrIgnore] = + "INSERT OR IGNORE INTO objects(hash, content) VALUES(@hash, @content)"; + Commands[(int)CacheOperation.InsertOrReplace] = "REPLACE INTO objects(hash, content) VALUES(@hash, @content)"; + Commands[(int)CacheOperation.Has] = "SELECT 1 FROM objects WHERE hash = @hash LIMIT 1"; + Commands[(int)CacheOperation.Get] = "SELECT content FROM objects WHERE hash = @hash LIMIT 1"; + Commands[(int)CacheOperation.Delete] = "DELETE FROM objects WHERE hash = @hash"; + Commands[(int)CacheOperation.GetAll] = "SELECT hash, content FROM objects"; + + Commands[(int)CacheOperation.BulkInsertOrIgnore] = "INSERT OR IGNORE INTO objects (hash, content) VALUES "; + } +} diff --git a/src/Speckle.Sdk/SQLite/SQLiteJsonCacheManager.cs b/src/Speckle.Sdk/SQLite/SQLiteJsonCacheManager.cs index 8ffffddd..6a2983f4 100644 --- a/src/Speckle.Sdk/SQLite/SQLiteJsonCacheManager.cs +++ b/src/Speckle.Sdk/SQLite/SQLiteJsonCacheManager.cs @@ -1,19 +1,28 @@ +using System.Text; using Microsoft.Data.Sqlite; using Speckle.InterfaceGenerator; +using Speckle.Sdk.Dependencies; namespace Speckle.Sdk.SQLite; +public partial interface ISqLiteJsonCacheManager : IDisposable; + [GenerateAutoInterface] -public class SqLiteJsonCacheManager : ISqLiteJsonCacheManager +public sealed class SqLiteJsonCacheManager : ISqLiteJsonCacheManager { private readonly string _connectionString; + private readonly CacheDbCommandPool _pool; - public SqLiteJsonCacheManager(string rootPath) + public SqLiteJsonCacheManager(string connectionString, int concurrency) { - _connectionString = $"Data Source={rootPath};"; + _connectionString = connectionString; Initialize(); + _pool = new CacheDbCommandPool(_connectionString, concurrency); } + [AutoInterfaceIgnore] + public void Dispose() => _pool.Dispose(); + private void Initialize() { // NOTE: used for creating partioned object tables. @@ -57,100 +66,107 @@ content TEXT using SqliteCommand cmd4 = new("PRAGMA page_size = 32768;", c); cmd4.ExecuteNonQuery(); + c.Close(); } - public IEnumerable GetAllObjects() - { - using var c = new SqliteConnection(_connectionString); - c.Open(); - using var command = new SqliteCommand("SELECT * FROM objects", c); - - using var reader = command.ExecuteReader(); - while (reader.Read()) - { - yield return reader.GetString(1); - } - } - - public void DeleteObject(string id) - { - using var c = new SqliteConnection(_connectionString); - c.Open(); - using var command = new SqliteCommand("DELETE FROM objects WHERE hash = @hash", c); - command.Parameters.AddWithValue("@hash", id); - command.ExecuteNonQuery(); - } - - public string? GetObject(string id) - { - using var c = new SqliteConnection(_connectionString); - c.Open(); - using var command = new SqliteCommand("SELECT * FROM objects WHERE hash = @hash LIMIT 1 ", c); - command.Parameters.AddWithValue("@hash", id); - using var reader = command.ExecuteReader(); - if (reader.Read()) - { - return reader.GetString(1); - } - - return null; // pass on the duty of null checks to consumers - } + public IReadOnlyCollection<(string Id, string Json)> GetAllObjects() => + _pool.Use( + CacheOperation.GetAll, + command => + { + var list = new HashSet<(string, string)>(); + using var reader = command.ExecuteReader(); + while (reader.Read()) + { + list.Add((reader.GetString(0), reader.GetString(1))); + } + return list; + } + ); + + public void DeleteObject(string id) => + _pool.Use( + CacheOperation.Delete, + command => + { + command.Parameters.AddWithValue("@hash", id); + command.ExecuteNonQuery(); + } + ); + + public string? GetObject(string id) => + _pool.Use( + CacheOperation.Get, + command => + { + command.Parameters.AddWithValue("@hash", id); + return (string?)command.ExecuteScalar(); + } + ); //This does an insert or ignores if already exists - public void SaveObject(string id, string json) - { - using var c = new SqliteConnection(_connectionString); - c.Open(); - const string COMMAND_TEXT = "INSERT OR IGNORE INTO objects(hash, content) VALUES(@hash, @content)"; - - using var command = new SqliteCommand(COMMAND_TEXT, c); - command.Parameters.AddWithValue("@hash", id); - command.Parameters.AddWithValue("@content", json); - command.ExecuteNonQuery(); - } + public void SaveObject(string id, string json) => + _pool.Use( + CacheOperation.InsertOrIgnore, + command => + { + command.Parameters.AddWithValue("@hash", id); + command.Parameters.AddWithValue("@content", json); + command.ExecuteNonQuery(); + } + ); //This does an insert or replaces if already exists - public void UpdateObject(string id, string json) - { - using var c = new SqliteConnection(_connectionString); - c.Open(); - const string COMMAND_TEXT = "REPLACE INTO objects(hash, content) VALUES(@hash, @content)"; - using var command = new SqliteCommand(COMMAND_TEXT, c); - command.Parameters.AddWithValue("@hash", id); - command.Parameters.AddWithValue("@content", json); - command.ExecuteNonQuery(); - } - - public void SaveObjects(IEnumerable<(string id, string json)> items) + public void UpdateObject(string id, string json) => + _pool.Use( + CacheOperation.InsertOrReplace, + command => + { + command.Parameters.AddWithValue("@hash", id); + command.Parameters.AddWithValue("@content", json); + command.ExecuteNonQuery(); + } + ); + + public void SaveObjects(IEnumerable<(string id, string json)> items) => + _pool.Use( + CacheOperation.BulkInsertOrIgnore, + cmd => + { + CreateBulkInsert(cmd, items); + return cmd.ExecuteNonQuery(); + } + ); + + private void CreateBulkInsert(SqliteCommand cmd, IEnumerable<(string id, string json)> items) { - using var c = new SqliteConnection(_connectionString); - c.Open(); - using var t = c.BeginTransaction(); - const string COMMAND_TEXT = "INSERT OR IGNORE INTO objects(hash, content) VALUES(@hash, @content)"; - - using var command = new SqliteCommand(COMMAND_TEXT, c); - command.Transaction = t; - var idParam = command.Parameters.Add("@hash", SqliteType.Text); - var jsonParam = command.Parameters.Add("@content", SqliteType.Text); + StringBuilder sb = Pools.StringBuilders.Get(); + sb.AppendLine(CacheDbCommands.Commands[(int)CacheOperation.BulkInsertOrIgnore]); + int i = 0; foreach (var (id, json) in items) { - idParam.Value = id; - jsonParam.Value = json; - command.ExecuteNonQuery(); + sb.Append($"(@key{i}, @value{i}),"); + cmd.Parameters.AddWithValue($"@key{i}", id); + cmd.Parameters.AddWithValue($"@value{i}", json); + i++; } - t.Commit(); + sb.Remove(sb.Length - 1, 1); + sb.Append(';'); +#pragma warning disable CA2100 + cmd.CommandText = sb.ToString(); +#pragma warning restore CA2100 + Pools.StringBuilders.Return(sb); } - public bool HasObject(string objectId) - { - using var c = new SqliteConnection(_connectionString); - c.Open(); - const string COMMAND_TEXT = "SELECT 1 FROM objects WHERE hash = @hash LIMIT 1 "; - using var command = new SqliteCommand(COMMAND_TEXT, c); - command.Parameters.AddWithValue("@hash", objectId); - - using var reader = command.ExecuteReader(); - bool rowFound = reader.Read(); - return rowFound; - } + public bool HasObject(string objectId) => + _pool.Use( + CacheOperation.Has, + command => + { + command.Parameters.AddWithValue("@hash", objectId); + using var reader = command.ExecuteReader(); + bool rowFound = reader.Read(); + return rowFound; + } + ); } diff --git a/src/Speckle.Sdk/SQLite/SqLiteJsonCacheManagerFactory.cs b/src/Speckle.Sdk/SQLite/SqLiteJsonCacheManagerFactory.cs index c73a28a7..94eb9339 100644 --- a/src/Speckle.Sdk/SQLite/SqLiteJsonCacheManagerFactory.cs +++ b/src/Speckle.Sdk/SQLite/SqLiteJsonCacheManagerFactory.cs @@ -7,10 +7,14 @@ namespace Speckle.Sdk.SQLite; [GenerateAutoInterface] public class SqLiteJsonCacheManagerFactory : ISqLiteJsonCacheManagerFactory { - private ISqLiteJsonCacheManager Create(string path) => new SqLiteJsonCacheManager(path); + public const int INITIAL_CONCURRENCY = 4; + + private ISqLiteJsonCacheManager Create(string path, int concurrency) => + new SqLiteJsonCacheManager($"Data Source={path};", concurrency); public ISqLiteJsonCacheManager CreateForUser(string scope) => - Create(Path.Combine(SpecklePathProvider.UserApplicationDataPath(), "Speckle", $"{scope}.db")); + Create(Path.Combine(SpecklePathProvider.UserApplicationDataPath(), "Speckle", $"{scope}.db"), 1); - public ISqLiteJsonCacheManager CreateFromStream(string streamId) => Create(SqlitePaths.GetDBPath(streamId)); + public ISqLiteJsonCacheManager CreateFromStream(string streamId) => + Create(SqlitePaths.GetDBPath(streamId), INITIAL_CONCURRENCY); } diff --git a/src/Speckle.Sdk/Serialisation/V2/DummySendServerObjectManager.cs b/src/Speckle.Sdk/Serialisation/V2/DummySendServerObjectManager.cs index 827ed34a..26e50477 100644 --- a/src/Speckle.Sdk/Serialisation/V2/DummySendServerObjectManager.cs +++ b/src/Speckle.Sdk/Serialisation/V2/DummySendServerObjectManager.cs @@ -5,9 +5,11 @@ namespace Speckle.Sdk.Serialisation.V2; -public class DummySqLiteJsonCacheManager : ISqLiteJsonCacheManager +public sealed class DummySqLiteJsonCacheManager : ISqLiteJsonCacheManager { - public IEnumerable GetAllObjects() => throw new NotImplementedException(); + public void Dispose() { } + + public IReadOnlyCollection<(string, string)> GetAllObjects() => throw new NotImplementedException(); public void DeleteObject(string id) => throw new NotImplementedException(); diff --git a/src/Speckle.Sdk/Serialisation/V2/Receive/DeserializeProcess.cs b/src/Speckle.Sdk/Serialisation/V2/Receive/DeserializeProcess.cs index 87aa22a3..5be948fd 100644 --- a/src/Speckle.Sdk/Serialisation/V2/Receive/DeserializeProcess.cs +++ b/src/Speckle.Sdk/Serialisation/V2/Receive/DeserializeProcess.cs @@ -13,6 +13,8 @@ public record DeserializeProcessOptions( bool SkipInvalidConverts = false ); +public partial interface IDeserializeProcess : IDisposable; + [GenerateAutoInterface] public sealed class DeserializeProcess( IProgress? progress, @@ -30,6 +32,9 @@ public sealed class DeserializeProcess( public IReadOnlyDictionary BaseCache => _baseCache; public long Total { get; private set; } + [AutoInterfaceIgnore] + public void Dispose() => objectLoader.Dispose(); + public async Task Deserialize(string rootId, CancellationToken cancellationToken) { var (rootJson, childrenIds) = await objectLoader diff --git a/src/Speckle.Sdk/Serialisation/V2/Receive/ObjectLoader.cs b/src/Speckle.Sdk/Serialisation/V2/Receive/ObjectLoader.cs index 7201c4d0..345190c5 100644 --- a/src/Speckle.Sdk/Serialisation/V2/Receive/ObjectLoader.cs +++ b/src/Speckle.Sdk/Serialisation/V2/Receive/ObjectLoader.cs @@ -9,6 +9,8 @@ namespace Speckle.Sdk.Serialisation.V2.Receive; +public partial interface IObjectLoader : IDisposable; + [GenerateAutoInterface] public sealed class ObjectLoader( ISqLiteJsonCacheManager sqLiteJsonCacheManager, @@ -21,6 +23,9 @@ public sealed class ObjectLoader( private long _cached; private DeserializeProcessOptions _options = new(false); + [AutoInterfaceIgnore] + public void Dispose() => sqLiteJsonCacheManager.Dispose(); + public async Task<(string, IReadOnlyCollection)> GetAndCache( string rootId, DeserializeProcessOptions options, diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/BaseItem.cs b/src/Speckle.Sdk/Serialisation/V2/Send/BaseItem.cs new file mode 100644 index 00000000..38f2922f --- /dev/null +++ b/src/Speckle.Sdk/Serialisation/V2/Send/BaseItem.cs @@ -0,0 +1,19 @@ +using System.Text; + +namespace Speckle.Sdk.Serialisation.V2.Send; + +public readonly record struct BaseItem(Id Id, Json Json, bool NeedsStorage, Dictionary? Closures) : IHasSize +{ + public int Size { get; } = Encoding.UTF8.GetByteCount(Json.Value); + + public bool Equals(BaseItem? other) + { + if (other is null) + { + return false; + } + return string.Equals(Id.Value, other.Value.Id.Value, StringComparison.OrdinalIgnoreCase); + } + + public override int GetHashCode() => Id.GetHashCode(); +} diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/PriorityScheduler.cs b/src/Speckle.Sdk/Serialisation/V2/Send/PriorityScheduler.cs index c38a3df3..46eacf4a 100644 --- a/src/Speckle.Sdk/Serialisation/V2/Send/PriorityScheduler.cs +++ b/src/Speckle.Sdk/Serialisation/V2/Send/PriorityScheduler.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; namespace Speckle.Sdk.Serialisation.V2.Send; diff --git a/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs b/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs index 8f08173a..242fc5a0 100644 --- a/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs +++ b/src/Speckle.Sdk/Serialisation/V2/Send/SerializeProcess.cs @@ -1,5 +1,4 @@ using System.Collections.Concurrent; -using System.Text; using Speckle.InterfaceGenerator; using Speckle.Sdk.Common; using Speckle.Sdk.Dependencies; @@ -23,22 +22,6 @@ public readonly record struct SerializeProcessResults( IReadOnlyDictionary ConvertedReferences ); -public readonly record struct BaseItem(Id Id, Json Json, bool NeedsStorage, Closures? Closures) : IHasSize -{ - public int Size { get; } = Encoding.UTF8.GetByteCount(Json.Value); - - public bool Equals(BaseItem? other) - { - if (other is null) - { - return false; - } - return string.Equals(Id.Value, other.Value.Id.Value, StringComparison.OrdinalIgnoreCase); - } - - public override int GetHashCode() => Id.GetHashCode(); -} - public partial interface ISerializeProcess : IDisposable; [GenerateAutoInterface] @@ -77,6 +60,7 @@ public void Dispose() { _highest.Dispose(); _belowNormal.Dispose(); + sqLiteJsonCacheManager.Dispose(); } public async Task Serialize(Base root, CancellationToken cancellationToken) @@ -223,7 +207,7 @@ private BaseItem CheckCache(Id id, Json json, Dictionary closures) return new BaseItem(id, json, true, closures); } - public override async Task> SendToServer(Batch batch, CancellationToken cancellationToken) + public override async Task SendToServer(Batch batch, CancellationToken cancellationToken) { if (!_options.SkipServer && batch.Items.Count != 0) { @@ -238,9 +222,7 @@ public override async Task> SendToServer(Batch batch, C Interlocked.Exchange(ref _uploaded, _uploaded + batch.Items.Count); } progress?.Report(new(ProgressEvent.UploadedObjects, _uploaded, null)); - return objectBatch; } - return batch.Items; } public override void SaveToCache(List batch) diff --git a/src/Speckle.Sdk/Serialisation/V2/SerializeProcessFactory.cs b/src/Speckle.Sdk/Serialisation/V2/SerializeProcessFactory.cs index 36cc08af..68615ec5 100644 --- a/src/Speckle.Sdk/Serialisation/V2/SerializeProcessFactory.cs +++ b/src/Speckle.Sdk/Serialisation/V2/SerializeProcessFactory.cs @@ -1,5 +1,3 @@ -using Speckle.Sdk.Helpers; -using Speckle.Sdk.Logging; using Speckle.Sdk.Serialisation.V2.Receive; using Speckle.Sdk.Serialisation.V2.Send; using Speckle.Sdk.SQLite; @@ -23,20 +21,14 @@ IDeserializeProcess CreateDeserializeProcess( IProgress? progress, DeserializeProcessOptions? options = null ); - - public ISerializeProcess CreateSerializeProcess( - SerializeProcessOptions? options = null, - IProgress? progress = null - ); } public class SerializeProcessFactory( - ISpeckleHttp speckleHttp, - ISdkActivityFactory activityFactory, IBaseChildFinder baseChildFinder, IObjectSerializerFactory objectSerializerFactory, IObjectDeserializerFactory objectDeserializerFactory, - ISqLiteJsonCacheManagerFactory sqLiteJsonCacheManagerFactory + ISqLiteJsonCacheManagerFactory sqLiteJsonCacheManagerFactory, + IServerObjectManagerFactory serverObjectManagerFactory ) : ISerializeProcessFactory { public ISerializeProcess CreateSerializeProcess( @@ -48,24 +40,7 @@ public ISerializeProcess CreateSerializeProcess( ) { var sqLiteJsonCacheManager = sqLiteJsonCacheManagerFactory.CreateFromStream(streamId); - var serverObjectManager = new ServerObjectManager(speckleHttp, activityFactory, url, streamId, authorizationToken); - return new SerializeProcess( - progress, - sqLiteJsonCacheManager, - serverObjectManager, - baseChildFinder, - objectSerializerFactory, - options - ); - } - - public ISerializeProcess CreateSerializeProcess( - SerializeProcessOptions? options = null, - IProgress? progress = null - ) - { - var sqLiteJsonCacheManager = new DummySqLiteJsonCacheManager(); - var serverObjectManager = new DummySendServerObjectManager(); + var serverObjectManager = serverObjectManagerFactory.Create(url, streamId, authorizationToken); return new SerializeProcess( progress, sqLiteJsonCacheManager, @@ -85,9 +60,12 @@ public IDeserializeProcess CreateDeserializeProcess( ) { var sqLiteJsonCacheManager = sqLiteJsonCacheManagerFactory.CreateFromStream(streamId); - var serverObjectManager = new ServerObjectManager(speckleHttp, activityFactory, url, streamId, authorizationToken); + var serverObjectManager = serverObjectManagerFactory.Create(url, streamId, authorizationToken); +#pragma warning disable CA2000 + //owned by process, refactor later var objectLoader = new ObjectLoader(sqLiteJsonCacheManager, serverObjectManager, progress); +#pragma warning restore CA2000 return new DeserializeProcess(progress, objectLoader, objectDeserializerFactory, options); } } diff --git a/src/Speckle.Sdk/Serialisation/V2/ServerObjectManagerFactory.cs b/src/Speckle.Sdk/Serialisation/V2/ServerObjectManagerFactory.cs new file mode 100644 index 00000000..071a62b5 --- /dev/null +++ b/src/Speckle.Sdk/Serialisation/V2/ServerObjectManagerFactory.cs @@ -0,0 +1,13 @@ +using Speckle.InterfaceGenerator; +using Speckle.Sdk.Helpers; +using Speckle.Sdk.Logging; + +namespace Speckle.Sdk.Serialisation.V2; + +[GenerateAutoInterface] +public class ServerObjectManagerFactory(ISpeckleHttp speckleHttp, ISdkActivityFactory activityFactory) + : IServerObjectManagerFactory +{ + public IServerObjectManager Create(Uri url, string streamId, string? authorizationToken, int timeoutSeconds = 120) => + new ServerObjectManager(speckleHttp, activityFactory, url, streamId, authorizationToken, timeoutSeconds); +} diff --git a/tests/Speckle.Sdk.Serialization.Testing/Program.cs b/tests/Speckle.Sdk.Serialization.Testing/Program.cs index b5a324f4..77e81387 100644 --- a/tests/Speckle.Sdk.Serialization.Testing/Program.cs +++ b/tests/Speckle.Sdk.Serialization.Testing/Program.cs @@ -3,9 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using Speckle.Sdk; using Speckle.Sdk.Credentials; -using Speckle.Sdk.Helpers; using Speckle.Sdk.Host; -using Speckle.Sdk.Logging; using Speckle.Sdk.Models; using Speckle.Sdk.Serialisation.V2; using Speckle.Sdk.Serialisation.V2.Receive; @@ -43,12 +41,11 @@ var progress = new Progress(true); var factory = new SerializeProcessFactory( - serviceProvider.GetRequiredService(), - serviceProvider.GetRequiredService(), new BaseChildFinder(new BasePropertyGatherer()), new ObjectSerializerFactory(new BasePropertyGatherer()), new ObjectDeserializerFactory(), - serviceProvider.GetRequiredService() + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService() ); var process = factory.CreateDeserializeProcess(new Uri(url), streamId, token, progress, new(skipCacheReceive)); var @base = await process.Deserialize(rootId, default).ConfigureAwait(false); diff --git a/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs b/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs index 94a396e2..0448cb4a 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs @@ -529,7 +529,9 @@ CancellationToken cancellationToken public class DummySendCacheManager(Dictionary objects) : ISqLiteJsonCacheManager { - public IEnumerable GetAllObjects() => throw new NotImplementedException(); + public void Dispose() { } + + public IReadOnlyCollection<(string, string)> GetAllObjects() => throw new NotImplementedException(); public void DeleteObject(string id) => throw new NotImplementedException(); diff --git a/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteReceiveManager.cs b/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteReceiveManager.cs index 42961d6b..797eacd9 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteReceiveManager.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteReceiveManager.cs @@ -4,7 +4,9 @@ namespace Speckle.Sdk.Serialization.Tests; public class DummySqLiteReceiveManager(Dictionary savedObjects) : ISqLiteJsonCacheManager { - public IEnumerable GetAllObjects() => throw new NotImplementedException(); + public void Dispose() { } + + public IReadOnlyCollection<(string, string)> GetAllObjects() => throw new NotImplementedException(); public void DeleteObject(string id) => throw new NotImplementedException(); diff --git a/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteSendManager.cs b/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteSendManager.cs index ce1dae8c..0b93af62 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteSendManager.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/DummySqLiteSendManager.cs @@ -14,7 +14,9 @@ public class DummySqLiteSendManager : ISqLiteJsonCacheManager public bool HasObject(string objectId) => throw new NotImplementedException(); - public IEnumerable GetAllObjects() => throw new NotImplementedException(); + public IReadOnlyCollection<(string, string)> GetAllObjects() => throw new NotImplementedException(); public void DeleteObject(string id) => throw new NotImplementedException(); + + public void Dispose() { } } diff --git a/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs b/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs index aaf98d30..076730a2 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs @@ -33,6 +33,8 @@ CancellationToken cancellationToken } public string? LoadId(string id) => null; + + public void Dispose() { } } private readonly Assembly _assembly = Assembly.GetExecutingAssembly(); @@ -103,6 +105,8 @@ CancellationToken cancellationToken } public string? LoadId(string id) => idToObject.GetValueOrDefault(id); + + public void Dispose() { } } [Test] @@ -154,7 +158,7 @@ public async Task Basic_Namespace_Validation_New(string fileName) var fullName = _assembly.GetManifestResourceNames().Single(x => x.EndsWith(fileName)); var json = await ReadJson(fullName); var closures = ReadAsObjects(json); - var process = new DeserializeProcess(null, new TestObjectLoader(closures), new ObjectDeserializerFactory()); + using var process = new DeserializeProcess(null, new TestObjectLoader(closures), new ObjectDeserializerFactory()); await process.Deserialize("3416d3fe01c9196115514c4a2f41617b", default); foreach (var (id, objJson) in closures) { @@ -251,7 +255,7 @@ public async Task Roundtrip_Test_New(string fileName, string rootId, int oldCoun new DummyReceiveServerObjectManager(closure), null ); - var process = new DeserializeProcess(null, o, new ObjectDeserializerFactory(), new(true)); + using var process = new DeserializeProcess(null, o, new ObjectDeserializerFactory(), new(true)); var root = await process.Deserialize(rootId, default); process.BaseCache.Count.ShouldBe(oldCount); process.Total.ShouldBe(oldCount); diff --git a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs index 6ffc63ea..59fc7b33 100644 --- a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs +++ b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralDeserializerTest.cs @@ -57,7 +57,7 @@ public async Task RunTest_New() null ); var o = new ObjectLoader(sqlite, serverObjects, null); - var process = new DeserializeProcess(null, o, new ObjectDeserializerFactory(), new(skipCache)); + using var process = new DeserializeProcess(null, o, new ObjectDeserializerFactory(), new(skipCache)); return await process.Deserialize(rootId, default).ConfigureAwait(false); } diff --git a/tests/Speckle.Sdk.Tests.Unit/SQLite/SQLiteJsonCacheManagerTests.cs b/tests/Speckle.Sdk.Tests.Unit/SQLite/SQLiteJsonCacheManagerTests.cs new file mode 100644 index 00000000..961729a5 --- /dev/null +++ b/tests/Speckle.Sdk.Tests.Unit/SQLite/SQLiteJsonCacheManagerTests.cs @@ -0,0 +1,91 @@ +using Microsoft.Data.Sqlite; +using NUnit.Framework; +using Shouldly; +using Speckle.Sdk.Common; +using Speckle.Sdk.SQLite; + +namespace Speckle.Sdk.Tests.Unit.SQLite; + +[TestFixture] +public class SQLiteJsonCacheManagerTests +{ + private readonly string _basePath = $"{Guid.NewGuid()}.db"; + private string? _connectionString; + + [SetUp] + public void Setup() => _connectionString = $"Data Source={_basePath};"; + + [TearDown] + public void TearDown() + { + if (File.Exists(_basePath)) + { + SqliteConnection.ClearAllPools(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + File.Delete(_basePath); + } + } + + [Test] + public void TestGetAll() + { + var data = new List<(string id, string json)>() { ("id1", "1"), ("id2", "2") }; + using var manager = new SqLiteJsonCacheManager(_connectionString.NotNull(), 2); + manager.SaveObjects(data); + var items = manager.GetAllObjects(); + items.Count.ShouldBe(data.Count); + var i = items.ToDictionary(); + foreach (var (id, json) in data) + { + i.TryGetValue(id, out var j).ShouldBeTrue(); + j.ShouldBe(json); + } + } + + [Test] + public void TestGet() + { + var data = new List<(string id, string json)>() { ("id1", "1"), ("id2", "2") }; + using var manager = new SqLiteJsonCacheManager(_connectionString.NotNull(), 2); + foreach (var d in data) + { + manager.SaveObject(d.id, d.json); + } + foreach (var d in data) + { + manager.SaveObject(d.id, d.json); + } + var items = manager.GetAllObjects(); + items.Count.ShouldBe(data.Count); + + var id1 = data[0].id; + var json1 = manager.GetObject(id1); + json1.ShouldBe(data[0].json); + manager.HasObject(id1).ShouldBeTrue(); + + manager.UpdateObject(id1, "3"); + json1 = manager.GetObject(id1); + json1.ShouldBe("3"); + manager.HasObject(id1).ShouldBeTrue(); + + manager.DeleteObject(id1); + json1 = manager.GetObject(id1); + json1.ShouldBeNull(); + manager.HasObject(id1).ShouldBeFalse(); + + manager.UpdateObject(id1, "3"); + json1 = manager.GetObject(id1); + json1.ShouldBe("3"); + manager.HasObject(id1).ShouldBeTrue(); + + var id2 = data[1].id; + var json2 = manager.GetObject(id2); + json2.ShouldBe(data[1].json); + manager.HasObject(id2).ShouldBeTrue(); + manager.DeleteObject(id2); + json2 = manager.GetObject(id2); + json2.ShouldBeNull(); + manager.HasObject(id2).ShouldBeFalse(); + } +} diff --git a/tests/Speckle.Sdk.Tests.Unit/Serialisation/BatchTests.cs b/tests/Speckle.Sdk.Tests.Unit/Serialisation/BatchTests.cs index e994288d..a16b1937 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Serialisation/BatchTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Serialisation/BatchTests.cs @@ -1,5 +1,6 @@ -using NUnit.Framework; +using NUnit.Framework; using Shouldly; +using Speckle.Sdk.Dependencies; using Speckle.Sdk.Serialisation.V2.Send; namespace Speckle.Sdk.Tests.Unit.Serialisation; @@ -20,7 +21,7 @@ public BatchItem(int size) [Test] public void TestBatchSize_Calc() { - var batch = new Batch(4); + using var batch = new Batch(); batch.Add(new BatchItem(1)); batch.Size.ShouldBe(1); batch.Add(new BatchItem(2)); @@ -30,12 +31,12 @@ public void TestBatchSize_Calc() [Test] public void TestBatchSize_Trim() { - var batch = new Batch(4); + using var batch = new Batch(); batch.Add(new BatchItem(1)); batch.Add(new BatchItem(2)); batch.Size.ShouldBe(3); - batch.Items.Capacity.ShouldBe(4); + batch.Items.Capacity.ShouldBe(Pools.DefaultCapacity); batch.TrimExcess(); batch.Items.Capacity.ShouldBe(2); From 465f635142abe2ef9c2db791570f659a159c9ed3 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Thu, 9 Jan 2025 11:04:14 +0000 Subject: [PATCH 12/13] Add details to sqlite exceptions (#198) * add ServerObjectManagerFactory * add usage of a command pool * add more disposal * save saving increase * fix tests * fixes * push out concurrency and disposablity * Add a custom task scheduler * Better usage, don't wait to enqueue to save to channels * Completely pre-cal batch size to avoid spinning issues * Try to fix cache counting * properly dispose things * format * clean up * adjust count and save on current thread * move batch it's own file * update a few packages * fix build and add batch tests * revert and format * Revert "save saving increase" This reverts commit 3b50c857fb9a9da3fbf8193d36b149d2e0d2cb72. * revert change * adjust and add tests * Dispose sqlite manager properly * Make Batch a IMemoryOwner to allow for pooling * Fix tests * Upgrade some deps * try to make tests more explicit * remove return value * Add detailed SqLiteJsonCacheException * details changes --- src/Speckle.Sdk/SQLite/CacheDbCommandPool.cs | 10 +- .../SQLite/SqLiteJsonCacheException.cs | 35 ++++++ src/Speckle.Sdk/SQLite/SqliteExceptions.cs | 107 ++++++++++++++++++ .../SQLite/SQLiteJsonExceptionTests.cs | 27 +++++ 4 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 src/Speckle.Sdk/SQLite/SqLiteJsonCacheException.cs create mode 100644 src/Speckle.Sdk/SQLite/SqliteExceptions.cs create mode 100644 tests/Speckle.Sdk.Tests.Unit/SQLite/SQLiteJsonExceptionTests.cs diff --git a/src/Speckle.Sdk/SQLite/CacheDbCommandPool.cs b/src/Speckle.Sdk/SQLite/CacheDbCommandPool.cs index 500d2230..3adede1b 100644 --- a/src/Speckle.Sdk/SQLite/CacheDbCommandPool.cs +++ b/src/Speckle.Sdk/SQLite/CacheDbCommandPool.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using Microsoft.Data.Sqlite; namespace Speckle.Sdk.SQLite; @@ -47,6 +47,10 @@ private T Use(Func handler) { return handler(db); } + catch (SqliteException se) + { + throw SqLiteJsonCacheException.Create(se); + } finally { _connections.Add(db); @@ -69,6 +73,10 @@ public T Use(CacheOperation type, Func handler) => command.Connection = conn; return handler(command); } + catch (SqliteException se) + { + throw SqLiteJsonCacheException.Create(se); + } finally { command.Connection = null; diff --git a/src/Speckle.Sdk/SQLite/SqLiteJsonCacheException.cs b/src/Speckle.Sdk/SQLite/SqLiteJsonCacheException.cs new file mode 100644 index 00000000..b98de798 --- /dev/null +++ b/src/Speckle.Sdk/SQLite/SqLiteJsonCacheException.cs @@ -0,0 +1,35 @@ +using Microsoft.Data.Sqlite; + +namespace Speckle.Sdk.SQLite; + +public class SqLiteJsonCacheException : SpeckleException +{ + public SqLiteJsonCacheException() { } + + public SqLiteJsonCacheException(string message) + : base(message) { } + + public SqLiteJsonCacheException(string message, Exception inner) + : base(message, inner) { } + + public static SqLiteJsonCacheException Create(SqliteException inner) + { + if (!SqliteExceptions.SqliteErrorCodes.TryGetValue(inner.SqliteErrorCode, out string? errorMessage)) + { + errorMessage = $"An error occurred while executing a SQLite command: {inner.SqliteErrorCode}"; + } + if ( + !SqliteExceptions.SqliteExtendedResultCodes.TryGetValue( + inner.SqliteExtendedErrorCode, + out string? detailedMessage + ) + ) + { + detailedMessage = $"Detail: {inner.SqliteExtendedErrorCode}"; + } + return new SqLiteJsonCacheException( + $"An error occured with the SQLite cache: {inner.Message}{Environment.NewLine}{errorMessage}{Environment.NewLine}{detailedMessage}", + inner + ); + } +} diff --git a/src/Speckle.Sdk/SQLite/SqliteExceptions.cs b/src/Speckle.Sdk/SQLite/SqliteExceptions.cs new file mode 100644 index 00000000..eec7738c --- /dev/null +++ b/src/Speckle.Sdk/SQLite/SqliteExceptions.cs @@ -0,0 +1,107 @@ +namespace Speckle.Sdk.SQLite; + +internal static class SqliteExceptions +{ + public static readonly IReadOnlyDictionary SqliteErrorCodes = new Dictionary + { + { 0, "Successful result" }, + { 1, "Generic error" }, + { 2, "Internal logic error in SQLite" }, + { 3, "Access permission denied" }, + { 4, "Callback routine requested an abort" }, + { 5, "The database file is locked" }, + { 6, "A table in the database is locked" }, + { 7, "A malloc() failed" }, + { 8, "Attempt to write a readonly database" }, + { 9, "Operation terminated by sqlite3_interrupt()" }, + { 10, "Some kind of disk I/O error occurred" }, + { 11, "The database disk image is malformed" }, + { 12, "Unknown opcode in sqlite3_file_control()" }, + { 13, "Insertion failed because database is full" }, + { 14, "Unable to open the database file" }, + { 15, "Database lock protocol error" }, + { 16, "Internal use only" }, + { 17, "The database schema changed" }, + { 18, "String or BLOB exceeds size limit" }, + { 19, "Abort due to constraint violation" }, + { 20, "Data type mismatch" }, + { 21, "Library used incorrectly" }, + { 22, "Uses OS features not supported on host" }, + { 23, "Authorization denied" }, + { 24, "Not used" }, + { 25, "2nd parameter to sqlite3_bind out of range" }, + { 26, "File opened that is not a database file" }, + { 27, "Notifications from sqlite3_log()" }, + { 28, "Warnings from sqlite3_log()" }, + { 100, "sqlite3_step() has another row ready" }, + { 101, "sqlite3_step() has finished executing" }, + }; + + public static readonly IReadOnlyDictionary SqliteExtendedResultCodes = new Dictionary() + { + { 516, "SQLITE_ABORT_ROLLBACK: A rollback occurred due to a constraint violation." }, + { 279, "SQLITE_AUTH_USER: Authorization denied by a user authentication callback." }, + { 261, "SQLITE_BUSY_RECOVERY: The database file is locked because another connection is recovering the WAL." }, + { 517, "SQLITE_BUSY_SNAPSHOT: The database file is locked because another connection has a conflicting snapshot." }, + { 773, "SQLITE_BUSY_TIMEOUT: A blocking operation was interrupted by a call to sqlite3_interrupt()." }, + { 1038, "SQLITE_CANTOPEN_CONVPATH: Unable to open the database file due to a conversion error." }, + { + 1294, + "SQLITE_CANTOPEN_DIRTYWAL: The database file cannot be opened because the Write-Ahead Log contains uncommitted changes." + }, + { 782, "SQLITE_CANTOPEN_FULLPATH: Unable to open the database file with the full pathname." }, + { 526, "SQLITE_CANTOPEN_ISDIR: The database file cannot be opened because it is a directory." }, + { + 270, + "SQLITE_CANTOPEN_NOTEMPDIR: Unable to open a temporary database file because a temporary directory is not available." + }, + { 1550, "SQLITE_CANTOPEN_SYMLINK: The database file cannot be opened because it is a symbolic link." }, + { 275, "SQLITE_CONSTRAINT_CHECK: A CHECK constraint failed." }, + { 531, "SQLITE_CONSTRAINT_COMMITHOOK: A commit hook caused the transaction to roll back." }, + { 3091, "SQLITE_CONSTRAINT_DATATYPE: A datatype mismatch occurred." }, + { 787, "SQLITE_CONSTRAINT_FOREIGNKEY: A foreign key constraint failed." }, + { 1043, "SQLITE_CONSTRAINT_FUNCTION: A function constraint failed." }, + { 1299, "SQLITE_CONSTRAINT_NOTNULL: A NOT NULL constraint failed." }, + { 1555, "SQLITE_CONSTRAINT_PRIMARYKEY: A PRIMARY KEY constraint failed." }, + { 1803, "SQLITE_CONSTRAINT_TRIGGER: A trigger constraint failed." }, + { 2059, "SQLITE_CONSTRAINT_UNIQUE: A UNIQUE constraint failed." }, + { 2315, "SQLITE_CONSTRAINT_VTAB: A virtual table constraint failed." }, + { 2571, "SQLITE_CONSTRAINT_ROWID: A rowid constraint failed." }, + { 1034, "SQLITE_IOERR_FSYNC: An I/O error occurred during the fsync() system call." }, + { 6410, "SQLITE_IOERR_GETTEMPPATH: An I/O error occurred while trying to get the temporary file path." }, + { 3850, "SQLITE_IOERR_LOCK: An I/O error occurred while trying to lock the database file." }, + { 6154, "SQLITE_IOERR_MMAP: An I/O error occurred during memory mapping." }, + { 3082, "SQLITE_IOERR_NOMEM: An I/O error occurred due to a memory allocation failure." }, + { 2314, "SQLITE_IOERR_RDLOCK: An I/O error occurred while trying to read-lock the database file." }, + { 266, "SQLITE_IOERR_READ: An I/O error occurred while reading from the database file." }, + { 7946, "SQLITE_IOERR_ROLLBACK_ATOMIC: An I/O error occurred during an atomic rollback." }, + { 5642, "SQLITE_IOERR_SEEK: An I/O error occurred while seeking in the database file." }, + { 5130, "SQLITE_IOERR_SHMLOCK: An I/O error occurred while locking a shared memory segment." }, + { 5386, "SQLITE_IOERR_SHMMAP: An I/O error occurred while mapping a shared memory segment." }, + { 4618, "SQLITE_IOERR_SHMOPEN: An I/O error occurred while opening a shared memory segment." }, + { 4874, "SQLITE_IOERR_SHMSIZE: An I/O error occurred while setting the size of a shared memory segment." }, + { 522, "SQLITE_IOERR_SHORT_READ: An I/O error occurred due to a short read." }, + { 1546, "SQLITE_IOERR_TRUNCATE: An I/O error occurred while truncating the database file." }, + { 2058, "SQLITE_IOERR_UNLOCK: An I/O error occurred while unlocking the database file." }, + { 6922, "SQLITE_IOERR_VNODE: A virtual node I/O error occurred." }, + { 778, "SQLITE_IOERR_WRITE: An I/O error occurred while writing to the database file." }, + { + 262, + "SQLITE_LOCKED_SHAREDCACHE: A write operation could not continue due to a conflict within the shared cache." + }, + { 518, "SQLITE_LOCKED_VTAB: A virtual table is locked." }, + { 539, "SQLITE_NOTICE_RECOVER_ROLLBACK: A rollback was performed to recover from a previous error." }, + { 283, "SQLITE_NOTICE_RECOVER_WAL: Recovery was performed from the Write-Ahead Log." }, + { 256, "SQLITE_OK_LOAD_PERMANENTLY: Operation completed successfully; the extension was loaded permanently." }, + { + 1288, + "SQLITE_READONLY_CANTINIT: Attempt to write to a read-only database failed because initialization is not allowed." + }, + { 520, "SQLITE_READONLY_CANTLOCK: A read-only database cannot be locked." }, + { 1032, "SQLITE_READONLY_DBMOVED: The database file has been moved, making it read-only." }, + { 1544, "SQLITE_READONLY_DIRECTORY: The database is read-only because it is a directory." }, + { 264, "SQLITE_READONLY_RECOVERY: The database is read-only due to recovery mode." }, + { 776, "SQLITE_READONLY_ROLLBACK: The database is read-only because a rollback is required." }, + { 284, "SQLITE_WARNING_AUTOINDEX: Automatic indexing is in use." }, + }; +} diff --git a/tests/Speckle.Sdk.Tests.Unit/SQLite/SQLiteJsonExceptionTests.cs b/tests/Speckle.Sdk.Tests.Unit/SQLite/SQLiteJsonExceptionTests.cs new file mode 100644 index 00000000..a5d80fd6 --- /dev/null +++ b/tests/Speckle.Sdk.Tests.Unit/SQLite/SQLiteJsonExceptionTests.cs @@ -0,0 +1,27 @@ +using Microsoft.Data.Sqlite; +using NUnit.Framework; +using Speckle.Sdk.SQLite; + +namespace Speckle.Sdk.Tests.Unit.SQLite; + +[TestFixture] +public class SqLiteJsonCacheExceptionTests +{ + [Test] + public void ExpectedExceptionFires_Void() + { + using var pool = new CacheDbCommandPool("DataSource=:memory:", 1); + Assert.Throws( + () => pool.Use(CacheOperation.Get, new Action(_ => throw new SqliteException("test", 1, 1))) + ); + } + + [Test] + public void ExpectedExceptionFires_Return() + { + using var pool = new CacheDbCommandPool("DataSource=:memory:", 1); + Assert.Throws( + () => pool.Use(CacheOperation.Get, new Func(_ => throw new SqliteException("test", 1, 1))) + ); + } +} From 14d959834fbabf767a5c9c520416229ceb77e406 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Thu, 9 Jan 2025 15:32:28 +0000 Subject: [PATCH 13/13] Convert to Xunit (#196) * xunit unit tests * most pass with formatting * convert objects to xunit * remove nunit * format * merge fixes * switch objects to fluent assertions * update to fluent assertions * more FA * convert all to FA * Format * Fix tests * formatting * hopefully made credential test better * Catch more specific exception * use another more specific exception * Fix tests * update to xunit * update packages --- Directory.Build.targets | 2 - Directory.Packages.props | 13 +- src/Speckle.Sdk.Dependencies/Collections.cs | 5 + tests/Speckle.Objects.Tests.Unit/Assembly.cs | 3 + .../Geometry/ArcTests.cs | 109 +++++----- .../Geometry/BoxTests.cs | 35 ++-- .../Geometry/CircleTests.cs | 15 +- .../Geometry/MeshTests.cs | 31 +-- .../Geometry/PointTests.cs | 72 ++++--- .../Geometry/TransformTests.cs | 78 ++++--- .../ModelPropertySupportedTypes.cs | 25 ++- .../ObjectBaseValidityTests.cs | 20 +- .../Speckle.Objects.Tests.Unit.csproj | 6 +- .../Utils/MeshTriangulationHelperTests.cs | 69 ++++--- .../Utils/ShallowCopyTests.cs | 15 +- .../packages.lock.json | 178 ++++++++++------ .../Assembly.cs | 3 + .../BaseComparer.cs | 17 ++ .../DetachedTests.cs | 115 ++++++----- .../DummyReceiveServerObjectManager.cs | 1 - .../DummySendServerObjectManager.cs | 4 - .../ExplicitInterfaceTests.cs | 28 +-- .../ExternalIdTests.cs | 47 +++-- .../SerializationTests.cs | 80 ++++---- .../Speckle.Sdk.Serialization.Tests.csproj | 6 +- .../packages.lock.json | 178 ++++++++++------ .../GraphQL/GraphQLClientExceptionHandling.cs | 65 +++--- .../Resources/ActiveUserResourceTests.cs | 58 +++--- .../GraphQL/Resources/CommentResourceTests.cs | 99 ++++----- .../ModelResourceExceptionalTests.cs | 121 +++++++---- .../GraphQL/Resources/ModelResourceTests.cs | 109 ++++++---- .../Resources/OtherUserResourceTests.cs | 48 +++-- .../ProjectInviteResourceExceptionalTests.cs | 45 +++-- .../Resources/ProjectInviteResourceTests.cs | 76 ++++--- .../ProjectResourceExceptionalTests.cs | 98 ++++----- .../GraphQL/Resources/ProjectResourceTests.cs | 107 ++++++---- .../Resources/SubscriptionResourceTests.cs | 69 ++++--- .../GraphQL/Resources/VersionResourceTests.cs | 75 ++++--- .../Credentials/UserServerInfoTests.cs | 94 +++++---- .../Speckle.Sdk.Tests.Integration/Fixtures.cs | 4 +- .../MemoryTransportTests.cs | 35 ++-- .../Speckle.Sdk.Tests.Integration.csproj | 5 +- tests/Speckle.Sdk.Tests.Integration/Usings.cs | 1 - .../packages.lock.json | 188 +++++++++++------ .../Benchmarks/GeneralReceiveTest.cs | 5 - .../Benchmarks/GeneralSerializerTest.cs | 1 - .../Speckle.Sdk.Tests.Performance/Program.cs | 1 - .../Api/ClientResiliencyPolicyTest.cs | 30 ++- .../Api/GraphQLErrorHandler.cs | 60 +++--- .../Api/Operations/ClosureTests.cs | 39 ++-- .../OperationsReceiveTests.Exceptional.cs | 26 +-- .../Api/Operations/OperationsReceiveTests.cs | 66 +++--- .../Api/Operations/SendObjectReferences.cs | 38 ++-- .../Api/Operations/SendReceiveLocal.cs | 113 ++++------- .../Api/Operations/SerializationTests.cs | 63 +++--- tests/Speckle.Sdk.Tests.Unit/Assembly.cs | 3 + .../Common/NotNullTests.cs | 99 ++++----- .../Common/RangeFromTests.cs | 14 ++ .../Common/UnitsTest.cs | 90 ++++++--- .../AccountServerMigrationTests.cs | 46 ++--- .../Credentials/Accounts.cs | 77 ++++--- tests/Speckle.Sdk.Tests.Unit/Fixtures.cs | 10 +- tests/Speckle.Sdk.Tests.Unit/Helpers/Path.cs | 13 +- .../Host/HostApplicationTests.cs | 17 +- .../Models/BaseTests.cs | 124 ++++++------ .../Models/Extensions/BaseExtensionsTests.cs | 45 ++--- .../Models/Extensions/DisplayValueTests.cs | 35 ++-- .../Models/Extensions/ExceptionTests.cs | 17 +- .../GraphTraversal/GraphTraversalTests.cs | 68 +++---- .../TraversalContextExtensionsTests.cs | 47 +++-- .../Speckle.Sdk.Tests.Unit/Models/Hashing.cs | 42 ++-- .../Models/SpeckleType.cs | 52 +++-- .../Models/TraversalTests.cs | 47 +++-- .../Models/UtilitiesTests.cs | 131 +++++++----- .../SQLite/SQLiteJsonCacheManagerTests.cs | 53 +++-- .../SQLite/SQLiteJsonExceptionTests.cs | 7 +- .../Serialisation/BatchTests.cs | 21 +- .../Serialisation/ChunkingTests.cs | 38 ++-- .../Serialisation/JsonIgnoreAttributeTests.cs | 70 ++++--- .../ObjectModelDeprecationTests.cs | 25 +-- .../Serialisation/PrimitiveTestFixture.cs | 48 +++++ .../SerializerBreakingChanges.cs | 59 +++--- .../SerializerNonBreakingChanges.cs | 190 ++++++------------ .../Serialisation/SimpleRoundTripTests.cs | 33 ++- .../Speckle.Sdk.Tests.Unit.csproj | 6 +- .../Transports/DiskTransportTests.cs | 43 ++-- .../Transports/MemoryTransportTests.cs | 17 +- .../Transports/SQLiteTransport2Tests.cs | 70 +++---- .../Transports/SQLiteTransportTests.cs | 86 ++++---- .../Transports/TransportTests.cs | 80 ++++---- .../Speckle.Sdk.Tests.Unit/packages.lock.json | 178 ++++++++++------ 91 files changed, 2684 insertions(+), 2211 deletions(-) create mode 100644 tests/Speckle.Objects.Tests.Unit/Assembly.cs create mode 100644 tests/Speckle.Sdk.Serialization.Tests/Assembly.cs delete mode 100644 tests/Speckle.Sdk.Tests.Integration/Usings.cs create mode 100644 tests/Speckle.Sdk.Tests.Unit/Assembly.cs create mode 100644 tests/Speckle.Sdk.Tests.Unit/Common/RangeFromTests.cs create mode 100644 tests/Speckle.Sdk.Tests.Unit/Serialisation/PrimitiveTestFixture.cs diff --git a/Directory.Build.targets b/Directory.Build.targets index db7cc978..10e64e3a 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,6 +1,5 @@ - Recommended $(NoWarn); @@ -9,7 +8,6 @@ CA5394;CA2007;CA1852;CA1819;CA1711;CA1063;CA1816;CA2234;CS8618;CA1054;CA1810;CA2208;CA1019;CA1831; - false diff --git a/Directory.Packages.props b/Directory.Packages.props index f2acca28..322df41a 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,8 +1,9 @@ - + + @@ -10,24 +11,22 @@ - + - + - - - - + + diff --git a/src/Speckle.Sdk.Dependencies/Collections.cs b/src/Speckle.Sdk.Dependencies/Collections.cs index 457a9079..e391b3d6 100644 --- a/src/Speckle.Sdk.Dependencies/Collections.cs +++ b/src/Speckle.Sdk.Dependencies/Collections.cs @@ -9,3 +9,8 @@ public static class Collections public static IReadOnlyDictionary Freeze(this IDictionary source) where TKey : notnull => source.ToFrozenDictionary(); } + +public static class EnumerableExtensions +{ + public static IEnumerable RangeFrom(int from, int to) => Enumerable.Range(from, to - from + 1); +} diff --git a/tests/Speckle.Objects.Tests.Unit/Assembly.cs b/tests/Speckle.Objects.Tests.Unit/Assembly.cs new file mode 100644 index 00000000..a4bcec54 --- /dev/null +++ b/tests/Speckle.Objects.Tests.Unit/Assembly.cs @@ -0,0 +1,3 @@ +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/tests/Speckle.Objects.Tests.Unit/Geometry/ArcTests.cs b/tests/Speckle.Objects.Tests.Unit/Geometry/ArcTests.cs index d6dc9d52..6c9627ff 100644 --- a/tests/Speckle.Objects.Tests.Unit/Geometry/ArcTests.cs +++ b/tests/Speckle.Objects.Tests.Unit/Geometry/ArcTests.cs @@ -1,87 +1,78 @@ -using NUnit.Framework; +using FluentAssertions; using Speckle.Objects.Geometry; using Speckle.Sdk.Common; +using Xunit; namespace Speckle.Objects.Tests.Unit.Geometry; -[TestFixture, TestOf(typeof(Arc))] public class ArcTests { - private Plane TestPlaneCounterClockwise - { - get + private Plane TestPlaneCounterClockwise => + new() { - const string UNITS = Units.Meters; - return new() - { - origin = new Point(0, 0, 0, UNITS), - normal = new Vector(0, 0, 1, UNITS), - xdir = new Vector(1, 0, 0, UNITS), - ydir = new Vector(0, 1, 0, UNITS), - units = UNITS, - }; - } - } + origin = new Point(0, 0, 0, Units.Meters), + normal = new Vector(0, 0, 1, Units.Meters), + xdir = new Vector(1, 0, 0, Units.Meters), + ydir = new Vector(0, 1, 0, Units.Meters), + units = Units.Meters, + }; - private Plane TestPlaneClockwise - { - get + private Plane TestPlaneClockwise => + new() { - const string UNITS = Units.Meters; - return new() - { - origin = new Point(0, 0, 0, UNITS), - normal = new Vector(0, 0, -1, UNITS), - xdir = new Vector(-1, 0, 0, UNITS), - ydir = new Vector(0, 1, 0, UNITS), - units = UNITS, - }; - } - } + origin = new Point(0, 0, 0, Units.Meters), + normal = new Vector(0, 0, -1, Units.Meters), + xdir = new Vector(-1, 0, 0, Units.Meters), + ydir = new Vector(0, 1, 0, Units.Meters), + units = Units.Meters, + }; - [Test] + [Fact] public void CanCreateArc_HalfCircle_CounterClockwise() { - const string UNITS = Units.Meters; var counterClockwiseArc = new Arc() { plane = TestPlaneCounterClockwise, - startPoint = new Point(1, 0, 0, UNITS), - endPoint = new Point(-1, 0, 0, UNITS), - midPoint = new Point(0, 1, 0, UNITS), - units = UNITS, + startPoint = new Point(1, 0, 0, Units.Meters), + endPoint = new Point(-1, 0, 0, Units.Meters), + midPoint = new Point(0, 1, 0, Units.Meters), + units = Units.Meters, }; - Assert.That(Point.Distance(counterClockwiseArc.midPoint, new Point(0, 1, 0, UNITS)), Is.EqualTo(0).Within(0.0001)); - Assert.That( - Point.Distance(counterClockwiseArc.plane.origin, new Point(0, 0, 0, UNITS)), - Is.EqualTo(0).Within(0.0001) - ); - Assert.That(counterClockwiseArc.measure - Math.PI, Is.EqualTo(0).Within(0.0001)); - Assert.That(counterClockwiseArc.radius, Is.EqualTo(1).Within(0.0001)); - Assert.That(counterClockwiseArc.length, Is.EqualTo(Math.PI).Within(0.0001)); + Point.Distance(counterClockwiseArc.midPoint, new Point(0, 1, 0, Units.Meters)).Should().BeApproximately(0, 0.0001); + + Point + .Distance(counterClockwiseArc.plane.origin, new Point(0, 0, 0, Units.Meters)) + .Should() + .BeApproximately(0, 0.0001); + + (counterClockwiseArc.measure - Math.PI).Should().BeApproximately(0, 0.0001); + + counterClockwiseArc.radius.Should().BeApproximately(1, 0.0001); + + counterClockwiseArc.length.Should().BeApproximately(Math.PI, 0.0001); } - [Test] + [Fact] public void CanCreateArc_HalfCircle_Clockwise() { - const string UNITS = Units.Meters; - var counterClockwiseArc = new Arc() + var clockwiseArc = new Arc() { plane = TestPlaneClockwise, - endPoint = new Point(1, 0, 0, UNITS), - startPoint = new Point(-1, 0, 0, UNITS), - midPoint = new Point(0, 1, 0, UNITS), - units = UNITS, + endPoint = new Point(1, 0, 0, Units.Meters), + startPoint = new Point(-1, 0, 0, Units.Meters), + midPoint = new Point(0, 1, 0, Units.Meters), + units = Units.Meters, }; - Assert.That(Point.Distance(counterClockwiseArc.midPoint, new Point(0, 1, 0, UNITS)), Is.EqualTo(0).Within(0.0001)); - Assert.That( - Point.Distance(counterClockwiseArc.plane.origin, new Point(0, 0, 0, UNITS)), - Is.EqualTo(0).Within(0.0001) - ); - Assert.That(counterClockwiseArc.measure - Math.PI, Is.EqualTo(0).Within(0.0001)); - Assert.That(counterClockwiseArc.radius, Is.EqualTo(1).Within(0.0001)); - Assert.That(counterClockwiseArc.length, Is.EqualTo(Math.PI).Within(0.0001)); + Point.Distance(clockwiseArc.midPoint, new Point(0, 1, 0, Units.Meters)).Should().BeApproximately(0, 0.0001); + + Point.Distance(clockwiseArc.plane.origin, new Point(0, 0, 0, Units.Meters)).Should().BeApproximately(0, 0.0001); + + (clockwiseArc.measure - Math.PI).Should().BeApproximately(0, 0.0001); + + clockwiseArc.radius.Should().BeApproximately(1, 0.0001); + + clockwiseArc.length.Should().BeApproximately(Math.PI, 0.0001); } } diff --git a/tests/Speckle.Objects.Tests.Unit/Geometry/BoxTests.cs b/tests/Speckle.Objects.Tests.Unit/Geometry/BoxTests.cs index 05a83663..79a73133 100644 --- a/tests/Speckle.Objects.Tests.Unit/Geometry/BoxTests.cs +++ b/tests/Speckle.Objects.Tests.Unit/Geometry/BoxTests.cs @@ -1,29 +1,23 @@ -using NUnit.Framework; +using FluentAssertions; using Speckle.Objects.Geometry; using Speckle.Sdk.Common; +using Xunit; namespace Speckle.Objects.Tests.Unit.Geometry; -[TestFixture, TestOf(typeof(Box))] public class BoxTests { - private Plane TestPlane - { - get + private Plane TestPlane => + new() { - const string UNITS = Units.Meters; - return new() - { - origin = new Point(0, 0, 0, UNITS), - normal = new Vector(0, 0, 1, UNITS), - xdir = new Vector(1, 0, 0, UNITS), - ydir = new Vector(0, 1, 0, UNITS), - units = UNITS, - }; - } - } + origin = new Point(0, 0, 0, Units.Meters), + normal = new Vector(0, 0, 1, Units.Meters), + xdir = new Vector(1, 0, 0, Units.Meters), + ydir = new Vector(0, 1, 0, Units.Meters), + units = Units.Meters, + }; - [Test] + [Fact] public void CanCreateBox() { const string UNITS = Units.Meters; @@ -36,7 +30,10 @@ public void CanCreateBox() units = UNITS, }; - Assert.That(box.area, Is.EqualTo(2 * (2 * 4 + 2 * 6 + 4 * 6)).Within(0.0001)); - Assert.That(box.volume, Is.EqualTo(2 * 4 * 6).Within(0.0001)); + // Assert area + box.area.Should().BeApproximately(2 * (2 * 4 + 2 * 6 + 4 * 6), 0.0001); + + // Assert volume + box.volume.Should().BeApproximately(2 * 4 * 6, 0.0001); } } diff --git a/tests/Speckle.Objects.Tests.Unit/Geometry/CircleTests.cs b/tests/Speckle.Objects.Tests.Unit/Geometry/CircleTests.cs index 12501ce8..07bfe533 100644 --- a/tests/Speckle.Objects.Tests.Unit/Geometry/CircleTests.cs +++ b/tests/Speckle.Objects.Tests.Unit/Geometry/CircleTests.cs @@ -1,10 +1,10 @@ -using NUnit.Framework; +using FluentAssertions; using Speckle.Objects.Geometry; using Speckle.Sdk.Common; +using Xunit; namespace Speckle.Objects.Tests.Unit.Geometry; -[TestFixture, TestOf(typeof(Circle))] public class CircleTests { private Plane TestPlane @@ -12,7 +12,7 @@ private Plane TestPlane get { const string UNITS = Units.Meters; - return new() + return new Plane { origin = new Point(0, 0, 0, UNITS), normal = new Vector(0, 0, 1, UNITS), @@ -23,18 +23,19 @@ private Plane TestPlane } } - [Test] + [Fact] public void CanCreateCircle() { const string UNITS = Units.Meters; - var circle = new Circle() + var circle = new Circle { plane = TestPlane, radius = 5, units = UNITS, }; - Assert.That(circle.length, Is.EqualTo(2 * Math.PI * 5).Within(0.0001)); - Assert.That(circle.area, Is.EqualTo(Math.PI * 5 * 5).Within(0.0001)); + // Use Shouldly assertions + circle.length.Should().BeApproximately(2 * Math.PI * 5, 0.0001); + circle.area.Should().BeApproximately(Math.PI * 5 * 5, 0.0001); } } diff --git a/tests/Speckle.Objects.Tests.Unit/Geometry/MeshTests.cs b/tests/Speckle.Objects.Tests.Unit/Geometry/MeshTests.cs index dae8dca6..bb5c9f6d 100644 --- a/tests/Speckle.Objects.Tests.Unit/Geometry/MeshTests.cs +++ b/tests/Speckle.Objects.Tests.Unit/Geometry/MeshTests.cs @@ -1,35 +1,38 @@ -using NUnit.Framework; +using FluentAssertions; using Speckle.Objects.Geometry; using Speckle.Sdk.Common; +using Xunit; namespace Speckle.Objects.Tests.Unit.Geometry; -[TestFixture, TestOf(typeof(Mesh))] public class MeshTests { - private static Mesh[] s_testCaseSource = { CreateBlenderStylePolygon(), CreateRhinoStylePolygon() }; + private static readonly Mesh[] TestCaseSource = { CreateBlenderStylePolygon(), CreateRhinoStylePolygon() }; - [Test, TestCaseSource(nameof(s_testCaseSource))] + [Theory] + [MemberData(nameof(GetTestCaseSource))] public void CanAlignVertices(Mesh inPolygon) { inPolygon.AlignVerticesWithTexCoordsByIndex(); - Assert.That(inPolygon.VerticesCount, Is.EqualTo(inPolygon.TextureCoordinatesCount)); + inPolygon.VerticesCount.Should().Be(inPolygon.TextureCoordinatesCount); var expectedPolygon = CreateRhinoStylePolygon(); - Assert.That(inPolygon.vertices, Is.EquivalentTo(expectedPolygon.vertices)); - Assert.That(inPolygon.faces, Is.EquivalentTo(expectedPolygon.faces)); - Assert.That(inPolygon.textureCoordinates, Is.EquivalentTo(expectedPolygon.textureCoordinates)); + inPolygon.vertices.Should().BeEquivalentTo(expectedPolygon.vertices); + inPolygon.faces.Should().BeEquivalentTo(expectedPolygon.faces); + inPolygon.textureCoordinates.Should().BeEquivalentTo(expectedPolygon.textureCoordinates); } + public static IEnumerable GetTestCaseSource() => TestCaseSource.Select(mesh => new object[] { mesh }); + private static Mesh CreateRhinoStylePolygon() { return new Mesh { - vertices = [0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0], - faces = [3, 0, 1, 2, 3, 3, 4, 5], - textureCoordinates = [0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0], + vertices = new List { 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0 }, + faces = new List { 3, 0, 1, 2, 3, 3, 4, 5 }, + textureCoordinates = new List { 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0 }, units = Units.Meters, }; } @@ -38,9 +41,9 @@ private static Mesh CreateBlenderStylePolygon() { return new Mesh { - vertices = [0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0], - faces = [3, 0, 1, 2, 3, 0, 2, 3], - textureCoordinates = [0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0], + vertices = new List { 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0 }, + faces = new List { 3, 0, 1, 2, 3, 0, 2, 3 }, + textureCoordinates = new List { 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0 }, units = Units.Meters, }; } diff --git a/tests/Speckle.Objects.Tests.Unit/Geometry/PointTests.cs b/tests/Speckle.Objects.Tests.Unit/Geometry/PointTests.cs index 18f518ba..eabe68c6 100644 --- a/tests/Speckle.Objects.Tests.Unit/Geometry/PointTests.cs +++ b/tests/Speckle.Objects.Tests.Unit/Geometry/PointTests.cs @@ -1,18 +1,18 @@ using System.Diagnostics.CodeAnalysis; -using NUnit.Framework; +using FluentAssertions; using Speckle.Objects.Geometry; using Speckle.Sdk.Common; +using Xunit; namespace Speckle.Objects.Tests.Unit.Geometry; -[TestFixture, TestOf(typeof(Point))] public class PointTests { - [Test] + [Fact] [SuppressMessage( "Assertion", - "NUnit2010:Use EqualConstraint for better assertion messages in case of failure", - Justification = "Need to explicitly test equality operator" + "xUnit2013:Do not use equality check to assert boolean value", + Justification = "Explicit equality operator tests are necessary" )] public void TestNull() { @@ -20,35 +20,49 @@ public void TestNull() Point? b = null; Point c = new(0, 0, 0, Units.Meters); - Assert.Multiple(() => - { - Assert.That(a == b, Is.True); - Assert.That(a != b, Is.False); - Assert.That(b == a, Is.True); - Assert.That(b != a, Is.False); - - Assert.That(a == c, Is.False); - Assert.That(a != c, Is.True); - Assert.That(c == a, Is.False); - Assert.That(c != a, Is.True); - }); + a.Should().Be(b); + (a != b).Should().BeFalse(); + + b.Should().Be(a); + (b != a).Should().BeFalse(); + + (a == c).Should().BeFalse(); + (a != c).Should().BeTrue(); + + (c == a).Should().BeFalse(); + (c != a).Should().BeTrue(); } - [Test] - [TestCase(1, 1, 1, "m", 1, 1, 1, "m", ExpectedResult = true)] - [TestCase(1, 1, 1, "m", 0, 1, 1, "m", ExpectedResult = false)] - [TestCase(1, 1, 1, "m", 1, 0, 1, "m", ExpectedResult = false)] - [TestCase(1, 1, 1, "m", 1, 1, 0, "m", ExpectedResult = false)] - [TestCase(1, 1, 1, "", 1, 1, 1, "", ExpectedResult = true)] - [TestCase(1, 1, 1, null, 1, 1, 1, null, ExpectedResult = true)] - [TestCase(1, 1, 1, "m", 1, 1, 1, "meters", ExpectedResult = false)] - [TestCase(1, 1, 1, "m", 1, 1, 1, "M", ExpectedResult = false)] - // Units - public bool TestEqual(double x1, double y1, double z1, string units1, double x2, double y2, double z2, string units2) + //TODO: Should(). units be allowed to be string? + [Theory] + [InlineData(1, 1, 1, "m", 1, 1, 1, "m", true)] + [InlineData(1, 1, 1, "m", 0, 1, 1, "m", false)] + [InlineData(1, 1, 1, "m", 1, 0, 1, "m", false)] + [InlineData(1, 1, 1, "m", 1, 1, 0, "m", false)] + [InlineData(1, 1, 1, "", 1, 1, 1, "", false)] + [InlineData(1, 1, 1, null, 1, 1, 1, null, false)] + [InlineData(1, 1, 1, "m", 1, 1, 1, "meters", false)] + [InlineData(1, 1, 1, "m", 1, 1, 1, "M", false)] + public void TestEqual( + double x1, + double y1, + double z1, + string? units1, + double x2, + double y2, + double z2, + string? units2, + bool expectedResult + ) { + if (string.IsNullOrEmpty(units1) || string.IsNullOrEmpty(units2)) + { + expectedResult.Should().BeFalse(); + return; + } Point p1 = new(x1, y1, z1, units1); Point p2 = new(x2, y2, z2, units2); - return p1 == p2; + (p1 == p2).Should().Be(expectedResult); } } diff --git a/tests/Speckle.Objects.Tests.Unit/Geometry/TransformTests.cs b/tests/Speckle.Objects.Tests.Unit/Geometry/TransformTests.cs index 819d28a7..8581600c 100644 --- a/tests/Speckle.Objects.Tests.Unit/Geometry/TransformTests.cs +++ b/tests/Speckle.Objects.Tests.Unit/Geometry/TransformTests.cs @@ -1,100 +1,98 @@ -using System.Collections; -using NUnit.Framework; +using FluentAssertions; using Speckle.DoubleNumerics; using Speckle.Objects.Other; using Speckle.Sdk.Common; +using Xunit; namespace Speckle.Objects.Tests.Unit.Geometry; -[TestFixture, TestOf(typeof(Transform))] public class TransformTests { private const float FLOAT_TOLERANCE = 1e-6f; - [Test, TestCaseSource(nameof(TransformTestCases))] + [Theory] + [MemberData(nameof(TransformTestCases))] public void ArrayBackAndForth(Matrix4x4 data) { + // Arrange var start = new Transform() { matrix = data, units = Units.None }; + + // Act var asArr = Transform.CreateMatrix(start.ToArray()); var end = new Transform() { matrix = asArr, units = Units.None }; - Assert.That(end.matrix, Is.EqualTo(data)); + // Assert + end.matrix.Should().Be(data); } - [Test, TestCaseSource(nameof(TransformTestCases))] + [Theory] + [MemberData(nameof(TransformTestCases))] public void ConvertToUnits(Matrix4x4 data) { const float SF = 1000f; - var transpose = Matrix4x4.Transpose(data); //NOTE: Transform expects matrices transposed (translation in column 4) + // Arrange + var transpose = Matrix4x4.Transpose(data); // Transform expects matrices transposed (translation in column 4) var mm = Matrix4x4.Transpose( Transform.CreateMatrix( new Transform() { matrix = transpose, units = Units.Meters }.ConvertToUnits(Units.Millimeters) ) ); + // Act Matrix4x4.Decompose(data, out var ms, out var mr, out var mt); Matrix4x4.Decompose(mm, out var mms, out var mmr, out var mmt); - Assert.Multiple(() => - { - Assert.That(mms.X, Is.EqualTo(ms.X).Within(FLOAT_TOLERANCE), "Expect scale x to be unchanged"); - Assert.That(mms.Y, Is.EqualTo(ms.Y).Within(FLOAT_TOLERANCE), "Expect scale y to be unchanged"); - Assert.That(mms.Z, Is.EqualTo(ms.Z).Within(FLOAT_TOLERANCE), "Expect scale z to be unchanged"); + // Assert + mms.X.Should().BeApproximately(ms.X, FLOAT_TOLERANCE, "Expect scale x to be unchanged"); + mms.Y.Should().BeApproximately(ms.Y, FLOAT_TOLERANCE, "Expect scale y to be unchanged"); + mms.Z.Should().BeApproximately(ms.Z, FLOAT_TOLERANCE, "Expect scale z to be unchanged"); - Assert.That(Quaternion.Dot(mr, mmr), Is.LessThan(1).Within(FLOAT_TOLERANCE), "Expect rot x to be equivalent"); + Quaternion.Dot(mr, mmr).Should().BeLessThan(1 + FLOAT_TOLERANCE, "Expect rotation to be equivalent"); - Assert.That(mmt.X, Is.EqualTo(mt.X * SF).Within(FLOAT_TOLERANCE), $"Expect translation x to be scaled by {SF}"); - Assert.That(mmt.Y, Is.EqualTo(mt.Y * SF).Within(FLOAT_TOLERANCE), $"Expect translation y to be scaled by {SF}"); - Assert.That(mmt.Z, Is.EqualTo(mt.Z * SF).Within(FLOAT_TOLERANCE), $"Expect translation z to be scaled by {SF}"); - }); + mmt.X.Should().BeApproximately(mt.X * SF, FLOAT_TOLERANCE, $"Expect translation x to be scaled by {SF}"); + mmt.Y.Should().BeApproximately(mt.Y * SF, FLOAT_TOLERANCE, $"Expect translation y to be scaled by {SF}"); + mmt.Z.Should().BeApproximately(mt.Z * SF, FLOAT_TOLERANCE, $"Expect translation z to be scaled by {SF}"); } /// /// Set of TRS transforms (row dominant i.e. translation in row 4) /// All with non-negative scale and rotation (for ease of testing scale and rot independently) /// - /// - private static IEnumerable TransformTestCases() + public static IEnumerable TransformTestCases() { var t = new Vector3(128.128f, 255.255f, 512.512f); var r = Quaternion.CreateFromYawPitchRoll(1.9f, 0.6666667f, 0.5f); var s = new Vector3(123f, 32f, 0.5f); - yield return new TestCaseData(Matrix4x4.Identity).SetName("{m} Identity Matrix"); + yield return [Matrix4x4.Identity]; - yield return new TestCaseData(Matrix4x4.CreateTranslation(t)).SetName("{m} Translation Only (positive)"); + yield return [Matrix4x4.CreateTranslation(t)]; - yield return new TestCaseData(Matrix4x4.CreateTranslation(t * -Vector3.UnitX)).SetName("{m} Translation Only -X"); + yield return [Matrix4x4.CreateTranslation(t * -Vector3.UnitX)]; - yield return new TestCaseData(Matrix4x4.CreateTranslation(t * -Vector3.UnitY)).SetName("{m} Translation Only -Y"); + yield return [Matrix4x4.CreateTranslation(t * -Vector3.UnitY)]; - yield return new TestCaseData(Matrix4x4.CreateTranslation(t * -Vector3.UnitZ)).SetName("{m} Translation Only -Z"); + yield return [Matrix4x4.CreateTranslation(t * -Vector3.UnitZ)]; - yield return new TestCaseData(Matrix4x4.CreateTranslation(-t)).SetName("{m} Translation Only -XYZ "); + yield return [Matrix4x4.CreateTranslation(-t)]; - yield return new TestCaseData(Matrix4x4.CreateFromYawPitchRoll(0.5f, 0.0f, 0.0f)).SetName("{m} Rotation Only X "); + yield return [Matrix4x4.CreateFromYawPitchRoll(0.5f, 0.0f, 0.0f)]; - yield return new TestCaseData(Matrix4x4.CreateFromYawPitchRoll(0.0f, 0.5f, 0.0f)).SetName("{m} Rotation Only Y "); + yield return [Matrix4x4.CreateFromYawPitchRoll(0.0f, 0.5f, 0.0f)]; - yield return new TestCaseData(Matrix4x4.CreateFromYawPitchRoll(0.0f, 0.0f, 0.5f)).SetName("{m} Rotation Only Z "); + yield return [Matrix4x4.CreateFromYawPitchRoll(0.0f, 0.0f, 0.5f)]; - yield return new TestCaseData(Matrix4x4.CreateFromYawPitchRoll(0.5f, 0.5f, 0.5f)).SetName("{m} Rotation Only XYZ "); + yield return [Matrix4x4.CreateFromYawPitchRoll(0.5f, 0.5f, 0.5f)]; - yield return new TestCaseData(Matrix4x4.CreateFromQuaternion(r)).SetName("{m} Rotation Only"); + yield return [Matrix4x4.CreateFromQuaternion(r)]; - yield return new TestCaseData(Matrix4x4.Identity + Matrix4x4.CreateScale(s)).SetName("{m} Scale Only"); + yield return [Matrix4x4.Identity + Matrix4x4.CreateScale(s)]; - yield return new TestCaseData(Matrix4x4.CreateTranslation(t) + Matrix4x4.CreateFromQuaternion(r)).SetName( - "{m} Translation + Rotation" - ); + yield return [Matrix4x4.CreateTranslation(t) + Matrix4x4.CreateFromQuaternion(r)]; - yield return new TestCaseData( - Matrix4x4.CreateTranslation(t) + Matrix4x4.CreateFromQuaternion(r) + Matrix4x4.CreateScale(s) - ).SetName("{m} Translation + Rotation + Scale"); + yield return [Matrix4x4.CreateTranslation(t) + Matrix4x4.CreateFromQuaternion(r) + Matrix4x4.CreateScale(s)]; - yield return new TestCaseData( - Matrix4x4.CreateTranslation(t) + Matrix4x4.CreateFromQuaternion(r) + Matrix4x4.CreateScale(-s) - ).SetName("{m} Translation + Rotation + -Scale"); + yield return [Matrix4x4.CreateTranslation(t) + Matrix4x4.CreateFromQuaternion(r) + Matrix4x4.CreateScale(-s)]; } } diff --git a/tests/Speckle.Objects.Tests.Unit/ModelPropertySupportedTypes.cs b/tests/Speckle.Objects.Tests.Unit/ModelPropertySupportedTypes.cs index 7cb8639b..70287fc2 100644 --- a/tests/Speckle.Objects.Tests.Unit/ModelPropertySupportedTypes.cs +++ b/tests/Speckle.Objects.Tests.Unit/ModelPropertySupportedTypes.cs @@ -1,10 +1,11 @@ -using System.Drawing; -using NUnit.Framework; +using System.Drawing; +using FluentAssertions; using Speckle.DoubleNumerics; using Speckle.Newtonsoft.Json; using Speckle.Sdk.Host; using Speckle.Sdk.Models; using Speckle.Sdk.Serialisation; +using Xunit; namespace Speckle.Objects.Tests.Unit; @@ -15,8 +16,7 @@ namespace Speckle.Objects.Tests.Unit; ///
public class ModelPropertySupportedTypes { - [SetUp] - public void Setup() + public ModelPropertySupportedTypes() { TypeLoader.Reset(); TypeLoader.Initialize(typeof(Base).Assembly, typeof(Speckle.Objects.Geometry.Arc).Assembly); @@ -62,7 +62,7 @@ public void Setup() typeof(Matrix4x4), }; - [Test] + [Fact] public void TestObjects() { foreach ((string _, Type type, List _) in TypeLoader.Types) @@ -72,19 +72,24 @@ public void TestObjects() foreach (var prop in members) { if (prop.PropertyType.IsAssignableTo(typeof(Base))) + { continue; + } + if (prop.PropertyType.IsEnum) + { continue; + } + if (prop.PropertyType.IsSZArray) + { continue; + } Type propType = prop.PropertyType; Type typeDef = propType.IsGenericType ? propType.GetGenericTypeDefinition() : propType; - Assert.That( - _allowedTypes, - Does.Contain(typeDef), - $"{typeDef} was not in allowedTypes. (Origin: {type}.{prop.Name})" - ); + + _allowedTypes.Should().Contain(typeDef, $"{typeDef} was not in allowedTypes. (Origin: {type}.{prop.Name})"); } } } diff --git a/tests/Speckle.Objects.Tests.Unit/ObjectBaseValidityTests.cs b/tests/Speckle.Objects.Tests.Unit/ObjectBaseValidityTests.cs index 99c985e9..e3eb68db 100644 --- a/tests/Speckle.Objects.Tests.Unit/ObjectBaseValidityTests.cs +++ b/tests/Speckle.Objects.Tests.Unit/ObjectBaseValidityTests.cs @@ -1,23 +1,23 @@ -using NUnit.Framework; -using Shouldly; +using FluentAssertions; using Speckle.Objects.Geometry; using Speckle.Objects.Geometry.Autocad; using Speckle.Sdk.Host; using Speckle.Sdk.Models; +using Xunit; using Point = Speckle.Objects.Geometry.Point; namespace Speckle.Objects.Tests.Unit; public class ObjectBaseValidityTests { - [Test] + [Fact] public void TestThatTypeWithoutAttributeFails() { TypeLoader.Reset(); TypeLoader.Initialize(typeof(Base).Assembly, typeof(Point).Assembly); } - [Test] + [Fact] public void InheritanceTest_Disallow() { var exception = Assert.Throws(() => @@ -25,19 +25,19 @@ public void InheritanceTest_Disallow() TypeLoader.Reset(); TypeLoader.Initialize(typeof(Base).Assembly, typeof(Point).Assembly, typeof(Test).Assembly); }); - exception.ShouldNotBeNull(); - exception.Message.ShouldBe( - "Speckle.Objects.Tests.Unit.ObjectBaseValidityTests+Test inherits from Base has no SpeckleTypeAttribute" - ); + exception.Should().NotBeNull(); + exception + .Message.Should() + .Be("Speckle.Objects.Tests.Unit.ObjectBaseValidityTests+Test inherits from Base has no SpeckleTypeAttribute"); } - [Test] + [Fact] public void InheritanceTest_Allow() { TypeLoader.Reset(); TypeLoader.Initialize(typeof(Base).Assembly, typeof(Point).Assembly); var fullTypeString = TypeLoader.GetFullTypeString(typeof(AutocadPolycurve)); - fullTypeString.ShouldBe("Objects.Geometry.Polycurve:Objects.Geometry.Autocad.AutocadPolycurve"); + fullTypeString.Should().Be("Objects.Geometry.Polycurve:Objects.Geometry.Autocad.AutocadPolycurve"); } public class Test : Polycurve; diff --git a/tests/Speckle.Objects.Tests.Unit/Speckle.Objects.Tests.Unit.csproj b/tests/Speckle.Objects.Tests.Unit/Speckle.Objects.Tests.Unit.csproj index fb22cc63..ce38ab83 100644 --- a/tests/Speckle.Objects.Tests.Unit/Speckle.Objects.Tests.Unit.csproj +++ b/tests/Speckle.Objects.Tests.Unit/Speckle.Objects.Tests.Unit.csproj @@ -8,10 +8,10 @@ + - - - + + diff --git a/tests/Speckle.Objects.Tests.Unit/Utils/MeshTriangulationHelperTests.cs b/tests/Speckle.Objects.Tests.Unit/Utils/MeshTriangulationHelperTests.cs index c7582df0..2baaf8cf 100644 --- a/tests/Speckle.Objects.Tests.Unit/Utils/MeshTriangulationHelperTests.cs +++ b/tests/Speckle.Objects.Tests.Unit/Utils/MeshTriangulationHelperTests.cs @@ -1,17 +1,28 @@ -using NUnit.Framework; +using FluentAssertions; using Speckle.Objects.Geometry; using Speckle.Objects.Utils; using Speckle.Sdk.Common; +using Speckle.Sdk.Dependencies; +using Xunit; namespace Speckle.Objects.Tests.Unit.Utils; -[TestFixture, TestOf(typeof(MeshTriangulationHelper))] public class MeshTriangulationHelperTests { - [Test] - public void PolygonTest([Range(3, 9)] int n, [Values] bool planar) + public static IEnumerable PolygonTestSource() { - //Test Setup + foreach (var x in EnumerableExtensions.RangeFrom(3, 9)) + { + yield return new object[] { x, true }; + yield return new object[] { x, false }; + } + } + + [Theory] + [MemberData(nameof(PolygonTestSource))] + public void PolygonTest(int n, bool planar) + { + // Test Setup List vertices = new(n) { 0, planar ? 0 : 1, 1 }; for (int i = 1; i < n; i++) { @@ -30,30 +41,30 @@ public void PolygonTest([Range(3, 9)] int n, [Values] bool planar) units = Units.Meters, }; - //Test + // Test mesh.TriangulateMesh(); - //Results + // Results int numExpectedTriangles = n - 2; int expectedFaceCount = numExpectedTriangles * 4; - Assert.That(mesh.faces, Has.Count.EqualTo(expectedFaceCount)); + mesh.faces.Count.Should().Be(expectedFaceCount); + for (int i = 0; i < expectedFaceCount; i += 4) { - Assert.That(mesh.faces[i], Is.EqualTo(3)); - Assert.That(mesh.faces.GetRange(i + 1, 3), Is.Unique); + mesh.faces[i].Should().Be(3); + mesh.faces.GetRange(i + 1, 3).Should().OnlyHaveUniqueItems(); } - Assert.That(mesh.faces, Is.SupersetOf(Enumerable.Range(0, n))); - - Assert.That(mesh.faces, Is.All.GreaterThanOrEqualTo(0)); - Assert.That(mesh.faces, Is.All.LessThan(Math.Max(n, 4))); + var range = EnumerableExtensions.RangeFrom(0, n).ToList(); + mesh.faces.Should().BeSubsetOf(range); + mesh.faces.Should().AllSatisfy(x => x.Should().BeGreaterThanOrEqualTo(0).And.BeLessThan(Math.Max(n, 4))); } - [Test] + [Fact] public void DoesntFlipNormals() { - //Test Setup + // Test Setup List vertices = new() { 0, 0, 0, 1, 0, 0, 1, 0, 1 }; List faces = new() { 3, 0, 1, 2 }; @@ -65,22 +76,26 @@ public void DoesntFlipNormals() units = Units.Meters, }; - //Test + // Test mesh.TriangulateMesh(); - //Results - + // Results List shift1 = faces; List shift2 = new() { 3, 1, 2, 0 }; List shift3 = new() { 3, 2, 0, 1 }; - Assert.That(mesh.faces, Is.AnyOf(shift1, shift2, shift3)); + new List[] { shift1, shift2, shift3 } + .Any(x => mesh.faces.SequenceEqual(x)) + .Should() + .BeTrue(); } - [Test] - public void PreserveQuads([Values] bool preserveQuads) + [Theory] + [InlineData(true)] + [InlineData(false)] + public void PreserveQuads(bool preserveQuads) { - //Test Setup + // Test Setup List vertices = new() { 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1 }; List faces = new() { 4, 0, 1, 2, 3 }; @@ -92,14 +107,14 @@ public void PreserveQuads([Values] bool preserveQuads) units = Units.Meters, }; - //Tests + // Tests mesh.TriangulateMesh(preserveQuads); - //Results + // Results int expectedN = preserveQuads ? 4 : 3; int expectedFaceCount = preserveQuads ? 5 : 8; - Assert.That(mesh.faces, Has.Count.EqualTo(expectedFaceCount)); - Assert.That(mesh.faces[0], Is.EqualTo(expectedN)); + mesh.faces.Count.Should().Be(expectedFaceCount); + mesh.faces[0].Should().Be(expectedN); } } diff --git a/tests/Speckle.Objects.Tests.Unit/Utils/ShallowCopyTests.cs b/tests/Speckle.Objects.Tests.Unit/Utils/ShallowCopyTests.cs index 47ea8580..d27962e1 100644 --- a/tests/Speckle.Objects.Tests.Unit/Utils/ShallowCopyTests.cs +++ b/tests/Speckle.Objects.Tests.Unit/Utils/ShallowCopyTests.cs @@ -1,16 +1,23 @@ using System.Collections; -using NUnit.Framework; +using FluentAssertions; using Speckle.Objects.Data; using Speckle.Objects.Geometry; using Speckle.Sdk.Common; +using Speckle.Sdk.Host; using Speckle.Sdk.Models; +using Xunit; namespace Speckle.Objects.Tests.Unit.Utils; -[TestFixture] public class ShallowCopyTests { - [Test] + public ShallowCopyTests() + { + TypeLoader.Reset(); + TypeLoader.Initialize(typeof(Base).Assembly, typeof(Point).Assembly); + } + + [Fact] public void CanShallowCopy_Wall() { const string UNITS = Units.Meters; @@ -37,6 +44,6 @@ public void CanShallowCopy_Wall() var shallow = ds.ShallowCopy(); var displayValue = (IList)shallow["displayValue"].NotNull(); - Assert.That(ds.displayValue, Has.Count.EqualTo(displayValue.Count)); + ds.displayValue.Count.Should().Be(displayValue.Count); } } diff --git a/tests/Speckle.Objects.Tests.Unit/packages.lock.json b/tests/Speckle.Objects.Tests.Unit/packages.lock.json index fcd06d85..0e91de90 100644 --- a/tests/Speckle.Objects.Tests.Unit/packages.lock.json +++ b/tests/Speckle.Objects.Tests.Unit/packages.lock.json @@ -4,9 +4,18 @@ "net8.0": { "altcover": { "type": "Direct", - "requested": "[8.9.3, )", - "resolved": "8.9.3", - "contentHash": "auKC+pDCkLjfhFkSRaAUBu25BOmlLSqucR7YBs/Lkbdc0XRuJoklWafs1KKp+M+VoJ1f0TeMS6B/FO5IeIcu7w==" + "requested": "[9.0.1, )", + "resolved": "9.0.1", + "contentHash": "aadciFNDT5bnylaYUkKal+s5hF7yU/lmZxImQWAlk1438iPqK1Uf79H5ylELpyLIU49HL5ql+tnWBihp3WVLCA==" + }, + "FluentAssertions": { + "type": "Direct", + "requested": "[7.0.0, )", + "resolved": "7.0.0", + "contentHash": "mTLbcU991EQ1SEmNbVBaGGGJy0YFzvGd1sYJGNZ07nlPKuyHSn1I22aeKzqQXgEiaKyRO6MSCto9eN9VxMwBdA==", + "dependencies": { + "System.Configuration.ConfigurationManager": "6.0.0" + } }, "GitVersion.MsBuild": { "type": "Direct", @@ -16,12 +25,12 @@ }, "Microsoft.NET.Test.Sdk": { "type": "Direct", - "requested": "[17.11.1, )", - "resolved": "17.11.1", - "contentHash": "U3Ty4BaGoEu+T2bwSko9tWqWUOU16WzSFkq6U8zve75oRBMSLTBdMAZrVNNz1Tq12aCdDom9fcOcM9QZaFHqFg==", + "requested": "[17.12.0, )", + "resolved": "17.12.0", + "contentHash": "kt/PKBZ91rFCWxVIJZSgVLk+YR+4KxTuHf799ho8WNiK5ZQpJNAEZCAWX86vcKrs+DiYjiibpYKdGZP6+/N17w==", "dependencies": { - "Microsoft.CodeCoverage": "17.11.1", - "Microsoft.TestPlatform.TestHost": "17.11.1" + "Microsoft.CodeCoverage": "17.12.0", + "Microsoft.TestPlatform.TestHost": "17.12.0" } }, "Microsoft.SourceLink.GitHub": { @@ -34,53 +43,34 @@ "Microsoft.SourceLink.Common": "8.0.0" } }, - "NUnit": { - "type": "Direct", - "requested": "[4.2.2, )", - "resolved": "4.2.2", - "contentHash": "mon0OPko28yZ/foVXrhiUvq1LReaGsBdziumyyYGxV/pOE4q92fuYeN+AF+gEU5pCjzykcdBt5l7xobTaiBjsg==" - }, - "NUnit3TestAdapter": { - "type": "Direct", - "requested": "[4.6.0, )", - "resolved": "4.6.0", - "contentHash": "R7e1+a4vuV/YS+ItfL7f//rG+JBvVeVLX4mHzFEZo4W1qEKl8Zz27AqvQSAqo+BtIzUCo4aAJMYa56VXS4hudw==" - }, "PolySharp": { "type": "Direct", "requested": "[1.15.0, )", "resolved": "1.15.0", "contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g==" }, - "Shouldly": { - "type": "Direct", - "requested": "[4.2.1, )", - "resolved": "4.2.1", - "contentHash": "dKAKiSuhLKqD2TXwLKtqNg1nwzJcIKOOMncZjk9LYe4W+h+SCftpWdxwR79YZUIHMH+3Vu9s0s0UHNrgICLwRQ==", - "dependencies": { - "DiffEngine": "11.3.0", - "EmptyFiles": "4.4.0" - } - }, "Speckle.InterfaceGenerator": { "type": "Direct", "requested": "[0.9.6, )", "resolved": "0.9.6", "contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w==" }, - "DiffEngine": { - "type": "Transitive", - "resolved": "11.3.0", - "contentHash": "k0ZgZqd09jLZQjR8FyQbSQE86Q7QZnjEzq1LPHtj1R2AoWO8sjV5x+jlSisL7NZAbUOI4y+7Bog8gkr9WIRBGw==", + "xunit": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==", "dependencies": { - "EmptyFiles": "4.4.0", - "System.Management": "6.0.1" + "xunit.analyzers": "1.18.0", + "xunit.assert": "2.9.3", + "xunit.core": "[2.9.3]" } }, - "EmptyFiles": { - "type": "Transitive", - "resolved": "4.4.0", - "contentHash": "gwJEfIGS7FhykvtZoscwXj/XwW+mJY6UbAZk+qtLKFUGWC95kfKXnj8VkxsZQnWBxJemM/q664rGLN5nf+OHZw==" + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.0.0, )", + "resolved": "3.0.0", + "contentHash": "HggUqjQJe8PtDxcP25Q+CnR6Lz4oX3GElhD9V4oU2+75x9HI6A6sxbfKGS4UwU4t4yJaS9fBmAuriz8bQApNjw==" }, "GraphQL.Client.Abstractions": { "type": "Transitive", @@ -110,8 +100,8 @@ }, "Microsoft.CodeCoverage": { "type": "Transitive", - "resolved": "17.11.1", - "contentHash": "nPJqrcA5iX+Y0kqoT3a+pD/8lrW/V7ayqnEJQsTonSoPz59J8bmoQhcSN4G8+UJ64Hkuf0zuxnfuj2lkHOq4cA==" + "resolved": "17.12.0", + "contentHash": "4svMznBd5JM21JIG2xZKGNanAHNXplxf/kQDFfLHXQ3OnpJkayRK/TjacFjA+EYmoyuNXHo/sOETEfcYtAzIrA==" }, "Microsoft.Data.Sqlite.Core": { "type": "Transitive", @@ -176,21 +166,26 @@ }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", - "resolved": "17.11.1", - "contentHash": "E2jZqAU6JeWEVsyOEOrSW1o1bpHLgb25ypvKNB/moBXPVsFYBPd/Jwi7OrYahG50J83LfHzezYI+GaEkpAotiA==", + "resolved": "17.12.0", + "contentHash": "TDqkTKLfQuAaPcEb3pDDWnh7b3SyZF+/W9OZvWFp6eJCIiiYFdSB6taE2I6tWrFw5ywhzOb6sreoGJTI6m3rSQ==", "dependencies": { "System.Reflection.Metadata": "1.6.0" } }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", - "resolved": "17.11.1", - "contentHash": "DnG+GOqJXO/CkoqlJWeDFTgPhqD/V6VqUIL3vINizCWZ3X+HshCtbbyDdSHQQEjrc2Sl/K3yaxX6s+5LFEdYuw==", + "resolved": "17.12.0", + "contentHash": "MiPEJQNyADfwZ4pJNpQex+t9/jOClBGMiCiVVFuELCMSX2nmNfvUor3uFVxNNCg30uxDP8JDYfPnMXQzsfzYyg==", "dependencies": { - "Microsoft.TestPlatform.ObjectModel": "17.11.1", + "Microsoft.TestPlatform.ObjectModel": "17.12.0", "Newtonsoft.Json": "13.0.1" } }, + "Microsoft.Win32.SystemEvents": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A==" + }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "13.0.1", @@ -226,22 +221,26 @@ "SQLitePCLRaw.core": "2.1.4" } }, - "System.CodeDom": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA==" - }, "System.ComponentModel.Annotations": { "type": "Transitive", "resolved": "4.5.0", "contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg==" }, - "System.Management": { + "System.Configuration.ConfigurationManager": { "type": "Transitive", - "resolved": "6.0.1", - "contentHash": "10J1D0h/lioojphfJ4Fuh5ZUThT/xOVHdV9roGBittKKNP2PMjrvibEdbVTGZcPra1399Ja3tqIJLyQrc5Wmhg==", + "resolved": "6.0.0", + "contentHash": "7T+m0kDSlIPTHIkPMIu6m6tV6qsMqJpvQWW2jIc2qi7sn40qxFo0q+7mEQAhMPXZHMKnWrnv47ntGlM/ejvw3g==", "dependencies": { - "System.CodeDom": "6.0.0" + "System.Security.Cryptography.ProtectedData": "6.0.0", + "System.Security.Permissions": "6.0.0" + } + }, + "System.Drawing.Common": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "NfuoKUiP2nUWwKZN6twGqXioIe1zVD0RIj2t976A+czLHr2nY454RwwXs6JU9Htc6mwqL6Dn/nEL3dpVf2jOhg==", + "dependencies": { + "Microsoft.Win32.SystemEvents": "6.0.0" } }, "System.Memory": { @@ -264,6 +263,73 @@ "resolved": "4.5.1", "contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw==" }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ==" + }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "rp1gMNEZpvx9vP0JW0oHLxlf8oSiQgtno77Y4PLUBjSiDYoD77Y8uXHr1Ea5XG4/pIKhqAdxZ8v8OTUtqo9PeQ==" + }, + "System.Security.Permissions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "T/uuc7AklkDoxmcJ7LGkyX1CcSviZuLCa4jg3PekfJ7SU0niF0IVTXwUiNVP9DSpzou2PpxJ+eNY2IfDM90ZCg==", + "dependencies": { + "System.Security.AccessControl": "6.0.0", + "System.Windows.Extensions": "6.0.0" + } + }, + "System.Windows.Extensions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "IXoJOXIqc39AIe+CIR7koBtRGMiCt/LPM3lI+PELtDIy9XdyeSrwXFdWV9dzJ2Awl0paLWUaknLxFQ5HpHZUog==", + "dependencies": { + "System.Drawing.Common": "6.0.0" + } + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.18.0", + "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==" + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]", + "xunit.extensibility.execution": "[2.9.3]" + } + }, + "xunit.extensibility.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==", + "dependencies": { + "xunit.abstractions": "2.0.3" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]" + } + }, "speckle.objects": { "type": "Project", "dependencies": { diff --git a/tests/Speckle.Sdk.Serialization.Tests/Assembly.cs b/tests/Speckle.Sdk.Serialization.Tests/Assembly.cs new file mode 100644 index 00000000..a4bcec54 --- /dev/null +++ b/tests/Speckle.Sdk.Serialization.Tests/Assembly.cs @@ -0,0 +1,3 @@ +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/tests/Speckle.Sdk.Serialization.Tests/BaseComparer.cs b/tests/Speckle.Sdk.Serialization.Tests/BaseComparer.cs index 6630f219..a7db9618 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/BaseComparer.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/BaseComparer.cs @@ -7,19 +7,34 @@ public class BaseComparer : IEqualityComparer public bool Equals(Base? x, Base? y) { if (ReferenceEquals(x, y)) + { return true; + } + if (x is null) + { return false; + } + if (y is null) + { return false; + } + Type type = x.GetType(); if (type != y.GetType()) + { return false; + } + var types = DynamicBaseMemberType.Instance | DynamicBaseMemberType.Dynamic | DynamicBaseMemberType.SchemaIgnored; var membersX = x.GetMembers(types); var membersY = y.GetMembers(types); if (membersX.Count != membersY.Count) + { return false; + } + foreach (var kvp in membersX) { var propertyInfo = type.GetProperty(kvp.Key); @@ -28,7 +43,9 @@ public bool Equals(Base? x, Base? y) continue; } if (y[kvp.Key] != kvp.Value) + { return false; + } } return x.id == y.id && x.applicationId == y.applicationId; } diff --git a/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs b/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs index 0448cb4a..28bc847f 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/DetachedTests.cs @@ -1,8 +1,6 @@ using System.Collections.Concurrent; using System.Text; -using NUnit.Framework; -using Shouldly; -using Speckle.Newtonsoft.Json; +using FluentAssertions; using Speckle.Newtonsoft.Json.Linq; using Speckle.Objects.Geometry; using Speckle.Sdk.Host; @@ -12,19 +10,19 @@ using Speckle.Sdk.Serialisation.V2.Send; using Speckle.Sdk.SQLite; using Speckle.Sdk.Transports; +using Xunit; namespace Speckle.Sdk.Serialization.Tests; public class DetachedTests { - [SetUp] - public void Setup() + public DetachedTests() { TypeLoader.Reset(); TypeLoader.Initialize(typeof(Base).Assembly, typeof(DetachedTests).Assembly, typeof(Polyline).Assembly); } - [Test(Description = "Checks that all typed properties (including obsolete ones) are returned")] + [Fact(DisplayName = "Checks that all typed properties (including obsolete ones) are returned")] public async Task CanSerialize_New_Detached() { var expectedJson = """ @@ -75,20 +73,22 @@ public async Task CanSerialize_New_Detached() new ObjectSerializerFactory(new BasePropertyGatherer()), new SerializeProcessOptions(false, false, true, true) ); - await process2.Serialize(@base, default).ConfigureAwait(false); + await process2.Serialize(@base, default); - objects.Count.ShouldBe(2); - objects.ContainsKey("9ff8efb13c62fa80f3d1c4519376ba13").ShouldBeTrue(); - objects.ContainsKey("d3dd4621b2f68c3058c2b9c023a9de19").ShouldBeTrue(); + objects.Count.Should().Be(2); + objects.ContainsKey("9ff8efb13c62fa80f3d1c4519376ba13").Should().BeTrue(); + objects.ContainsKey("d3dd4621b2f68c3058c2b9c023a9de19").Should().BeTrue(); JToken .DeepEquals(JObject.Parse(expectedJson), JObject.Parse(objects["9ff8efb13c62fa80f3d1c4519376ba13"])) - .ShouldBeTrue(); + .Should() + .BeTrue(); JToken .DeepEquals(JObject.Parse(detachedJson), JObject.Parse(objects["d3dd4621b2f68c3058c2b9c023a9de19"])) - .ShouldBeTrue(); + .Should() + .BeTrue(); } - [Test(Description = "Checks that all typed properties (including obsolete ones) are returned")] + [Fact(DisplayName = "Checks that all typed properties (including obsolete ones) are returned")] public void CanSerialize_Old_Detached() { var expectedJson = """ @@ -133,19 +133,24 @@ public void CanSerialize_Old_Detached() var serializer = new SpeckleObjectSerializer(new[] { new MemoryTransport(objects) }); var json = serializer.Serialize(@base); - objects.Count.ShouldBe(2); - objects.ContainsKey("9ff8efb13c62fa80f3d1c4519376ba13").ShouldBeTrue(); - objects.ContainsKey("d3dd4621b2f68c3058c2b9c023a9de19").ShouldBeTrue(); - JToken.DeepEquals(JObject.Parse(json), JObject.Parse(objects["9ff8efb13c62fa80f3d1c4519376ba13"])).ShouldBeTrue(); + objects.Count.Should().Be(2); + objects.ContainsKey("9ff8efb13c62fa80f3d1c4519376ba13").Should().BeTrue(); + objects.ContainsKey("d3dd4621b2f68c3058c2b9c023a9de19").Should().BeTrue(); + JToken + .DeepEquals(JObject.Parse(json), JObject.Parse(objects["9ff8efb13c62fa80f3d1c4519376ba13"])) + .Should() + .BeTrue(); JToken .DeepEquals(JObject.Parse(expectedJson), JObject.Parse(objects["9ff8efb13c62fa80f3d1c4519376ba13"])) - .ShouldBeTrue(); + .Should() + .BeTrue(); JToken .DeepEquals(JObject.Parse(detachedJson), JObject.Parse(objects["d3dd4621b2f68c3058c2b9c023a9de19"])) - .ShouldBeTrue(); + .Should() + .BeTrue(); } - [Test] + [Fact] public void GetPropertiesExpected_Detached() { var @base = new SampleObjectBase(); @@ -157,14 +162,14 @@ public void GetPropertiesExpected_Detached() var children = new BaseChildFinder(new BasePropertyGatherer()).GetChildProperties(@base).ToList(); - children.Count.ShouldBe(4); - children.First(x => x.Name == "detachedProp").PropertyAttributeInfo.IsDetachable.ShouldBeTrue(); - children.First(x => x.Name == "list").PropertyAttributeInfo.IsDetachable.ShouldBeTrue(); - children.First(x => x.Name == "arr").PropertyAttributeInfo.IsDetachable.ShouldBeTrue(); - children.First(x => x.Name == "@prop2").PropertyAttributeInfo.IsDetachable.ShouldBeTrue(); + children.Count.Should().Be(4); + children.First(x => x.Name == "detachedProp").PropertyAttributeInfo.IsDetachable.Should().BeTrue(); + children.First(x => x.Name == "list").PropertyAttributeInfo.IsDetachable.Should().BeTrue(); + children.First(x => x.Name == "arr").PropertyAttributeInfo.IsDetachable.Should().BeTrue(); + children.First(x => x.Name == "@prop2").PropertyAttributeInfo.IsDetachable.Should().BeTrue(); } - [Test] + [Fact] public void GetPropertiesExpected_All() { var @base = new SampleObjectBase(); @@ -176,20 +181,20 @@ public void GetPropertiesExpected_All() var children = new BasePropertyGatherer().ExtractAllProperties(@base).ToList(); - children.Count.ShouldBe(9); - children.First(x => x.Name == "dynamicProp").PropertyAttributeInfo.IsDetachable.ShouldBeFalse(); - children.First(x => x.Name == "attachedProp").PropertyAttributeInfo.IsDetachable.ShouldBeFalse(); - children.First(x => x.Name == "crazyProp").PropertyAttributeInfo.IsDetachable.ShouldBeFalse(); - children.First(x => x.Name == "speckle_type").PropertyAttributeInfo.IsDetachable.ShouldBeFalse(); - children.First(x => x.Name == "applicationId").PropertyAttributeInfo.IsDetachable.ShouldBeFalse(); - - children.First(x => x.Name == "detachedProp").PropertyAttributeInfo.IsDetachable.ShouldBeTrue(); - children.First(x => x.Name == "list").PropertyAttributeInfo.IsDetachable.ShouldBeTrue(); - children.First(x => x.Name == "arr").PropertyAttributeInfo.IsDetachable.ShouldBeTrue(); - children.First(x => x.Name == "@prop2").PropertyAttributeInfo.IsDetachable.ShouldBeTrue(); + children.Count.Should().Be(9); + children.First(x => x.Name == "dynamicProp").PropertyAttributeInfo.IsDetachable.Should().BeFalse(); + children.First(x => x.Name == "attachedProp").PropertyAttributeInfo.IsDetachable.Should().BeFalse(); + children.First(x => x.Name == "crazyProp").PropertyAttributeInfo.IsDetachable.Should().BeFalse(); + children.First(x => x.Name == "speckle_type").PropertyAttributeInfo.IsDetachable.Should().BeFalse(); + children.First(x => x.Name == "applicationId").PropertyAttributeInfo.IsDetachable.Should().BeFalse(); + + children.First(x => x.Name == "detachedProp").PropertyAttributeInfo.IsDetachable.Should().BeTrue(); + children.First(x => x.Name == "list").PropertyAttributeInfo.IsDetachable.Should().BeTrue(); + children.First(x => x.Name == "arr").PropertyAttributeInfo.IsDetachable.Should().BeTrue(); + children.First(x => x.Name == "@prop2").PropertyAttributeInfo.IsDetachable.Should().BeTrue(); } - [Test(Description = "Checks that all typed properties (including obsolete ones) are returned")] + [Fact(DisplayName = "Checks that all typed properties (including obsolete ones) are returned")] public async Task CanSerialize_New_Detached2() { var root = """ @@ -267,17 +272,17 @@ public async Task CanSerialize_New_Detached2() new ObjectSerializerFactory(new BasePropertyGatherer()), new SerializeProcessOptions(false, false, true, true) ); - var results = await process2.Serialize(@base, default).ConfigureAwait(false); + var results = await process2.Serialize(@base, default); - objects.Count.ShouldBe(9); + objects.Count.Should().Be(9); var x = JObject.Parse(objects["2ebfd4f317754fce14cadd001151441e"]); - JToken.DeepEquals(JObject.Parse(root), x).ShouldBeTrue(); + JToken.DeepEquals(JObject.Parse(root), x).Should().BeTrue(); - results.RootId.ShouldBe(@base.id); - results.ConvertedReferences.Count.ShouldBe(2); + results.RootId.Should().Be(@base.id); + results.ConvertedReferences.Count.Should().Be(2); } - [Test(Description = "Checks that all typed properties (including obsolete ones) are returned")] + [Fact(DisplayName = "Checks that all typed properties (including obsolete ones) are returned")] public async Task CanSerialize_New_Detached_With_DataChunks() { var root = """ @@ -340,20 +345,20 @@ public async Task CanSerialize_New_Detached_With_DataChunks() new ObjectSerializerFactory(new BasePropertyGatherer()), new SerializeProcessOptions(false, false, true, true) ); - var results = await process2.Serialize(@base, default).ConfigureAwait(false); + var results = await process2.Serialize(@base, default); - objects.Count.ShouldBe(3); + objects.Count.Should().Be(3); var x = JObject.Parse(objects["efeadaca70a85ae6d3acfc93a8b380db"]); - JToken.DeepEquals(JObject.Parse(root), x).ShouldBeTrue(); + JToken.DeepEquals(JObject.Parse(root), x).Should().BeTrue(); x = JObject.Parse(objects["0e61e61edee00404ec6e0f9f594bce24"]); - JToken.DeepEquals(JObject.Parse(list1), x).ShouldBeTrue(); + JToken.DeepEquals(JObject.Parse(list1), x).Should().BeTrue(); x = JObject.Parse(objects["f70738e3e3e593ac11099a6ed6b71154"]); - JToken.DeepEquals(JObject.Parse(list2), x).ShouldBeTrue(); + JToken.DeepEquals(JObject.Parse(list2), x).Should().BeTrue(); } - [Test(Description = "Checks that all typed properties (including obsolete ones) are returned")] + [Fact(DisplayName = "Checks that all typed properties (including obsolete ones) are returned")] public async Task CanSerialize_New_Detached_With_DataChunks2() { var root = """ @@ -421,17 +426,17 @@ public async Task CanSerialize_New_Detached_With_DataChunks2() new ObjectSerializerFactory(new BasePropertyGatherer()), new SerializeProcessOptions(false, false, true, true) ); - var results = await process2.Serialize(@base, default).ConfigureAwait(false); + var results = await process2.Serialize(@base, default); - objects.Count.ShouldBe(3); + objects.Count.Should().Be(3); var x = JObject.Parse(objects["525b1e9eef4d07165abb4ffc518395fc"]); - JToken.DeepEquals(JObject.Parse(root), x).ShouldBeTrue(); + JToken.DeepEquals(JObject.Parse(root), x).Should().BeTrue(); x = JObject.Parse(objects["0e61e61edee00404ec6e0f9f594bce24"]); - JToken.DeepEquals(JObject.Parse(list1), x).ShouldBeTrue(); + JToken.DeepEquals(JObject.Parse(list1), x).Should().BeTrue(); x = JObject.Parse(objects["f70738e3e3e593ac11099a6ed6b71154"]); - JToken.DeepEquals(JObject.Parse(list2), x).ShouldBeTrue(); + JToken.DeepEquals(JObject.Parse(list2), x).Should().BeTrue(); } } diff --git a/tests/Speckle.Sdk.Serialization.Tests/DummyReceiveServerObjectManager.cs b/tests/Speckle.Sdk.Serialization.Tests/DummyReceiveServerObjectManager.cs index 797d7b67..fb4c09fb 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/DummyReceiveServerObjectManager.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/DummyReceiveServerObjectManager.cs @@ -1,6 +1,5 @@ using System.Runtime.CompilerServices; using System.Text; -using Speckle.Sdk.Dependencies.Serialization; using Speckle.Sdk.Serialisation.V2; using Speckle.Sdk.Serialisation.V2.Send; using Speckle.Sdk.Transports; diff --git a/tests/Speckle.Sdk.Serialization.Tests/DummySendServerObjectManager.cs b/tests/Speckle.Sdk.Serialization.Tests/DummySendServerObjectManager.cs index 0d8433fc..b0873527 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/DummySendServerObjectManager.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/DummySendServerObjectManager.cs @@ -1,8 +1,4 @@ using System.Collections.Concurrent; -using Shouldly; -using Speckle.Newtonsoft.Json.Linq; -using Speckle.Sdk.Common; -using Speckle.Sdk.Dependencies.Serialization; using Speckle.Sdk.Serialisation.V2; using Speckle.Sdk.Serialisation.V2.Send; using Speckle.Sdk.Transports; diff --git a/tests/Speckle.Sdk.Serialization.Tests/ExplicitInterfaceTests.cs b/tests/Speckle.Sdk.Serialization.Tests/ExplicitInterfaceTests.cs index e90293de..63f77daf 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/ExplicitInterfaceTests.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/ExplicitInterfaceTests.cs @@ -1,21 +1,21 @@ -using NUnit.Framework; -using Shouldly; +using FluentAssertions; using Speckle.Sdk.Host; using Speckle.Sdk.Models; using Speckle.Sdk.Serialisation.V2.Send; +using Xunit; namespace Speckle.Sdk.Serialization.Tests; public class ExplicitInterfaceTests { - [SetUp] - public void Setup() + // Constructor to replace [SetUp] + public ExplicitInterfaceTests() { TypeLoader.Reset(); TypeLoader.Initialize(typeof(Base).Assembly, typeof(TestClass).Assembly); } - [Test] + [Fact] // Replaces [Test] public async Task Test_Json() { var testClass = new TestClass() { RegularProperty = "Hello" }; @@ -29,24 +29,28 @@ public async Task Test_Json() new ObjectSerializerFactory(new BasePropertyGatherer()), new SerializeProcessOptions(false, false, true, true) ); - await process2.Serialize(testClass, default).ConfigureAwait(false); - objects.Count.ShouldBe(1); + + await process2.Serialize(testClass, default); + + objects.Count.Should().Be(1); objects["daaa67cfd73a957247cf2d631b7ca4f3"] - .ShouldBe( + .Should() + .Be( "{\"RegularProperty\":\"Hello\",\"applicationId\":null,\"speckle_type\":\"Speckle.Core.Serialisation.TestClass\",\"id\":\"daaa67cfd73a957247cf2d631b7ca4f3\"}" ); } - [Test] + [Fact] // Replaces [Test] public void Test_ExtractAllProperties() { var testClass = new TestClass() { RegularProperty = "Hello" }; var gatherer = new BasePropertyGatherer(); var properties = gatherer.ExtractAllProperties(testClass).ToList(); - properties.Count.ShouldBe(3); - properties.Select(x => x.Name).ShouldContain("RegularProperty"); - properties.Select(x => x.Name).ShouldNotContain("TestProperty"); + + properties.Count.Should().Be(3); + properties.Select(x => x.Name).Should().Contain("RegularProperty"); + properties.Select(x => x.Name).Should().NotContain("TestProperty"); } } diff --git a/tests/Speckle.Sdk.Serialization.Tests/ExternalIdTests.cs b/tests/Speckle.Sdk.Serialization.Tests/ExternalIdTests.cs index 925d73f8..d3d7baed 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/ExternalIdTests.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/ExternalIdTests.cs @@ -1,5 +1,4 @@ -using NUnit.Framework; -using Shouldly; +using FluentAssertions; using Speckle.Newtonsoft.Json.Linq; using Speckle.Objects.Geometry; using Speckle.Objects.Primitive; @@ -9,20 +8,20 @@ using Speckle.Sdk.Models.Extensions; using Speckle.Sdk.Serialisation; using Speckle.Sdk.Serialisation.V2.Send; +using Xunit; namespace Speckle.Sdk.Serialization.Tests; public class ExternalIdTests { - [SetUp] - public void Setup() + public ExternalIdTests() { TypeLoader.Reset(); TypeLoader.Initialize(typeof(Base).Assembly, typeof(Polyline).Assembly); } - [Test] - [TestCase("cfaf7ae0dfc5a7cf3343bb6db46ed238", "8d27f5c7fac36d985d89bb6d6d8acddc")] + [Theory] + [InlineData("cfaf7ae0dfc5a7cf3343bb6db46ed238", "8d27f5c7fac36d985d89bb6d6d8acddc")] public void ExternalIdTest_Detached(string lineId, string valueId) { var p = new Polyline() { units = "cm", value = [1, 2] }; @@ -31,16 +30,16 @@ public void ExternalIdTest_Detached(string lineId, string valueId) default ); var list = serializer.Serialize(p).ToDictionary(x => x.Item1, x => x.Item2); - list.ContainsKey(new Id(lineId)).ShouldBeTrue(); + list.ContainsKey(new Id(lineId)).Should().BeTrue(); var json = list[new Id(lineId)]; var jObject = JObject.Parse(json.Value); - jObject.ContainsKey("__closure").ShouldBeTrue(); + jObject.ContainsKey("__closure").Should().BeTrue(); var closures = (JObject)jObject["__closure"].NotNull(); - closures.ContainsKey(valueId).ShouldBeTrue(); + closures.ContainsKey(valueId).Should().BeTrue(); } - [Test] - [TestCase("cfaf7ae0dfc5a7cf3343bb6db46ed238", "8d27f5c7fac36d985d89bb6d6d8acddc")] + [Theory] + [InlineData("cfaf7ae0dfc5a7cf3343bb6db46ed238", "8d27f5c7fac36d985d89bb6d6d8acddc")] public void ExternalIdTest_Detached_Nested(string lineId, string valueId) { var curve = new Curve() @@ -61,16 +60,16 @@ public void ExternalIdTest_Detached_Nested(string lineId, string valueId) default ); var list = serializer.Serialize(curve).ToDictionary(x => x.Item1, x => x.Item2); - list.ContainsKey(new Id(lineId)).ShouldBeTrue(); + list.ContainsKey(new Id(lineId)).Should().BeTrue(); var json = list[new Id(lineId)]; var jObject = JObject.Parse(json.Value); - jObject.ContainsKey("__closure").ShouldBeTrue(); + jObject.ContainsKey("__closure").Should().BeTrue(); var closures = (JObject)jObject["__closure"].NotNull(); - closures.ContainsKey(valueId).ShouldBeTrue(); + closures.ContainsKey(valueId).Should().BeTrue(); } - [Test] - [TestCase("cfaf7ae0dfc5a7cf3343bb6db46ed238", "8d27f5c7fac36d985d89bb6d6d8acddc")] + [Theory] + [InlineData("cfaf7ae0dfc5a7cf3343bb6db46ed238", "8d27f5c7fac36d985d89bb6d6d8acddc")] public void ExternalIdTest_Detached_Nested_More(string lineId, string valueId) { var curve = new Curve() @@ -92,16 +91,16 @@ public void ExternalIdTest_Detached_Nested_More(string lineId, string valueId) default ); var list = serializer.Serialize(polycurve).ToDictionary(x => x.Item1, x => x.Item2); - list.ContainsKey(new Id(lineId)).ShouldBeTrue(); + list.ContainsKey(new Id(lineId)).Should().BeTrue(); var json = list[new Id(lineId)]; var jObject = JObject.Parse(json.Value); - jObject.ContainsKey("__closure").ShouldBeTrue(); + jObject.ContainsKey("__closure").Should().BeTrue(); var closures = (JObject)jObject["__closure"].NotNull(); - closures.ContainsKey(valueId).ShouldBeTrue(); + closures.ContainsKey(valueId).Should().BeTrue(); } - [Test] - [TestCase("cfaf7ae0dfc5a7cf3343bb6db46ed238", "8d27f5c7fac36d985d89bb6d6d8acddc")] + [Theory] + [InlineData("cfaf7ae0dfc5a7cf3343bb6db46ed238", "8d27f5c7fac36d985d89bb6d6d8acddc")] public void ExternalIdTest_Detached_Nested_More_Too(string lineId, string valueId) { var curve = new Curve() @@ -125,11 +124,11 @@ public void ExternalIdTest_Detached_Nested_More_Too(string lineId, string valueI default ); var list = serializer.Serialize(@base).ToDictionary(x => x.Item1, x => x.Item2); - list.ContainsKey(new Id(lineId)).ShouldBeTrue(); + list.ContainsKey(new Id(lineId)).Should().BeTrue(); var json = list[new Id(lineId)]; var jObject = JObject.Parse(json.Value); - jObject.ContainsKey("__closure").ShouldBeTrue(); + jObject.ContainsKey("__closure").Should().BeTrue(); var closures = (JObject)jObject["__closure"].NotNull(); - closures.ContainsKey(valueId).ShouldBeTrue(); + closures.ContainsKey(valueId).Should().BeTrue(); } } diff --git a/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs b/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs index 076730a2..c562fd08 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs +++ b/tests/Speckle.Sdk.Serialization.Tests/SerializationTests.cs @@ -1,8 +1,7 @@ using System.Collections.Concurrent; using System.IO.Compression; using System.Reflection; -using NUnit.Framework; -using Shouldly; +using FluentAssertions; using Speckle.Newtonsoft.Json; using Speckle.Newtonsoft.Json.Linq; using Speckle.Objects.Data; @@ -13,11 +12,10 @@ using Speckle.Sdk.Serialisation.Utilities; using Speckle.Sdk.Serialisation.V2.Receive; using Speckle.Sdk.Serialisation.V2.Send; +using Xunit; namespace Speckle.Sdk.Serialization.Tests; -[TestFixture] -[Description("For certain types, changing property from one type to another should be implicitly backwards compatible")] public class SerializationTests { private class TestLoader(string json) : IObjectLoader @@ -39,8 +37,7 @@ public void Dispose() { } private readonly Assembly _assembly = Assembly.GetExecutingAssembly(); - [SetUp] - public void Setup() + public SerializationTests() { TypeLoader.Reset(); TypeLoader.Initialize(typeof(Base).Assembly, typeof(DataObject).Assembly, _assembly); @@ -83,7 +80,7 @@ public async Task RunTest2(string fileName) var closure = await ReadAsObjects(json); using DeserializeProcess sut = new(null, new TestLoader(json), new TestTransport(closure)); var @base = await sut.Deserialize("551513ff4f3596024547fc818f1f3f70"); - @base.ShouldNotBeNull(); + @base.Should().NotBeNull(); }*/ public class TestObjectLoader(Dictionary idToObject) : IObjectLoader @@ -109,8 +106,8 @@ CancellationToken cancellationToken public void Dispose() { } } - [Test] - [TestCase("RevitObject.json.gz")] + [Theory] + [InlineData("RevitObject.json.gz")] public async Task Basic_Namespace_Validation(string fileName) { var fullName = _assembly.GetManifestResourceNames().Single(x => x.EndsWith(fileName)); @@ -127,32 +124,32 @@ public async Task Basic_Namespace_Validation(string fileName) var jObject = JObject.Parse(objJson); var oldSpeckleType = jObject["speckle_type"].NotNull().Value().NotNull(); var starts = oldSpeckleType.StartsWith("Speckle.Core.") || oldSpeckleType.StartsWith("Objects."); - starts.ShouldBeTrue($"{oldSpeckleType} isn't expected"); + starts.Should().BeTrue($"{oldSpeckleType} isn't expected"); var baseType = await deserializer.DeserializeAsync(objJson); - baseType.id.ShouldBe(id); + baseType.id.Should().Be(id); var oldType = TypeLoader.GetAtomicType(oldSpeckleType); if (oldType == typeof(Base)) { - oldSpeckleType.ShouldNotContain("Base"); + oldSpeckleType.Should().NotContain("Base"); } else { starts = baseType.speckle_type.StartsWith("Speckle.Core.") || baseType.speckle_type.StartsWith("Objects."); - starts.ShouldBeTrue($"{baseType.speckle_type} isn't expected"); + starts.Should().BeTrue($"{baseType.speckle_type} isn't expected"); var type = TypeLoader.GetAtomicType(baseType.speckle_type); - type.ShouldNotBeNull(); + type.Should().NotBeNull(); var name = TypeLoader.GetTypeString(type) ?? throw new ArgumentNullException($"Could not find: {type}"); starts = name.StartsWith("Speckle.Core") || name.StartsWith("Objects"); - starts.ShouldBeTrue($"{name} isn't expected"); + starts.Should().BeTrue($"{name} isn't expected"); } } } - [Test] - [TestCase("RevitObject.json.gz")] + [Theory] + [InlineData("RevitObject.json.gz")] public async Task Basic_Namespace_Validation_New(string fileName) { var fullName = _assembly.GetManifestResourceNames().Single(x => x.EndsWith(fileName)); @@ -165,33 +162,34 @@ public async Task Basic_Namespace_Validation_New(string fileName) var jObject = JObject.Parse(objJson); var oldSpeckleType = jObject["speckle_type"].NotNull().Value().NotNull(); var starts = oldSpeckleType.StartsWith("Speckle.Core.") || oldSpeckleType.StartsWith("Objects."); - starts.ShouldBeTrue($"{oldSpeckleType} isn't expected"); + starts.Should().BeTrue($"{oldSpeckleType} isn't expected"); var oldType = TypeLoader.GetAtomicType(oldSpeckleType); if (oldType == typeof(Base)) { - oldSpeckleType.ShouldNotContain("Base"); + oldSpeckleType.Should().NotContain("Base"); } else { var baseType = process.BaseCache[id]; starts = baseType.speckle_type.StartsWith("Speckle.Core.") || baseType.speckle_type.StartsWith("Objects."); - starts.ShouldBeTrue($"{baseType.speckle_type} isn't expected"); + starts.Should().BeTrue($"{baseType.speckle_type} isn't expected"); var type = TypeLoader.GetAtomicType(baseType.speckle_type); - type.ShouldNotBeNull(); + type.Should().NotBeNull(); var name = TypeLoader.GetTypeString(type) ?? throw new ArgumentNullException(); starts = name.StartsWith("Speckle.Core") || name.StartsWith("Objects"); - starts.ShouldBeTrue($"{name} isn't expected"); + starts.Should().BeTrue($"{name} isn't expected"); } } } - [TestCase( + [Theory] + [InlineData( "{\"applicationId\":null,\"speckle_type\":\"Base\",\"IFC_GUID\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcGUID\",\"units\":null,\"value\":\"18HX_ys0P5uu77f1wwA7bn\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_GUID\",\"id\":\"1f4e29b7198e25221300c684876ec187\"},\"DOOR_COST\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Cost\",\"units\":\"\u0e3f\",\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:currency-1.0.0\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"DOOR_COST\",\"id\":\"80ff4c5df5170b75916a873a394cfbdf\"},\"ALL_MODEL_URL\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"URL\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_URL\",\"id\":\"140c53fcea5deaa35115b23cd2ba48c6\"},\"IFC_TYPE_GUID\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type IfcGUID\",\"units\":null,\"value\":\"0w69BRwHvBsBXN3bEBjQin\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_TYPE_GUID\",\"id\":\"99d5d914df5c50c879e73c50246a9249\"},\"KEYNOTE_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Keynote\",\"units\":null,\"value\":\"S0905\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"KEYNOTE_PARAM\",\"id\":\"c2272311800b04ab4d2b0052df68ecdc\"},\"PHASE_CREATED\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Phase Created\",\"units\":null,\"value\":\"0\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"PHASE_CREATED\",\"id\":\"72ecbbd5d29ea1b48df89d8f88b29120\"},\"ALL_MODEL_MARK\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Mark\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_MARK\",\"id\":\"f2e0ed6ebfbab4d4780c5143b774558e\"},\"FUNCTION_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Function\",\"units\":null,\"value\":0,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"FUNCTION_PARAM\",\"id\":\"a43000484f3fa3c5cf60a2ccd79a573c\"},\"UNIFORMAT_CODE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Assembly Code\",\"units\":null,\"value\":\"\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"UNIFORMAT_CODE\",\"id\":\"b797bb20d49af57eecbe718df4ebd411\"},\"WINDOW_TYPE_ID\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Mark\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"WINDOW_TYPE_ID\",\"id\":\"d74f026a13a539bd24369ea78b34aa6b\"},\"ALL_MODEL_IMAGE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Image\",\"units\":null,\"value\":\"-1\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_IMAGE\",\"id\":\"4ef25c5fcd2ee32d9b3d6ce9b1047904\"},\"ALL_MODEL_MODEL\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Model\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_MODEL\",\"id\":\"13597261389f532c0778e134623eff85\"},\"CLEAR_COVER_TOP\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Rebar Cover - Top Face\",\"units\":null,\"value\":\"95743\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"CLEAR_COVER_TOP\",\"id\":\"ed5f5e056314ee8435a1658a54261e94\"},\"ELEM_TYPE_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type\",\"units\":null,\"value\":\"5432827\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ELEM_TYPE_PARAM\",\"id\":\"e502cb5aed1fda357926c7ca9927c42c\"},\"RELATED_TO_MASS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Related to Mass\",\"units\":null,\"value\":false,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"RELATED_TO_MASS\",\"id\":\"a43b8424564b8f14738f4dbaa78be150\"},\"SYMBOL_ID_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Id\",\"units\":null,\"value\":\"5432827\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"SYMBOL_ID_PARAM\",\"id\":\"1947cb98e61f79da57f573a3a785b436\"},\"DESIGN_OPTION_ID\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Design Option\",\"units\":null,\"value\":\"-1\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"DESIGN_OPTION_ID\",\"id\":\"cf30f731c41543dd134a4877fbdab105\"},\"PHASE_DEMOLISHED\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Phase Demolished\",\"units\":null,\"value\":\"-1\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"PHASE_DEMOLISHED\",\"id\":\"47066bac7728f9b93f4acdb697284a59\"},\"CLEAR_COVER_OTHER\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Rebar Cover - Other Faces\",\"units\":null,\"value\":\"95743\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"CLEAR_COVER_OTHER\",\"id\":\"1afe7d22a897aff809bd92aea1acafd2\"},\"ELEM_FAMILY_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Family\",\"units\":null,\"value\":\"5432827\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ELEM_FAMILY_PARAM\",\"id\":\"ec3a159572a58b85b3a3650e5cc23e90\"},\"CLEAR_COVER_BOTTOM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Rebar Cover - Bottom Face\",\"units\":null,\"value\":\"95743\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"CLEAR_COVER_BOTTOM\",\"id\":\"476161776fc4c6ceb3c544c792a08120\"},\"HOST_AREA_COMPUTED\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Area\",\"units\":\"m\u00b2\",\"value\":7.128858225722908,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:squareMeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"HOST_AREA_COMPUTED\",\"id\":\"5e7567fea07a98c0cdd4903cabd897a3\"},\"IFC_EXPORT_ELEMENT\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Export to IFC\",\"units\":null,\"value\":0,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_EXPORT_ELEMENT\",\"id\":\"7de623e201f3fcfb16dbcacefe7f8403\"},\"totalChildrenCount\":0,\"ALL_MODEL_TYPE_NAME\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Name\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_TYPE_NAME\",\"id\":\"4699f3fc2fd4e84cc3b6296ded7225b5\"},\"DESIGN_OPTION_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Design Option\",\"units\":null,\"value\":\"Main Model\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"DESIGN_OPTION_PARAM\",\"id\":\"771449eae7f2fb96345b165954b2c797\"},\"ELEM_CATEGORY_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Category\",\"units\":null,\"value\":\"-2000032\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ELEM_CATEGORY_PARAM\",\"id\":\"fb0b948f7360b9415ea9ede20fb3cdd2\"},\"ALL_MODEL_TYPE_IMAGE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Image\",\"units\":null,\"value\":\"-1\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_TYPE_IMAGE\",\"id\":\"4c95be61c11f5609f1fa649804bf9814\"},\"ANALYTICAL_ROUGHNESS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Roughness\",\"units\":null,\"value\":1,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ANALYTICAL_ROUGHNESS\",\"id\":\"eab64895ad089cf272ae6a7431f4cdac\"},\"HOST_VOLUME_COMPUTED\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Volume\",\"units\":\"m\u00b3\",\"value\":1.413211687704679,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:cubicMeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"HOST_VOLUME_COMPUTED\",\"id\":\"4cac83d2757bc70d7e1f299de124d028\"},\"SCHEDULE_LEVEL_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Level\",\"units\":null,\"value\":\"1100600\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"SCHEDULE_LEVEL_PARAM\",\"id\":\"d0ab715757ddbaedf5dc2df0726ed38c\"},\"ALL_MODEL_DESCRIPTION\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Description\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_DESCRIPTION\",\"id\":\"4abdaadbe23c12c349c65abcd5979f56\"},\"IFC_EXPORT_ELEMENT_AS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Export to IFC As\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_EXPORT_ELEMENT_AS\",\"id\":\"ccacac43b32ffd10c88a870492f98f96\"},\"UNIFORMAT_DESCRIPTION\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Assembly Description\",\"units\":null,\"value\":\"\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"UNIFORMAT_DESCRIPTION\",\"id\":\"abfe7173561e8b86cae8aa8dc34743d1\"},\"ALL_MODEL_MANUFACTURER\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Manufacturer\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_MANUFACTURER\",\"id\":\"e280ae740be9133f1001f218a137bb2f\"},\"ANALYTICAL_ABSORPTANCE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Absorptance\",\"units\":null,\"value\":0.1,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ANALYTICAL_ABSORPTANCE\",\"id\":\"e1618d04224fb3e11e650f8854e5eddb\"},\"ELEM_CATEGORY_PARAM_MT\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Category\",\"units\":null,\"value\":\"-2000032\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ELEM_CATEGORY_PARAM_MT\",\"id\":\"d4119e43880a3cc8632a137d4f3372ae\"},\"ALL_MODEL_TYPE_COMMENTS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Comments\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_TYPE_COMMENTS\",\"id\":\"8ea15d6198e1f5c632df36270be5433e\"},\"ANALYTICAL_THERMAL_MASS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Thermal Mass\",\"units\":\"kJ/(m\u00b2·K)\",\"value\":null,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:kilojoulesPerSquareMeterKelvin-1.0.0\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ANALYTICAL_THERMAL_MASS\",\"id\":\"d8b711b81d9e0ad7f072b60b69bd0239\"},\"HOST_PERIMETER_COMPUTED\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Perimeter\",\"units\":\"mm\",\"value\":11098.801755409942,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"HOST_PERIMETER_COMPUTED\",\"id\":\"eb73365794668bf73b3ffd2c80162ee1\"},\"IFC_EXPORT_ELEMENT_TYPE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Export Type to IFC\",\"units\":null,\"value\":0,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_EXPORT_ELEMENT_TYPE\",\"id\":\"0a96867bd313e951c229fb92b346b516\"},\"WALL_ATTR_ROOM_BOUNDING\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Room Bounding\",\"units\":null,\"value\":true,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"WALL_ATTR_ROOM_BOUNDING\",\"id\":\"fdf5bd19ac0a9f2878323c71e4ae80ea\"},\"FLOOR_STRUCTURE_ID_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Structure\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"FLOOR_STRUCTURE_ID_PARAM\",\"id\":\"43b858d8cfaf2bd27cb0b466dc6d425b\"},\"SYMBOL_FAMILY_NAME_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Family Name\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"SYMBOL_FAMILY_NAME_PARAM\",\"id\":\"d96f492f43f2b0e11ce86d66c23caf0f\"},\"IFC_EXPORT_PREDEFINEDTYPE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IFC Predefined Type\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_EXPORT_PREDEFINEDTYPE\",\"id\":\"b927074616bc0e6e323b52a99867b907\"},\"STRUCTURAL_MATERIAL_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Structural Material\",\"units\":null,\"value\":\"215194\",\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_MATERIAL_PARAM\",\"id\":\"1f7ffc00602d1944892885d68dff8867\"},\"ELEM_FAMILY_AND_TYPE_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Family and Type\",\"units\":null,\"value\":\"5432827\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ELEM_FAMILY_AND_TYPE_PARAM\",\"id\":\"9d58f36db73c0c248a6db682a6c6a6a0\"},\"FLOOR_ATTR_THICKNESS_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Thickness\",\"units\":\"mm\",\"value\":200,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"FLOOR_ATTR_THICKNESS_PARAM\",\"id\":\"22c96d409372e700936805b825b574e6\"},\"IFC_EXPORT_ELEMENT_TYPE_AS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Export Type to IFC As\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_EXPORT_ELEMENT_TYPE_AS\",\"id\":\"41a53fec385581b6af53942aff3cd2d3\"},\"ALL_MODEL_INSTANCE_COMMENTS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Comments\",\"units\":null,\"value\":\"\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ALL_MODEL_INSTANCE_COMMENTS\",\"id\":\"ca8cdcc0b3fd824a34dcb42749151cd1\"},\"STRUCTURAL_ELEVATION_AT_TOP\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Elevation at Top\",\"units\":\"mm\",\"value\":21099.999999999898,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_ELEVATION_AT_TOP\",\"id\":\"b1f293a63ff03c0c7456f8ba7b703f4f\"},\"FLOOR_HEIGHTABOVELEVEL_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Height Offset From Level\",\"units\":\"mm\",\"value\":-100,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"FLOOR_HEIGHTABOVELEVEL_PARAM\",\"id\":\"312773813c84648fc5ff2d78a8d8d8bc\"},\"ANALYTICAL_THERMAL_RESISTANCE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Thermal Resistance (R)\",\"units\":\"(m\u00b2·K)/W\",\"value\":null,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:squareMeterKelvinsPerWatt-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ANALYTICAL_THERMAL_RESISTANCE\",\"id\":\"fcfa5d36d656d4f8ca2b883a17c310b8\"},\"IFC_EXPORT_PREDEFINEDTYPE_TYPE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type IFC Predefined Type\",\"units\":null,\"value\":null,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"IFC_EXPORT_PREDEFINEDTYPE_TYPE\",\"id\":\"ac166cbccbcd8335272956f09d8d5d42\"},\"STRUCTURAL_ELEVATION_AT_BOTTOM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Elevation at Bottom\",\"units\":\"mm\",\"value\":20899.9999999999,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_ELEVATION_AT_BOTTOM\",\"id\":\"d9e8f0e4b57b00ca99d13df99ea6ac26\"},\"COARSE_SCALE_FILL_PATTERN_COLOR\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Coarse Scale Fill Color\",\"units\":null,\"value\":0,\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"COARSE_SCALE_FILL_PATTERN_COLOR\",\"id\":\"854d889fd71071f3b81d0e06f7f1095c\"},\"STRUCTURAL_FLOOR_CORE_THICKNESS\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Core Thickness\",\"units\":\"mm\",\"value\":199.99999999999784,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_FLOOR_CORE_THICKNESS\",\"id\":\"6fb6fb65a394c5c68d5a760289c1129d\"},\"STRUCTURAL_ELEVATION_AT_TOP_CORE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Elevation at Top Core\",\"units\":\"mm\",\"value\":21099.999999999898,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_ELEVATION_AT_TOP_CORE\",\"id\":\"aed8eedfb2527594e14ae4e5f74fb5c1\"},\"ANALYTICAL_ELEMENT_HAS_ASSOCIATION\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Has Association\",\"units\":null,\"value\":true,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ANALYTICAL_ELEMENT_HAS_ASSOCIATION\",\"id\":\"c9a6f771f05ef6072100c59c672dfb77\"},\"COARSE_SCALE_FILL_PATTERN_ID_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Coarse Scale Fill Pattern\",\"units\":null,\"value\":\"-1\",\"isShared\":false,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"COARSE_SCALE_FILL_PATTERN_ID_PARAM\",\"id\":\"1d271f4d80ffe772f9f8896971050ccc\"},\"FLOOR_ATTR_DEFAULT_THICKNESS_PARAM\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Default Thickness\",\"units\":\"mm\",\"value\":200,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"FLOOR_ATTR_DEFAULT_THICKNESS_PARAM\",\"id\":\"271b8b7f7e29c45065c1ccaa1095b32e\"},\"STRUCTURAL_ELEVATION_AT_TOP_SURVEY\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Elevation at Top Survey\",\"units\":\"mm\",\"value\":30899.999999999894,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_ELEVATION_AT_TOP_SURVEY\",\"id\":\"8f2b9f55e373736263d14002838194b4\"},\"STRUCTURAL_ELEVATION_AT_BOTTOM_CORE\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Elevation at Bottom Core\",\"units\":\"mm\",\"value\":20899.9999999999,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_ELEVATION_AT_BOTTOM_CORE\",\"id\":\"7a0b7e496383d08605ccb8c776cedbbf\"},\"02b58af4-afcd-404b-9011-3a25d6816e1b\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"ClassificationCode\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"02b58af4-afcd-404b-9011-3a25d6816e1b\",\"id\":\"141f6b021c701e5b4b4ee430652f7f91\"},\"042673e7-8ac4-413d-a393-e0785fbf8889\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 3\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"042673e7-8ac4-413d-a393-e0785fbf8889\",\"id\":\"f415ff5c972f45d7e0090b0849c54677\"},\"07afa150-f11f-40a1-a173-7a77ea32cf96\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 6\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"07afa150-f11f-40a1-a173-7a77ea32cf96\",\"id\":\"0e3acdf0b385d32d68caa8753c710849\"},\"07b6cf99-a3d2-4d7a-9ea4-246058cfae1a\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Classification.OmniClass.22.Description\",\"units\":null,\"value\":\"High-Tolerance Concrete Floor Finishing\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"07b6cf99-a3d2-4d7a-9ea4-246058cfae1a\",\"id\":\"8957914412a138b6255452dd485a25bd\"},\"082d16bb-7cf9-4968-ac22-b6f6ae068028\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcName\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"082d16bb-7cf9-4968-ac22-b6f6ae068028\",\"id\":\"f313bddfb2c5cf9f825ee6653021b04e\"},\"087f96a5-2dd2-42bb-a170-c22485216c09\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Element Condition\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"087f96a5-2dd2-42bb-a170-c22485216c09\",\"id\":\"8665b9cec7a39bffa030e6b415f78fa9\"},\"098e8d4f-1431-49e8-8ef6-69516cf72354\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"3D Model Element GUID\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"098e8d4f-1431-49e8-8ef6-69516cf72354\",\"id\":\"b81c608ec3bd9aa08ea1a2c5a2cea206\"},\"0a004b99-d4e6-4db6-8c88-9b77da33f012\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcDescription\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"0a004b99-d4e6-4db6-8c88-9b77da33f012\",\"id\":\"79b720b93988fd7840213380399408dd\"},\"0bf3a5d2-06c0-4b6c-9ba1-6985ef40c2b0\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 6\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"0bf3a5d2-06c0-4b6c-9ba1-6985ef40c2b0\",\"id\":\"622eab526502ce2a848c4aa932554f96\"},\"0c273fd8-260b-4f34-996e-921fa14a47fc\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 4\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"0c273fd8-260b-4f34-996e-921fa14a47fc\",\"id\":\"f70edec21839dfc410d94659d18d52c2\"},\"11f34dfe-4592-4c86-a455-2f020d9376e8\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"ClassificationCode\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"11f34dfe-4592-4c86-a455-2f020d9376e8\",\"id\":\"cd817060f1920a0194139bf2cdddecd4\"},\"12e4c976-0b76-4735-8664-e882b410ac7e\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon A1\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"12e4c976-0b76-4735-8664-e882b410ac7e\",\"id\":\"0d2df557e73500e0c3d72c527d4c36fe\"},\"1336888e-1fed-4e9e-b74b-794bff5b6046\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"GrossArea(BaseQuantities)\",\"units\":\"m\u00b2\",\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:squareMeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"1336888e-1fed-4e9e-b74b-794bff5b6046\",\"id\":\"7e0f5932a6c5ad37872436b3ed0cf07b\"},\"15212817-1c39-4c7f-bae4-436acd0e4598\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcDescription\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"15212817-1c39-4c7f-bae4-436acd0e4598\",\"id\":\"f2bba2dcccf5786df17c279367baab39\"},\"18a3daed-8579-45e2-97a0-412159986104\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 8\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"18a3daed-8579-45e2-97a0-412159986104\",\"id\":\"173b9745cf4e7cc4b67748c5a03acf04\"},\"18ab825f-ba4c-4a8f-b509-5ddd7c378267\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Item\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"18ab825f-ba4c-4a8f-b509-5ddd7c378267\",\"id\":\"2c130ba41bf9797a0e7e64315695dd7b\"},\"1948bc31-5a23-482a-b337-4bd1fce08aec\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Renovation Status(AC_Pset_RenovationAndPhasing)\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"1948bc31-5a23-482a-b337-4bd1fce08aec\",\"id\":\"59b7e7b981f77ff4e7b01e7d794234f9\"},\"1d9a0983-608b-4aed-b03f-f27e8e0e677a\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon B3\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"1d9a0983-608b-4aed-b03f-f27e8e0e677a\",\"id\":\"ec604873c2b3c4d53f5ea9c2e203fc70\"},\"219c8c15-4722-4b40-9e19-7fbbddeee30f\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 1\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"219c8c15-4722-4b40-9e19-7fbbddeee30f\",\"id\":\"abc83e3214698cefe7bbd9326b536b1d\"},\"226b84c2-b3b5-4a04-93f7-9523a21ef4e0\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Discipline\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"226b84c2-b3b5-4a04-93f7-9523a21ef4e0\",\"id\":\"f0e8a4a841062c6b5517b30002fd2325\"},\"244a8c27-edd6-4b09-8905-4cf403c61235\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Schedule Type Code/Number\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"244a8c27-edd6-4b09-8905-4cf403c61235\",\"id\":\"1ff35615eaa5e568fdb3c7c1bb21b72a\"},\"2cbf5041-2b36-4c7f-b65e-439af251d9f7\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Unique Asset ID - Equipment Type\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"2cbf5041-2b36-4c7f-b65e-439af251d9f7\",\"id\":\"1339498cfd39967f51d7d5a31feba974\"},\"2eac0fd8-0c8a-4c5a-9d54-62415d708f37\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Elemental Code\",\"units\":null,\"value\":\"UFSB\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"2eac0fd8-0c8a-4c5a-9d54-62415d708f37\",\"id\":\"e20483f71786c3e27291a3eeaa049b48\"},\"2f1ef0a4-09a2-4e80-ba30-57984a475e1d\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Unique Asset ID\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"2f1ef0a4-09a2-4e80-ba30-57984a475e1d\",\"id\":\"d083dfba92681370be68f19d761a9628\"},\"2fb9b7d9-d0b0-4ce2-bbc0-02464fda354c\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon C1\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"2fb9b7d9-d0b0-4ce2-bbc0-02464fda354c\",\"id\":\"71196d8fbc135c7386841bb439f8aaee\"},\"3490690f-a8be-46d9-9607-47c255e9ee89\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcExportAs [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"3490690f-a8be-46d9-9607-47c255e9ee89\",\"id\":\"cf2c87ffe9b6babcd2659d619fe3a4b7\"},\"35064971-5814-4b17-b572-49ea1320c516\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 7\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"35064971-5814-4b17-b572-49ea1320c516\",\"id\":\"ecf761dcb34a3e098f355f401eec5738\"},\"36d9a077-9301-47a0-b049-4f29e17d51dd\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcExportAs [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"36d9a077-9301-47a0-b049-4f29e17d51dd\",\"id\":\"f547ba1d204747353092fccb1970b8ee\"},\"392cae56-cd6b-4946-817f-242686e12441\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Zone\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"392cae56-cd6b-4946-817f-242686e12441\",\"id\":\"37152ed7e04c0ea23c442aad0be9a611\"},\"3c3d55ea-8a2f-41c1-97fd-d222d586b0b1\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Omniclass Classification Type Code\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"3c3d55ea-8a2f-41c1-97fd-d222d586b0b1\",\"id\":\"861b129e42aac2de8166ad76ded0781c\"},\"3d134cec-2e95-4d43-bc4b-e552d382f73c\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcPresentationLayer\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"3d134cec-2e95-4d43-bc4b-e552d382f73c\",\"id\":\"030b7e2842cde1ce8a1d8e545d0e068a\"},\"3f146225-bd3e-448b-b180-034b880bd662\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon A5\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"3f146225-bd3e-448b-b180-034b880bd662\",\"id\":\"3d573a80fc3bd747d292d75c7751c523\"},\"3f9a284a-7485-460c-b827-9df8cd50720e\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Classification.OmniClass.21.Description\",\"units\":null,\"value\":\"Insitu Concrete\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"3f9a284a-7485-460c-b827-9df8cd50720e\",\"id\":\"632da2ce6a52e51df22a05b776421b49\"},\"40e844db-ba22-4ddc-bc15-1fc47f5b12e7\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcSpatialContainer\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"40e844db-ba22-4ddc-bc15-1fc47f5b12e7\",\"id\":\"a8e9d80b93cd5f965d00aaffad6786e9\"},\"444d97fc-d9b6-4424-943d-37ac498a46c4\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Unique Asset ID - Item Number\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"444d97fc-d9b6-4424-943d-37ac498a46c4\",\"id\":\"d7cf7e867db30b45ac62757a6cc11b1b\"},\"46baf2f0-9232-4c37-aa7c-57e37fd5db17\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Product Type\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"46baf2f0-9232-4c37-aa7c-57e37fd5db17\",\"id\":\"b87714b8187aabde851b900a3755655a\"},\"4803c7b6-ded1-46b1-b5eb-ffe9ddcdc20b\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcSpatialContainer\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"4803c7b6-ded1-46b1-b5eb-ffe9ddcdc20b\",\"id\":\"9d6e8c6691db06081c2332c589ed2a31\"},\"48e76a50-9a4f-47a9-8074-79cc7fce9f14\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcPropertySetList\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"48e76a50-9a4f-47a9-8074-79cc7fce9f14\",\"id\":\"38621e3cf898c0fa404d29b88cf6ea5a\"},\"4ac5fa74-7864-45a3-9d89-1ab998b7731a\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon A3\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"4ac5fa74-7864-45a3-9d89-1ab998b7731a\",\"id\":\"34e84e5eed664e1ca94385e405141ef1\"},\"4c575161-247d-46a5-8ae2-72829f37725f\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"NetArea(BaseQuantities)\",\"units\":\"m\u00b2\",\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:squareMeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"4c575161-247d-46a5-8ae2-72829f37725f\",\"id\":\"c5741774b669ce685c5e53a704cc0320\"},\"4d293b70-da1a-4830-80a0-4f63b356ff61\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon A2\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"4d293b70-da1a-4830-80a0-4f63b356ff61\",\"id\":\"e629dd3ed399e1b8328aeaa2e90afae9\"},\"4dabccff-7cc0-42ff-a6db-29a28162d3f3\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type IfcPropertySetList\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"4dabccff-7cc0-42ff-a6db-29a28162d3f3\",\"id\":\"3aa01feade7f65bbd785f7083c04bacf\"},\"4fc2bd83-f0b1-41ba-8663-89e3d7f3e660\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcDescription [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"4fc2bd83-f0b1-41ba-8663-89e3d7f3e660\",\"id\":\"e7639f4355cebda699431a60345609c4\"},\"50a015d9-917f-4ac5-884e-42f7f36b47b1\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon B5\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"50a015d9-917f-4ac5-884e-42f7f36b47b1\",\"id\":\"9a71039a2e2272f527084f2096f9429b\"},\"51778754-e984-42cf-8a6d-a2226baf316f\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcPresentationLayer\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"51778754-e984-42cf-8a6d-a2226baf316f\",\"id\":\"392005ecea2408b03939ef80fbb34a8f\"},\"5188c780-2bf1-460c-8bbf-043dcb4649eb\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcExportAs\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"5188c780-2bf1-460c-8bbf-043dcb4649eb\",\"id\":\"f38369c4acdfa96b7d45ac6187f8a183\"},\"5402c013-1b09-474f-b399-344a0e55a182\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Material\",\"units\":null,\"value\":\"PT\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"5402c013-1b09-474f-b399-344a0e55a182\",\"id\":\"e8252b753c969222390cf77fc56237ef\"},\"579138d4-c882-45f1-bfd6-5ec6f8189161\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Rate Reo Area\",\"units\":null,\"value\":12,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"579138d4-c882-45f1-bfd6-5ec6f8189161\",\"id\":\"09ca602feb656474737d2cf84e89e26f\"},\"5d8a425f-4cff-44fe-9896-932e8e5639ef\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 5\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"5d8a425f-4cff-44fe-9896-932e8e5639ef\",\"id\":\"805c08afb49a184af8649f63531be0e3\"},\"6248687a-e43d-4380-9f28-b98a14157187\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcExportAs [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"6248687a-e43d-4380-9f28-b98a14157187\",\"id\":\"c300499e47eea08ec5bfd25acc9abae3\"},\"6cbcfae1-3598-4ddd-a606-41f3788c0362\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Managed Asset YesNo\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"6cbcfae1-3598-4ddd-a606-41f3788c0362\",\"id\":\"b9473955cae7232a6d5ba4a2c7669e7c\"},\"76daee20-cdee-48b9-bf5f-1dc46079927e\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 1\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"76daee20-cdee-48b9-bf5f-1dc46079927e\",\"id\":\"90563b7e8ed0acad2128d08383966907\"},\"7949a6c6-d3e4-45bf-bad0-208f3ba33483\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Unique Asset ID - Discipline Abbreviation\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"7949a6c6-d3e4-45bf-bad0-208f3ba33483\",\"id\":\"e427ecb62bb5a5f69dc56b12dfd4583a\"},\"79dbaeea-7a11-4cb4-a521-bc04d1b7a25b\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcName [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"79dbaeea-7a11-4cb4-a521-bc04d1b7a25b\",\"id\":\"d8a8a1f5d6315d9748e29bdd293d77a5\"},\"7a4a2609-0307-4c38-9a8c-4ffcd19a2d00\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Location\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"7a4a2609-0307-4c38-9a8c-4ffcd19a2d00\",\"id\":\"a4c9b4d5ce413090459c99efad3f1832\"},\"7d013fce-228f-4f3c-aa01-db40e458cc6e\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 8\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"7d013fce-228f-4f3c-aa01-db40e458cc6e\",\"id\":\"07281437b9e050cd5ade4cca8a96227b\"},\"834ba6cf-7f91-49e5-ad0c-717d52a2507a\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcExportAs\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"834ba6cf-7f91-49e5-ad0c-717d52a2507a\",\"id\":\"7783908b6376a626de1c39d735b6cbfc\"},\"86d20f83-240f-44b9-8b27-6311eab2abcd\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon B6\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"86d20f83-240f-44b9-8b27-6311eab2abcd\",\"id\":\"006791a1d0d5566c3b892202b08943fd\"},\"8a91c179-c4cb-471a-b108-ad540b8267e3\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Global Inherited Properties.Level Number\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"8a91c179-c4cb-471a-b108-ad540b8267e3\",\"id\":\"3d3d901b52bacdd137ac67f702680f3d\"},\"8c59bf6c-99ff-455b-bdfa-aeb7861e522e\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcTag\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"8c59bf6c-99ff-455b-bdfa-aeb7861e522e\",\"id\":\"363ec752016b710969c8b4454fd2d35f\"},\"8f645e8b-7523-4462-a0af-858ffeaf44dc\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon D\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"8f645e8b-7523-4462-a0af-858ffeaf44dc\",\"id\":\"0e36907ad605957b17ba8556547d8ceb\"},\"91b7eb2f-0caf-45b0-a65d-83ce1eaca70e\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcExportAs\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"91b7eb2f-0caf-45b0-a65d-83ce1eaca70e\",\"id\":\"04140c6ff8418bd2c731affd5691de8e\"},\"93b76d01-67c3-4799-a5f3-296f97489bd3\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Calc Room Number\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"93b76d01-67c3-4799-a5f3-296f97489bd3\",\"id\":\"4e270753bda04b81fd1c11bf1da5d852\"},\"9898cedf-179a-42e7-8cdc-c6c4212ec3e8\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcPresentationLayer\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"9898cedf-179a-42e7-8cdc-c6c4212ec3e8\",\"id\":\"7fd3f00069f6f1fcdab2ea0307661061\"},\"9a208060-4948-49b2-a1bf-1ab383705469\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcDescription\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"9a208060-4948-49b2-a1bf-1ab383705469\",\"id\":\"75340685a08f1a15df5c7aebbc41a85a\"},\"9a49a9a9-21c9-4f52-aeab-ae4727be6e1d\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type IfcPropertySetList\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"9a49a9a9-21c9-4f52-aeab-ae4727be6e1d\",\"id\":\"93f8195337085871545af0176f0ba282\"},\"9c2f84e0-2489-4a03-b4c8-eb44bb26fb0a\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 2\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"9c2f84e0-2489-4a03-b4c8-eb44bb26fb0a\",\"id\":\"fa24d55d23008ef6be1dbf3e8636c67e\"},\"9dd225d2-722b-4cb1-b972-babca7520f7e\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"System Name\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"9dd225d2-722b-4cb1-b972-babca7520f7e\",\"id\":\"056eec36214c733f8ecc448c27785434\"},\"9f484ce3-e8ae-4c20-b21c-0210b770935c\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Unique Asset ID - Tenancy Identifier\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"9f484ce3-e8ae-4c20-b21c-0210b770935c\",\"id\":\"416b24b324db272078942a4420775ec0\"},\"ANALYTICAL_HEAT_TRANSFER_COEFFICIENT\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Heat Transfer Coefficient (U)\",\"units\":\"W/(m\u00b2·K)\",\"value\":null,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:wattsPerSquareMeterKelvin-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ANALYTICAL_HEAT_TRANSFER_COEFFICIENT\",\"id\":\"8d290ae8d6ec3896b8dda96a83bb2d12\"},\"a5cb3364-d1f0-4ea5-a2d2-44114efbcf65\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcName\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"a5cb3364-d1f0-4ea5-a2d2-44114efbcf65\",\"id\":\"23ac4c2000599c233e5c390330e9108e\"},\"a77ddcdc-4c89-42c3-9d28-bc9e476c0fbe\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcName [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"a77ddcdc-4c89-42c3-9d28-bc9e476c0fbe\",\"id\":\"c60915bcbf4e2e070ccabc17694ee836\"},\"aa967357-e0b5-49f3-95a0-085e5d7d8951\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcMaterial\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"aa967357-e0b5-49f3-95a0-085e5d7d8951\",\"id\":\"08650ab84cd1be1235a60fccfcb1f39e\"},\"ac9b78b9-e138-483b-9796-6214cf7a5bd8\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Documentation.Home Story Name\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ac9b78b9-e138-483b-9796-6214cf7a5bd8\",\"id\":\"759d341196e21570a2f2d6199b5a9f2f\"},\"aeb679c1-1b82-4476-9099-7d13fd8ae3b8\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type IfcPropertySetList\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"aeb679c1-1b82-4476-9099-7d13fd8ae3b8\",\"id\":\"0a235c1836cd2ce0c5fd3869b938946e\"},\"af8efc07-fec4-4419-8513-4a268c4141c8\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon C3\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"af8efc07-fec4-4419-8513-4a268c4141c8\",\"id\":\"181c0245de21c9820a83396914565762\"},\"afb0a36f-1fa3-4f07-9b05-f86f48b3c3f0\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon C2\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"afb0a36f-1fa3-4f07-9b05-f86f48b3c3f0\",\"id\":\"cd929dfb8d143639a136ff3716800dfc\"},\"b13fb213-450c-4f92-859a-05cd5779daf1\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 7\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"b13fb213-450c-4f92-859a-05cd5779daf1\",\"id\":\"1972d1f9e161892f2ba47ccb04f5e784\"},\"b41f116c-c6f7-418e-bf4b-61cc815f8d99\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Level Number(Global Inherited Properties)\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"b41f116c-c6f7-418e-bf4b-61cc815f8d99\",\"id\":\"09caeb5d7565c779852e7802437af4a2\"},\"b594497d-7b5e-4221-aa1d-063f073aa326\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 2\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"b594497d-7b5e-4221-aa1d-063f073aa326\",\"id\":\"ee718148bffebbcfb7e50f5f2be7f91f\"},\"b753aced-e142-45f9-9bb6-d7edce1df108\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Calc Location\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"b753aced-e142-45f9-9bb6-d7edce1df108\",\"id\":\"ab32fc38096b2f538301b596be3ab123\"},\"b90ec63a-c51f-400a-bff1-66b8d0765f47\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Rate Reo Volume\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"b90ec63a-c51f-400a-bff1-66b8d0765f47\",\"id\":\"5642967c62f928019c93b1bbc0e81c26\"},\"b958fd3c-5ea1-43a2-bc5c-df212ed8cf33\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcDescription [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"b958fd3c-5ea1-43a2-bc5c-df212ed8cf33\",\"id\":\"ea40993e42d6379ab80b5b7d526347c2\"},\"b9d05d9c-a5f5-4a92-8811-9c5e2eefabd8\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"ClassificationCode\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"b9d05d9c-a5f5-4a92-8811-9c5e2eefabd8\",\"id\":\"c93f07ca1b6c3ac1c114474dc9aeeaca\"},\"bf3519b8-7b28-497e-97d8-afd4bf76203b\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcDescription [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"bf3519b8-7b28-497e-97d8-afd4bf76203b\",\"id\":\"3c46724925e301e2f88d4ecf4b6150f9\"},\"bfb5b2c0-aa1f-47ae-9cc9-70f7feaef0ea\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcName [Type]\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"bfb5b2c0-aa1f-47ae-9cc9-70f7feaef0ea\",\"id\":\"e8a6f622ac2589ad9fadd82e76c4c10c\"},\"bfd7311c-35b9-447d-9683-8ce244f8c1ad\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Remarks\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"bfd7311c-35b9-447d-9683-8ce244f8c1ad\",\"id\":\"05d34f2e81b12d25b60b1cd6a3788468\"},\"c4520aa3-cdf4-46b7-9539-180edc16d223\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon A4\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"c4520aa3-cdf4-46b7-9539-180edc16d223\",\"id\":\"54c0d9d278b045cbcb57fe4b2d477b86\"},\"c5b0f410-b4ee-4552-ac04-06fa0c13ec3b\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Unique Asset ID - Level/Floor\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"c5b0f410-b4ee-4552-ac04-06fa0c13ec3b\",\"id\":\"7365bc4a64abd37f8dbed69316983d60\"},\"c67d4cb4-b2d6-426f-8d05-ac6fbe0bd267\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Data 5\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"c67d4cb4-b2d6-426f-8d05-ac6fbe0bd267\",\"id\":\"7caf73cac38e203374836cef4827f0ae\"},\"c7ce9441-9aba-45ab-acbb-74e687481466\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Classification.OmniClass.22.Number\",\"units\":null,\"value\":\"22-03 35 13\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"c7ce9441-9aba-45ab-acbb-74e687481466\",\"id\":\"8b96e2130eddfb81c6a35635c5654079\"},\"cab1938b-5da8-4b53-9f0c-bb473c1966a4\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Level Number(Global Inherited Properties)\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"cab1938b-5da8-4b53-9f0c-bb473c1966a4\",\"id\":\"baed41d276e750b374a5ca62366d0e56\"},\"cda17719-cef4-4c69-92f1-e111e85353b1\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcTag\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"cda17719-cef4-4c69-92f1-e111e85353b1\",\"id\":\"a00fb37b573181a2bb6070b416ac2c87\"},\"ce24f3b1-369d-42bb-987e-ac0b45c4f8da\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Classification.OmniClass.23.Description\",\"units\":null,\"value\":\"Concrete Structural Floor Decks\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ce24f3b1-369d-42bb-987e-ac0b45c4f8da\",\"id\":\"57445c90784f528c2b0f414f9233a42d\"},\"ce25a030-e3d0-4856-bbc0-a1cdd8f4d4ff\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon B4\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ce25a030-e3d0-4856-bbc0-a1cdd8f4d4ff\",\"id\":\"deb71e96bcf8157d78c59266aaaa86d1\"},\"d05b3c99-0643-409d-ad3b-2704d324bbcd\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon B1\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"d05b3c99-0643-409d-ad3b-2704d324bbcd\",\"id\":\"34d4a2c23bc26a1545523ae8597223ca\"},\"d608342a-e8f5-4ec9-9626-771914eb3da2\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 4\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"d608342a-e8f5-4ec9-9626-771914eb3da2\",\"id\":\"b640302338235776966b78b0735abcca\"},\"d8b20410-414f-4777-8614-a7564519c6cd\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Classification.OmniClass.21.Number\",\"units\":null,\"value\":\"21-02 10 10 20 04\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"d8b20410-414f-4777-8614-a7564519c6cd\",\"id\":\"60e194032a186046e1a6db66376b97fb\"},\"db575143-7118-4f29-813e-2ced4535a170\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon B2\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"db575143-7118-4f29-813e-2ced4535a170\",\"id\":\"29e7f431d56d49569acb66b3f0621eb5\"},\"dd0cf380-59d8-4d9f-82da-3e2be59e23a1\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon B7\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"dd0cf380-59d8-4d9f-82da-3e2be59e23a1\",\"id\":\"b2887224f02b48130a75948e3f924e61\"},\"dedec34c-f507-4242-a85f-07a816ff1128\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcName\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"dedec34c-f507-4242-a85f-07a816ff1128\",\"id\":\"eef57991b50ee252c88dcee0dc06fddd\"},\"e4af54de-6137-43b0-97d4-c2260a1a68c3\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcTag\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"e4af54de-6137-43b0-97d4-c2260a1a68c3\",\"id\":\"f00202e050fa8a6ea223906e11e870bd\"},\"ea3ba87c-ae3c-47ed-886d-754f2359389c\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcMaterial\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ea3ba87c-ae3c-47ed-886d-754f2359389c\",\"id\":\"0cd769e3fd9905a59cae6cea37f77b46\"},\"ed6b5c87-b77c-45ab-9d90-8f26e365bca1\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcSpatialContainer\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ed6b5c87-b77c-45ab-9d90-8f26e365bca1\",\"id\":\"76cd1f61df7f11197ead15a92693abae\"},\"ee8153af-4866-45f2-a9a2-b6342ccb1dd6\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcMaterial\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ee8153af-4866-45f2-a9a2-b6342ccb1dd6\",\"id\":\"c32a465b6682dbc0908107858c6b9b6b\"},\"ef2d5da9-0b71-4617-a78c-cf4395808169\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Type Data 3\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"ef2d5da9-0b71-4617-a78c-cf4395808169\",\"id\":\"28aa3437bdf40659590c753a3e842193\"},\"f3b20cd0-059c-48a6-b744-7a9babf1cb29\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Embodied Carbon C4\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"f3b20cd0-059c-48a6-b744-7a9babf1cb29\",\"id\":\"094df4c6e1d89bda1c3d32d19b859e57\"},\"f69d55e2-e23c-4a77-999d-45bae64d5856\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcPropertySetList\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"f69d55e2-e23c-4a77-999d-45bae64d5856\",\"id\":\"3fffe0348a0f398a26003c9552c4dbc9\"},\"f6a1fceb-536f-4261-ad25-4f1fb4dcda76\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Concrete Grade\",\"units\":null,\"value\":\"40 MPa\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"f6a1fceb-536f-4261-ad25-4f1fb4dcda76\",\"id\":\"acd05a5a6632bf49f56fe8d9b32ca1ff\"},\"f7c3a959-7884-46fd-97a6-cca3cea07fe7\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"IfcPropertySetList\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"f7c3a959-7884-46fd-97a6-cca3cea07fe7\",\"id\":\"131c5cc690b61877eac2f73f73cd69ad\"},\"fac5d675-3756-485d-9500-4f2aa3096a38\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Product Name\",\"units\":null,\"value\":null,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"fac5d675-3756-485d-9500-4f2aa3096a38\",\"id\":\"b5ba47412a41ee8f0e0b6309435f08e7\"},\"fb16e643-73bd-4c8d-a506-99506b010546\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Rate PT Area\",\"units\":null,\"value\":4.5,\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":\"autodesk.unit.unit:general-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"fb16e643-73bd-4c8d-a506-99506b010546\",\"id\":\"8607ae9e4c1350ec9eee23830a12c77e\"},\"fb272f85-666a-45a4-ae16-fa4d620d81b7\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Classification.OmniClass.23.Number\",\"units\":null,\"value\":\"23-13 35 23 11 11\",\"isShared\":true,\"isReadOnly\":false,\"applicationUnit\":null,\"isTypeParameter\":true,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"fb272f85-666a-45a4-ae16-fa4d620d81b7\",\"id\":\"5bcbae5ccbbdc5366970db0d9dd64eca\"},\"STRUCTURAL_ELEVATION_AT_BOTTOM_SURVEY\":{\"applicationId\":null,\"speckle_type\":\"Base\",\"name\":\"Elevation at Bottom Survey\",\"units\":\"mm\",\"value\":30699.999999999898,\"isShared\":false,\"isReadOnly\":true,\"applicationUnit\":\"autodesk.unit.unit:millimeters-1.0.1\",\"isTypeParameter\":false,\"totalChildrenCount\":0,\"applicationUnitType\":null,\"applicationInternalName\":\"STRUCTURAL_ELEVATION_AT_BOTTOM_SURVEY\",\"id\":\"830d5e76c5b5b84bbab7f9f52e80dfef\"},\"id\":\"e24896645d6932e8d2edc7b56bcd65b2\"}" )] - [TestCase( + [InlineData( "{\n \"applicationId\": null,\n \"speckle_type\": \"Base\",\n \"name\": \"Physically Based (1)\",\n \"diffuse\": -11810867,\n \"opacity\": 1,\n \"emissive\": -16777216,\n \"metalness\": 0,\n \"roughness\": 0.2,\n \"totalChildrenCount\": 0,\n \"id\": \"3ef9f1e3deb7e8057f9eceb29ff2ea88\"\n}" )] public void Serialize_Id_Stable(string json) @@ -202,12 +200,12 @@ public void Serialize_Id_Stable(string json) jObject.Remove("__closure"); var jsonWithoutId = jObject.ToString(Formatting.None); var newId = IdGenerator.ComputeId(new Json(jsonWithoutId)); - id.ShouldBe(newId.Value); + id.Should().Be(newId.Value); } - [Test] - [TestCase("RevitObject.json.gz", "3416d3fe01c9196115514c4a2f41617b", 7818)] - public async Task Roundtrip_Test_Old(string fileName, string rootId, int count) + [Theory] + [InlineData("RevitObject.json.gz", "3416d3fe01c9196115514c4a2f41617b", 7818)] + public async Task Roundtrip_Test_Old(string fileName, string _, int count) { var fullName = _assembly.GetManifestResourceNames().Single(x => x.EndsWith(fileName)); var json = await ReadJson(fullName); @@ -224,31 +222,31 @@ public async Task Roundtrip_Test_Old(string fileName, string rootId, int count) var newIds = new Dictionary(); var oldIds = new Dictionary(); var idToBase = new Dictionary(); - closure.Count.ShouldBe(count); + closure.Count.Should().Be(count); foreach (var (id, objJson) in closure) { var base1 = await deserializer.DeserializeAsync(objJson); - base1.id.ShouldBe(id); + base1.id.Should().Be(id); var j = serializer.Serialize(base1); - //j.ShouldBe(objJson); + //j.Should().Be(objJson); JToken.DeepEquals(JObject.Parse(j), JObject.Parse(objJson)); newIds.Add(base1.id.NotNull(), j); oldIds.Add(id, j); idToBase.Add(id, base1); } - newIds.Count.ShouldBe(count); - oldIds.Count.ShouldBe(count); - idToBase.Count.ShouldBe(count); + newIds.Count.Should().Be(count); + oldIds.Count.Should().Be(count); + idToBase.Count.Should().Be(count); } - [Test] - [TestCase("RevitObject.json.gz", "3416d3fe01c9196115514c4a2f41617b", 7818, 4674)] + [Theory] + [InlineData("RevitObject.json.gz", "3416d3fe01c9196115514c4a2f41617b", 7818, 4674)] public async Task Roundtrip_Test_New(string fileName, string rootId, int oldCount, int newCount) { var fullName = _assembly.GetManifestResourceNames().Single(x => x.EndsWith(fileName)); var json = await ReadJson(fullName); var closure = ReadAsObjects(json); - closure.Count.ShouldBe(oldCount); + closure.Count.Should().Be(oldCount); var o = new ObjectLoader( new DummySqLiteReceiveManager(closure), @@ -257,8 +255,8 @@ public async Task Roundtrip_Test_New(string fileName, string rootId, int oldCoun ); using var process = new DeserializeProcess(null, o, new ObjectDeserializerFactory(), new(true)); var root = await process.Deserialize(rootId, default); - process.BaseCache.Count.ShouldBe(oldCount); - process.Total.ShouldBe(oldCount); + process.BaseCache.Count.Should().Be(oldCount); + process.Total.Should().Be(oldCount); var newIdToJson = new ConcurrentDictionary(); using var serializeProcess = new SerializeProcess( @@ -271,8 +269,8 @@ public async Task Roundtrip_Test_New(string fileName, string rootId, int oldCoun ); var (rootId2, _) = await serializeProcess.Serialize(root, default); - rootId2.ShouldBe(root.id); - newIdToJson.Count.ShouldBe(newCount); + rootId2.Should().Be(root.id); + newIdToJson.Count.Should().Be(newCount); foreach (var newKvp in newIdToJson) { diff --git a/tests/Speckle.Sdk.Serialization.Tests/Speckle.Sdk.Serialization.Tests.csproj b/tests/Speckle.Sdk.Serialization.Tests/Speckle.Sdk.Serialization.Tests.csproj index ce4c686e..b8028ef4 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/Speckle.Sdk.Serialization.Tests.csproj +++ b/tests/Speckle.Sdk.Serialization.Tests/Speckle.Sdk.Serialization.Tests.csproj @@ -9,10 +9,10 @@ + - - - + + diff --git a/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json b/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json index fcd06d85..0e91de90 100644 --- a/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json +++ b/tests/Speckle.Sdk.Serialization.Tests/packages.lock.json @@ -4,9 +4,18 @@ "net8.0": { "altcover": { "type": "Direct", - "requested": "[8.9.3, )", - "resolved": "8.9.3", - "contentHash": "auKC+pDCkLjfhFkSRaAUBu25BOmlLSqucR7YBs/Lkbdc0XRuJoklWafs1KKp+M+VoJ1f0TeMS6B/FO5IeIcu7w==" + "requested": "[9.0.1, )", + "resolved": "9.0.1", + "contentHash": "aadciFNDT5bnylaYUkKal+s5hF7yU/lmZxImQWAlk1438iPqK1Uf79H5ylELpyLIU49HL5ql+tnWBihp3WVLCA==" + }, + "FluentAssertions": { + "type": "Direct", + "requested": "[7.0.0, )", + "resolved": "7.0.0", + "contentHash": "mTLbcU991EQ1SEmNbVBaGGGJy0YFzvGd1sYJGNZ07nlPKuyHSn1I22aeKzqQXgEiaKyRO6MSCto9eN9VxMwBdA==", + "dependencies": { + "System.Configuration.ConfigurationManager": "6.0.0" + } }, "GitVersion.MsBuild": { "type": "Direct", @@ -16,12 +25,12 @@ }, "Microsoft.NET.Test.Sdk": { "type": "Direct", - "requested": "[17.11.1, )", - "resolved": "17.11.1", - "contentHash": "U3Ty4BaGoEu+T2bwSko9tWqWUOU16WzSFkq6U8zve75oRBMSLTBdMAZrVNNz1Tq12aCdDom9fcOcM9QZaFHqFg==", + "requested": "[17.12.0, )", + "resolved": "17.12.0", + "contentHash": "kt/PKBZ91rFCWxVIJZSgVLk+YR+4KxTuHf799ho8WNiK5ZQpJNAEZCAWX86vcKrs+DiYjiibpYKdGZP6+/N17w==", "dependencies": { - "Microsoft.CodeCoverage": "17.11.1", - "Microsoft.TestPlatform.TestHost": "17.11.1" + "Microsoft.CodeCoverage": "17.12.0", + "Microsoft.TestPlatform.TestHost": "17.12.0" } }, "Microsoft.SourceLink.GitHub": { @@ -34,53 +43,34 @@ "Microsoft.SourceLink.Common": "8.0.0" } }, - "NUnit": { - "type": "Direct", - "requested": "[4.2.2, )", - "resolved": "4.2.2", - "contentHash": "mon0OPko28yZ/foVXrhiUvq1LReaGsBdziumyyYGxV/pOE4q92fuYeN+AF+gEU5pCjzykcdBt5l7xobTaiBjsg==" - }, - "NUnit3TestAdapter": { - "type": "Direct", - "requested": "[4.6.0, )", - "resolved": "4.6.0", - "contentHash": "R7e1+a4vuV/YS+ItfL7f//rG+JBvVeVLX4mHzFEZo4W1qEKl8Zz27AqvQSAqo+BtIzUCo4aAJMYa56VXS4hudw==" - }, "PolySharp": { "type": "Direct", "requested": "[1.15.0, )", "resolved": "1.15.0", "contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g==" }, - "Shouldly": { - "type": "Direct", - "requested": "[4.2.1, )", - "resolved": "4.2.1", - "contentHash": "dKAKiSuhLKqD2TXwLKtqNg1nwzJcIKOOMncZjk9LYe4W+h+SCftpWdxwR79YZUIHMH+3Vu9s0s0UHNrgICLwRQ==", - "dependencies": { - "DiffEngine": "11.3.0", - "EmptyFiles": "4.4.0" - } - }, "Speckle.InterfaceGenerator": { "type": "Direct", "requested": "[0.9.6, )", "resolved": "0.9.6", "contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w==" }, - "DiffEngine": { - "type": "Transitive", - "resolved": "11.3.0", - "contentHash": "k0ZgZqd09jLZQjR8FyQbSQE86Q7QZnjEzq1LPHtj1R2AoWO8sjV5x+jlSisL7NZAbUOI4y+7Bog8gkr9WIRBGw==", + "xunit": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==", "dependencies": { - "EmptyFiles": "4.4.0", - "System.Management": "6.0.1" + "xunit.analyzers": "1.18.0", + "xunit.assert": "2.9.3", + "xunit.core": "[2.9.3]" } }, - "EmptyFiles": { - "type": "Transitive", - "resolved": "4.4.0", - "contentHash": "gwJEfIGS7FhykvtZoscwXj/XwW+mJY6UbAZk+qtLKFUGWC95kfKXnj8VkxsZQnWBxJemM/q664rGLN5nf+OHZw==" + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.0.0, )", + "resolved": "3.0.0", + "contentHash": "HggUqjQJe8PtDxcP25Q+CnR6Lz4oX3GElhD9V4oU2+75x9HI6A6sxbfKGS4UwU4t4yJaS9fBmAuriz8bQApNjw==" }, "GraphQL.Client.Abstractions": { "type": "Transitive", @@ -110,8 +100,8 @@ }, "Microsoft.CodeCoverage": { "type": "Transitive", - "resolved": "17.11.1", - "contentHash": "nPJqrcA5iX+Y0kqoT3a+pD/8lrW/V7ayqnEJQsTonSoPz59J8bmoQhcSN4G8+UJ64Hkuf0zuxnfuj2lkHOq4cA==" + "resolved": "17.12.0", + "contentHash": "4svMznBd5JM21JIG2xZKGNanAHNXplxf/kQDFfLHXQ3OnpJkayRK/TjacFjA+EYmoyuNXHo/sOETEfcYtAzIrA==" }, "Microsoft.Data.Sqlite.Core": { "type": "Transitive", @@ -176,21 +166,26 @@ }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", - "resolved": "17.11.1", - "contentHash": "E2jZqAU6JeWEVsyOEOrSW1o1bpHLgb25ypvKNB/moBXPVsFYBPd/Jwi7OrYahG50J83LfHzezYI+GaEkpAotiA==", + "resolved": "17.12.0", + "contentHash": "TDqkTKLfQuAaPcEb3pDDWnh7b3SyZF+/W9OZvWFp6eJCIiiYFdSB6taE2I6tWrFw5ywhzOb6sreoGJTI6m3rSQ==", "dependencies": { "System.Reflection.Metadata": "1.6.0" } }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", - "resolved": "17.11.1", - "contentHash": "DnG+GOqJXO/CkoqlJWeDFTgPhqD/V6VqUIL3vINizCWZ3X+HshCtbbyDdSHQQEjrc2Sl/K3yaxX6s+5LFEdYuw==", + "resolved": "17.12.0", + "contentHash": "MiPEJQNyADfwZ4pJNpQex+t9/jOClBGMiCiVVFuELCMSX2nmNfvUor3uFVxNNCg30uxDP8JDYfPnMXQzsfzYyg==", "dependencies": { - "Microsoft.TestPlatform.ObjectModel": "17.11.1", + "Microsoft.TestPlatform.ObjectModel": "17.12.0", "Newtonsoft.Json": "13.0.1" } }, + "Microsoft.Win32.SystemEvents": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A==" + }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "13.0.1", @@ -226,22 +221,26 @@ "SQLitePCLRaw.core": "2.1.4" } }, - "System.CodeDom": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA==" - }, "System.ComponentModel.Annotations": { "type": "Transitive", "resolved": "4.5.0", "contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg==" }, - "System.Management": { + "System.Configuration.ConfigurationManager": { "type": "Transitive", - "resolved": "6.0.1", - "contentHash": "10J1D0h/lioojphfJ4Fuh5ZUThT/xOVHdV9roGBittKKNP2PMjrvibEdbVTGZcPra1399Ja3tqIJLyQrc5Wmhg==", + "resolved": "6.0.0", + "contentHash": "7T+m0kDSlIPTHIkPMIu6m6tV6qsMqJpvQWW2jIc2qi7sn40qxFo0q+7mEQAhMPXZHMKnWrnv47ntGlM/ejvw3g==", "dependencies": { - "System.CodeDom": "6.0.0" + "System.Security.Cryptography.ProtectedData": "6.0.0", + "System.Security.Permissions": "6.0.0" + } + }, + "System.Drawing.Common": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "NfuoKUiP2nUWwKZN6twGqXioIe1zVD0RIj2t976A+czLHr2nY454RwwXs6JU9Htc6mwqL6Dn/nEL3dpVf2jOhg==", + "dependencies": { + "Microsoft.Win32.SystemEvents": "6.0.0" } }, "System.Memory": { @@ -264,6 +263,73 @@ "resolved": "4.5.1", "contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw==" }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ==" + }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "rp1gMNEZpvx9vP0JW0oHLxlf8oSiQgtno77Y4PLUBjSiDYoD77Y8uXHr1Ea5XG4/pIKhqAdxZ8v8OTUtqo9PeQ==" + }, + "System.Security.Permissions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "T/uuc7AklkDoxmcJ7LGkyX1CcSviZuLCa4jg3PekfJ7SU0niF0IVTXwUiNVP9DSpzou2PpxJ+eNY2IfDM90ZCg==", + "dependencies": { + "System.Security.AccessControl": "6.0.0", + "System.Windows.Extensions": "6.0.0" + } + }, + "System.Windows.Extensions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "IXoJOXIqc39AIe+CIR7koBtRGMiCt/LPM3lI+PELtDIy9XdyeSrwXFdWV9dzJ2Awl0paLWUaknLxFQ5HpHZUog==", + "dependencies": { + "System.Drawing.Common": "6.0.0" + } + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.18.0", + "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==" + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]", + "xunit.extensibility.execution": "[2.9.3]" + } + }, + "xunit.extensibility.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==", + "dependencies": { + "xunit.abstractions": "2.0.3" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]" + } + }, "speckle.objects": { "type": "Project", "dependencies": { diff --git a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/GraphQLClientExceptionHandling.cs b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/GraphQLClientExceptionHandling.cs index dfa31a87..4f81fd93 100644 --- a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/GraphQLClientExceptionHandling.cs +++ b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/GraphQLClientExceptionHandling.cs @@ -1,36 +1,39 @@ -using GraphQL; +using System.ComponentModel; +using FluentAssertions; +using GraphQL; using GraphQL.Client.Http; using Speckle.Newtonsoft.Json; using Speckle.Sdk.Api; using Speckle.Sdk.Api.GraphQL.Inputs; +using Xunit; namespace Speckle.Sdk.Tests.Integration.Api.GraphQL; -[TestOf(typeof(Client))] -public class GraphQLClientExceptionHandling +public class GraphQLClientExceptionHandling : IAsyncLifetime { private Client _sut; - [SetUp] - public async Task Setup() + public Task DisposeAsync() => Task.CompletedTask; + + public async Task InitializeAsync() { _sut = await Fixtures.SeedUserWithClient(); } - [Test] + [Fact] [Description($"Attempts to execute a query on a non-existent server, expect a {nameof(GraphQLHttpRequestException)}")] - public void TestHttpLayer() + public async Task TestHttpLayer() { _sut.GQLClient.Options.EndPoint = new Uri("http://127.0.0.1:1234"); //There is no server on this port... - Assert.ThrowsAsync(async () => await _sut.ActiveUser.Get().ConfigureAwait(false)); + await Assert.ThrowsAsync(async () => await _sut.ActiveUser.Get().ConfigureAwait(false)); } - [Test] + [Fact] [Description( $"Attempts to execute a admin only command from a regular user, expect an inner {nameof(SpeckleGraphQLForbiddenException)}" )] - public void TestGraphQLLayer_Forbidden() + public async Task TestGraphQLLayer_Forbidden() { //language=graphql const string QUERY = """ @@ -46,14 +49,14 @@ query Query { """; GraphQLRequest request = new(query: QUERY); - var ex = Assert.ThrowsAsync( + var ex = await Assert.ThrowsAsync( async () => await _sut.ExecuteGraphQLRequest(request).ConfigureAwait(false) ); - Assert.That(ex?.InnerExceptions, Has.Exactly(1).TypeOf()); + ex.InnerExceptions.OfType().Count().Should().Be(1); } - [Test, Description($"Attempts to execute a bad query, expect an inner {nameof(SpeckleGraphQLInvalidQueryException)}")] - public void TestGraphQLLayer_BadQuery() + [Fact, Description($"Attempts to execute a bad query, expect an inner {nameof(SpeckleGraphQLInvalidQueryException)}")] + public async Task TestGraphQLLayer_BadQuery() { //language=graphql const string QUERY = """ @@ -64,41 +67,39 @@ query User { } """; GraphQLRequest request = new(query: QUERY); - var ex = Assert.ThrowsAsync( + var ex = await Assert.ThrowsAsync( async () => await _sut.ExecuteGraphQLRequest(request).ConfigureAwait(false) ); - - Assert.That(ex?.InnerExceptions, Has.Exactly(1).TypeOf()); + ex.InnerExceptions.OfType().Count().Should().Be(1); } - [Test] + [Fact] [Description( $"Attempts to execute a query with an invalid input, expect an inner {nameof(SpeckleGraphQLBadInputException)}" )] - public void TestGraphQLLayer_BadInput() + public async Task TestGraphQLLayer_BadInput() { ProjectUpdateRoleInput input = new(null!, null!, null); - var ex = Assert.ThrowsAsync( + var ex = await Assert.ThrowsAsync( async () => await _sut.Project.UpdateRole(input).ConfigureAwait(false) ); - - Assert.That(ex?.InnerExceptions, Has.Exactly(2).TypeOf()); + ex.InnerExceptions.OfType().Count().Should().Be(2); } - [Test] - public void TestCancel() + [Fact] + public async Task TestCancel() { using CancellationTokenSource cts = new(); - cts.Cancel(); + await cts.CancelAsync(); - var ex = Assert.CatchAsync( + var ex = await Assert.ThrowsAsync( async () => await _sut.ActiveUser.Get(cts.Token).ConfigureAwait(false) ); - Assert.That(ex?.CancellationToken, Is.EqualTo(cts.Token)); + ex.CancellationToken.Should().BeEquivalentTo(cts.Token); } - [Test] + [Fact] public void TestDisposal() { _sut.Dispose(); @@ -107,10 +108,10 @@ public void TestDisposal() } [ - Test, + Fact, Description($"Attempts to execute a query with a mismatched type, expect an {nameof(JsonSerializationException)}") ] - public void TestDeserialization() + public async Task TestDeserialization() { //language=graphql const string QUERY = """ @@ -121,6 +122,8 @@ query User { } """; GraphQLRequest request = new(query: QUERY); - Assert.CatchAsync(async () => await _sut.ExecuteGraphQLRequest(request).ConfigureAwait(false)); + await Assert.ThrowsAsync( + async () => await _sut.ExecuteGraphQLRequest(request).ConfigureAwait(false) + ); } } diff --git a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ActiveUserResourceTests.cs b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ActiveUserResourceTests.cs index 46b325ad..f4f99370 100644 --- a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ActiveUserResourceTests.cs +++ b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ActiveUserResourceTests.cs @@ -1,53 +1,60 @@ -using Speckle.Sdk.Api; +using FluentAssertions; +using Speckle.Sdk.Api; using Speckle.Sdk.Api.GraphQL.Inputs; -using Speckle.Sdk.Api.GraphQL.Models; using Speckle.Sdk.Api.GraphQL.Resources; +using Xunit; namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources; -[TestOf(typeof(ActiveUserResource))] -public class ActiveUserResourceTests +public class ActiveUserResourceTests : IAsyncLifetime { private Client _testUser; private ActiveUserResource Sut => _testUser.ActiveUser; - [OneTimeSetUp] - public async Task Setup() + // Setup method for xUnit using IAsyncLifetime + public async Task InitializeAsync() { _testUser = await Fixtures.SeedUserWithClient(); } - [Test] + public Task DisposeAsync() + { + // No resources to dispose + return Task.CompletedTask; + } + + [Fact] public async Task ActiveUserGet() { var res = await Sut.Get(); - Assert.That(res, Is.Not.Null); - Assert.That(res!.id, Is.EqualTo(_testUser.Account.userInfo.id)); + res.Should().NotBeNull(); + res!.id.Should().Be(_testUser.Account.userInfo.id); } - [Test] + [Fact] public async Task ActiveUserGet_NonAuthed() { var result = await Fixtures.Unauthed.ActiveUser.Get(); - Assert.That(result, Is.EqualTo(null)); + result.Should().BeNull(); } - [Test] + [Fact] public async Task ActiveUserUpdate() { const string NEW_NAME = "Ron"; const string NEW_BIO = "Now I have a bio, isn't that nice!"; const string NEW_COMPANY = "Limited Cooperation Organization Inc"; + var res = await Sut.Update(new UserUpdateInput(name: NEW_NAME, bio: NEW_BIO, company: NEW_COMPANY)); - Assert.That(res, Is.Not.Null); - Assert.That(res.id, Is.EqualTo(_testUser.Account.userInfo.id)); - Assert.That(res.name, Is.EqualTo(NEW_NAME)); - Assert.That(res.company, Is.EqualTo(NEW_COMPANY)); - Assert.That(res.bio, Is.EqualTo(NEW_BIO)); + res.Should().NotBeNull(); + res.id.Should().Be(_testUser.Account.userInfo.id); + res.name.Should().Be(NEW_NAME); + res.company.Should().Be(NEW_COMPANY); + res.bio.Should().Be(NEW_BIO); } - [Test] + [Fact] public async Task ActiveUserGetProjects() { var p1 = await _testUser.Project.Create(new("Project 1", null, null)); @@ -55,14 +62,17 @@ public async Task ActiveUserGetProjects() var res = await Sut.GetProjects(); - Assert.That(res.items, Has.Exactly(1).Items.With.Property(nameof(Project.id)).EqualTo(p1.id)); - Assert.That(res.items, Has.Exactly(1).Items.With.Property(nameof(Project.id)).EqualTo(p2.id)); - Assert.That(res.items, Has.Count.EqualTo(2)); + res.items.Should().Contain(x => x.id == p1.id); + res.items.Should().Contain(x => x.id == p2.id); + res.items.Count.Should().Be(2); } - [Test] - public void ActiveUserGetProjects_NoAuth() + [Fact] + public async Task ActiveUserGetProjects_NoAuth() { - Assert.ThrowsAsync(async () => await Fixtures.Unauthed.ActiveUser.GetProjects()); + await FluentActions + .Invoking(async () => await Fixtures.Unauthed.ActiveUser.GetProjects()) + .Should() + .ThrowAsync(); } } diff --git a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/CommentResourceTests.cs b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/CommentResourceTests.cs index a6b90605..83373aad 100644 --- a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/CommentResourceTests.cs +++ b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/CommentResourceTests.cs @@ -1,104 +1,111 @@ -using Speckle.Sdk.Api; +using FluentAssertions; +using Speckle.Sdk.Api; using Speckle.Sdk.Api.GraphQL.Inputs; using Speckle.Sdk.Api.GraphQL.Models; using Speckle.Sdk.Api.GraphQL.Resources; using Speckle.Sdk.Common; +using Xunit; namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources; -[TestOf(typeof(CommentResource))] public class CommentResourceTests { - private Client _testUser; - private CommentResource Sut => _testUser.Comment; - private Project _project; - private Model _model; - private string _versionId; - private Comment _comment; - - [SetUp] - public async Task Setup() + private readonly Client _testUser; + private readonly CommentResource Sut; + private readonly Project _project; + private readonly Model _model; + private readonly string _versionId; + private readonly Comment _comment; + + // Constructor for setup + public CommentResourceTests() { - _testUser = await Fixtures.SeedUserWithClient(); - _project = await _testUser.Project.Create(new("Test project", "", null)); - _model = await _testUser.Model.Create(new("Test Model 1", "", _project.id)); - _versionId = await Fixtures.CreateVersion(_testUser, _project.id, _model.id); - _comment = await CreateComment(); + // Synchronous operations converted to async Task.Run for constructor + _testUser = Task.Run(async () => await Fixtures.SeedUserWithClient()).Result!; + _project = Task.Run(async () => await _testUser.Project.Create(new("Test project", "", null))).Result!; + _model = Task.Run(async () => await _testUser.Model.Create(new("Test Model 1", "", _project.id))).Result!; + _versionId = Task.Run(async () => await Fixtures.CreateVersion(_testUser, _project.id, _model.id)).Result!; + _comment = Task.Run(CreateComment).Result!; + Sut = _testUser.Comment; } - [Test] + [Fact] public async Task Get() { var comment = await Sut.Get(_comment.id, _project.id); - Assert.That(comment.id, Is.EqualTo(_comment.id)); - Assert.That(comment.authorId, Is.EqualTo(_testUser.Account.userInfo.id)); + + comment.Should().NotBeNull(); + comment.id.Should().Be(_comment.id); + comment.authorId.Should().Be(_testUser.Account.userInfo.id); } - [Test] + [Fact] public async Task GetProjectComments() { var comments = await Sut.GetProjectComments(_project.id); - Assert.That(comments.items.Count, Is.EqualTo(1)); - Assert.That(comments.totalCount, Is.EqualTo(1)); + + comments.Should().NotBeNull(); + comments.items.Count.Should().Be(1); + comments.totalCount.Should().Be(1); Comment comment = comments.items[0]; - Assert.That(comment, Is.Not.Null); - Assert.That(comment, Has.Property(nameof(Comment.authorId)).EqualTo(_testUser.Account.userInfo.id)); - - Assert.That(comment, Has.Property(nameof(Comment.id)).EqualTo(_comment.id)); - Assert.That(comment, Has.Property(nameof(Comment.authorId)).EqualTo(_comment.authorId)); - Assert.That(comment, Has.Property(nameof(Comment.archived)).EqualTo(_comment.archived)); - Assert.That(comment, Has.Property(nameof(Comment.archived)).EqualTo(false)); - Assert.That(comment, Has.Property(nameof(Comment.createdAt)).EqualTo(_comment.createdAt)); + comment.Should().NotBeNull(); + comment.authorId.Should().Be(_testUser.Account.userInfo.id); + comment.id.Should().Be(_comment.id); + comment.authorId.Should().Be(_comment.authorId); + comment.archived.Should().Be(false); + comment.createdAt.Should().Be(_comment.createdAt); } - [Test] + [Fact] public async Task MarkViewed() { await Sut.MarkViewed(new(_comment.id, _project.id)); - var res = await Sut.Get(_comment.id, _project.id); - Assert.That(res.viewedAt, Is.Not.Null); + var res = await Sut.Get(_comment.id, _project.id); + res.viewedAt.Should().NotBeNull(); } - [Test] + [Fact] public async Task Archive() { await Sut.Archive(new(_comment.id, _project.id, true)); var archived = await Sut.Get(_comment.id, _project.id); - Assert.That(archived.archived, Is.True); + + archived.archived.Should().BeTrue(); await Sut.Archive(new(_comment.id, _project.id, false)); var unarchived = await Sut.Get(_comment.id, _project.id); - Assert.That(unarchived.archived, Is.False); + + unarchived.archived.Should().BeFalse(); } - [Test] + [Fact] public async Task Edit() { var blobs = await Fixtures.SendBlobData(_testUser.Account, _project.id); var blobIds = blobs.Select(b => b.id.NotNull()).ToList(); - EditCommentInput input = new(new(blobIds, null), _comment.id, _project.id); + var input = new EditCommentInput(new(blobIds, null), _comment.id, _project.id); var editedComment = await Sut.Edit(input); - Assert.That(editedComment, Is.Not.Null); - Assert.That(editedComment, Has.Property(nameof(Comment.id)).EqualTo(_comment.id)); - Assert.That(editedComment, Has.Property(nameof(Comment.authorId)).EqualTo(_comment.authorId)); - Assert.That(editedComment, Has.Property(nameof(Comment.createdAt)).EqualTo(_comment.createdAt)); - Assert.That(editedComment, Has.Property(nameof(Comment.updatedAt)).GreaterThanOrEqualTo(_comment.updatedAt)); + editedComment.Should().NotBeNull(); + editedComment.id.Should().Be(_comment.id); + editedComment.authorId.Should().Be(_comment.authorId); + editedComment.createdAt.Should().Be(_comment.createdAt); + editedComment.updatedAt.Should().BeOnOrAfter(_comment.updatedAt); } - [Test] + [Fact] public async Task Reply() { var blobs = await Fixtures.SendBlobData(_testUser.Account, _project.id); var blobIds = blobs.Select(b => b.id.NotNull()).ToList(); - CreateCommentReplyInput input = new(new(blobIds, null), _comment.id, _project.id); + var input = new CreateCommentReplyInput(new(blobIds, null), _comment.id, _project.id); var editedComment = await Sut.Reply(input); - Assert.That(editedComment, Is.Not.Null); + editedComment.Should().NotBeNull(); } private async Task CreateComment() diff --git a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelResourceExceptionalTests.cs b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelResourceExceptionalTests.cs index 7b72fb3f..91c995fd 100644 --- a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelResourceExceptionalTests.cs +++ b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelResourceExceptionalTests.cs @@ -1,94 +1,137 @@ -using Speckle.Sdk.Api; +using FluentAssertions; +using Speckle.Sdk.Api; using Speckle.Sdk.Api.GraphQL.Enums; using Speckle.Sdk.Api.GraphQL.Inputs; using Speckle.Sdk.Api.GraphQL.Models; using Speckle.Sdk.Api.GraphQL.Resources; +using Xunit; namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources; -[TestOf(typeof(ModelResource))] -public class ModelResourceExceptionalTests +public class ModelResourceExceptionalTests : IAsyncLifetime { private Client _testUser; private ModelResource Sut => _testUser.Model; private Project _project; private Model _model; - [OneTimeSetUp] - public async Task Setup() + // Replaces NUnit's OneTimeSetUp with an async constructor logic or initializer pattern + public async Task InitializeAsync() { _testUser = await Fixtures.SeedUserWithClient(); _project = await _testUser.Project.Create(new("Test project", "", ProjectVisibility.Private)); _model = await _testUser.Model.Create(new("Test Model", "", _project.id)); } - [TestCase("")] - [TestCase(" ")] - public void ModelCreate_Throws_InvalidInput(string name) + public Task DisposeAsync() => Task.CompletedTask; + + [Theory] + [InlineData("")] + [InlineData(" ")] + public async Task ModelCreate_Throws_InvalidInput(string name) { + // Arrange CreateModelInput input = new(name, null, _project.id); - var ex = Assert.ThrowsAsync(async () => await Sut.Create(input)); - Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf()); + + // Act & Assert + var ex = await FluentActions + .Invoking(async () => await Sut.Create(input)) + .Should() + .ThrowAsync(); + + ex.WithInnerExceptionExactly(); } - [Test] - public void ModelGet_Throws_NoAuth() + [Fact] + public async Task ModelGet_Throws_NoAuth() { - var ex = Assert.ThrowsAsync( - async () => await Fixtures.Unauthed.Model.Get(_model.id, _project.id) - ); - Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf()); + // Act & Assert + var ex = await FluentActions + .Invoking(async () => await Fixtures.Unauthed.Model.Get(_model.id, _project.id)) + .Should() + .ThrowAsync(); + + ex.WithInnerExceptionExactly(); } - [Test] - public void ModelGet_Throws_NonExistentModel() + [Fact] + public async Task ModelGet_Throws_NonExistentModel() { - var ex = Assert.ThrowsAsync(async () => await Sut.Get("non existent model", _project.id)); - Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf()); + // Act & Assert + var ex = await FluentActions + .Invoking(async () => await Sut.Get("non existent model", _project.id)) + .Should() + .ThrowAsync(); + ex.WithInnerExceptionExactly(); } - [Test] - public void ModelGet_Throws_NonExistentProject() + [Fact] + public async Task ModelGet_Throws_NonExistentProject() { - var ex = Assert.ThrowsAsync(async () => await Sut.Get(_model.id, "non existent project")); - Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf()); + // Act & Assert + var ex = await FluentActions + .Invoking(async () => await Sut.Get(_model.id, "non existent project")) + .Should() + .ThrowAsync(); + ex.WithInnerExceptionExactly(); } - [Test] - public void ModelUpdate_Throws_NonExistentModel() + [Fact] + public async Task ModelUpdate_Throws_NonExistentModel() { + // Arrange UpdateModelInput input = new("non-existent model", "MY new name", "MY new desc", _project.id); - var ex = Assert.ThrowsAsync(async () => await Sut.Update(input)); - Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf()); + // Act & Assert + var ex = await FluentActions + .Invoking(async () => await Sut.Update(input)) + .Should() + .ThrowAsync(); + ex.WithInnerExceptionExactly(); } - [Test] - public void ModelUpdate_Throws_NonExistentProject() + [Fact] + public async Task ModelUpdate_Throws_NonExistentProject() { + // Arrange UpdateModelInput input = new(_model.id, "MY new name", "MY new desc", "non-existent project"); - var ex = Assert.ThrowsAsync(async () => await Sut.Update(input)); - Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf()); + // Act & Assert + var ex = await FluentActions + .Invoking(async () => await Sut.Update(input)) + .Should() + .ThrowAsync(); + ex.WithInnerExceptionExactly(); } - [Test] - public void ModelUpdate_Throws_NonAuthProject() + [Fact] + public async Task ModelUpdate_Throws_NonAuthProject() { + // Arrange UpdateModelInput input = new(_model.id, "MY new name", "MY new desc", _project.id); - var ex = Assert.ThrowsAsync(async () => await Fixtures.Unauthed.Model.Update(input)); - Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf()); + // Act & Assert + var ex = await FluentActions + .Invoking(async () => await Fixtures.Unauthed.Model.Update(input)) + .Should() + .ThrowAsync(); + ex.WithInnerExceptionExactly(); } - [Test] + [Fact] public async Task ModelDelete_Throws_NoAuth() { + // Arrange Model toDelete = await Sut.Create(new("Delete me", null, _project.id)); DeleteModelInput input = new(toDelete.id, _project.id); + await Sut.Delete(input); - var ex = Assert.ThrowsAsync(async () => await Sut.Delete(input)); - Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf()); + // Act & Assert + var ex = await FluentActions + .Invoking(async () => await Sut.Delete(input)) + .Should() + .ThrowAsync(); + ex.WithInnerExceptionExactly(); } } diff --git a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelResourceTests.cs b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelResourceTests.cs index b3bcdedc..19051efb 100644 --- a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelResourceTests.cs +++ b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelResourceTests.cs @@ -1,100 +1,127 @@ -using Speckle.Sdk.Api; +using FluentAssertions; +using Speckle.Sdk.Api; using Speckle.Sdk.Api.GraphQL.Inputs; using Speckle.Sdk.Api.GraphQL.Models; using Speckle.Sdk.Api.GraphQL.Resources; +using Xunit; namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources; -[TestOf(typeof(ModelResource))] -public class ModelResourceTests +public class ModelResourceTests : IAsyncLifetime { private Client _testUser; private ModelResource Sut => _testUser.Model; private Project _project; private Model _model; - [SetUp] - public async Task Setup() + public async Task InitializeAsync() { + // Runs instead of [SetUp] in NUnit _testUser = await Fixtures.SeedUserWithClient(); _project = await _testUser.Project.Create(new("Test project", "", null)); _model = await _testUser.Model.Create(new("Test Model", "", _project.id)); } - [Order(1)] - [TestCase("My Model", "My model description")] - [TestCase("my/nested/model", null)] - public async Task ModelCreate(string name, string description) + public Task DisposeAsync() { + // Perform any cleanup, if needed + return Task.CompletedTask; + } + + [Theory] + [InlineData("My Model", "My model description")] + [InlineData("my/nested/model", null)] + public async Task ModelCreate(string name, string? description) + { + // Arrange CreateModelInput input = new(name, description, _project.id); + + // Act Model result = await Sut.Create(input); - Assert.That(result, Is.Not.Null); - Assert.That(result, Has.Property(nameof(result.id)).Not.Null); - Assert.That(result, Has.Property(nameof(result.name)).EqualTo(input.name).IgnoreCase); - Assert.That(result, Has.Property(nameof(result.description)).EqualTo(input.description)); + // Assert + result.Should().NotBeNull(); + result.id.Should().NotBeNull(); + result.name.Should().ContainEquivalentOf(input.name); + result.description.Should().Be(input.description); } - [Test] + [Fact] public async Task ModelGet() { + // Act Model result = await Sut.Get(_model.id, _project.id); - Assert.That(result.id, Is.EqualTo(_model.id)); - Assert.That(result.name, Is.EqualTo(_model.name)); - Assert.That(result.description, Is.EqualTo(_model.description)); - Assert.That(result.createdAt, Is.EqualTo(_model.createdAt)); - Assert.That(result.updatedAt, Is.EqualTo(_model.updatedAt)); + // Assert + result.id.Should().Be(_model.id); + result.name.Should().Be(_model.name); + result.description.Should().Be(_model.description); + result.createdAt.Should().Be(_model.createdAt); + result.updatedAt.Should().Be(_model.updatedAt); } - [Test] - [Order(2)] + [Fact] public async Task GetModels() { + // Act var result = await Sut.GetModels(_project.id); - Assert.That(result.items, Has.Count.EqualTo(1)); - Assert.That(result.totalCount, Is.EqualTo(1)); - Assert.That(result.items[0], Has.Property(nameof(Model.id)).EqualTo(_model.id)); + // Assert + result.items.Count.Should().Be(1); + result.totalCount.Should().Be(1); + result.items[0].id.Should().Be(_model.id); } - [Test] + [Fact] public async Task Project_GetModels() { + // Act var result = await _testUser.Project.GetWithModels(_project.id); - Assert.That(result, Has.Property(nameof(Project.id)).EqualTo(_project.id)); - Assert.That(result.models.items, Has.Count.EqualTo(1)); - Assert.That(result.models.totalCount, Is.EqualTo(1)); - Assert.That(result.models.items[0], Has.Property(nameof(Model.id)).EqualTo(_model.id)); + // Assert + result.id.Should().Be(_project.id); + result.models.items.Count.Should().Be(1); + result.models.totalCount.Should().Be(1); + result.models.items[0].id.Should().Be(_model.id); } - [Test] + [Fact] public async Task ModelUpdate() { + // Arrange const string NEW_NAME = "MY new name"; const string NEW_DESCRIPTION = "MY new desc"; - UpdateModelInput input = new(_model.id, NEW_NAME, NEW_DESCRIPTION, _project.id); + var input = new UpdateModelInput(_model.id, NEW_NAME, NEW_DESCRIPTION, _project.id); + + // Act Model updatedModel = await Sut.Update(input); - Assert.That(updatedModel.id, Is.EqualTo(_model.id)); - Assert.That(updatedModel.name, Is.EqualTo(NEW_NAME).IgnoreCase); - Assert.That(updatedModel.description, Is.EqualTo(NEW_DESCRIPTION)); - Assert.That(updatedModel.updatedAt, Is.GreaterThanOrEqualTo(_model.updatedAt)); + // Assert + updatedModel.id.Should().Be(_model.id); + updatedModel.name.Should().ContainEquivalentOf(NEW_NAME); + updatedModel.description.Should().Be(NEW_DESCRIPTION); + updatedModel.updatedAt.Should().BeOnOrAfter(_model.updatedAt); } - [Test] + [Fact] public async Task ModelDelete() { - DeleteModelInput input = new(_model.id, _project.id); + // Arrange + var input = new DeleteModelInput(_model.id, _project.id); + // Act await Sut.Delete(input); - var getEx = Assert.CatchAsync(async () => await Sut.Get(_model.id, _project.id)); - Assert.That(getEx?.InnerExceptions, Has.One.Items.And.All.TypeOf()); + // Assert: Ensure fetching the deleted model throws an exception + var getEx = await FluentActions + .Invoking(() => Sut.Get(_model.id, _project.id)) + .Should() + .ThrowAsync(); + getEx.WithInnerExceptionExactly(); - var delEx = Assert.CatchAsync(async () => await Sut.Delete(input)); - Assert.That(delEx?.InnerExceptions, Has.One.Items.And.All.TypeOf()); + // Assert: Ensure deleting the non-existing model again throws an exception + var delEx = await FluentActions.Invoking(() => Sut.Delete(input)).Should().ThrowAsync(); + getEx.WithInnerExceptionExactly(); } } diff --git a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/OtherUserResourceTests.cs b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/OtherUserResourceTests.cs index 6ac9818e..86e41c71 100644 --- a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/OtherUserResourceTests.cs +++ b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/OtherUserResourceTests.cs @@ -1,50 +1,54 @@ -using Speckle.Sdk.Api; +using FluentAssertions; +using Speckle.Sdk.Api; using Speckle.Sdk.Api.GraphQL.Resources; using Speckle.Sdk.Credentials; +using Xunit; namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources; -[TestOf(typeof(OtherUserResource))] public class OtherUserResourceTests { - private Client _testUser; - private Account _testData; + private readonly Client _testUser; + private readonly Account _testData; private OtherUserResource Sut => _testUser.OtherUser; - [OneTimeSetUp] - public async Task Setup() + public OtherUserResourceTests() { - _testUser = await Fixtures.SeedUserWithClient(); - _testData = await Fixtures.SeedUser(); + _testUser = Fixtures.SeedUserWithClient().GetAwaiter().GetResult(); + _testData = Fixtures.SeedUser().GetAwaiter().GetResult(); } - [Test] - public async Task OtherUserGet() + [Fact] + public async Task OtherUserGet_Should_ReturnCorrectUser() { var res = await Sut.Get(_testData.userInfo.id); - Assert.That(res, Is.Not.Null); - Assert.That(res!.name, Is.EqualTo(_testData.userInfo.name)); + + res.Should().NotBeNull(); + res!.name.Should().Be(_testData.userInfo.name); } - [Test] - public async Task OtherUserGet_NonExistentUser() + [Fact] + public async Task OtherUserGet_NonExistentUser_Should_ReturnNull() { var result = await Sut.Get("AnIdThatDoesntExist"); - Assert.That(result, Is.Null); + + result.Should().BeNull(); } - [Test] - public async Task UserSearch() + [Fact] + public async Task UserSearch_Should_ReturnMatchingUser() { var res = await Sut.UserSearch(_testData.userInfo.email, 25); - Assert.That(res.items, Has.Count.EqualTo(1)); - Assert.That(res.items[0].id, Is.EqualTo(_testData.userInfo.id)); + + res.items.Should().ContainSingle(); + res.items[0].id.Should().Be(_testData.userInfo.id); } - [Test] - public async Task UserSearch_NonExistentUser() + [Fact] + public async Task UserSearch_NonExistentUser_Should_ReturnEmptyList() { var res = await Sut.UserSearch("idontexist@example.com", 25); - Assert.That(res.items, Has.Count.EqualTo(0)); + + res.items.Should().BeEmpty(); } } diff --git a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ProjectInviteResourceExceptionalTests.cs b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ProjectInviteResourceExceptionalTests.cs index 07151fbc..23137093 100644 --- a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ProjectInviteResourceExceptionalTests.cs +++ b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ProjectInviteResourceExceptionalTests.cs @@ -1,33 +1,48 @@ -using Speckle.Sdk.Api; +using FluentAssertions; +using Speckle.Sdk.Api; using Speckle.Sdk.Api.GraphQL.Inputs; using Speckle.Sdk.Api.GraphQL.Models; using Speckle.Sdk.Api.GraphQL.Resources; +using Xunit; namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources; -[TestOf(typeof(ProjectInviteResource))] -public class ProjectInviteResourceExceptionalTests +public class ProjectInviteResourceExceptionalTests : IAsyncLifetime { private Client _testUser; private Project _project; private ProjectInviteResource Sut => _testUser.ProjectInvite; - [OneTimeSetUp] - public async Task Setup() + // Replacing OneTimeSetUp with IAsyncLifetime's InitializeAsync + public async Task InitializeAsync() { _testUser = await Fixtures.SeedUserWithClient(); - _project = await _testUser.Project.Create(new("test", null, null)); + _project = await _testUser.Project.Create(new ProjectCreateInput("test", null, null)); } - [TestCase(null, null, null, null)] - [TestCase(null, "something", "something", null)] - public void ProjectInviteCreate_InvalidInput(string email, string role, string serverRole, string userId) + // Implementing IAsyncLifetime's DisposeAsync (optional if no cleanup is needed) + public Task DisposeAsync() => Task.CompletedTask; + + [Theory] + [InlineData(null, null, null, null)] + [InlineData(null, "something", "something", null)] + public async Task ProjectInviteCreate_InvalidInput_ShouldThrowSpeckleGraphQLException( + string? email, + string? role, + string? serverRole, + string? userId + ) { - var ex = Assert.CatchAsync(async () => - { - var input = new ProjectInviteCreateInput(email, role, serverRole, userId); - await Sut.Create(_project.id, input); - }); - Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf()); + var input = new ProjectInviteCreateInput(email, role, serverRole, userId); + + var exception = await FluentActions + .Invoking(async () => + { + await Sut.Create(_project.id, input); + }) + .Should() + .ThrowAsync(); + + exception.WithInnerExceptionExactly(); } } diff --git a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ProjectInviteResourceTests.cs b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ProjectInviteResourceTests.cs index 046b1796..0a4efd57 100644 --- a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ProjectInviteResourceTests.cs +++ b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ProjectInviteResourceTests.cs @@ -1,22 +1,21 @@ -using Speckle.Sdk.Api; +using FluentAssertions; +using Speckle.Sdk.Api; using Speckle.Sdk.Api.GraphQL; using Speckle.Sdk.Api.GraphQL.Inputs; using Speckle.Sdk.Api.GraphQL.Models; -using Speckle.Sdk.Api.GraphQL.Resources; using Speckle.Sdk.Common; +using Xunit; namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources; -[TestOf(typeof(ProjectInviteResource))] -public class ProjectInviteResourceTests +public class ProjectInviteResourceTests : IAsyncLifetime { private Client _inviter, _invitee; private Project _project; private PendingStreamCollaborator _createdInvite; - [SetUp] - public async Task Setup() + public async Task InitializeAsync() { _inviter = await Fixtures.SeedUserWithClient(); _invitee = await Fixtures.SeedUserWithClient(); @@ -24,6 +23,8 @@ public async Task Setup() _createdInvite = await SeedInvite(); } + public Task DisposeAsync() => Task.CompletedTask; + private async Task SeedInvite() { ProjectInviteCreateInput input = new(_invitee.Account.userInfo.email, null, null, null); @@ -32,7 +33,7 @@ private async Task SeedInvite() return invites.First(i => i.projectId == res.id); } - [Test] + [Fact] public async Task ProjectInviteCreate_ByEmail() { ProjectInviteCreateInput input = new(_invitee.Account.userInfo.email, null, null, null); @@ -41,75 +42,72 @@ public async Task ProjectInviteCreate_ByEmail() var invites = await _invitee.ActiveUser.GetProjectInvites(); var invite = invites.First(i => i.projectId == res.id); - Assert.That(res, Has.Property(nameof(_project.id)).EqualTo(_project.id)); - Assert.That(res.invitedTeam, Has.Count.EqualTo(1)); - Assert.That(invite.user!.id, Is.EqualTo(_invitee.Account.userInfo.id)); - Assert.That(invite.token, Is.Not.Null); + res.id.Should().Be(_project.id); + res.invitedTeam.Should().ContainSingle(); + invite.user!.id.Should().Be(_invitee.Account.userInfo.id); + invite.token.Should().NotBeNull(); } - [Test] + [Fact] public async Task ProjectInviteCreate_ByUserId() { ProjectInviteCreateInput input = new(null, null, null, _invitee.Account.userInfo.id); var res = await _inviter.ProjectInvite.Create(_project.id, input); - Assert.That(res, Has.Property(nameof(_project.id)).EqualTo(_project.id)); - Assert.That(res.invitedTeam, Has.Count.EqualTo(1)); - Assert.That(res.invitedTeam[0].user!.id, Is.EqualTo(_invitee.Account.userInfo.id)); + res.id.Should().Be(_project.id); + res.invitedTeam.Should().ContainSingle(); + res.invitedTeam[0].user!.id.Should().Be(_invitee.Account.userInfo.id); } - [Test] + [Fact] public async Task ProjectInviteGet() { - var collaborator = await _invitee.ProjectInvite.Get(_project.id, _createdInvite.token); + var collaborator = await _invitee.ProjectInvite.Get(_project.id, _createdInvite.token).NotNull(); - Assert.That( - collaborator, - Has.Property(nameof(PendingStreamCollaborator.inviteId)).EqualTo(_createdInvite.inviteId) - ); - Assert.That(collaborator!.user!.id, Is.EqualTo(_createdInvite.user!.id)); + collaborator.inviteId.Should().Be(_createdInvite.inviteId); + collaborator.user!.id.Should().Be(_createdInvite.user!.id); } - [Test] + [Fact] public async Task ProjectInviteGet_NonExisting() { var collaborator = await _invitee.ProjectInvite.Get(_project.id, "this is not a real token"); - - Assert.That(collaborator, Is.Null); + collaborator.Should().BeNull(); } - [Test] + [Fact] public async Task ProjectInviteUse_MemberAdded() { ProjectInviteUseInput input = new(true, _createdInvite.projectId, _createdInvite.token.NotNull()); await _invitee.ProjectInvite.Use(input); var project = await _inviter.Project.GetWithTeam(_project.id); - var teamMembers = project.team.Select(c => c.user.id); + var teamMembers = project.team.Select(c => c.user.id).ToArray(); var expectedTeamMembers = new[] { _inviter.Account.userInfo.id, _invitee.Account.userInfo.id }; - Assert.That(teamMembers, Is.EquivalentTo(expectedTeamMembers)); + + teamMembers.Should().BeEquivalentTo(expectedTeamMembers); } - [Test] + [Fact] public async Task ProjectInviteCancel_MemberNotAdded() { var res = await _inviter.ProjectInvite.Cancel(_createdInvite.projectId, _createdInvite.inviteId); - - Assert.That(res.invitedTeam, Is.Empty); + res.invitedTeam.Should().BeEmpty(); } - [Test] - [TestCase(StreamRoles.STREAM_OWNER)] - [TestCase(StreamRoles.STREAM_CONTRIBUTOR)] - [TestCase(StreamRoles.STREAM_REVIEWER)] - [TestCase(StreamRoles.REVOKE)] + [Theory] + [InlineData(StreamRoles.STREAM_OWNER)] + [InlineData(StreamRoles.STREAM_CONTRIBUTOR)] + [InlineData(StreamRoles.STREAM_REVIEWER)] + [InlineData(StreamRoles.REVOKE)] public async Task ProjectUpdateRole(string? newRole) { await ProjectInviteUse_MemberAdded(); + ProjectUpdateRoleInput input = new(_invitee.Account.userInfo.id, _project.id, newRole); - _ = await _inviter.Project.UpdateRole(input); + await _inviter.Project.UpdateRole(input); - Project finalProject = await _invitee.Project.Get(_project.id); - Assert.That(finalProject.role, Is.EqualTo(newRole)); + var finalProject = await _invitee.Project.Get(_project.id); + finalProject.role.Should().Be(newRole); } } diff --git a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ProjectResourceExceptionalTests.cs b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ProjectResourceExceptionalTests.cs index 5a938fff..2fd429b3 100644 --- a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ProjectResourceExceptionalTests.cs +++ b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ProjectResourceExceptionalTests.cs @@ -1,15 +1,16 @@ -using Speckle.Sdk.Api; +using FluentAssertions; +using Speckle.Sdk.Api; using Speckle.Sdk.Api.GraphQL; using Speckle.Sdk.Api.GraphQL.Enums; using Speckle.Sdk.Api.GraphQL.Inputs; using Speckle.Sdk.Api.GraphQL.Models; using Speckle.Sdk.Api.GraphQL.Resources; using Speckle.Sdk.Common; +using Xunit; namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources; -[TestOf(typeof(ProjectResource))] -public class ProjectResourceExceptionalTests +public class ProjectResourceExceptionalTests : IAsyncLifetime { private Client _testUser, _secondUser, @@ -17,8 +18,9 @@ public class ProjectResourceExceptionalTests private Project _testProject; private ProjectResource Sut => _testUser.Project; - [OneTimeSetUp] - public async Task Setup() + public Task DisposeAsync() => Task.CompletedTask; + + public async Task InitializeAsync() { _testUser = await Fixtures.SeedUserWithClient(); _secondUser = await Fixtures.SeedUserWithClient(); @@ -33,8 +35,8 @@ public async Task Setup() // 4. Server doesn't exist (is down) //There's got to be a smarter way to parametrise these... - [Test] - public void ProjectCreate_WithoutAuth() + [Fact] + public async Task ProjectCreate_WithoutAuth() { ProjectCreateInput input = new( "The best project", @@ -42,84 +44,88 @@ public void ProjectCreate_WithoutAuth() ProjectVisibility.Private ); - var ex = Assert.ThrowsAsync(async () => _ = await _unauthedUser.Project.Create(input)); - Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf()); + var ex = await Assert.ThrowsAsync(async () => _ = await _unauthedUser.Project.Create(input)); + ex.InnerExceptions.Single().Should().BeOfType(); } - [Test] + [Fact] public async Task ProjectGet_WithoutAuth() { ProjectCreateInput input = new("Private Stream", "A very private stream", ProjectVisibility.Private); Project privateStream = await Sut.Create(input); - var ex = Assert.ThrowsAsync(async () => _ = await _unauthedUser.Project.Get(privateStream.id)); - Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf()); + var ex = await Assert.ThrowsAsync( + async () => _ = await _unauthedUser.Project.Get(privateStream.id) + ); + ex.InnerExceptions.Single().Should().BeOfType(); } - [Test] - public void ProjectGet_NonExistentProject() + [Fact] + public async Task ProjectGet_NonExistentProject() { - var ex = Assert.ThrowsAsync(async () => await Sut.Get("NonExistentProject")); - Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf()); + var ex = await Assert.ThrowsAsync(async () => await Sut.Get("NonExistentProject")); + ex.InnerExceptions.Single().Should().BeOfType(); } - [Test] - public void ProjectUpdate_NonExistentProject() + [Fact] + public async Task ProjectUpdate_NonExistentProject() { - var ex = Assert.ThrowsAsync( + var ex = await Assert.ThrowsAsync( async () => _ = await Sut.Update(new("NonExistentProject", "My new name")) ); - Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf()); + ex.InnerExceptions.Single().Should().BeOfType(); } - [Test] - public void ProjectUpdate_NoAuth() + [Fact] + public async Task ProjectUpdate_NoAuth() { - var ex = Assert.ThrowsAsync( + var ex = await Assert.ThrowsAsync( async () => _ = await _unauthedUser.Project.Update(new(_testProject.id, "My new name")) ); - Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf()); + ex.InnerExceptions.Single().Should().BeOfType(); } - [Test] - [TestCase(StreamRoles.STREAM_OWNER)] - [TestCase(StreamRoles.STREAM_CONTRIBUTOR)] - [TestCase(StreamRoles.STREAM_REVIEWER)] - [TestCase(StreamRoles.REVOKE)] - public void ProjectUpdateRole_NonExistentProject(string newRole) + [Theory] + [InlineData(StreamRoles.STREAM_OWNER)] + [InlineData(StreamRoles.STREAM_CONTRIBUTOR)] + [InlineData(StreamRoles.STREAM_REVIEWER)] + [InlineData(StreamRoles.REVOKE)] + public async Task ProjectUpdateRole_NonExistentProject(string? newRole) { ProjectUpdateRoleInput input = new(_secondUser.Account.id.NotNull(), "NonExistentProject", newRole); - var ex = Assert.ThrowsAsync(async () => _ = await Sut.UpdateRole(input)); - Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf()); + var ex = await Assert.ThrowsAsync(async () => _ = await Sut.UpdateRole(input)); + ex.InnerExceptions.Single().Should().BeOfType(); } - [Test] - [TestCase(StreamRoles.STREAM_OWNER)] - [TestCase(StreamRoles.STREAM_CONTRIBUTOR)] - [TestCase(StreamRoles.STREAM_REVIEWER)] - [TestCase(StreamRoles.REVOKE)] - public void ProjectUpdateRole_NonAuth(string newRole) + [Theory] + [InlineData(StreamRoles.STREAM_OWNER)] + [InlineData(StreamRoles.STREAM_CONTRIBUTOR)] + [InlineData(StreamRoles.STREAM_REVIEWER)] + [InlineData(StreamRoles.REVOKE)] + public async Task ProjectUpdateRole_NonAuth(string? newRole) { ProjectUpdateRoleInput input = new(_secondUser.Account.id.NotNull(), "NonExistentProject", newRole); - var ex = Assert.ThrowsAsync(async () => _ = await _unauthedUser.Project.UpdateRole(input)); - Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf()); + var ex = await Assert.ThrowsAsync( + async () => _ = await _unauthedUser.Project.UpdateRole(input) + ); + ex.InnerExceptions.Single().Should().BeOfType(); } - [Test] + [Fact] public async Task ProjectDelete_NonExistentProject() { await Sut.Delete(_testProject.id); - var ex = Assert.ThrowsAsync(async () => _ = await Sut.Get(_testProject.id)); - Assert.That(ex?.InnerExceptions, Has.One.Items.And.All.TypeOf()); + var ex = await Assert.ThrowsAsync(async () => _ = await Sut.Get(_testProject.id)); + ex.InnerExceptions.Single().Should().BeOfType(); } - [Test] - public void ProjectInvites_NoAuth() + [Fact] + public async Task ProjectInvites_NoAuth() { - Assert.ThrowsAsync(async () => await Fixtures.Unauthed.ActiveUser.ProjectInvites()); + await Assert.ThrowsAsync(async () => await Fixtures.Unauthed.ActiveUser.ProjectInvites()); } } diff --git a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ProjectResourceTests.cs b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ProjectResourceTests.cs index 18f4eb2b..98cee1fe 100644 --- a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ProjectResourceTests.cs +++ b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ProjectResourceTests.cs @@ -1,72 +1,103 @@ -using Speckle.Sdk.Api; +using FluentAssertions; +using Speckle.Sdk.Api; using Speckle.Sdk.Api.GraphQL.Enums; using Speckle.Sdk.Api.GraphQL.Inputs; using Speckle.Sdk.Api.GraphQL.Models; using Speckle.Sdk.Api.GraphQL.Resources; +using Xunit; namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources; -[TestOf(typeof(ProjectResource))] public class ProjectResourceTests { - private Client _testUser; - private Project _testProject; + private readonly Client _testUser; + private readonly Project _testProject; private ProjectResource Sut => _testUser.Project; - [OneTimeSetUp] - public async Task Setup() + public ProjectResourceTests() { - _testUser = await Fixtures.SeedUserWithClient(); - _testProject = await _testUser.Project.Create(new("test project123", "desc", null)); + var setupTask = Setup(); + setupTask.Wait(); // Ensure setup runs synchronously for the constructor + (_testUser, _testProject) = setupTask.Result; } - [TestCase("Very private project", "My secret project", ProjectVisibility.Private)] - [TestCase("Very public project", null, ProjectVisibility.Public)] - public async Task ProjectCreate(string name, string desc, ProjectVisibility visibility) + private async Task<(Client TestUser, Project TestProject)> Setup() { - ProjectCreateInput input = new(name, desc, visibility); - Project result = await Sut.Create(input); - Assert.That(result, Is.Not.Null); - Assert.That(result, Has.Property(nameof(Project.id)).Not.Null); - Assert.That(result, Has.Property(nameof(Project.name)).EqualTo(input.name)); - Assert.That(result, Has.Property(nameof(Project.description)).EqualTo(input.description ?? string.Empty)); - Assert.That(result, Has.Property(nameof(Project.visibility)).EqualTo(input.visibility)); + var testUser = await Fixtures.SeedUserWithClient(); + var testProject = await testUser.Project.Create(new ProjectCreateInput("test project123", "desc", null)); + return (testUser, testProject); } - [Test] - public async Task ProjectGet() + [Theory] + [InlineData("Very private project", "My secret project", ProjectVisibility.Private)] + [InlineData("Very public project", null, ProjectVisibility.Public)] + public async Task ProjectCreate_Should_CreateProjectSuccessfully( + string name, + string? description, + ProjectVisibility visibility + ) { - Project result = await Sut.Get(_testProject.id); + // Arrange + var input = new ProjectCreateInput(name, description, visibility); - Assert.That(result.id, Is.EqualTo(_testProject.id)); - Assert.That(result.name, Is.EqualTo(_testProject.name)); - Assert.That(result.description, Is.EqualTo(_testProject.description)); - Assert.That(result.visibility, Is.EqualTo(_testProject.visibility)); - Assert.That(result.createdAt, Is.EqualTo(_testProject.createdAt)); + // Act + var result = await Sut.Create(input); + + // Assert + result.Should().NotBeNull(); + result.id.Should().NotBeNullOrWhiteSpace(); + result.name.Should().Be(input.name); + result.description.Should().Be(input.description ?? string.Empty); + input.visibility.Should().NotBeNull(); + } + + [Fact] + public async Task ProjectGet_Should_ReturnCorrectProject() + { + // Act + var result = await Sut.Get(_testProject.id); + + // Assert + result.id.Should().Be(_testProject.id); + result.name.Should().Be(_testProject.name); + result.description.Should().Be(_testProject.description); + result.visibility.Should().Be(_testProject.visibility); + result.createdAt.Should().Be(_testProject.createdAt); } - [Test] - public async Task ProjectUpdate() + [Fact] + public async Task ProjectUpdate_Should_UpdateProjectSuccessfully() { + // Arrange const string NEW_NAME = "MY new name"; const string NEW_DESCRIPTION = "MY new desc"; const ProjectVisibility NEW_VISIBILITY = ProjectVisibility.Public; - Project newProject = await Sut.Update(new(_testProject.id, NEW_NAME, NEW_DESCRIPTION, null, NEW_VISIBILITY)); + // Act + var newProject = await Sut.Update( + new ProjectUpdateInput(_testProject.id, NEW_NAME, NEW_DESCRIPTION, null, NEW_VISIBILITY) + ); - Assert.That(newProject.id, Is.EqualTo(_testProject.id)); - Assert.That(newProject.name, Is.EqualTo(NEW_NAME)); - Assert.That(newProject.description, Is.EqualTo(NEW_DESCRIPTION)); - Assert.That(newProject.visibility, Is.EqualTo(NEW_VISIBILITY)); + // Assert + newProject.id.Should().Be(_testProject.id); + newProject.name.Should().Be(NEW_NAME); + newProject.description.Should().Be(NEW_DESCRIPTION); + newProject.visibility.Should().Be(NEW_VISIBILITY); } - [Test] - public async Task ProjectDelete() + [Fact] + public async Task ProjectDelete_Should_DeleteProjectSuccessfully() { - Project toDelete = await Sut.Create(new("Delete me", null, null)); + // Arrange + var toDelete = await Sut.Create(new ProjectCreateInput("Delete me", null, null)); + + // Act await Sut.Delete(toDelete.id); - var getEx = Assert.ThrowsAsync(async () => _ = await Sut.Get(toDelete.id)); - Assert.That(getEx?.InnerExceptions, Has.Exactly(1).TypeOf()); + // Assert + await FluentActions + .Invoking(async () => await Sut.Get(toDelete.id)) + .Should() + .ThrowAsync(); } } diff --git a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/SubscriptionResourceTests.cs b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/SubscriptionResourceTests.cs index a52985e2..596a9cac 100644 --- a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/SubscriptionResourceTests.cs +++ b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/SubscriptionResourceTests.cs @@ -1,13 +1,14 @@ -using Speckle.Sdk.Api; +using FluentAssertions; +using Speckle.Sdk.Api; using Speckle.Sdk.Api.GraphQL.Enums; using Speckle.Sdk.Api.GraphQL.Inputs; using Speckle.Sdk.Api.GraphQL.Models; using Speckle.Sdk.Api.GraphQL.Resources; +using Xunit; namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources; -[TestOf(typeof(SubscriptionResource))] -public class SubscriptionResourceTests : IDisposable +public class SubscriptionResourceTests : IAsyncLifetime { private const int WAIT_PERIOD = 300; private Client _testUser; @@ -17,8 +18,13 @@ public class SubscriptionResourceTests : IDisposable private SubscriptionResource Sut => _testUser.Subscription; - [OneTimeSetUp] - public async Task Setup() + public Task DisposeAsync() + { + _testUser.Dispose(); + return Task.CompletedTask; + } + + public async Task InitializeAsync() { _testUser = await Fixtures.SeedUserWithClient(); _testProject = await _testUser.Project.Create(new("test project123", "desc", null)); @@ -26,7 +32,7 @@ public async Task Setup() _testVersion = await Fixtures.CreateVersion(_testUser, _testProject.id, _testModel.id); } - [Test] + [Fact] public async Task UserProjectsUpdated_SubscriptionIsCalled() { UserProjectsUpdatedMessage? subscriptionMessage = null; @@ -40,13 +46,13 @@ public async Task UserProjectsUpdated_SubscriptionIsCalled() await Task.Delay(WAIT_PERIOD); // Give time for subscription to be triggered - Assert.That(subscriptionMessage, Is.Not.Null); - Assert.That(subscriptionMessage!.id, Is.EqualTo(created.id)); - Assert.That(subscriptionMessage.type, Is.EqualTo(UserProjectsUpdatedMessageType.ADDED)); - Assert.That(subscriptionMessage.project, Is.Not.Null); + subscriptionMessage.Should().NotBeNull(); + subscriptionMessage!.id.Should().Be(created.id); + subscriptionMessage.type.Should().Be(UserProjectsUpdatedMessageType.ADDED); + subscriptionMessage.project.Should().NotBeNull(); } - [Test] + [Fact] public async Task ProjectModelsUpdated_SubscriptionIsCalled() { ProjectModelsUpdatedMessage? subscriptionMessage = null; @@ -61,13 +67,13 @@ public async Task ProjectModelsUpdated_SubscriptionIsCalled() await Task.Delay(WAIT_PERIOD); // Give time for subscription to be triggered - Assert.That(subscriptionMessage, Is.Not.Null); - Assert.That(subscriptionMessage!.id, Is.EqualTo(created.id)); - Assert.That(subscriptionMessage.type, Is.EqualTo(ProjectModelsUpdatedMessageType.CREATED)); - Assert.That(subscriptionMessage.model, Is.Not.Null); + subscriptionMessage.Should().NotBeNull(); + subscriptionMessage!.id.Should().Be(created.id); + subscriptionMessage.type.Should().Be(ProjectModelsUpdatedMessageType.CREATED); + subscriptionMessage.model.Should().NotBeNull(); } - [Test] + [Fact] public async Task ProjectUpdated_SubscriptionIsCalled() { ProjectUpdatedMessage? subscriptionMessage = null; @@ -82,13 +88,13 @@ public async Task ProjectUpdated_SubscriptionIsCalled() await Task.Delay(WAIT_PERIOD); // Give time for subscription to be triggered - Assert.That(subscriptionMessage, Is.Not.Null); - Assert.That(subscriptionMessage!.id, Is.EqualTo(created.id)); - Assert.That(subscriptionMessage.type, Is.EqualTo(ProjectUpdatedMessageType.UPDATED)); - Assert.That(subscriptionMessage.project, Is.Not.Null); + subscriptionMessage.Should().NotBeNull(); + subscriptionMessage!.id.Should().Be(created.id); + subscriptionMessage.type.Should().Be(ProjectUpdatedMessageType.UPDATED); + subscriptionMessage.project.Should().NotBeNull(); } - [Test] + [Fact] public async Task ProjectVersionsUpdated_SubscriptionIsCalled() { ProjectVersionsUpdatedMessage? subscriptionMessage = null; @@ -102,13 +108,13 @@ public async Task ProjectVersionsUpdated_SubscriptionIsCalled() await Task.Delay(WAIT_PERIOD); // Give time for subscription to be triggered - Assert.That(subscriptionMessage, Is.Not.Null); - Assert.That(subscriptionMessage!.id, Is.EqualTo(created)); - Assert.That(subscriptionMessage.type, Is.EqualTo(ProjectVersionsUpdatedMessageType.CREATED)); - Assert.That(subscriptionMessage.version, Is.Not.Null); + subscriptionMessage.Should().NotBeNull(); + subscriptionMessage!.id.Should().Be(created); + subscriptionMessage.type.Should().Be(ProjectVersionsUpdatedMessageType.CREATED); + subscriptionMessage.version.Should().NotBeNull(); } - [Test] + [Fact] public async Task ProjectCommentsUpdated_SubscriptionIsCalled() { string resourceIdString = $"{_testProject.id},{_testModel.id},{_testVersion}"; @@ -123,12 +129,9 @@ public async Task ProjectCommentsUpdated_SubscriptionIsCalled() await Task.Delay(WAIT_PERIOD); // Give time for subscription to be triggered - Assert.That(subscriptionMessage, Is.Not.Null); - Assert.That(subscriptionMessage!.id, Is.EqualTo(created.id)); - Assert.That(subscriptionMessage.type, Is.EqualTo(ProjectCommentsUpdatedMessageType.CREATED)); - Assert.That(subscriptionMessage.comment, Is.Not.Null); + subscriptionMessage.Should().NotBeNull(); + subscriptionMessage!.id.Should().Be(created.id); + subscriptionMessage.type.Should().Be(ProjectCommentsUpdatedMessageType.CREATED); + subscriptionMessage.comment.Should().NotBeNull(); } - - [OneTimeTearDown] - public void Dispose() => _testUser.Dispose(); } diff --git a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/VersionResourceTests.cs b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/VersionResourceTests.cs index 28264563..717d8a13 100644 --- a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/VersionResourceTests.cs +++ b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/VersionResourceTests.cs @@ -1,13 +1,14 @@ -using Speckle.Sdk.Api; +using FluentAssertions; +using Speckle.Sdk.Api; using Speckle.Sdk.Api.GraphQL.Inputs; using Speckle.Sdk.Api.GraphQL.Models; using Speckle.Sdk.Api.GraphQL.Resources; +using Xunit; using Version = Speckle.Sdk.Api.GraphQL.Models.Version; namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources; -[TestOf(typeof(VersionResource))] -public class VersionResourceTests +public class VersionResourceTests : IAsyncLifetime { private Client _testUser; private VersionResource Sut => _testUser.Version; @@ -16,8 +17,9 @@ public class VersionResourceTests private Model _model2; private Version _version; - [SetUp] - public async Task Setup() + public Task DisposeAsync() => Task.CompletedTask; + + public async Task InitializeAsync() { _testUser = await Fixtures.SeedUserWithClient(); _project = await _testUser.Project.Create(new("Test project", "", null)); @@ -29,44 +31,44 @@ public async Task Setup() _version = await Sut.Get(versionId, _project.id); } - [Test] + [Fact] public async Task VersionGet() { Version result = await Sut.Get(_version.id, _project.id); - Assert.That(result, Has.Property(nameof(Version.id)).EqualTo(_version.id)); - Assert.That(result, Has.Property(nameof(Version.message)).EqualTo(_version.message)); + result.id.Should().Be(_version.id); + result.message.Should().Be(_version.message); } - [Test] + [Fact] public async Task VersionsGet() { ResourceCollection result = await Sut.GetVersions(_model1.id, _project.id); - Assert.That(result.items, Has.Count.EqualTo(1)); - Assert.That(result.totalCount, Is.EqualTo(1)); - Assert.That(result.items[0], Has.Property(nameof(Version.id)).EqualTo(_version.id)); + result.items.Count.Should().Be(1); + result.totalCount.Should().Be(1); + result.items[0].id.Should().Be(_version.id); } - [Test] + [Fact] public async Task VersionReceived() { MarkReceivedVersionInput input = new(_version.id, _project.id, "Integration test"); await Sut.Received(input); } - [Test] + [Fact] public async Task ModelGetWithVersions() { var result = await _testUser.Model.GetWithVersions(_model1.id, _project.id); - Assert.That(result, Has.Property(nameof(Model.id)).EqualTo(_model1.id)); - Assert.That(result.versions.items, Has.Count.EqualTo(1)); - Assert.That(result.versions.totalCount, Is.EqualTo(1)); - Assert.That(result.versions.items[0], Has.Property(nameof(Version.id)).EqualTo(_version.id)); + result.id.Should().Be(_model1.id); + result.versions.items.Count.Should().Be(1); + result.versions.totalCount.Should().Be(1); + result.versions.items[0].id.Should().Be(_version.id); } - [Test] + [Fact] public async Task VersionUpdate() { const string NEW_MESSAGE = "MY new version message"; @@ -74,34 +76,43 @@ public async Task VersionUpdate() UpdateVersionInput input = new(_version.id, _project.id, NEW_MESSAGE); Version updatedVersion = await Sut.Update(input); - Assert.That(updatedVersion, Has.Property(nameof(Version.id)).EqualTo(_version.id)); - Assert.That(updatedVersion, Has.Property(nameof(Version.message)).EqualTo(NEW_MESSAGE)); - Assert.That(updatedVersion, Has.Property(nameof(Version.previewUrl)).EqualTo(_version.previewUrl)); + updatedVersion.id.Should().Be(_version.id); + updatedVersion.message.Should().Be(NEW_MESSAGE); + updatedVersion.previewUrl.Should().Be(_version.previewUrl); } - [Test] + [Fact] public async Task VersionMoveToModel() { MoveVersionsInput input = new(_project.id, _model2.name, [_version.id]); string id = await Sut.MoveToModel(input); - Assert.That(id, Is.EqualTo(_model2.id)); + + id.Should().Be(_model2.id); + Version movedVersion = await Sut.Get(_version.id, _project.id); - Assert.That(movedVersion, Has.Property(nameof(Version.id)).EqualTo(_version.id)); - Assert.That(movedVersion, Has.Property(nameof(Version.message)).EqualTo(_version.message)); - Assert.That(movedVersion, Has.Property(nameof(Version.previewUrl)).EqualTo(_version.previewUrl)); + movedVersion.id.Should().Be(_version.id); + movedVersion.message.Should().Be(_version.message); + movedVersion.previewUrl.Should().Be(_version.previewUrl); } - [Test] + [Fact] public async Task VersionDelete() { DeleteVersionsInput input = new([_version.id], _project.id); await Sut.Delete(input); - var getEx = Assert.ThrowsAsync(async () => await Sut.Get(_version.id, _project.id)); - Assert.That(getEx?.InnerExceptions, Has.Exactly(1).TypeOf()); - var delEx = Assert.ThrowsAsync(async () => await Sut.Delete(input)); - Assert.That(delEx?.InnerExceptions, Has.Exactly(1).TypeOf()); + var getEx = await FluentActions + .Invoking(async () => await Sut.Get(_version.id, _project.id)) + .Should() + .ThrowAsync(); + getEx.WithInnerExceptionExactly(); + + var delEx = await FluentActions + .Invoking(async () => await Sut.Delete(input)) + .Should() + .ThrowAsync(); + delEx.WithInnerExceptionExactly(); } } diff --git a/tests/Speckle.Sdk.Tests.Integration/Credentials/UserServerInfoTests.cs b/tests/Speckle.Sdk.Tests.Integration/Credentials/UserServerInfoTests.cs index 782244f4..fe621978 100644 --- a/tests/Speckle.Sdk.Tests.Integration/Credentials/UserServerInfoTests.cs +++ b/tests/Speckle.Sdk.Tests.Integration/Credentials/UserServerInfoTests.cs @@ -1,40 +1,43 @@ -using GraphQL.Client.Http; +using FluentAssertions; +using GraphQL.Client.Http; using Microsoft.Extensions.DependencyInjection; using Speckle.Sdk.Api.GraphQL.Models; using Speckle.Sdk.Credentials; +using Xunit; namespace Speckle.Sdk.Tests.Integration.Credentials; -public class UserServerInfoTests +public class UserServerInfoTests : IAsyncLifetime { private Account _acc; - [SetUp] - public async Task Setup() + public Task DisposeAsync() => Task.CompletedTask; + + public async Task InitializeAsync() { _acc = await Fixtures.SeedUser(); } - [Test] + [Fact] public async Task IsFrontEnd2True() { ServerInfo? result = await Fixtures .ServiceProvider.GetRequiredService() .GetServerInfo(new("https://app.speckle.systems/")); - Assert.That(result, Is.Not.Null); - Assert.That(result!.frontend2, Is.True); + result.Should().NotBeNull(); + result.frontend2.Should().BeTrue(); } - [Test] + [Fact] public async Task IsFrontEnd2False() { ServerInfo? result = await Fixtures .ServiceProvider.GetRequiredService() .GetServerInfo(new("https://speckle.xyz/")); - Assert.That(result, Is.Not.Null); - Assert.That(result!.frontend2, Is.False); + result.Should().NotBeNull(); + result.frontend2.Should().BeFalse(); } /// @@ -43,27 +46,33 @@ public async Task IsFrontEnd2False() /// This is not doable in local server because there is no end-point on this to ping. /// This is a bad sign for mutation. /// - [Test] - public void GetServerInfo_ExpectFail_CantPing() + [Fact] + public async Task GetServerInfo_ExpectFail_CantPing() { Uri serverUrl = new(_acc.serverInfo.url); - Assert.ThrowsAsync( - async () => await Fixtures.ServiceProvider.GetRequiredService().GetServerInfo(serverUrl) - ); + await FluentActions + .Invoking( + async () => await Fixtures.ServiceProvider.GetRequiredService().GetServerInfo(serverUrl) + ) + .Should() + .ThrowAsync(); } - [Test] - public void GetServerInfo_ExpectFail_NoServer() + [Fact] + public async Task GetServerInfo_ExpectFail_NoServer() { Uri serverUrl = new("http://invalidserver.local"); - Assert.ThrowsAsync( - async () => await Fixtures.ServiceProvider.GetRequiredService().GetServerInfo(serverUrl) - ); + await FluentActions + .Invoking( + async () => await Fixtures.ServiceProvider.GetRequiredService().GetServerInfo(serverUrl) + ) + .Should() + .ThrowAsync(); } - [Test] + [Fact] public async Task GetUserInfo() { Uri serverUrl = new(_acc.serverInfo.url); @@ -71,33 +80,38 @@ public async Task GetUserInfo() .ServiceProvider.GetRequiredService() .GetUserInfo(_acc.token, serverUrl); - Assert.That(result.id, Is.EqualTo(_acc.userInfo.id)); - Assert.That(result.name, Is.EqualTo(_acc.userInfo.name)); - Assert.That(result.email, Is.EqualTo(_acc.userInfo.email)); - Assert.That(result.company, Is.EqualTo(_acc.userInfo.company)); - Assert.That(result.avatar, Is.EqualTo(_acc.userInfo.avatar)); + result.id.Should().Be(_acc.userInfo.id); + result.name.Should().Be(_acc.userInfo.name); + result.email.Should().Be(_acc.userInfo.email); + result.company.Should().Be(_acc.userInfo.company); + result.avatar.Should().Be(_acc.userInfo.avatar); } - [Test] - public void GetUserInfo_ExpectFail_NoServer() + [Fact] + public async Task GetUserInfo_ExpectFail_NoServer() { Uri serverUrl = new("http://invalidserver.local"); - Assert.ThrowsAsync( - async () => await Fixtures.ServiceProvider.GetRequiredService().GetUserInfo("", serverUrl) - ); + await FluentActions + .Invoking( + async () => await Fixtures.ServiceProvider.GetRequiredService().GetUserInfo("", serverUrl) + ) + .Should() + .ThrowAsync(); } - [Test] - public void GetUserInfo_ExpectFail_NoUser() + [Fact] + public async Task GetUserInfo_ExpectFail_NoUser() { Uri serverUrl = new(_acc.serverInfo.url); - - Assert.ThrowsAsync( - async () => - await Fixtures - .ServiceProvider.GetRequiredService() - .GetUserInfo("Bearer 08913c3c1e7ac65d779d1e1f11b942a44ad9672ca9", serverUrl) - ); + await FluentActions + .Invoking( + async () => + await Fixtures + .ServiceProvider.GetRequiredService() + .GetUserInfo("Bearer 08913c3c1e7ac65d779d1e1f11b942a44ad9672ca9", serverUrl) + ) + .Should() + .ThrowAsync(); } } diff --git a/tests/Speckle.Sdk.Tests.Integration/Fixtures.cs b/tests/Speckle.Sdk.Tests.Integration/Fixtures.cs index d57badd1..58241347 100644 --- a/tests/Speckle.Sdk.Tests.Integration/Fixtures.cs +++ b/tests/Speckle.Sdk.Tests.Integration/Fixtures.cs @@ -54,9 +54,9 @@ public static async Task SeedUser() var seed = Guid.NewGuid().ToString().ToLower(); Dictionary user = new() { - ["email"] = $"{seed.Substring(0, 7)}@example.com", + ["email"] = $"{seed[..7]}@example.com", ["password"] = "12ABC3456789DEF0GHO", - ["name"] = $"{seed.Substring(0, 5)} Name", + ["name"] = $"{seed[..5]} Name", }; using var httpClient = new HttpClient( diff --git a/tests/Speckle.Sdk.Tests.Integration/MemoryTransportTests.cs b/tests/Speckle.Sdk.Tests.Integration/MemoryTransportTests.cs index 230804b4..99ccf856 100644 --- a/tests/Speckle.Sdk.Tests.Integration/MemoryTransportTests.cs +++ b/tests/Speckle.Sdk.Tests.Integration/MemoryTransportTests.cs @@ -1,21 +1,20 @@ -using System.Collections.Concurrent; -using System.Reflection; +using System.Reflection; +using FluentAssertions; using Microsoft.Extensions.DependencyInjection; -using Shouldly; using Speckle.Sdk.Api; using Speckle.Sdk.Host; using Speckle.Sdk.Models; using Speckle.Sdk.Transports; +using Xunit; namespace Speckle.Sdk.Tests.Integration; -public class MemoryTransportTests +public class MemoryTransportTests : IDisposable { private readonly MemoryTransport _memoryTransport = new(blobStorageEnabled: true); private IOperations _operations; - [SetUp] - public void Setup() + public MemoryTransportTests() { CleanData(); TypeLoader.Reset(); @@ -24,8 +23,7 @@ public void Setup() _operations = serviceProvider.GetRequiredService(); } - [TearDown] - public void TearDown() => CleanData(); + public void Dispose() => CleanData(); private void CleanData() { @@ -33,10 +31,11 @@ private void CleanData() { Directory.Delete(_memoryTransport.BlobStorageFolder, true); } + Directory.CreateDirectory(_memoryTransport.BlobStorageFolder); } - [Test] + [Fact] public async Task SendAndReceiveObjectWithBlobs() { var myObject = Fixtures.GenerateSimpleObject(); @@ -53,20 +52,24 @@ public async Task SendAndReceiveObjectWithBlobs() .GetFiles(_memoryTransport.BlobStorageFolder) .Select(fp => fp.Split(Path.DirectorySeparatorChar).Last()) .ToList(); + var blobPaths = allFiles .Where(fp => fp.Length > Blob.LocalHashPrefixLength) // excludes things like .DS_store .ToList(); // Check that there are three downloaded blobs! - Assert.That(blobPaths, Has.Count.EqualTo(3)); + blobPaths.Count.Should().Be(3); + var objectBlobs = receivedObject["blobs"] as IList; - objectBlobs.ShouldNotBeNull(); - var blobs = objectBlobs.Cast().ToList(); + objectBlobs.Should().NotBeNull(); + + var blobs = objectBlobs!.Cast().ToList(); // Check that we have three blobs - Assert.That(blobs, Has.Count.EqualTo(3)); + blobs.Count.Should().Be(3); + // Check that received blobs point to local path (where they were received) - Assert.That(blobs[0].filePath, Contains.Substring(_memoryTransport.BlobStorageFolder)); - Assert.That(blobs[1].filePath, Contains.Substring(_memoryTransport.BlobStorageFolder)); - Assert.That(blobs[2].filePath, Contains.Substring(_memoryTransport.BlobStorageFolder)); + blobs[0].filePath.Should().Contain(_memoryTransport.BlobStorageFolder); + blobs[1].filePath.Should().Contain(_memoryTransport.BlobStorageFolder); + blobs[2].filePath.Should().Contain(_memoryTransport.BlobStorageFolder); } } diff --git a/tests/Speckle.Sdk.Tests.Integration/Speckle.Sdk.Tests.Integration.csproj b/tests/Speckle.Sdk.Tests.Integration/Speckle.Sdk.Tests.Integration.csproj index 5eb43529..e5204dc2 100644 --- a/tests/Speckle.Sdk.Tests.Integration/Speckle.Sdk.Tests.Integration.csproj +++ b/tests/Speckle.Sdk.Tests.Integration/Speckle.Sdk.Tests.Integration.csproj @@ -9,9 +9,8 @@ - - - + + diff --git a/tests/Speckle.Sdk.Tests.Integration/Usings.cs b/tests/Speckle.Sdk.Tests.Integration/Usings.cs deleted file mode 100644 index 32445676..00000000 --- a/tests/Speckle.Sdk.Tests.Integration/Usings.cs +++ /dev/null @@ -1 +0,0 @@ -global using NUnit.Framework; diff --git a/tests/Speckle.Sdk.Tests.Integration/packages.lock.json b/tests/Speckle.Sdk.Tests.Integration/packages.lock.json index 160e2842..2c652b68 100644 --- a/tests/Speckle.Sdk.Tests.Integration/packages.lock.json +++ b/tests/Speckle.Sdk.Tests.Integration/packages.lock.json @@ -4,9 +4,9 @@ "net8.0": { "altcover": { "type": "Direct", - "requested": "[8.9.3, )", - "resolved": "8.9.3", - "contentHash": "auKC+pDCkLjfhFkSRaAUBu25BOmlLSqucR7YBs/Lkbdc0XRuJoklWafs1KKp+M+VoJ1f0TeMS6B/FO5IeIcu7w==" + "requested": "[9.0.1, )", + "resolved": "9.0.1", + "contentHash": "aadciFNDT5bnylaYUkKal+s5hF7yU/lmZxImQWAlk1438iPqK1Uf79H5ylELpyLIU49HL5ql+tnWBihp3WVLCA==" }, "GitVersion.MsBuild": { "type": "Direct", @@ -16,12 +16,12 @@ }, "Microsoft.NET.Test.Sdk": { "type": "Direct", - "requested": "[17.11.1, )", - "resolved": "17.11.1", - "contentHash": "U3Ty4BaGoEu+T2bwSko9tWqWUOU16WzSFkq6U8zve75oRBMSLTBdMAZrVNNz1Tq12aCdDom9fcOcM9QZaFHqFg==", + "requested": "[17.12.0, )", + "resolved": "17.12.0", + "contentHash": "kt/PKBZ91rFCWxVIJZSgVLk+YR+4KxTuHf799ho8WNiK5ZQpJNAEZCAWX86vcKrs+DiYjiibpYKdGZP6+/N17w==", "dependencies": { - "Microsoft.CodeCoverage": "17.11.1", - "Microsoft.TestPlatform.TestHost": "17.11.1" + "Microsoft.CodeCoverage": "17.12.0", + "Microsoft.TestPlatform.TestHost": "17.12.0" } }, "Microsoft.SourceLink.GitHub": { @@ -34,53 +34,34 @@ "Microsoft.SourceLink.Common": "8.0.0" } }, - "NUnit": { - "type": "Direct", - "requested": "[4.2.2, )", - "resolved": "4.2.2", - "contentHash": "mon0OPko28yZ/foVXrhiUvq1LReaGsBdziumyyYGxV/pOE4q92fuYeN+AF+gEU5pCjzykcdBt5l7xobTaiBjsg==" - }, - "NUnit3TestAdapter": { - "type": "Direct", - "requested": "[4.6.0, )", - "resolved": "4.6.0", - "contentHash": "R7e1+a4vuV/YS+ItfL7f//rG+JBvVeVLX4mHzFEZo4W1qEKl8Zz27AqvQSAqo+BtIzUCo4aAJMYa56VXS4hudw==" - }, "PolySharp": { "type": "Direct", "requested": "[1.15.0, )", "resolved": "1.15.0", "contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g==" }, - "Shouldly": { - "type": "Direct", - "requested": "[4.2.1, )", - "resolved": "4.2.1", - "contentHash": "dKAKiSuhLKqD2TXwLKtqNg1nwzJcIKOOMncZjk9LYe4W+h+SCftpWdxwR79YZUIHMH+3Vu9s0s0UHNrgICLwRQ==", - "dependencies": { - "DiffEngine": "11.3.0", - "EmptyFiles": "4.4.0" - } - }, "Speckle.InterfaceGenerator": { "type": "Direct", "requested": "[0.9.6, )", "resolved": "0.9.6", "contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w==" }, - "DiffEngine": { - "type": "Transitive", - "resolved": "11.3.0", - "contentHash": "k0ZgZqd09jLZQjR8FyQbSQE86Q7QZnjEzq1LPHtj1R2AoWO8sjV5x+jlSisL7NZAbUOI4y+7Bog8gkr9WIRBGw==", + "xunit": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==", "dependencies": { - "EmptyFiles": "4.4.0", - "System.Management": "6.0.1" + "xunit.analyzers": "1.18.0", + "xunit.assert": "2.9.3", + "xunit.core": "[2.9.3]" } }, - "EmptyFiles": { - "type": "Transitive", - "resolved": "4.4.0", - "contentHash": "gwJEfIGS7FhykvtZoscwXj/XwW+mJY6UbAZk+qtLKFUGWC95kfKXnj8VkxsZQnWBxJemM/q664rGLN5nf+OHZw==" + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.0.0, )", + "resolved": "3.0.0", + "contentHash": "HggUqjQJe8PtDxcP25Q+CnR6Lz4oX3GElhD9V4oU2+75x9HI6A6sxbfKGS4UwU4t4yJaS9fBmAuriz8bQApNjw==" }, "GraphQL.Client.Abstractions": { "type": "Transitive", @@ -110,8 +91,8 @@ }, "Microsoft.CodeCoverage": { "type": "Transitive", - "resolved": "17.11.1", - "contentHash": "nPJqrcA5iX+Y0kqoT3a+pD/8lrW/V7ayqnEJQsTonSoPz59J8bmoQhcSN4G8+UJ64Hkuf0zuxnfuj2lkHOq4cA==" + "resolved": "17.12.0", + "contentHash": "4svMznBd5JM21JIG2xZKGNanAHNXplxf/kQDFfLHXQ3OnpJkayRK/TjacFjA+EYmoyuNXHo/sOETEfcYtAzIrA==" }, "Microsoft.Data.Sqlite.Core": { "type": "Transitive", @@ -176,21 +157,26 @@ }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", - "resolved": "17.11.1", - "contentHash": "E2jZqAU6JeWEVsyOEOrSW1o1bpHLgb25ypvKNB/moBXPVsFYBPd/Jwi7OrYahG50J83LfHzezYI+GaEkpAotiA==", + "resolved": "17.12.0", + "contentHash": "TDqkTKLfQuAaPcEb3pDDWnh7b3SyZF+/W9OZvWFp6eJCIiiYFdSB6taE2I6tWrFw5ywhzOb6sreoGJTI6m3rSQ==", "dependencies": { "System.Reflection.Metadata": "1.6.0" } }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", - "resolved": "17.11.1", - "contentHash": "DnG+GOqJXO/CkoqlJWeDFTgPhqD/V6VqUIL3vINizCWZ3X+HshCtbbyDdSHQQEjrc2Sl/K3yaxX6s+5LFEdYuw==", + "resolved": "17.12.0", + "contentHash": "MiPEJQNyADfwZ4pJNpQex+t9/jOClBGMiCiVVFuELCMSX2nmNfvUor3uFVxNNCg30uxDP8JDYfPnMXQzsfzYyg==", "dependencies": { - "Microsoft.TestPlatform.ObjectModel": "17.11.1", + "Microsoft.TestPlatform.ObjectModel": "17.12.0", "Newtonsoft.Json": "13.0.1" } }, + "Microsoft.Win32.SystemEvents": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A==" + }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "13.0.1", @@ -226,22 +212,26 @@ "SQLitePCLRaw.core": "2.1.4" } }, - "System.CodeDom": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA==" - }, "System.ComponentModel.Annotations": { "type": "Transitive", "resolved": "4.5.0", "contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg==" }, - "System.Management": { + "System.Configuration.ConfigurationManager": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "7T+m0kDSlIPTHIkPMIu6m6tV6qsMqJpvQWW2jIc2qi7sn40qxFo0q+7mEQAhMPXZHMKnWrnv47ntGlM/ejvw3g==", + "dependencies": { + "System.Security.Cryptography.ProtectedData": "6.0.0", + "System.Security.Permissions": "6.0.0" + } + }, + "System.Drawing.Common": { "type": "Transitive", - "resolved": "6.0.1", - "contentHash": "10J1D0h/lioojphfJ4Fuh5ZUThT/xOVHdV9roGBittKKNP2PMjrvibEdbVTGZcPra1399Ja3tqIJLyQrc5Wmhg==", + "resolved": "6.0.0", + "contentHash": "NfuoKUiP2nUWwKZN6twGqXioIe1zVD0RIj2t976A+czLHr2nY454RwwXs6JU9Htc6mwqL6Dn/nEL3dpVf2jOhg==", "dependencies": { - "System.CodeDom": "6.0.0" + "Microsoft.Win32.SystemEvents": "6.0.0" } }, "System.Memory": { @@ -264,6 +254,73 @@ "resolved": "4.5.1", "contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw==" }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ==" + }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "rp1gMNEZpvx9vP0JW0oHLxlf8oSiQgtno77Y4PLUBjSiDYoD77Y8uXHr1Ea5XG4/pIKhqAdxZ8v8OTUtqo9PeQ==" + }, + "System.Security.Permissions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "T/uuc7AklkDoxmcJ7LGkyX1CcSviZuLCa4jg3PekfJ7SU0niF0IVTXwUiNVP9DSpzou2PpxJ+eNY2IfDM90ZCg==", + "dependencies": { + "System.Security.AccessControl": "6.0.0", + "System.Windows.Extensions": "6.0.0" + } + }, + "System.Windows.Extensions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "IXoJOXIqc39AIe+CIR7koBtRGMiCt/LPM3lI+PELtDIy9XdyeSrwXFdWV9dzJ2Awl0paLWUaknLxFQ5HpHZUog==", + "dependencies": { + "System.Drawing.Common": "6.0.0" + } + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.18.0", + "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==" + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]", + "xunit.extensibility.execution": "[2.9.3]" + } + }, + "xunit.extensibility.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==", + "dependencies": { + "xunit.abstractions": "2.0.3" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]" + } + }, "speckle.sdk": { "type": "Project", "dependencies": { @@ -283,14 +340,23 @@ "speckle.sdk.tests.unit": { "type": "Project", "dependencies": { + "FluentAssertions": "[7.0.0, )", "Microsoft.Extensions.DependencyInjection": "[2.2.0, )", - "Microsoft.NET.Test.Sdk": "[17.11.1, )", - "NUnit": "[4.2.2, )", - "NUnit3TestAdapter": "[4.6.0, )", - "Shouldly": "[4.2.1, )", + "Microsoft.NET.Test.Sdk": "[17.12.0, )", "Speckle.DoubleNumerics": "[4.0.1, )", "Speckle.Sdk": "[1.0.0, )", - "altcover": "[8.9.3, )" + "altcover": "[9.0.1, )", + "xunit": "[2.9.3, )", + "xunit.runner.visualstudio": "[3.0.0, )" + } + }, + "FluentAssertions": { + "type": "CentralTransitive", + "requested": "[7.0.0, )", + "resolved": "7.0.0", + "contentHash": "mTLbcU991EQ1SEmNbVBaGGGJy0YFzvGd1sYJGNZ07nlPKuyHSn1I22aeKzqQXgEiaKyRO6MSCto9eN9VxMwBdA==", + "dependencies": { + "System.Configuration.ConfigurationManager": "6.0.0" } }, "GraphQL.Client": { diff --git a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralReceiveTest.cs b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralReceiveTest.cs index 60c2ee39..e0727f8d 100644 --- a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralReceiveTest.cs +++ b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralReceiveTest.cs @@ -4,13 +4,8 @@ using Speckle.Objects.Geometry; using Speckle.Sdk.Api; using Speckle.Sdk.Credentials; -using Speckle.Sdk.Helpers; using Speckle.Sdk.Host; -using Speckle.Sdk.Logging; using Speckle.Sdk.Models; -using Speckle.Sdk.Serialisation; -using Speckle.Sdk.Serialisation.V2; -using Speckle.Sdk.Serialisation.V2.Receive; using Speckle.Sdk.Transports; namespace Speckle.Sdk.Tests.Performance.Benchmarks; diff --git a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs index deb8a27d..48b544dd 100644 --- a/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs +++ b/tests/Speckle.Sdk.Tests.Performance/Benchmarks/GeneralSerializerTest.cs @@ -1,6 +1,5 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Engines; -using Microsoft.Extensions.Logging.Abstractions; using Speckle.Objects.Geometry; using Speckle.Sdk.Common; using Speckle.Sdk.Credentials; diff --git a/tests/Speckle.Sdk.Tests.Performance/Program.cs b/tests/Speckle.Sdk.Tests.Performance/Program.cs index 889b29d8..75694359 100644 --- a/tests/Speckle.Sdk.Tests.Performance/Program.cs +++ b/tests/Speckle.Sdk.Tests.Performance/Program.cs @@ -1,7 +1,6 @@ // See https://aka.ms/new-console-template for more information using BenchmarkDotNet.Running; -using Speckle.Sdk.Tests.Performance.Benchmarks; BenchmarkSwitcher.FromAssemblies([typeof(Program).Assembly]).Run(args); // var sut = new GeneralSendTest(); diff --git a/tests/Speckle.Sdk.Tests.Unit/Api/ClientResiliencyPolicyTest.cs b/tests/Speckle.Sdk.Tests.Unit/Api/ClientResiliencyPolicyTest.cs index 53f4c394..af764b5d 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Api/ClientResiliencyPolicyTest.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Api/ClientResiliencyPolicyTest.cs @@ -1,20 +1,18 @@ using System.Diagnostics; -using GraphQL; +using FluentAssertions; using Microsoft.Extensions.DependencyInjection; -using NUnit.Framework; using Speckle.Sdk.Api; using Speckle.Sdk.Api.GraphQL.Models; using Speckle.Sdk.Credentials; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Api; -[TestOf(typeof(Client))] public sealed class GraphQLClientTests : IDisposable { - private Client _client; + private readonly Client _client; - [OneTimeSetUp] - public void Setup() + public GraphQLClientTests() { var serviceProvider = TestServiceSetup.GetServiceProvider(); _client = serviceProvider @@ -28,17 +26,14 @@ public void Setup() ); } - public void Dispose() - { - _client?.Dispose(); - } + public void Dispose() => _client?.Dispose(); - [Test] - public void TestExecuteWithResiliencePoliciesDoesntRetryTaskCancellation() + [Fact] + public async Task TestExecuteWithResiliencePoliciesDoesntRetryTaskCancellation() { var timer = new Stopwatch(); timer.Start(); - Assert.ThrowsAsync(async () => + await Assert.ThrowsAsync(async () => { var tokenSource = new CancellationTokenSource(); tokenSource.Cancel(); @@ -55,14 +50,13 @@ await Task.Run( ); }); timer.Stop(); - var elapsed = timer.ElapsedMilliseconds; + timer.ElapsedMilliseconds.Should().BeLessThan(1000); // the default retry policy would retry 5 times with 1 second jitter backoff each // if the elapsed is less than a second, this was def not retried - Assert.That(elapsed, Is.LessThan(1000)); } - [Test] + [Fact] public async Task TestExecuteWithResiliencePoliciesRetry() { var counter = 0; @@ -82,8 +76,8 @@ public async Task TestExecuteWithResiliencePoliciesRetry() }); timer.Stop(); // The baseline for wait is 1 seconds between the jittered retry - Assert.That(timer.ElapsedMilliseconds, Is.GreaterThanOrEqualTo(5000)); - Assert.That(counter, Is.EqualTo(maxRetryCount)); + timer.ElapsedMilliseconds.Should().BeGreaterThanOrEqualTo(5000); + counter.Should().Be(maxRetryCount); } public class FakeGqlResponseModel { } diff --git a/tests/Speckle.Sdk.Tests.Unit/Api/GraphQLErrorHandler.cs b/tests/Speckle.Sdk.Tests.Unit/Api/GraphQLErrorHandler.cs index 72c78df2..3a06614f 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Api/GraphQLErrorHandler.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Api/GraphQLErrorHandler.cs @@ -1,55 +1,41 @@ -using GraphQL; -using NUnit.Framework; +using FluentAssertions; +using GraphQL; using Speckle.Sdk.Api; using Speckle.Sdk.Api.GraphQL; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Api; public class GraphQLErrorHandlerTests { - private static IEnumerable ErrorCases() + public static IEnumerable ErrorCases() { - yield return new TestCaseData(typeof(SpeckleGraphQLForbiddenException), new Map { { "code", "FORBIDDEN" } }); - yield return new TestCaseData(typeof(SpeckleGraphQLForbiddenException), new Map { { "code", "UNAUTHENTICATED" } }); - yield return new TestCaseData( - typeof(SpeckleGraphQLInternalErrorException), - new Map { { "code", "INTERNAL_SERVER_ERROR" } } - ); - yield return new TestCaseData( - typeof(SpeckleGraphQLStreamNotFoundException), - new Map { { "code", "STREAM_NOT_FOUND" } } - ); - yield return new TestCaseData(typeof(SpeckleGraphQLBadInputException), new Map { { "code", "BAD_USER_INPUT" } }); - yield return new TestCaseData( - typeof(SpeckleGraphQLInvalidQueryException), - new Map { { "code", "GRAPHQL_PARSE_FAILED" } } - ); - yield return new TestCaseData( - typeof(SpeckleGraphQLInvalidQueryException), - new Map { { "code", "GRAPHQL_VALIDATION_FAILED" } } - ); - yield return new TestCaseData(typeof(SpeckleGraphQLException), new Map { { "foo", "bar" } }); - yield return new TestCaseData(typeof(SpeckleGraphQLException), new Map { { "code", "CUSTOM_THING" } }); + yield return [typeof(SpeckleGraphQLForbiddenException), new Map { { "code", "FORBIDDEN" } }]; + yield return [typeof(SpeckleGraphQLForbiddenException), new Map { { "code", "UNAUTHENTICATED" } }]; + yield return [typeof(SpeckleGraphQLInternalErrorException), new Map { { "code", "INTERNAL_SERVER_ERROR" } }]; + yield return [typeof(SpeckleGraphQLStreamNotFoundException), new Map { { "code", "STREAM_NOT_FOUND" } }]; + yield return [typeof(SpeckleGraphQLBadInputException), new Map { { "code", "BAD_USER_INPUT" } }]; + yield return [typeof(SpeckleGraphQLInvalidQueryException), new Map { { "code", "GRAPHQL_PARSE_FAILED" } }]; + yield return [typeof(SpeckleGraphQLInvalidQueryException), new Map { { "code", "GRAPHQL_VALIDATION_FAILED" } }]; + yield return [typeof(SpeckleGraphQLException), new Map { { "foo", "bar" } }]; + yield return [typeof(SpeckleGraphQLException), new Map { { "code", "CUSTOM_THING" } }]; } - [Test, TestCaseSource(nameof(ErrorCases))] + [Theory] + [MemberData(nameof(ErrorCases))] public void TestExceptionThrowingFromGraphQLErrors(Type exType, Map extensions) { var ex = Assert.Throws( () => - GraphQLErrorHandler.EnsureGraphQLSuccess( - new GraphQLResponse - { - Errors = new GraphQLError[] { new() { Extensions = extensions } }, - } - ) + new GraphQLResponse + { + Errors = [new() { Extensions = extensions }], + }.EnsureGraphQLSuccess() ); - Assert.That(ex?.InnerExceptions, Has.Exactly(1).TypeOf(exType)); + ex.InnerExceptions.Count.Should().Be(1); + ex.InnerExceptions[0].Should().BeOfType(exType); } - [Test] - public void TestMaybeThrowsDoesntThrowForNoErrors() - { - Assert.DoesNotThrow(() => GraphQLErrorHandler.EnsureGraphQLSuccess(new GraphQLResponse())); - } + [Fact] + public void TestMaybeThrowsDoesntThrowForNoErrors() => new GraphQLResponse().EnsureGraphQLSuccess(); } diff --git a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/ClosureTests.cs b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/ClosureTests.cs index 88ea3a3c..1240254e 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/ClosureTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/ClosureTests.cs @@ -1,23 +1,21 @@ +using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; -using NUnit.Framework; using Speckle.Sdk.Api; using Speckle.Sdk.Common; using Speckle.Sdk.Host; using Speckle.Sdk.Models; using Speckle.Sdk.Tests.Unit.Host; using Speckle.Sdk.Transports; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Api.Operations; -[TestFixture] -[TestOf(typeof(Sdk.Api.Operations))] public class Closures { - private IOperations _operations; + private readonly IOperations _operations; - [SetUp] - public void Setup() + public Closures() { TypeLoader.Reset(); TypeLoader.Initialize(typeof(Base).Assembly, typeof(TableLegFixture).Assembly); @@ -25,7 +23,7 @@ public void Setup() _operations = serviceProvider.GetRequiredService(); } - [Test(Description = "Checks whether closures are generated correctly by the serialiser.")] + [Fact(DisplayName = "Checks whether closures are generated correctly by the serialiser.")] public async Task CorrectDecompositionTracking() { var d5 = new Base(); @@ -56,31 +54,31 @@ public async Task CorrectDecompositionTracking() var test = await _operations.Receive(sendResult.rootObjId, localTransport: transport); test.id.NotNull(); - Assert.That(d1.GetId(true), Is.EqualTo(test.id)); + d1.GetId(true).Should().BeEquivalentTo((test.id)); var d1_ = NotNullExtensions.NotNull(JsonConvert.DeserializeObject(transport.Objects[d1.GetId(true)])); var d2_ = NotNullExtensions.NotNull(JsonConvert.DeserializeObject(transport.Objects[d2.GetId(true)])); var d3_ = NotNullExtensions.NotNull(JsonConvert.DeserializeObject(transport.Objects[d3.GetId(true)])); - var d4_ = JsonConvert.DeserializeObject(transport.Objects[d4.GetId(true)]); - var d5_ = JsonConvert.DeserializeObject(transport.Objects[d5.GetId(true)]); + JsonConvert.DeserializeObject(transport.Objects[d4.GetId(true)]); + JsonConvert.DeserializeObject(transport.Objects[d5.GetId(true)]); var depthOf_d5_in_d1 = int.Parse((string)d1_.__closure[d5.GetId(true)]); - Assert.That(depthOf_d5_in_d1, Is.EqualTo(1)); + depthOf_d5_in_d1.Should().Be(1); var depthOf_d4_in_d1 = int.Parse((string)d1_.__closure[d4.GetId(true)]); - Assert.That(depthOf_d4_in_d1, Is.EqualTo(3)); + depthOf_d4_in_d1.Should().Be(3); var depthOf_d5_in_d3 = int.Parse((string)d3_.__closure[d5.GetId(true)]); - Assert.That(depthOf_d5_in_d3, Is.EqualTo(2)); + depthOf_d5_in_d3.Should().Be(2); var depthOf_d4_in_d3 = int.Parse((string)d3_.__closure[d4.GetId(true)]); - Assert.That(depthOf_d4_in_d3, Is.EqualTo(1)); + depthOf_d4_in_d3.Should().Be(1); var depthOf_d5_in_d2 = int.Parse((string)d2_.__closure[d5.GetId(true)]); - Assert.That(depthOf_d5_in_d2, Is.EqualTo(1)); + depthOf_d5_in_d2.Should().Be(1); } - [Test] + [Fact] public void DescendantsCounting() { Base myBase = new(); @@ -118,18 +116,15 @@ public void DescendantsCounting() myBase["@detachTheDictionary"] = dictionary; - var count = myBase.GetTotalChildrenCount(); - Assert.That(count, Is.EqualTo(112)); + myBase.GetTotalChildrenCount().Should().Be(112); var tableTest = new DiningTable(); - var tableKidsCount = tableTest.GetTotalChildrenCount(); - Assert.That(tableKidsCount, Is.EqualTo(10)); + tableTest.GetTotalChildrenCount().Should().Be(10); // Explicitely test for recurisve references! var recursiveRef = new Base { applicationId = "random" }; recursiveRef["@recursive"] = recursiveRef; - var supriseCount = recursiveRef.GetTotalChildrenCount(); - Assert.That(supriseCount, Is.EqualTo(2)); + recursiveRef.GetTotalChildrenCount().Should().Be(2); } } diff --git a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/OperationsReceiveTests.Exceptional.cs b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/OperationsReceiveTests.Exceptional.cs index 14caa794..1079ef52 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/OperationsReceiveTests.Exceptional.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/OperationsReceiveTests.Exceptional.cs @@ -1,43 +1,39 @@ -using Microsoft.Extensions.DependencyInjection; -using NUnit.Framework; -using Speckle.Sdk.Api; -using Speckle.Sdk.Host; -using Speckle.Sdk.Models; -using Speckle.Sdk.Transports; +using Speckle.Sdk.Transports; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Api.Operations; public partial class OperationsReceiveTests { - [Test, TestCaseSource(nameof(TestCases))] - public void Receive_ObjectsDontExist_ExceptionThrown(string id) + [Theory, MemberData(nameof(TestCases))] + public async Task Receive_ObjectsDontExist_ExceptionThrown(string id) { MemoryTransport emptyTransport1 = new(); MemoryTransport emptyTransport2 = new(); - Assert.ThrowsAsync(async () => + await Assert.ThrowsAsync(async () => { await _operations.Receive(id, emptyTransport1, emptyTransport2); }); } - [Test, TestCaseSource(nameof(TestCases))] - public void Receive_ObjectsDontExistNullRemote_ExceptionThrown(string id) + [Theory, MemberData(nameof(TestCases))] + public async Task Receive_ObjectsDontExistNullRemote_ExceptionThrown(string id) { MemoryTransport emptyTransport = new(); - Assert.ThrowsAsync(async () => + await Assert.ThrowsAsync(async () => { await _operations.Receive(id, null, emptyTransport); }); } - [Test, TestCaseSource(nameof(TestCases))] - public void Receive_OperationCanceled_ExceptionThrown(string id) + [Theory, MemberData(nameof(TestCases))] + public async Task Receive_OperationCanceled_ExceptionThrown(string id) { using CancellationTokenSource ctc = new(); ctc.Cancel(); MemoryTransport emptyTransport2 = new(); - Assert.CatchAsync(async () => + await Assert.ThrowsAsync(async () => { await _operations.Receive(id, _testCaseTransport, emptyTransport2, cancellationToken: ctc.Token); }); diff --git a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/OperationsReceiveTests.cs b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/OperationsReceiveTests.cs index 1a51c2f5..8df3a466 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/OperationsReceiveTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/OperationsReceiveTests.cs @@ -1,18 +1,18 @@ using System.Reflection; using Microsoft.Extensions.DependencyInjection; -using NUnit.Framework; using Speckle.Sdk.Api; using Speckle.Sdk.Host; using Speckle.Sdk.Models; using Speckle.Sdk.Transports; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Api.Operations; -[TestFixture, TestOf(nameof(Sdk.Api.Operations.Receive))] -public sealed partial class OperationsReceiveTests +public sealed partial class OperationsReceiveTests : IDisposable { private static readonly Base[] s_testObjects; - private IOperations _operations; + private readonly IOperations _operations; + private readonly MemoryTransport _testCaseTransport; static OperationsReceiveTests() { @@ -29,64 +29,57 @@ static OperationsReceiveTests() ]; } - public static IEnumerable TestCases() + public OperationsReceiveTests() { - List ret = new(); - foreach (var s in s_testObjects) + Reset(); + var serviceProvider = TestServiceSetup.GetServiceProvider(); + _operations = serviceProvider.GetRequiredService(); + _testCaseTransport = new MemoryTransport(); + + // Simulate a one-time setup action + foreach (var b in s_testObjects) { - ret.Add(s.GetId(true)); + _ = _operations.Send(b, _testCaseTransport, false).GetAwaiter().GetResult(); } - - return ret; } - private MemoryTransport _testCaseTransport; - private static void Reset() { TypeLoader.Reset(); TypeLoader.Initialize(typeof(Base).Assembly, Assembly.GetExecutingAssembly()); } - [OneTimeSetUp] - public async Task GlobalSetup() + public static IEnumerable TestCases() { - Reset(); - var serviceProvider = TestServiceSetup.GetServiceProvider(); - _operations = serviceProvider.GetRequiredService(); - _testCaseTransport = new MemoryTransport(); - foreach (var b in s_testObjects) + foreach (var s in s_testObjects) { - await _operations.Send(b, _testCaseTransport, false); + yield return [s.GetId(true)]; } } - [SetUp] - public void Setup() - { - Reset(); - var serviceProvider = TestServiceSetup.GetServiceProvider(); - _operations = serviceProvider.GetRequiredService(); - } - - [Test, TestCaseSource(nameof(TestCases))] + [Theory] + [MemberData(nameof(TestCases))] public async Task Receive_FromLocal_ExistingObjects(string id) { Base result = await _operations.Receive(id, null, _testCaseTransport); - Assert.That(result.id, Is.EqualTo(id)); + Assert.NotNull(result); + Assert.Equal(id, result.id); } - [Test, TestCaseSource(nameof(TestCases))] + [Theory] + [MemberData(nameof(TestCases))] public async Task Receive_FromRemote_ExistingObjects(string id) { MemoryTransport localTransport = new(); Base result = await _operations.Receive(id, _testCaseTransport, localTransport); - Assert.That(result.id, Is.EqualTo(id)); + Assert.NotNull(result); + Assert.Equal(id, result.id); } - [Test, TestCaseSource(nameof(TestCases))] + [Theory] + [MemberData(nameof(TestCases))] public async Task Receive_FromLocal_OnProgressActionCalled(string id) { bool wasCalled = false; @@ -97,6 +90,11 @@ public async Task Receive_FromLocal_OnProgressActionCalled(string id) onProgressAction: new UnitTestProgress(_ => wasCalled = true) ); - Assert.That(wasCalled, Is.True); + Assert.True(wasCalled); + } + + public void Dispose() + { + // Cleanup resources if necessary } } diff --git a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendObjectReferences.cs b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendObjectReferences.cs index dd486a7f..3e402fc8 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendObjectReferences.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendObjectReferences.cs @@ -1,18 +1,18 @@ -using Microsoft.Extensions.DependencyInjection; -using NUnit.Framework; +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; using Speckle.Sdk.Api; using Speckle.Sdk.Host; using Speckle.Sdk.Models; using Speckle.Sdk.Transports; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Api.Operations; public class SendObjectReferences { - private IOperations _operations; + private readonly IOperations _operations; - [SetUp] - public void Setup() + public SendObjectReferences() { TypeLoader.Reset(); TypeLoader.Initialize(typeof(Base).Assembly, typeof(DataChunk).Assembly); @@ -20,34 +20,38 @@ public void Setup() _operations = serviceProvider.GetRequiredService(); } - [TestCase(0)] - [TestCase(1)] - [TestCase(10)] + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(10)] public async Task SendObjectsWithApplicationIds(int testDepth) { Base testData = GenerateTestCase(testDepth, true); MemoryTransport transport = new(); var result = await _operations.Send(testData, [transport]); - Assert.That(result.rootObjId, Is.Not.Null); - Assert.That(result.rootObjId, Has.Length.EqualTo(32)); + result.rootObjId.Should().NotBeNull(); - Assert.That(result.convertedReferences, Has.Count.EqualTo(Math.Pow(2, testDepth + 1) - 2)); + result.rootObjId.Length.Should().Be(32); + + result.convertedReferences.Count.Should().Be((int)(Math.Pow(2, testDepth + 1) - 2)); } - [TestCase(0)] - [TestCase(1)] - [TestCase(10)] + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(10)] public async Task SendObjectsWithoutApplicationIds(int testDepth) { Base testData = GenerateTestCase(testDepth, false); MemoryTransport transport = new(); var result = await _operations.Send(testData, [transport]); - Assert.That(result.rootObjId, Is.Not.Null); - Assert.That(result.rootObjId, Has.Length.EqualTo(32)); + result.rootObjId.Should().NotBeNull(); + + result.rootObjId.Length.Should().Be(32); - Assert.That(result.convertedReferences, Is.Empty); + result.convertedReferences.Should().BeEmpty(); } private Base GenerateTestCase(int depth, bool withAppId) diff --git a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendReceiveLocal.cs b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendReceiveLocal.cs index c08e9ee5..813d9307 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendReceiveLocal.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SendReceiveLocal.cs @@ -1,23 +1,20 @@ -using System.Collections.Concurrent; +using FluentAssertions; using Microsoft.Extensions.DependencyInjection; -using NUnit.Framework; -using Shouldly; using Speckle.Sdk.Api; using Speckle.Sdk.Common; using Speckle.Sdk.Host; using Speckle.Sdk.Models; using Speckle.Sdk.Tests.Unit.Host; using Speckle.Sdk.Transports; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Api.Operations; -[TestFixture] public sealed class SendReceiveLocal : IDisposable { - private IOperations _operations; + private readonly IOperations _operations; - [SetUp] - public void Setup() + public SendReceiveLocal() { TypeLoader.Reset(); TypeLoader.Initialize(typeof(Base).Assembly, typeof(Point).Assembly); @@ -25,15 +22,14 @@ public void Setup() _operations = serviceProvider.GetRequiredService(); } - private string? _objId01; - private string? _commitId02; - private const int NUM_OBJECTS = 3001; private readonly SQLiteTransport _sut = new(); - [Test(Description = "Pushing a commit locally"), Order(1)] - public async Task LocalUpload() + public void Dispose() => _sut.Dispose(); + + [Fact(DisplayName = "Pushing a commit locally")] + public async Task LocalUploadAndDownload() { var myObject = new Base(); var rand = new Random(); @@ -48,24 +44,18 @@ public async Task LocalUpload() } using SQLiteTransport localTransport = new(); - (_objId01, var references) = await _operations.Send(myObject, localTransport, false); + (var objId01, var references) = await _operations.Send(myObject, localTransport, false); - Assert.That(_objId01, Is.Not.Null); - Assert.That(references, Has.Count.EqualTo(NUM_OBJECTS)); + objId01.Should().NotBeNull(); + references.Count.Should().Be(NUM_OBJECTS); - TestContext.Out.WriteLine($"Written {NUM_OBJECTS + 1} objects. Commit id is {_objId01}"); - } + var commitPulled = await _operations.Receive(objId01.NotNull()); - [Test(Description = "Pulling a commit locally"), Order(2)] - public async Task LocalDownload() - { - var commitPulled = await _operations.Receive(_objId01.NotNull()); - - Assert.That(((List)commitPulled["@items"].NotNull())[0], Is.TypeOf()); - Assert.That(((List)commitPulled["@items"].NotNull()), Has.Count.EqualTo(NUM_OBJECTS)); + ((List)commitPulled["@items"].NotNull())[0].Should().BeOfType(); + ((List)commitPulled["@items"].NotNull()).Count.Should().Be(NUM_OBJECTS); } - [Test(Description = "Pushing and Pulling a commit locally")] + [Fact(DisplayName = "Pushing and Pulling a commit locally")] public async Task LocalUploadDownload() { var myObject = new Base(); @@ -80,16 +70,15 @@ public async Task LocalUploadDownload() ); } - (_objId01, _) = await _operations.Send(myObject, _sut, false); + (var objId01, _) = await _operations.Send(myObject, _sut, false); - var commitPulled = await _operations.Receive(_objId01); + var commitPulled = await _operations.Receive(objId01); List items = (List)commitPulled["@items"].NotNull(); - - Assert.That(items, Has.All.TypeOf()); - Assert.That(items, Has.Count.EqualTo(NUM_OBJECTS)); + items.Should().AllSatisfy(x => x.Should().BeOfType()); + items.Count.Should().Be(NUM_OBJECTS); } - [Test(Description = "Pushing and pulling a commit locally"), Order(3)] + [Fact(DisplayName = "Pushing and pulling a commit locally")] public async Task LocalUploadDownloadSmall() { var myObject = new Base(); @@ -104,16 +93,15 @@ public async Task LocalUploadDownloadSmall() ); } - (_objId01, _) = await _operations.Send(myObject, _sut, false); + (var objId01, _) = await _operations.Send(myObject, _sut, false); - Assert.That(_objId01, Is.Not.Null); - TestContext.Out.WriteLine($"Written {NUM_OBJECTS + 1} objects. Commit id is {_objId01}"); + objId01.Should().NotBeNull(); - var objsPulled = await _operations.Receive(_objId01); - Assert.That(((List)objsPulled["@items"].NotNull()), Has.Count.EqualTo(30)); + var objsPulled = await _operations.Receive(objId01); + ((List)objsPulled["@items"].NotNull()).Count.Should().Be(30); } - [Test(Description = "Pushing and pulling a commit locally"), Order(3)] + [Fact(DisplayName = "Pushing and pulling a commit locally")] public async Task LocalUploadDownloadListDic() { var myList = new List { 1, 2, 3, "ciao" }; @@ -128,19 +116,16 @@ public async Task LocalUploadDownloadListDic() myObject["@dictionary"] = myDic; myObject["@list"] = myList; - (_objId01, _) = await _operations.Send(myObject, _sut, false); + (var _objId01, _) = await _operations.Send(myObject, _sut, false); - Assert.That(_objId01, Is.Not.Null); + _objId01.Should().NotBeNull(); var objsPulled = await _operations.Receive(_objId01); - Assert.That( - ((List)((Dictionary)objsPulled["@dictionary"].NotNull())["a"]).First(), - Is.EqualTo(1) - ); - Assert.That(((List)objsPulled["@list"].NotNull()).Last(), Is.EqualTo("ciao")); + ((List)((Dictionary)objsPulled["@dictionary"].NotNull())["a"]).First().Should().Be(1); + ((List)objsPulled["@list"].NotNull()).Last().Should().Be("ciao"); } - [Test(Description = "Pushing and pulling a random object, with our without detachment"), Order(3)] + [Fact(DisplayName = "Pushing and pulling a random object, with or without detachment")] public async Task UploadDownloadNonCommitObject() { var obj = new Base(); @@ -166,32 +151,31 @@ public async Task UploadDownloadNonCommitObject() ((List)((dynamic)obj)["@LayerC"]).Add(new Point(i, i, i + rand.NextDouble()) { applicationId = i + "baz" }); } - (_objId01, _) = await _operations.Send(obj, _sut, false); + (var objId01, _) = await _operations.Send(obj, _sut, false); - Assert.That(_objId01, Is.Not.Null); - TestContext.Out.WriteLine($"Written {NUM_OBJECTS + 1} objects. Commit id is {_objId01}"); + objId01.Should().NotBeNull(); - var objPulled = await _operations.Receive(_objId01); + var objPulled = await _operations.Receive(objId01); - Assert.That(objPulled, Is.TypeOf()); + objPulled.Should().BeOfType(); // Note: even if the layers were originally declared as lists of "Base" objects, on deserialisation we cannot know that, // as it's a dynamic property. Dynamic properties, if their content value is ambigous, will default to a common-sense standard. // This specifically manifests in the case of lists and dictionaries: List will become List, and // Dictionary will deserialize to Dictionary. var layerA = ((dynamic)objPulled)["LayerA"] as List; - Assert.That(layerA, Has.Count.EqualTo(30)); + layerA?.Count.Should().Be(30); var layerC = (List)((dynamic)objPulled)["@LayerC"]; - Assert.That(layerC, Has.Count.EqualTo(30)); - Assert.That(layerC[0], Is.TypeOf()); + layerC.Count.Should().Be(30); + layerC[0].Should().BeOfType(); var layerD = ((dynamic)objPulled)["@LayerD"] as List; - Assert.That(layerD, Has.Count.EqualTo(2)); + layerD?.Count.Should().Be(2); } - [Test(Description = "Should show progress!"), Order(4)] - public async Task UploadProgressReports() + [Fact(DisplayName = "Should show progress!")] + public async Task UploadAndDownloadProgressReports() { Base myObject = new() { ["items"] = new List() }; var rand = new Random(); @@ -203,24 +187,20 @@ public async Task UploadProgressReports() ); } - (_commitId02, _) = await _operations.Send(myObject, _sut, false); - } + (var commitId02, _) = await _operations.Send(myObject, _sut, false); - [Test(Description = "Should show progress!"), Order(5)] - public async Task DownloadProgressReports() - { ProgressArgs? progress = null; await _operations.Receive( - _commitId02.NotNull(), + commitId02.NotNull(), onProgressAction: new UnitTestProgress(x => { progress = x; }) ); - progress.ShouldNotBeNull(); + progress.Should().NotBeNull(); } - [Test(Description = "Should not dispose of transports if so specified.")] + [Fact(DisplayName = "Should not dispose of transports if so specified.")] public async Task ShouldNotDisposeTransports() { var @base = new Base(); @@ -233,9 +213,4 @@ public async Task ShouldNotDisposeTransports() _ = await _operations.Receive(sendResult.rootObjId, null, myLocalTransport); await _operations.Receive(sendResult.rootObjId, null, myLocalTransport); } - - public void Dispose() - { - _sut.Dispose(); - } } diff --git a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SerializationTests.cs b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SerializationTests.cs index 3dda4555..b5dc30fe 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SerializationTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Api/Operations/SerializationTests.cs @@ -1,24 +1,20 @@ using System.Drawing; +using FluentAssertions; using Microsoft.Extensions.DependencyInjection; -using NUnit.Framework; using Speckle.Sdk.Api; -using Speckle.Sdk.Api.GraphQL.Models; -using Speckle.Sdk.Credentials; using Speckle.Sdk.Host; using Speckle.Sdk.Models; using Speckle.Sdk.Tests.Unit.Host; +using Xunit; using Point = Speckle.Sdk.Tests.Unit.Host.Point; namespace Speckle.Sdk.Tests.Unit.Api.Operations; -[TestFixture] -[TestOf(typeof(Sdk.Api.Operations))] public class ObjectSerialization { - private IOperations _operations; + private readonly IOperations _operations; - [SetUp] - public void Setup() + public ObjectSerialization() { TypeLoader.Reset(); TypeLoader.Initialize(typeof(Base).Assembly, typeof(DataChunk).Assembly, typeof(ColorMock).Assembly); @@ -26,7 +22,7 @@ public void Setup() _operations = serviceProvider.GetRequiredService(); } - [Test] + [Fact] public async Task IgnoreCircularReferences() { var pt = new Point(1, 2, 3); @@ -36,10 +32,10 @@ public async Task IgnoreCircularReferences() var result = await _operations.DeserializeAsync(test); var circle = result["circle"]; - Assert.That(circle, Is.Null); + circle.Should().BeNull(); } - [Test] + [Fact] public async Task InterfacePropHandling() { Line tail = new() { Start = new Point(0, 0, 0), End = new Point(42, 42, 42) }; @@ -75,10 +71,10 @@ public async Task InterfacePropHandling() var deserialisedFeline = await _operations.DeserializeAsync(result); - Assert.That(deserialisedFeline.GetId(), Is.EqualTo(cat.GetId())); // If we're getting the same hash... we're probably fine! + deserialisedFeline.GetId().Should().Be(cat.GetId()); } - [Test] + [Fact] public async Task InheritanceTests() { var superPoint = new SuperPoint @@ -92,10 +88,10 @@ public async Task InheritanceTests() var str = _operations.Serialize(superPoint); var sstr = await _operations.DeserializeAsync(str); - Assert.That(sstr.speckle_type, Is.EqualTo(superPoint.speckle_type)); + sstr.speckle_type.Should().Be(superPoint.speckle_type); } - [Test] + [Fact] public async Task ListDynamicProp() { var point = new Point(); @@ -111,11 +107,12 @@ public async Task ListDynamicProp() var str = _operations.Serialize(point); var dsrls = await _operations.DeserializeAsync(str); - var list = dsrls["test"] as List; // NOTE: on dynamically added lists, we cannot infer the inner type and we always fall back to a generic list. - Assert.That(list, Has.Count.EqualTo(100)); + var list = dsrls["test"] as List; + list.Should().NotBeNull(); // Ensure the list isn't null in first place + list!.Count.Should().Be(100); } - [Test] + [Fact] public async Task ChunkSerialisation() { var baseBasedChunk = new DataChunk() { data = new() }; @@ -144,12 +141,12 @@ public async Task ChunkSerialisation() var stringChunkDeserialised = (DataChunk)await _operations.DeserializeAsync(stringChunkString); var doubleChunkDeserialised = (DataChunk)await _operations.DeserializeAsync(doubleChunkString); - Assert.That(baseChunkDeserialised.data, Has.Count.EqualTo(baseBasedChunk.data.Count)); - Assert.That(stringChunkDeserialised.data, Has.Count.EqualTo(stringBasedChunk.data.Count)); - Assert.That(doubleChunkDeserialised.data, Has.Count.EqualTo(doubleBasedChunk.data.Count)); + baseChunkDeserialised.data.Count.Should().Be(baseBasedChunk.data.Count); + stringChunkDeserialised.data.Count.Should().Be(stringBasedChunk.data.Count); + doubleChunkDeserialised.data.Count.Should().Be(doubleBasedChunk.data.Count); } - [Test] + [Fact] public async Task ObjectWithChunksSerialisation() { const int MAX_NUM = 2020; @@ -174,10 +171,10 @@ public async Task ObjectWithChunksSerialisation() var serialised = _operations.Serialize(mesh); var deserialised = await _operations.DeserializeAsync(serialised); - Assert.That(mesh.GetId(), Is.EqualTo(deserialised.GetId())); + mesh.GetId().Should().Be(deserialised.GetId()); } - [Test] + [Fact] public void EmptyListSerialisationTests() { // NOTE: expected behaviour is that empty lists should serialize as empty lists. Don't ask why, it's complicated. @@ -200,7 +197,7 @@ public void EmptyListSerialisationTests() && serialised.Contains("\"nestedList\":[[[]]]") && serialised.Contains("\"@nestedDetachableList\":[[[]]]"); - Assert.That(isCorrect, Is.EqualTo(true)); + isCorrect.Should().BeTrue(); } [SpeckleType("Speckle.Core.Tests.Unit.Api.Operations.ObjectSerialization+DateMock")] @@ -209,7 +206,7 @@ private class DateMock : Base public DateTime TestField { get; set; } } - [Test] + [Fact] public async Task DateSerialisation() { var date = new DateTime(2020, 1, 14); @@ -218,7 +215,7 @@ public async Task DateSerialisation() var result = _operations.Serialize(mockBase); var test = (DateMock)await _operations.DeserializeAsync(result); - Assert.That(test.TestField, Is.EqualTo(date)); + test.TestField.Should().Be(date); } [SpeckleType("Speckle.Core.Tests.Unit.Api.Operations.ObjectSerialization+GUIDMock")] @@ -227,7 +224,7 @@ private class GUIDMock : Base public Guid TestField { get; set; } } - [Test] + [Fact] public async Task GuidSerialisation() { var guid = Guid.NewGuid(); @@ -236,7 +233,7 @@ public async Task GuidSerialisation() var result = _operations.Serialize(mockBase); var test = (GUIDMock)await _operations.DeserializeAsync(result); - Assert.That(test.TestField, Is.EqualTo(guid)); + test.TestField.Should().Be(guid); } [SpeckleType("Speckle.Core.Tests.Unit.Api.Operations.ObjectSerialization+ColorMock")] @@ -245,7 +242,7 @@ private class ColorMock : Base public Color TestField { get; set; } } - [Test] + [Fact] public async Task ColorSerialisation() { var color = Color.FromArgb(255, 4, 126, 251); @@ -254,7 +251,7 @@ public async Task ColorSerialisation() var result = _operations.Serialize(mockBase); var test = (ColorMock)await _operations.DeserializeAsync(result); - Assert.That(test.TestField, Is.EqualTo(color)); + test.TestField.Should().Be(color); } [SpeckleType("Speckle.Core.Tests.Unit.Api.Operations.ObjectSerialization+StringDateTimeRegressionMock")] @@ -263,7 +260,7 @@ private class StringDateTimeRegressionMock : Base public string TestField { get; set; } } - [Test] + [Fact] public async Task StringDateTimeRegression() { var mockBase = new StringDateTimeRegressionMock { TestField = "2021-11-12T11:32:01" }; @@ -271,6 +268,6 @@ public async Task StringDateTimeRegression() var result = _operations.Serialize(mockBase); var test = (StringDateTimeRegressionMock)await _operations.DeserializeAsync(result); - Assert.That(test.TestField, Is.EqualTo(mockBase.TestField)); + test.TestField.Should().Be(mockBase.TestField); } } diff --git a/tests/Speckle.Sdk.Tests.Unit/Assembly.cs b/tests/Speckle.Sdk.Tests.Unit/Assembly.cs new file mode 100644 index 00000000..a4bcec54 --- /dev/null +++ b/tests/Speckle.Sdk.Tests.Unit/Assembly.cs @@ -0,0 +1,3 @@ +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/tests/Speckle.Sdk.Tests.Unit/Common/NotNullTests.cs b/tests/Speckle.Sdk.Tests.Unit/Common/NotNullTests.cs index 169b36cf..a9ac02d5 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Common/NotNullTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Common/NotNullTests.cs @@ -1,87 +1,94 @@ -using NUnit.Framework; -using Shouldly; +using FluentAssertions; using Speckle.Sdk.Common; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Common; public class NotNullTests { - [TestCase(null, 0)] - [TestCase(new string[0], 0)] - [TestCase(new[] { "yay" }, 1)] + [Theory] + [InlineData(null, 0)] + [InlineData(new string[0], 0)] + [InlineData(new[] { "yay" }, 1)] public void Empty(string[]? test, int length) { - var list = NotNullExtensions.Empty(test).ToList(); - list.Count.ShouldBe(length); + var list = test.Empty().ToList(); + list.Count.Should().Be(length); } - [Test] + [Fact] public void NotNullClass() { - var t = NotNullExtensions.NotNull("test"); - t.ShouldNotBeNull().ShouldBe("test"); + var t = "test".NotNull(); + t.Should().Be("test"); } - [Test] + [Fact] public void NotNullStruct() { var t = NotNullExtensions.NotNull(2); - t.ShouldBe(2); + t.Should().Be(2); } - [Test] + [Fact] public async Task NotNullClass_Task() { - var t = await NotNullExtensions.NotNull(Task.FromResult("test")); - t.ShouldNotBeNull().ShouldBe("test"); + var t = await Task.FromResult("test").NotNull(); + t.Should().Be("test"); } - [Test] + [Fact] public async Task NotNullStruct_Task() { - var t = await NotNullExtensions.NotNull(Task.FromResult(2)); - t.ShouldBe(2); + var t = await Task.FromResult(2).NotNull(); + t.Should().Be(2); } - [Test] + [Fact] public async Task NotNullClass_ValueTask() { - var t = await NotNullExtensions.NotNull(ValueTask.FromResult("test")); - t.ShouldNotBeNull().ShouldBe("test"); + var t = await ValueTask.FromResult("test").NotNull(); + t.Should().Be("test"); } - [Test] + [Fact] public async Task NotNullStruct_ValueTask() { - var t = await NotNullExtensions.NotNull(ValueTask.FromResult(2)); - t.ShouldBe(2); + var t = await ValueTask.FromResult(2).NotNull(); + t.Should().Be(2); } - [Test] - public void NotNullClass_Exception() => - Assert.Throws(() => NotNullExtensions.NotNull((string?)null)); + [Fact] + public void NotNullClass_Exception() => FluentActions.Invoking(() => ((string?)null).NotNull()); - [Test] - public void NotNullStruct_Exception() => - Assert.Throws(() => NotNullExtensions.NotNull((int?)null)); + [Fact] + public void NotNullStruct_Exception() => FluentActions.Invoking(() => ((int?)null).NotNull()); - [Test] - public void NotNullClass_Task_Exception() => - Assert.ThrowsAsync(() => NotNullExtensions.NotNull(Task.FromResult((string?)null))); + [Fact] + public async Task NotNullClass_Task_Exception() => + await FluentActions + .Invoking(async () => await Task.FromResult((string?)null).NotNull()) + .Should() + .ThrowAsync(); - [Test] - public void NotNullStruct_Task_Exception() => - Assert.ThrowsAsync(() => NotNullExtensions.NotNull(Task.FromResult((int?)null))); + [Fact] + public async Task NotNullStruct_Task_Exception() => + await FluentActions + .Invoking(async () => await Task.FromResult((int?)null).NotNull()) + .Should() + .ThrowAsync(); - [Test] - public void NotNullClass_ValueTask_Exception() => - Assert.ThrowsAsync( - async () => await NotNullExtensions.NotNull(ValueTask.FromResult((string?)null)) - ); + [Fact] + public async Task NotNullClass_ValueTask_Exception() => + await FluentActions + .Invoking(async () => await ValueTask.FromResult((string?)null).NotNull()) + .Should() + .ThrowAsync(); - [Test] - public void NotNullStruct_ValueTask_Exception() => - Assert.ThrowsAsync( - async () => await NotNullExtensions.NotNull(ValueTask.FromResult((int?)null)) - ); + [Fact] + public async Task NotNullStruct_ValueTask_Exception() => + await FluentActions + .Invoking(async () => await ValueTask.FromResult((int?)null).NotNull()) + .Should() + .ThrowAsync(); } diff --git a/tests/Speckle.Sdk.Tests.Unit/Common/RangeFromTests.cs b/tests/Speckle.Sdk.Tests.Unit/Common/RangeFromTests.cs new file mode 100644 index 00000000..575277bd --- /dev/null +++ b/tests/Speckle.Sdk.Tests.Unit/Common/RangeFromTests.cs @@ -0,0 +1,14 @@ +using Speckle.Sdk.Dependencies; +using Xunit; + +namespace Speckle.Sdk.Tests.Unit.Common; + +public class RangeFromTests +{ + [Fact] + public void EnsureRange() + { + var list = EnumerableExtensions.RangeFrom(1, 4).ToArray(); + Assert.Equal([1, 2, 3, 4], list); + } +} diff --git a/tests/Speckle.Sdk.Tests.Unit/Common/UnitsTest.cs b/tests/Speckle.Sdk.Tests.Unit/Common/UnitsTest.cs index 938d3a7e..c93a0b51 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Common/UnitsTest.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Common/UnitsTest.cs @@ -1,70 +1,98 @@ -using NUnit.Framework; +using FluentAssertions; using Speckle.Sdk.Common; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Common; -[TestOf(typeof(Units))] public class UnitsTest { private const double EPS = 0.00022; public static List OfficiallySupportedUnits => Units.SupportedUnits; + public static List NotSupportedUnits => ["feeters", "liters", "us_ft"]; - public static List ConversionSupport => [.. Units.SupportedUnits, null]; - - [Test, Combinatorial] - [DefaultFloatingPointTolerance(EPS)] - public void TestUnitConversion( - [ValueSource(nameof(ConversionSupport))] string? from, - [ValueSource(nameof(ConversionSupport))] string? to - ) + + public static List ConversionSupport => Units.SupportedUnits.Concat([null]).ToList(); + + [Theory] + [MemberData(nameof(ConversionSupportGenerator))] + public void TestUnitConversion(string? from, string? to) { var forwards = Units.GetConversionFactor(from, to); var backwards = Units.GetConversionFactor(to, from); - Assert.That( - backwards * forwards, - Is.EqualTo(1d), - $"Behaviour says that 1{from} == {forwards}{to}, and 1{to} == {backwards}{from}" - ); + (backwards * forwards) + .Should() + .BeApproximately(1d, EPS, $"Behaviour says that 1{from} == {forwards}{to}, and 1{to} == {backwards}{from}"); } - [TestCaseSource(nameof(OfficiallySupportedUnits))] + [Theory] + [MemberData(nameof(OfficiallySupportedUnitsGenerator))] public void IsUnitSupported_ReturnsTrue_AllSupportedUnits(string unit) { bool res = Units.IsUnitSupported(unit); - Assert.That(res, Is.True); + res.Should().BeTrue(); } - [TestCaseSource(nameof(NotSupportedUnits))] + [Theory] + [MemberData(nameof(NotSupportedUnitsGenerator))] public void IsUnitSupported_ReturnsFalse_NotSupportedUnits(string unit) { bool res = Units.IsUnitSupported(unit); - Assert.That(res, Is.False); + res.Should().BeFalse(); } - [TestCaseSource(nameof(OfficiallySupportedUnits))] + [Theory] + [MemberData(nameof(OfficiallySupportedUnitsGenerator))] public void GetUnitsFromString_ReturnsSupported(string unit) { - var lower = Units.GetUnitsFromString(unit); - var upper = Units.GetUnitsFromString(unit?.ToUpperInvariant()); + string? lower = Units.GetUnitsFromString(unit); + string? upper = Units.GetUnitsFromString(unit.ToUpperInvariant()); - Assert.That(lower, Is.EqualTo(unit)); - Assert.That(upper, Is.EqualTo(unit)); + lower.Should().Be(unit); + upper.Should().Be(unit); } - [TestCaseSource(nameof(NotSupportedUnits))] - public void GetUnitsFromString_ThrowsUnSupported(string unit) - { - Assert.Throws(() => _ = Units.GetUnitsFromString(unit)); - } + [Theory] + [MemberData(nameof(NotSupportedUnitsGenerator))] + public void GetUnitsFromString_ThrowsUnSupported(string unit) => + FluentActions.Invoking(() => Units.GetUnitsFromString(unit)).Should().Throw(); - [TestCaseSource(nameof(OfficiallySupportedUnits))] + [Theory] + [MemberData(nameof(OfficiallySupportedUnitsGenerator))] public void UnitEncoding_RoundTrip(string unit) { var encoded = Units.GetEncodingFromUnit(unit); var res = Units.GetUnitFromEncoding(encoded); - Assert.That(res, Is.EqualTo(unit)); + res.Should().Be(unit); + } + + // Generators for MemberData + public static IEnumerable OfficiallySupportedUnitsGenerator() + { + foreach (var unit in OfficiallySupportedUnits) + { + yield return [unit]; + } + } + + public static IEnumerable NotSupportedUnitsGenerator() + { + foreach (var unit in NotSupportedUnits) + { + yield return [unit]; + } + } + + public static IEnumerable ConversionSupportGenerator() + { + foreach (var from in ConversionSupport) + { + foreach (var to in ConversionSupport) + { + yield return [from, to]; + } + } } } diff --git a/tests/Speckle.Sdk.Tests.Unit/Credentials/AccountServerMigrationTests.cs b/tests/Speckle.Sdk.Tests.Unit/Credentials/AccountServerMigrationTests.cs index b0254631..b61eae75 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Credentials/AccountServerMigrationTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Credentials/AccountServerMigrationTests.cs @@ -1,68 +1,66 @@ +using FluentAssertions; using Microsoft.Extensions.DependencyInjection; -using NUnit.Framework; using Speckle.Sdk.Api.GraphQL.Models; using Speckle.Sdk.Credentials; -using Speckle.Sdk.Host; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Credentials; -public class AccountServerMigrationTests +public class AccountServerMigrationTests : IDisposable { - private readonly List _accountsToCleanUp = new(); + private readonly List _accountsToCleanUp = []; - public static IEnumerable MigrationTestCase() + public static IEnumerable MigrationTestCases() { const string OLD_URL = "https://old.example.com"; const string NEW_URL = "https://new.example.com"; const string OTHER_URL = "https://other.example.com"; + Account oldAccount = CreateTestAccount(OLD_URL, null, new(NEW_URL)); string accountId = oldAccount.userInfo.id; // new account user must match old account user id Account newAccount = CreateTestAccount(NEW_URL, new(OLD_URL), null, accountId); Account otherAccount = CreateTestAccount(OTHER_URL, null, null); - List givenAccounts = new() { oldAccount, newAccount, otherAccount }; + List givenAccounts = [oldAccount, newAccount, otherAccount]; - yield return new TestCaseData(givenAccounts, NEW_URL, new[] { newAccount }) - .SetName("Get New") - .SetDescription("When requesting for new account, ensure only this account is returned"); + yield return [givenAccounts, NEW_URL, new[] { newAccount }]; - yield return new TestCaseData(givenAccounts, OLD_URL, new[] { newAccount }) - .SetName("Get New via Old") - .SetDescription("When requesting for old account, ensure migrated account is returned first"); + yield return [givenAccounts, OLD_URL, new[] { newAccount }]; - var reversed = Enumerable.Reverse(givenAccounts).ToList(); + var reversed = givenAccounts.AsEnumerable().Reverse().ToList(); - yield return new TestCaseData(reversed, OLD_URL, new[] { newAccount }) - .SetName("Get New via Old (Reversed order)") - .SetDescription("Account order shouldn't matter"); + yield return [reversed, OLD_URL, new[] { newAccount }]; } - [Test] - [TestCaseSource(nameof(MigrationTestCase))] + [Theory] + [MemberData(nameof(MigrationTestCases))] public void TestServerMigration(IList accounts, string requestedUrl, IList expectedSequence) { + // Add accounts to the local setup AddAccounts(accounts); - var serviceProvider = TestServiceSetup.GetServiceProvider(); + var serviceProvider = TestServiceSetup.GetServiceProvider(); var result = serviceProvider.GetRequiredService().GetAccounts(requestedUrl).ToList(); - Assert.That(result, Is.EquivalentTo(expectedSequence)); + // Assert the result using Shouldly + result.Should().BeEquivalentTo(expectedSequence); } - [TearDown] - public void TearDown() + public void Dispose() { - //Clean up any of the test accounts we made + // Clean up accounts after each test foreach (var acc in _accountsToCleanUp) { Fixtures.DeleteLocalAccount(acc.id); } + _accountsToCleanUp.Clear(); } private static Account CreateTestAccount(string url, Uri? movedFrom, Uri? movedTo, string? id = null) { id ??= Guid.NewGuid().ToString(); + return new Account { token = "myToken", @@ -83,7 +81,7 @@ private static Account CreateTestAccount(string url, Uri? movedFrom, Uri? movedT private void AddAccounts(IEnumerable accounts) { - foreach (Account account in accounts) + foreach (var account in accounts) { _accountsToCleanUp.Add(account); Fixtures.UpdateOrSaveAccount(account); diff --git a/tests/Speckle.Sdk.Tests.Unit/Credentials/Accounts.cs b/tests/Speckle.Sdk.Tests.Unit/Credentials/Accounts.cs index e1643ed0..ac9df5f0 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Credentials/Accounts.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Credentials/Accounts.cs @@ -1,19 +1,19 @@ +using FluentAssertions; using Microsoft.Extensions.DependencyInjection; -using NUnit.Framework; -using Speckle.Sdk.Api; using Speckle.Sdk.Api.GraphQL.Models; using Speckle.Sdk.Credentials; -using Speckle.Sdk.Host; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Credentials; -[TestFixture] -public class CredentialInfrastructure +public class CredentialInfrastructure : IDisposable { - private IAccountManager _accountManager; + private readonly IAccountManager _accountManager; + private static readonly Account s_testAccount1; + private static readonly Account s_testAccount2; + private static readonly Account s_testAccount3; - [OneTimeSetUp] - public static void SetUp() + static CredentialInfrastructure() { s_testAccount1 = new Account { @@ -42,76 +42,69 @@ public static void SetUp() name = "Test Account 3", }, }; + } + public CredentialInfrastructure() + { Fixtures.UpdateOrSaveAccount(s_testAccount1); Fixtures.UpdateOrSaveAccount(s_testAccount2); Fixtures.SaveLocalAccount(s_testAccount3); - } - [SetUp] - public void Setup2() - { var serviceProvider = TestServiceSetup.GetServiceProvider(); _accountManager = serviceProvider.GetRequiredService(); } - [OneTimeTearDown] - public static void TearDown() + public void Dispose() { + _accountManager.Dispose(); Fixtures.DeleteLocalAccount(s_testAccount1.id); Fixtures.DeleteLocalAccount(s_testAccount2.id); + Fixtures.DeleteLocalAccount(s_testAccount3.id); Fixtures.DeleteLocalAccountFile(); } - private static Account s_testAccount1, - s_testAccount2, - s_testAccount3; - - [Test] + [Fact] public void GetAllAccounts() { var accs = _accountManager.GetAccounts().ToList(); - Assert.That(accs, Has.Count.GreaterThanOrEqualTo(3)); // Tests are adding three accounts, you might have extra accounts on your machine when testing :D + accs.Count.Should().BeGreaterThanOrEqualTo(3); // Tests are adding three accounts, there might be extra accounts locally } - [Test] + [Fact] public void GetAccount_ById() { var result = _accountManager.GetAccount(s_testAccount1.id); - Assert.That(result, Is.EqualTo(s_testAccount1)); + result.Should().Be(s_testAccount1); // Uses `Shouldly` for a clean assertion } - [Test] - public void GetAccount_ById_ThrowsWhenNotFound() - { - Assert.Throws(() => _accountManager.GetAccount("Non_existent_id")); - } + [Fact] + public void GetAccount_ById_ThrowsWhenNotFound() => + FluentActions + .Invoking(() => _accountManager.GetAccount("Non_existent_id")) + .Should() + .Throw(); - public static IEnumerable TestCases() - { - SetUp(); - return new[] { s_testAccount1, s_testAccount2, s_testAccount3 }; - } + public static TheoryData TestCases() => new() { s_testAccount1, s_testAccount2, s_testAccount3 }; - [Test] - [TestCaseSource(nameof(TestCases))] + [Theory] + [MemberData(nameof(TestCases))] public void GetAccountsForServer(Account target) { var accs = _accountManager.GetAccounts(target.serverInfo.url).ToList(); - Assert.That(accs, Has.Count.EqualTo(1)); + accs.Count.Should().Be(1); var acc = accs[0]; - Assert.That(acc, Is.Not.SameAs(target), "We expect new objects (no reference equality)"); - Assert.That(acc.serverInfo.company, Is.EqualTo(target.serverInfo.company)); - Assert.That(acc.serverInfo.url, Is.EqualTo(target.serverInfo.url)); - Assert.That(acc.refreshToken, Is.EqualTo(target.refreshToken)); - Assert.That(acc.token, Is.EqualTo(target.token)); + acc.Should().NotBeSameAs(target); // We expect new objects (no reference equality) + acc.serverInfo.company.Should().Be(target.serverInfo.company); + acc.serverInfo.url.Should().Be(target.serverInfo.url); + acc.refreshToken.Should().Be(target.refreshToken); + acc.token.Should().Be(target.token); } - [Test] + [Fact] public void EnsureLocalIdentifiers_AreUniqueAcrossServers() { // Accounts with the same user ID in different servers should always result in different local identifiers. @@ -128,6 +121,6 @@ public void EnsureLocalIdentifiers_AreUniqueAcrossServers() userInfo = new UserInfo { id = id }, }.GetLocalIdentifier(); - Assert.That(acc1, Is.Not.EqualTo(acc2)); + acc1.Should().NotBe(acc2); } } diff --git a/tests/Speckle.Sdk.Tests.Unit/Fixtures.cs b/tests/Speckle.Sdk.Tests.Unit/Fixtures.cs index 88746a75..65f49f12 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Fixtures.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Fixtures.cs @@ -28,13 +28,7 @@ public static void SaveLocalAccount(Account account) File.WriteAllText(s_accountPath, json); } - public static void DeleteLocalAccount(string id) - { - s_accountStorage.DeleteObject(id); - } + public static void DeleteLocalAccount(string id) => s_accountStorage.DeleteObject(id); - public static void DeleteLocalAccountFile() - { - File.Delete(s_accountPath); - } + public static void DeleteLocalAccountFile() => File.Delete(s_accountPath); } diff --git a/tests/Speckle.Sdk.Tests.Unit/Helpers/Path.cs b/tests/Speckle.Sdk.Tests.Unit/Helpers/Path.cs index ab992217..422dcbbe 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Helpers/Path.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Helpers/Path.cs @@ -1,14 +1,13 @@ using System.Runtime.InteropServices; -using NUnit.Framework; +using FluentAssertions; using Speckle.Sdk.Logging; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Helpers; -[TestFixture] -[TestOf(nameof(SpecklePathProvider))] public class SpecklePathTests { - [Test] + [Fact] public void TestUserApplicationDataPath() { var userPath = SpecklePathProvider.UserApplicationDataPath(); @@ -39,10 +38,10 @@ public void TestUserApplicationDataPath() throw new NotImplementedException("Your OS platform is not supported"); } - Assert.That(userPath, Does.Match(pattern)); + userPath.Should().MatchRegex(pattern); } - [Test] + [Fact] public void TestInstallApplicationDataPath() { var installPath = SpecklePathProvider.InstallApplicationDataPath; @@ -78,6 +77,6 @@ public void TestInstallApplicationDataPath() throw new NotImplementedException("Your OS platform is not supported"); } - Assert.That(installPath, Does.Match(pattern)); + installPath.Should().MatchRegex(pattern); } } diff --git a/tests/Speckle.Sdk.Tests.Unit/Host/HostApplicationTests.cs b/tests/Speckle.Sdk.Tests.Unit/Host/HostApplicationTests.cs index 728266c8..87d5aa3c 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Host/HostApplicationTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Host/HostApplicationTests.cs @@ -1,19 +1,22 @@ -using NUnit.Framework; -using Shouldly; +using FluentAssertions; using Speckle.Sdk.Host; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Host; public class HostApplicationTests { - private static List s_hostAppVersion = Enum.GetValues().ToList(); + public static TheoryData HostAppVersionData => new(Enum.GetValues().ToList()); - [Test] - [TestCaseSource(nameof(s_hostAppVersion))] + [Theory] + [MemberData(nameof(HostAppVersionData))] public void HostAppVersionParsingTests(HostAppVersion appVersion) { - appVersion.ToString().StartsWith('v').ShouldBeTrue(); + // Assert that the string representation starts with 'v' + appVersion.ToString().StartsWith('v').Should().BeTrue(); + + // Assert that the parsed version is a positive integer var version = HostApplications.GetVersion(appVersion); - int.Parse(version).ShouldBePositive(); + int.Parse(version).Should().BePositive(); } } diff --git a/tests/Speckle.Sdk.Tests.Unit/Models/BaseTests.cs b/tests/Speckle.Sdk.Tests.Unit/Models/BaseTests.cs index 36190b59..c658b48a 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Models/BaseTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Models/BaseTests.cs @@ -1,50 +1,38 @@ -using System.Collections.Concurrent; -using System.Text; -using NUnit.Framework; -using Shouldly; -using Speckle.Newtonsoft.Json.Linq; +using FluentAssertions; using Speckle.Sdk.Common; -using Speckle.Sdk.Dependencies.Serialization; using Speckle.Sdk.Host; using Speckle.Sdk.Models; -using Speckle.Sdk.Serialisation; -using Speckle.Sdk.Serialisation.V2; -using Speckle.Sdk.Serialisation.V2.Send; -using Speckle.Sdk.Transports; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Models; -[TestFixture] -[TestOf(typeof(Base))] -[TestOf(typeof(DynamicBase))] public class BaseTests { - [SetUp] - public void Setup() + public BaseTests() { TypeLoader.Reset(); TypeLoader.Initialize(typeof(Base).Assembly, typeof(BaseTests).Assembly); } - [Test] + [Fact] public void CanGetSetDynamicItemProp() { var @base = new Base(); @base["Item"] = "Item"; - Assert.That(@base["Item"], Is.EqualTo("Item")); + @base["Item"].Should().Be("Item"); } - [Test] + [Fact] public void CanGetSetTypedItemProp() { var @base = new ObjectWithItemProp { Item = "baz" }; - Assert.That(@base["Item"], Is.EqualTo("baz")); - Assert.That(@base.Item, Is.EqualTo("baz")); + @base["Item"].Should().Be("baz"); + @base.Item.Should().Be("baz"); } - [Test(Description = "Checks if validation is performed in property names")] + [Fact(DisplayName = "Checks if validation is performed in property names")] public void CanValidatePropNames() { dynamic @base = new Base(); @@ -54,27 +42,35 @@ public void CanValidatePropNames() // Only single leading @ allowed @base["@something"] = "A"; - Assert.Throws(() => - { - @base["@@@something"] = "Testing"; - }); + FluentActions + .Invoking(() => + { + @base["@@@something"] = "Testing"; + }) + .Should() + .Throw(); // Invalid chars: ./ - Assert.Throws(() => - { - @base["some.thing"] = "Testing"; - }); - Assert.Throws(() => - { - @base["some/thing"] = "Testing"; - }); - + FluentActions + .Invoking(() => + { + @base["some.thing"] = "Testing"; + }) + .Should() + .Throw(); + FluentActions + .Invoking(() => + { + @base["some/thing"] = "Testing"; + }) + .Should() + .Throw(); // Trying to change a class member value will throw exceptions. //Assert.Throws(() => { @base["speckle_type"] = "Testing"; }); //Assert.Throws(() => { @base["id"] = "Testing"; }); } - [Test] + [Fact] public void CountDynamicChunkables() { const int MAX_NUM = 3000; @@ -92,10 +88,10 @@ public void CountDynamicChunkables() @base["@(1000)cc2"] = customChunkArr; var num = @base.GetTotalChildrenCount(); - Assert.That(num, Is.EqualTo(MAX_NUM / 1000 * 2 + 1)); + num.Should().Be(MAX_NUM / 1000 * 2 + 1); } - [Test] + [Fact] public void CountTypedChunkables() { const int MAX_NUM = 3000; @@ -114,33 +110,33 @@ public void CountTypedChunkables() var num = @base.GetTotalChildrenCount(); var actualNum = 1 + MAX_NUM / 300 + MAX_NUM / 1000; - Assert.That(num, Is.EqualTo(actualNum)); + num.Should().Be(actualNum); } - [Test(Description = "Checks that no ignored or obsolete properties are returned")] + [Fact(DisplayName = "Checks that no ignored or obsolete properties are returned")] public void CanGetMemberNames() { var @base = new SampleObject(); var dynamicProp = "dynamicProp"; @base[dynamicProp] = 123; var names = @base.GetMembers().Keys; - Assert.That(names, Has.No.Member(nameof(@base.IgnoredSchemaProp))); - Assert.That(names, Has.No.Member(nameof(@base.ObsoleteSchemaProp))); - Assert.That(names, Has.Member(dynamicProp)); - Assert.That(names, Has.Member(nameof(@base.attachedProp))); + names.Should().NotContain(nameof(@base.IgnoredSchemaProp)); + names.Should().NotContain(nameof(@base.ObsoleteSchemaProp)); + names.Should().Contain(dynamicProp); + names.Should().Contain(nameof(@base.attachedProp)); } - [Test(Description = "Checks that only instance properties are returned, excluding obsolete and ignored.")] + [Fact(DisplayName = "Checks that only instance properties are returned, excluding obsolete and ignored.")] public void CanGetMembers_OnlyInstance() { var @base = new SampleObject(); @base["dynamicProp"] = 123; var names = @base.GetMembers(DynamicBaseMemberType.Instance).Keys; - Assert.That(names, Has.Member(nameof(@base.attachedProp))); + names.Should().Contain(nameof(@base.attachedProp)); } - [Test(Description = "Checks that only dynamic properties are returned")] + [Fact(DisplayName = "Checks that only dynamic properties are returned")] public void CanGetMembers_OnlyDynamic() { var @base = new SampleObject(); @@ -148,33 +144,33 @@ public void CanGetMembers_OnlyDynamic() @base[dynamicProp] = 123; var names = @base.GetMembers(DynamicBaseMemberType.Dynamic).Keys; - Assert.That(names, Has.Member(dynamicProp)); - Assert.That(names, Has.Count.EqualTo(1)); + names.Should().Contain(dynamicProp); + names.Count.Should().Be(1); } - [Test(Description = "Checks that all typed properties (including ignored ones) are returned")] + [Fact(DisplayName = "Checks that all typed properties (including ignored ones) are returned")] public void CanGetMembers_OnlyInstance_IncludeIgnored() { var @base = new SampleObject(); @base["dynamicProp"] = 123; var names = @base.GetMembers(DynamicBaseMemberType.Instance | DynamicBaseMemberType.SchemaIgnored).Keys; - Assert.That(names, Has.Member(nameof(@base.IgnoredSchemaProp))); - Assert.That(names, Has.Member(nameof(@base.attachedProp))); + names.Should().Contain(nameof(@base.IgnoredSchemaProp)); + names.Should().Contain(nameof(@base.attachedProp)); } - [Test(Description = "Checks that all typed properties (including obsolete ones) are returned")] + [Fact(DisplayName = "Checks that all typed properties (including obsolete ones) are returned")] public void CanGetMembers_OnlyInstance_IncludeObsolete() { var @base = new SampleObject(); @base["dynamicProp"] = 123; var names = @base.GetMembers(DynamicBaseMemberType.Instance | DynamicBaseMemberType.Obsolete).Keys; - Assert.That(names, Has.Member(nameof(@base.ObsoleteSchemaProp))); - Assert.That(names, Has.Member(nameof(@base.attachedProp))); + names.Should().Contain(nameof(@base.ObsoleteSchemaProp)); + names.Should().Contain(nameof(@base.attachedProp)); } - [Test] + [Fact] public void CanGetDynamicMembers() { var @base = new SampleObject(); @@ -182,11 +178,11 @@ public void CanGetDynamicMembers() @base[dynamicProp] = null; var names = @base.GetDynamicMemberNames(); - Assert.That(names, Has.Member(dynamicProp)); - Assert.That(@base[dynamicProp], Is.Null); + names.Should().Contain(dynamicProp); + @base[dynamicProp].Should().BeNull(); } - [Test] + [Fact] public void CanSetDynamicMembers() { var @base = new SampleObject(); @@ -194,19 +190,19 @@ public void CanSetDynamicMembers() var value = "something"; // Can create a new dynamic member @base[key] = value; - Assert.That(value, Is.EqualTo((string)@base[key].NotNull())); + value.Should().Be((string)@base[key].NotNull()); // Can overwrite existing value = "some other value"; @base[key] = value; - Assert.That(value, Is.EqualTo((string)@base[key].NotNull())); + value.Should().Be((string)@base[key].NotNull()); // Accepts null values @base[key] = null; - Assert.That(@base[key], Is.Null); + @base[key].Should().BeNull(); } - [Test] + [Fact] public void CanShallowCopy() { var sample = new SampleObject(); @@ -217,8 +213,8 @@ public void CanShallowCopy() var sampleMembers = sample.GetMembers(selectedMembers); var copyMembers = copy.GetMembers(selectedMembers); - Assert.That(copyMembers.Keys, Is.EquivalentTo(sampleMembers.Keys)); - Assert.That(copyMembers.Values, Is.EquivalentTo(sampleMembers.Values)); + copyMembers.Keys.Should().BeEquivalentTo(sampleMembers.Keys); + copyMembers.Values.Should().BeEquivalentTo(sampleMembers.Values); } [SpeckleType("Speckle.Core.Tests.Unit.Models.BaseTests+SampleObject")] diff --git a/tests/Speckle.Sdk.Tests.Unit/Models/Extensions/BaseExtensionsTests.cs b/tests/Speckle.Sdk.Tests.Unit/Models/Extensions/BaseExtensionsTests.cs index 5af6a011..f0a24de5 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Models/Extensions/BaseExtensionsTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Models/Extensions/BaseExtensionsTests.cs @@ -1,46 +1,44 @@ -using NUnit.Framework; +using FluentAssertions; using Speckle.Sdk.Host; using Speckle.Sdk.Models; using Speckle.Sdk.Models.Collections; using Speckle.Sdk.Models.Extensions; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Models.Extensions; -[TestFixture] -[TestOf(nameof(BaseExtensions))] public class BaseExtensionsTests { - [SetUp] - public void Setup() + public BaseExtensionsTests() { TypeLoader.Reset(); TypeLoader.Initialize(typeof(Base).Assembly); } - [Test] - [TestCase("myDynamicProp")] - [TestCase("elements")] + [Theory] + [InlineData("myDynamicProp")] + [InlineData("elements")] public void GetDetachedPropName_Dynamic(string propertyName) { var data = new TestBase(); var result = data.GetDetachedPropName(propertyName); var expected = $"@{propertyName}"; - Assert.That(result, Is.EqualTo(expected)); + result.Should().Be(expected); } - [Test] - [TestCase(nameof(TestBase.myProperty))] - [TestCase(nameof(TestBase.myOtherProperty))] + [Theory] + [InlineData(nameof(TestBase.myProperty))] + [InlineData(nameof(TestBase.myOtherProperty))] public void GetDetachedPropName_Instance(string propertyName) { var data = new TestBase(); var result = data.GetDetachedPropName(propertyName); - Assert.That(result, Is.EqualTo(propertyName)); + result.Should().Be(propertyName); } - [Test] + [Fact] public void TraverseWithPath() { var collection = new Collection() { name = "collection" }; @@ -51,17 +49,18 @@ public void TraverseWithPath() var basePaths = collection.TraverseWithPath((obj => obj is not Collection)).ToList(); - Assert.That(basePaths.Count, Is.EqualTo(3)); - Assert.That(basePaths[0].Item2.speckle_type, Is.EqualTo("Speckle.Core.Models.Collections.Collection")); - Assert.That(basePaths[0].Item2["name"], Is.EqualTo("collection")); - Assert.That(basePaths[0].Item1, Is.EqualTo(new List())); + basePaths.Count.Should().Be(3); - Assert.That(basePaths[1].Item2.speckle_type, Is.EqualTo("Speckle.Core.Models.Collections.Collection")); - Assert.That(basePaths[1].Item2["name"], Is.EqualTo("subCollection")); - Assert.That(basePaths[1].Item1, Is.EqualTo(new List() { "collection" })); + basePaths[0].Item2.speckle_type.Should().Be("Speckle.Core.Models.Collections.Collection"); + basePaths[0].Item2["name"].Should().Be("collection"); + basePaths[0].Item1.Should().BeEquivalentTo(new List()); - Assert.That(basePaths[2].Item2.speckle_type, Is.EqualTo("Base")); - Assert.That(basePaths[2].Item1, Is.EqualTo(new List() { "collection", "subCollection" })); + basePaths[1].Item2.speckle_type.Should().Be("Speckle.Core.Models.Collections.Collection"); + basePaths[1].Item2["name"].Should().Be("subCollection"); + basePaths[1].Item1.Should().BeEquivalentTo(new List() { "collection" }); + + basePaths[2].Item2.speckle_type.Should().Be("Base"); + basePaths[2].Item1.Should().BeEquivalentTo(new List() { "collection", "subCollection" }); } [SpeckleType("Speckle.Core.Tests.Unit.Models.Extensions.BaseExtensionsTests+TestBase")] diff --git a/tests/Speckle.Sdk.Tests.Unit/Models/Extensions/DisplayValueTests.cs b/tests/Speckle.Sdk.Tests.Unit/Models/Extensions/DisplayValueTests.cs index 07b00adf..b000afcc 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Models/Extensions/DisplayValueTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Models/Extensions/DisplayValueTests.cs @@ -1,11 +1,11 @@ -using NUnit.Framework; +using FluentAssertions; using Speckle.Sdk.Host; using Speckle.Sdk.Models; using Speckle.Sdk.Models.Extensions; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Models.Extensions; -[TestOf(typeof(BaseExtensions))] public class DisplayValueTests { private const string PAYLOAD = "This is my payload"; @@ -22,28 +22,35 @@ private static void Reset() TypeLoader.Initialize(typeof(Base).Assembly); } - [SetUp] + [Fact] public void Setup() => Reset(); - [TestCaseSource(nameof(TestCases))] + [Theory] + [MemberData(nameof(TestCases))] public void TestTryGetDisplayValue_WithValue(Base testCase) { var res = testCase.TryGetDisplayValue(); - Assert.That(res, Has.Count.EqualTo(1)); - Assert.That(res, Has.One.Items.TypeOf().With.Property(nameof(Base.applicationId)).EqualTo(PAYLOAD)); + // Assert collection count + res?.Count.Should().Be(1); + + // Assert the single item matches the expected type and property + var displayValue = res?[0]; + displayValue.Should().NotBeNull(); + displayValue!.applicationId.Should().Be(PAYLOAD); } - public static IEnumerable TestCases() + public static IEnumerable TestCases() { - var listOfBase = new List { s_displayValue }; //This is what our deserializer will output + var listOfBase = new List { s_displayValue }; // This is what our deserializer will output var listOfMesh = new List { s_displayValue }; - yield return new Base { ["@displayValue"] = s_displayValue }; - yield return new Base { ["displayValue"] = s_displayValue }; - yield return new Base { ["@displayValue"] = listOfBase }; - yield return new Base { ["displayValue"] = listOfBase }; - yield return new TypedDisplayValue { displayValue = s_displayValue }; - yield return new TypedDisplayValueList { displayValue = listOfMesh }; + + yield return [new Base { ["@displayValue"] = s_displayValue }]; + yield return [new Base { ["displayValue"] = s_displayValue }]; + yield return [new Base { ["@displayValue"] = listOfBase }]; + yield return [new Base { ["displayValue"] = listOfBase }]; + yield return [new TypedDisplayValue { displayValue = s_displayValue }]; + yield return [new TypedDisplayValueList { displayValue = listOfMesh }]; } [SpeckleType("Speckle.Core.Tests.Unit.Models.Extensions.DisplayValueTests+TypedDisplayValue")] diff --git a/tests/Speckle.Sdk.Tests.Unit/Models/Extensions/ExceptionTests.cs b/tests/Speckle.Sdk.Tests.Unit/Models/Extensions/ExceptionTests.cs index bf6640bd..4b5c3750 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Models/Extensions/ExceptionTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Models/Extensions/ExceptionTests.cs @@ -1,26 +1,27 @@ -using NUnit.Framework; +using FluentAssertions; using Speckle.Sdk.Models.Extensions; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Models.Extensions; -[TestFixture] -[TestOf(typeof(BaseExtensions))] public class ExceptionTests { - [Test] + [Fact] public void CanPrintAllInnerExceptions() { + // Test with a single exception var ex = new Exception("Some error"); var exMsg = ex.ToFormattedString(); - Assert.That(exMsg, Is.Not.Null); + exMsg.Should().NotBeNull(); + // Test with an inner exception var ex2 = new Exception("One or more errors occurred", ex); var ex2Msg = ex2.ToFormattedString(); - Assert.That(ex2Msg, Is.Not.Null); + ex2Msg.Should().NotBeNull(); + // Test with an aggregate exception var ex3 = new AggregateException("One or more errors occurred", ex2); var ex3Msg = ex3.ToFormattedString(); - - Assert.That(ex3Msg, Is.Not.Null); + ex3Msg.Should().NotBeNull(); } } diff --git a/tests/Speckle.Sdk.Tests.Unit/Models/GraphTraversal/GraphTraversalTests.cs b/tests/Speckle.Sdk.Tests.Unit/Models/GraphTraversal/GraphTraversalTests.cs index 4f200fae..945102a4 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Models/GraphTraversal/GraphTraversalTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Models/GraphTraversal/GraphTraversalTests.cs @@ -1,17 +1,15 @@ using System.Collections; -using NUnit.Framework; +using FluentAssertions; using Speckle.Sdk.Host; using Speckle.Sdk.Models; using Speckle.Sdk.Models.GraphTraversal; -using Speckle.Sdk.Tests.Unit.Host; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Models.GraphTraversal; -[TestFixture, TestOf(typeof(Sdk.Models.GraphTraversal.GraphTraversal))] public class GraphTraversalTests { - [SetUp] - public void Setup() + public GraphTraversalTests() { TypeLoader.Reset(); TypeLoader.Initialize(typeof(Base).Assembly, typeof(TraversalMock).Assembly); @@ -23,7 +21,7 @@ private static IEnumerable Traverse(Base testCase, params ITra return sut.Traverse(testCase); } - [Test] + [Fact] public void Traverse_TraversesListMembers() { var traverseListsRule = TraversalRule @@ -38,23 +36,23 @@ public void Traverse_TraversesListMembers() TraversalMock testCase = new() { - ListChildren = new List { expectTraverse }, + ListChildren = [expectTraverse], DictChildren = new Dictionary { ["myprop"] = expectIgnored }, Child = expectIgnored, }; var ret = Traverse(testCase, traverseListsRule).Select(b => b.Current).ToList(); - //Assert expected members present - Assert.That(ret, Has.Exactly(1).Items.EqualTo(testCase)); - Assert.That(ret, Has.Exactly(1).Items.EqualTo(expectTraverse)); + // Assert expected members present + ret.Should().Contain(testCase); + ret.Should().Contain(expectTraverse); - //Assert unexpected members not present - Assert.That(ret, Has.No.Member(expectIgnored)); - Assert.That(ret, Has.Count.EqualTo(2)); + // Assert unexpected members not present + ret.Should().NotContain(expectIgnored); + ret.Count.Should().Be(2); } - [Test] + [Fact] public void Traverse_TraversesDictMembers() { var traverseListsRule = TraversalRule @@ -69,23 +67,23 @@ public void Traverse_TraversesDictMembers() TraversalMock testCase = new() { - ListChildren = new List { expectIgnored }, + ListChildren = [expectIgnored], DictChildren = new Dictionary { ["myprop"] = expectTraverse }, Child = expectIgnored, }; var ret = Traverse(testCase, traverseListsRule).Select(b => b.Current).ToList(); - //Assert expected members present - Assert.That(ret, Has.Exactly(1).Items.EqualTo(testCase)); - Assert.That(ret, Has.Exactly(1).Items.EqualTo(expectTraverse)); + // Assert expected members present + ret.Should().Contain(testCase); + ret.Should().Contain(expectTraverse); - //Assert unexpected members not present - Assert.That(ret, Has.No.Member(expectIgnored)); - Assert.That(ret, Has.Count.EqualTo(2)); + // Assert unexpected members not present + ret.Should().NotContain(expectIgnored); + ret.Count.Should().Be(2); } - [Test] + [Fact] public void Traverse_TraversesDynamic() { var traverseListsRule = TraversalRule @@ -105,16 +103,16 @@ public void Traverse_TraversesDynamic() var ret = Traverse(testCase, traverseListsRule).Select(b => b.Current).ToList(); - //Assert expected members present - Assert.That(ret, Has.Exactly(1).Items.EqualTo(testCase)); - Assert.That(ret, Has.Exactly(2).Items.EqualTo(expectTraverse)); + // Assert expected members present + ret.Should().Contain(testCase); + ret.Count(x => x == expectTraverse).Should().Be(2); - //Assert unexpected members not present - Assert.That(ret, Has.No.Member(expectIgnored)); - Assert.That(ret, Has.Count.EqualTo(3)); + // Assert unexpected members not present + ret.Should().NotContain(expectIgnored); + ret.Count.Should().Be(3); } - [Test] + [Fact] public void Traverse_ExclusiveRule() { var expectTraverse = new Base { id = "List Member" }; @@ -134,12 +132,12 @@ public void Traverse_ExclusiveRule() var ret = Traverse(testCase, traverseListsRule).Select(b => b.Current).ToList(); - //Assert expected members present - Assert.That(ret, Has.Exactly(1).Items.EqualTo(testCase)); - Assert.That(ret, Has.Exactly(2).Items.EqualTo(expectTraverse)); + // Assert expected members present + ret.Should().Contain(testCase); + ret.Count(x => x == expectTraverse).Should().Be(2); - //Assert unexpected members not present - Assert.That(ret, Has.No.Member(expectIgnored)); - Assert.That(ret, Has.Count.EqualTo(3)); + // Assert unexpected members not present + ret.Should().NotContain(expectIgnored); + ret.Count.Should().Be(3); } } diff --git a/tests/Speckle.Sdk.Tests.Unit/Models/GraphTraversal/TraversalContextExtensionsTests.cs b/tests/Speckle.Sdk.Tests.Unit/Models/GraphTraversal/TraversalContextExtensionsTests.cs index a386ca14..d8c6f072 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Models/GraphTraversal/TraversalContextExtensionsTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Models/GraphTraversal/TraversalContextExtensionsTests.cs @@ -1,62 +1,71 @@ -using NUnit.Framework; +using FluentAssertions; using Speckle.Sdk.Common; using Speckle.Sdk.Models; using Speckle.Sdk.Models.Collections; using Speckle.Sdk.Models.GraphTraversal; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Models.GraphTraversal; -[TestOf(typeof(TraversalContextExtensions))] +// Mark test class for xUnit public class TraversalContextExtensionsTests { - public static int[] TestDepths => new[] { 1, 2, 10 }; - private TraversalContext? CreateLinkedList(int depth, Func createBaseFunc) { if (depth <= 0) + { return null; + } + return new TraversalContext(createBaseFunc(depth), $"{depth}", CreateLinkedList(depth - 1, createBaseFunc)); } - [TestCaseSource(nameof(TestDepths))] + [Theory] // replaces [TestCaseSource] + [MemberData(nameof(GetTestDepths))] public void GetPropertyPath_ReturnsSequentialPath(int depth) { - var testData = CreateLinkedList(depth, i => new()).NotNull(); + var testData = CreateLinkedList(depth, i => new Base()).NotNull(); - var path = TraversalContextExtensions.GetPropertyPath(testData); + var path = testData.GetPropertyPath(); var expected = Enumerable.Range(1, depth).Select(i => i.ToString()); - Assert.That(path, Is.EquivalentTo(expected)); + path.Should().BeEquivalentTo(expected); } - [TestCaseSource(nameof(TestDepths))] + [Theory] + [MemberData(nameof(GetTestDepths))] public void GetAscendant(int depth) { - var testData = CreateLinkedList(depth, i => new()).NotNull(); + var testData = CreateLinkedList(depth, i => new Base()).NotNull(); - var all = TraversalContextExtensions.GetAscendants(testData).ToArray(); + var all = testData.GetAscendants().ToArray(); - Assert.That(all, Has.Length.EqualTo(depth)); + all.Length.Should().Be(depth); } - [TestCaseSource(nameof(TestDepths))] + [Theory] + [MemberData(nameof(GetTestDepths))] public void GetAscendantOfType_AllBase(int depth) { - var testData = CreateLinkedList(depth, i => new()).NotNull(); + var testData = CreateLinkedList(depth, i => new Base()).NotNull(); - var all = TraversalContextExtensions.GetAscendantOfType(testData).ToArray(); + var all = testData.GetAscendantOfType().ToArray(); - Assert.That(all, Has.Length.EqualTo(depth)); + all.Length.Should().Be(depth); } - [TestCaseSource(nameof(TestDepths))] + [Theory] + [MemberData(nameof(GetTestDepths))] public void GetAscendantOfType_EveryOtherIsCollection(int depth) { var testData = CreateLinkedList(depth, i => i % 2 == 0 ? new Base() : new Collection()).NotNull(); - var all = TraversalContextExtensions.GetAscendantOfType(testData).ToArray(); + var all = testData.GetAscendantOfType().ToArray(); - Assert.That(all, Has.Length.EqualTo(Math.Ceiling(depth / 2.0))); + all.Length.Should().Be((int)Math.Ceiling(depth / 2.0)); } + + // Providing the test depths to [MemberData] for xUnit + public static IEnumerable GetTestDepths() => new[] { 1, 2, 10 }.Select(depth => new object[] { depth }); } diff --git a/tests/Speckle.Sdk.Tests.Unit/Models/Hashing.cs b/tests/Speckle.Sdk.Tests.Unit/Models/Hashing.cs index bbc97251..decd2b33 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Models/Hashing.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Models/Hashing.cs @@ -1,50 +1,50 @@ using System.Diagnostics; -using NUnit.Framework; +using FluentAssertions; using Speckle.Sdk.Host; using Speckle.Sdk.Models; using Speckle.Sdk.Tests.Unit.Host; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Models; -[TestFixture] -[TestOf(typeof(Base))] +// Removed [TestFixture] and [TestOf] annotations as they are NUnit specific public class Hashing { - [SetUp] - public void Setup() + public Hashing() { TypeLoader.Reset(); TypeLoader.Initialize(typeof(Base).Assembly, typeof(DiningTable).Assembly); } - [Test(Description = "Checks that hashing (as represented by object ids) actually works.")] - public void HashChangeCheck() + [Fact(DisplayName = "Checks that hashing (as represented by object IDs) actually works.")] + public void HashChangeCheck_Test() { var table = new DiningTable(); var secondTable = new DiningTable(); - Assert.That(secondTable.GetId(), Is.EqualTo(table.GetId())); + secondTable.GetId().Should().Be(table.GetId(), "Object IDs of identical objects should match."); ((dynamic)secondTable).testProp = "wonderful"; - Assert.That(secondTable.GetId(), Is.Not.EqualTo(table.GetId())); + secondTable.GetId().Should().NotBe(table.GetId(), "Changing a property should alter the object ID."); } - [Test( - Description = "Tests the convention that dynamic properties that have key names prepended with '__' are ignored." - )] - public void IgnoredDynamicPropertiesCheck() + [Fact(DisplayName = "Verifies that dynamic properties with '__' prefix are ignored during hashing.")] + public void IgnoredDynamicPropertiesCheck_Test() { var table = new DiningTable(); var originalHash = table.GetId(); ((dynamic)table).__testProp = "wonderful"; - Assert.That(table.GetId(), Is.EqualTo(originalHash)); + table + .GetId() + .Should() + .Be(originalHash, "Hashing of table should not change when '__' prefixed properties are added."); } - [Test(Description = "Rather stupid test as results vary wildly even on one machine.")] - public void HashingPerformance() + [Fact(DisplayName = "Performance test: Hash computation time for large and small objects.")] + public void HashingPerformance_Test() { var polyline = new Polyline(); @@ -62,7 +62,7 @@ public void HashingPerformance() _ = polyline.GetId(); var diff1 = stopWatch.ElapsedMilliseconds - stopWatchStep; - Assert.That(diff1, Is.LessThan(300), $"Hashing shouldn't take that long ({diff1} ms) for the test object used."); + diff1.Should().BeLessThan(300, $"Hashing shouldn't take that long ({diff1} ms) for the test object used."); Console.WriteLine($"Big obj hash duration: {diff1} ms"); var pt = new Point @@ -75,12 +75,12 @@ public void HashingPerformance() _ = pt.GetId(); var diff2 = stopWatch.ElapsedMilliseconds - stopWatchStep; - Assert.That(diff2, Is.LessThan(10), $"Hashing shouldn't take that long ({diff2} ms)for the point object used."); + diff2.Should().BeLessThan(10, $"Hashing shouldn't take that long ({diff2} ms) for the point object used."); Console.WriteLine($"Small obj hash duration: {diff2} ms"); } - [Test(Description = "The hash of a decomposed object is different that that of a non-decomposed object.")] - public void DecompositionHashes() + [Fact(DisplayName = "Verifies that decomposed and non-decomposed objects have different hashes.")] + public void DecompositionHashes_Test() { var table = new DiningTable(); ((dynamic)table)["@decomposeMePlease"] = new Point(); @@ -88,6 +88,6 @@ public void DecompositionHashes() var hash1 = table.GetId(); var hash2 = table.GetId(true); - Assert.That(hash2, Is.Not.EqualTo(hash1)); + hash2.Should().NotBe(hash1, "Hash values should differ for decomposed and non-decomposed objects."); } } diff --git a/tests/Speckle.Sdk.Tests.Unit/Models/SpeckleType.cs b/tests/Speckle.Sdk.Tests.Unit/Models/SpeckleType.cs index 344b3e39..63500570 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Models/SpeckleType.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Models/SpeckleType.cs @@ -1,45 +1,43 @@ -using NUnit.Framework; +using FluentAssertions; using Speckle.Sdk.Host; using Speckle.Sdk.Models; -using TestModels; +using Speckle.Sdk.Tests.Unit.Models.TestModels; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Models { - [TestFixture] - [TestOf(typeof(Base))] public class SpeckleTypeTests { - [SetUp] - public void Setup() + public SpeckleTypeTests() { + // Setup logic during test class initialization TypeLoader.Reset(); TypeLoader.Initialize(typeof(Base).Assembly, typeof(Foo).Assembly); } - [Test, TestCaseSource(nameof(s_cases))] - public void SpeckleTypeIsProperlyBuilt(Base foo, string expectedType) - { - Assert.That(foo.speckle_type, Is.EqualTo(expectedType)); - } + [Theory] + [MemberData(nameof(Cases))] + public void SpeckleTypeIsProperlyBuilt(Base foo, string expectedType) => foo.speckle_type.Should().Be(expectedType); - private static readonly object[] s_cases = - { - new object[] { new Base(), "Base" }, - new object[] { new Foo(), "TestModels.Foo" }, - new object[] { new Bar(), "TestModels.Foo:TestModels.Bar" }, - new object[] { new Baz(), "TestModels.Foo:TestModels.Bar:TestModels.Baz" }, - }; + public static IEnumerable Cases => + new List + { + new object[] { new Base(), "Base" }, + new object[] { new Foo(), "TestModels.Foo" }, + new object[] { new Bar(), "TestModels.Foo:TestModels.Bar" }, + new object[] { new Baz(), "TestModels.Foo:TestModels.Bar:TestModels.Baz" }, + }; } -} -namespace TestModels -{ - [SpeckleType("TestModels.Foo")] - public class Foo : Base { } + namespace TestModels + { + [SpeckleType("TestModels.Foo")] + public class Foo : Base { } - [SpeckleType("TestModels.Bar")] - public class Bar : Foo { } + [SpeckleType("TestModels.Bar")] + public class Bar : Foo { } - [SpeckleType("TestModels.Baz")] - public class Baz : Bar { } + [SpeckleType("TestModels.Baz")] + public class Baz : Bar { } + } } diff --git a/tests/Speckle.Sdk.Tests.Unit/Models/TraversalTests.cs b/tests/Speckle.Sdk.Tests.Unit/Models/TraversalTests.cs index 5c36f3e6..da0c804b 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Models/TraversalTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Models/TraversalTests.cs @@ -1,14 +1,14 @@ -using NUnit.Framework; +using FluentAssertions; using Speckle.Sdk.Common; using Speckle.Sdk.Models; using Speckle.Sdk.Models.Extensions; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Models; -[TestFixture, TestOf(typeof(BaseExtensions))] public class TraversalTests { - [Test, Description("Tests that provided breaker rules are respected")] + [Fact(DisplayName = "Tests that provided breaker rules are respected")] public void TestFlattenWithBreaker() { //Setup @@ -32,13 +32,19 @@ public void TestFlattenWithBreaker() var ret = root.Flatten(BreakRule).ToList(); //Test - Assert.That(ret, Has.Count.EqualTo(3)); - Assert.That(ret, Is.Unique); - Assert.That(ret.Where(BreakRule), Is.Not.Empty); - Assert.That(ret, Has.No.Member(Contains.Substring("should have ignored me"))); + ret.Count.Should().Be(3); + + ret.Should().OnlyHaveUniqueItems(); + + ret.Where(BreakRule).Should().NotBeEmpty(); + + ret.Should().NotContain(x => x.id == "should have ignored me"); } - [Test, TestCase(5, 5), TestCase(5, 10), TestCase(10, 5), Description("Tests breaking after a fixed number of items")] + [Theory(DisplayName = "Tests breaking after a fixed number of items")] + [InlineData(5, 5)] + [InlineData(5, 10)] + [InlineData(10, 5)] public void TestBreakerFixed(int nestDepth, int flattenDepth) { //Setup @@ -56,11 +62,12 @@ public void TestBreakerFixed(int nestDepth, int flattenDepth) var ret = rootObject.Flatten(_ => ++counter >= flattenDepth).ToList(); //Test - Assert.That(ret, Has.Count.EqualTo(Math.Min(flattenDepth, nestDepth))); - Assert.That(ret, Is.Unique); + ret.Count.Should().Be(Math.Min(flattenDepth, nestDepth)); + + ret.Should().OnlyHaveUniqueItems(); } - [Test, Timeout(2000), Description("Tests that the flatten function does not get stuck on circular references")] + [Fact(DisplayName = "Tests that the flatten function does not get stuck on circular references")] public void TestCircularReference() { //Setup @@ -76,12 +83,14 @@ public void TestCircularReference() var ret = objectA.Flatten().ToList(); //Test - Assert.That(ret, Is.Unique); - Assert.That(ret, Is.EquivalentTo(new[] { objectA, objectB, objectC })); - Assert.That(ret, Has.Count.EqualTo(3)); + ret.Should().OnlyHaveUniqueItems(); + + ret.Should().BeEquivalentTo([objectA, objectB, objectC]); + + ret.Count.Should().Be(3); } - [Test, Description("Tests that the flatten function correctly handles (non circular) duplicates")] + [Fact(DisplayName = "Tests that the flatten function correctly handles (non circular) duplicates")] public void TestDuplicates() { //Setup @@ -95,8 +104,10 @@ public void TestDuplicates() var ret = objectA.Flatten().ToList(); //Test - Assert.That(ret, Is.Unique); - Assert.That(ret, Is.EquivalentTo(new[] { objectA, objectB })); - Assert.That(ret, Has.Count.EqualTo(2)); + ret.Should().OnlyHaveUniqueItems(); + + ret.Should().BeEquivalentTo([objectA, objectB]); + + ret.Count.Should().Be(2); } } diff --git a/tests/Speckle.Sdk.Tests.Unit/Models/UtilitiesTests.cs b/tests/Speckle.Sdk.Tests.Unit/Models/UtilitiesTests.cs index 32286cc5..72f71821 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Models/UtilitiesTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Models/UtilitiesTests.cs @@ -1,92 +1,121 @@ -using NUnit.Framework; +using FluentAssertions; +using Speckle.Sdk.Dependencies; using Speckle.Sdk.Helpers; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Models; -[TestFixture(TestOf = typeof(Crypt))] public sealed class HashUtilityTests { - public static IEnumerable<(string input, string sha256, string md5)> SmallTestCases() + public static IEnumerable SmallTestCases() { - yield return ( + yield return + [ "fxFB14cBcXvoENN", "491267c87e343c2a4f9070034f4f8966e8ee4c14e5baf6f49289833142e5b509", - "d38572fdb20fe90c4871178df3f9570d" - ); - yield return ( + "d38572fdb20fe90c4871178df3f9570d", + ]; + yield return + [ "tgWsOH8frdAwJT7", "dd62d2028d8243f07cbdbb0cd4c3460a96c88dd6322dd9fceba4e4912ad88fa7", - "a7eecf20d68f836f462963928cd0f1a1" - ); - yield return ( + "a7eecf20d68f836f462963928cd0f1a1", + ]; + yield return + [ "wQKrSUzBB7FI1o6", "70be5055f737e05d287c8898c7fcd3342733a337b67fe64f91fd34dcdf92fc88", - "2424cff4a88055b149e5ff2aaf0b3131" - ); - yield return ( + "2424cff4a88055b149e5ff2aaf0b3131", + ]; + yield return + [ "WnAbz1hCznVmDh1", "511433f4bb8d24d4ef7d4478984fd36f17ab6c58676f40ad0f4bcb615de0e313", - "ad48ff1e60ea2369de178aaab2fa99af" - ); + "ad48ff1e60ea2369de178aaab2fa99af", + ]; } - public static IEnumerable LargeTestCases() + public static IEnumerable SmallTestCases(IEnumerable cases, IEnumerable range) + { + foreach (var length in range) + { + foreach (var testCase in cases) + { + yield return [.. testCase, length]; + } + } + } + + public static IEnumerable SmallTestCasesMd5() => + SmallTestCases(SmallTestCases(), EnumerableExtensions.RangeFrom(0, 32)); + + public static IEnumerable SmallTestCasesSha256() => + SmallTestCases(SmallTestCases(), EnumerableExtensions.RangeFrom(2, 64)); + + public static IEnumerable SmallTestCasesSha256Span() => + SmallTestCases(SmallTestCases(), EnumerableExtensions.RangeFrom(2, 64).Where(x => x % 2 == 0)); + + public static IEnumerable LargeTestCases() { Random random = new(420); - yield return new TestCaseData( + yield return + [ new string(Enumerable.Range(0, 1_000_000).Select(_ => (char)random.Next(32, 127)).ToArray()), - "b919b9e60cd6bb86ab395ee1408e12efd4d3e4e7b58f02b4cda6b4120086959a" - ).SetName("1_000_000 random chars"); - yield return new TestCaseData( + "b919b9e60cd6bb86ab395ee1408e12efd4d3e4e7b58f02b4cda6b4120086959a", + ]; + yield return + [ new string(Enumerable.Range(0, 10_000_000).Select(_ => (char)random.Next(32, 127)).ToArray()), - "f2e83101c3066c8a2983acdb92df53504ec00ac1e5afb71b7c3798cb4daf6162" - ).SetName("10_000_000 random chars"); + "f2e83101c3066c8a2983acdb92df53504ec00ac1e5afb71b7c3798cb4daf6162", + ]; } - [Test, TestOf(nameof(Crypt.Md5))] - public void Md5( - [ValueSource(nameof(SmallTestCases))] (string input, string _, string expected) testCase, - [Range(0, 32)] int length - ) + [Theory] + [MemberData(nameof(SmallTestCasesMd5))] + public void Md5(string input, string _, string expected, int length) { - var lower = Crypt.Md5(testCase.input, "x2", length); - var upper = Crypt.Md5(testCase.input, "X2", length); + var resultLower = Crypt.Md5(input, "x2", length); + var resultUpper = Crypt.Md5(input, "X2", length); + + resultLower.Should().Be(new string(expected.ToLower()[..length])); - Assert.That(lower, Is.EqualTo(new string(testCase.expected.ToLower()[..length]))); - Assert.That(upper, Is.EqualTo(new string(testCase.expected.ToUpper()[..length]))); + resultUpper.Should().Be(new string(expected.ToUpper()[..length])); } - [Test, TestOf(nameof(Crypt.Sha256))] - public void Sha256( - [ValueSource(nameof(SmallTestCases))] (string input, string expected, string _) testCase, - [Range(2, 64)] int length - ) + [Theory] + [MemberData(nameof(SmallTestCasesSha256))] + public void Sha256(string input, string expected, string _, int length) { - var lower = Crypt.Sha256(testCase.input, "x2", length); - var upper = Crypt.Sha256(testCase.input, "X2", length); + var resultLower = Crypt.Sha256(input, "x2", length); + var resultUpper = Crypt.Sha256(input, "X2", length); - Assert.That(lower, Is.EqualTo(new string(testCase.expected.ToLower()[..length]))); - Assert.That(upper, Is.EqualTo(new string(testCase.expected.ToUpper()[..length]))); + resultLower.Should().Be(new string(expected.ToLower()[..length])); + + resultUpper.Should().Be(new string(expected.ToUpper()[..length])); } - [Test, TestOf(nameof(Crypt.Sha256))] + [Theory] + [MemberData(nameof(SmallTestCasesSha256Span))] public void Sha256_Span( - [ValueSource(nameof(SmallTestCases))] (string input, string expected, string _) testCase, - [Range(2, 64, 2)] int length //Span version of the function must have multiple of 2 + string input, + string expected, + string _, + int length //Span version of the function must have multiple of 2 ) { - var lower64 = Crypt.Sha256(testCase.input.AsSpan(), "x2", length); - var upper64 = Crypt.Sha256(testCase.input.AsSpan(), "X2", length); + var resultLowerSpan = Crypt.Sha256(input.AsSpan(), "x2", length); + var resultUpperSpan = Crypt.Sha256(input.AsSpan(), "X2", length); + + resultLowerSpan.Should().Be(new string(expected.ToLower()[..length])); - Assert.That(lower64, Is.EqualTo(new string(testCase.expected.ToLower()[..length]))); - Assert.That(upper64, Is.EqualTo(new string(testCase.expected.ToUpper()[..length]))); + resultUpperSpan.Should().Be(new string(expected.ToUpper()[..length])); } - [Test, TestOf(nameof(Crypt.Sha256))] - [TestCaseSource(nameof(LargeTestCases))] + [Theory] + [MemberData(nameof(LargeTestCases))] public void Sha256_LargeDataTests(string input, string expected) { - var test = Crypt.Sha256(input.AsSpan()); - Assert.That(test, Is.EqualTo(expected)); + var computedHash = Crypt.Sha256(input.AsSpan()); + computedHash.Should().Be(expected); } } diff --git a/tests/Speckle.Sdk.Tests.Unit/SQLite/SQLiteJsonCacheManagerTests.cs b/tests/Speckle.Sdk.Tests.Unit/SQLite/SQLiteJsonCacheManagerTests.cs index 961729a5..582bae34 100644 --- a/tests/Speckle.Sdk.Tests.Unit/SQLite/SQLiteJsonCacheManagerTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/SQLite/SQLiteJsonCacheManagerTests.cs @@ -1,22 +1,19 @@ -using Microsoft.Data.Sqlite; -using NUnit.Framework; -using Shouldly; +using FluentAssertions; +using Microsoft.Data.Sqlite; using Speckle.Sdk.Common; using Speckle.Sdk.SQLite; +using Xunit; namespace Speckle.Sdk.Tests.Unit.SQLite; -[TestFixture] -public class SQLiteJsonCacheManagerTests +public class SQLiteJsonCacheManagerTests : IDisposable { private readonly string _basePath = $"{Guid.NewGuid()}.db"; - private string? _connectionString; + private readonly string? _connectionString; - [SetUp] - public void Setup() => _connectionString = $"Data Source={_basePath};"; + public SQLiteJsonCacheManagerTests() => _connectionString = $"Data Source={_basePath};"; - [TearDown] - public void TearDown() + public void Dispose() { if (File.Exists(_basePath)) { @@ -27,23 +24,23 @@ public void TearDown() } } - [Test] + [Fact] public void TestGetAll() { var data = new List<(string id, string json)>() { ("id1", "1"), ("id2", "2") }; using var manager = new SqLiteJsonCacheManager(_connectionString.NotNull(), 2); manager.SaveObjects(data); var items = manager.GetAllObjects(); - items.Count.ShouldBe(data.Count); + items.Count.Should().Be(data.Count); var i = items.ToDictionary(); foreach (var (id, json) in data) { - i.TryGetValue(id, out var j).ShouldBeTrue(); - j.ShouldBe(json); + i.TryGetValue(id, out var j).Should().BeTrue(); + j.Should().Be(json); } } - [Test] + [Fact] public void TestGet() { var data = new List<(string id, string json)>() { ("id1", "1"), ("id2", "2") }; @@ -57,35 +54,35 @@ public void TestGet() manager.SaveObject(d.id, d.json); } var items = manager.GetAllObjects(); - items.Count.ShouldBe(data.Count); + items.Count.Should().Be(data.Count); var id1 = data[0].id; var json1 = manager.GetObject(id1); - json1.ShouldBe(data[0].json); - manager.HasObject(id1).ShouldBeTrue(); + json1.Should().Be(data[0].json); + manager.HasObject(id1).Should().BeTrue(); manager.UpdateObject(id1, "3"); json1 = manager.GetObject(id1); - json1.ShouldBe("3"); - manager.HasObject(id1).ShouldBeTrue(); + json1.Should().Be("3"); + manager.HasObject(id1).Should().BeTrue(); manager.DeleteObject(id1); json1 = manager.GetObject(id1); - json1.ShouldBeNull(); - manager.HasObject(id1).ShouldBeFalse(); + json1.Should().BeNull(); + manager.HasObject(id1).Should().BeFalse(); manager.UpdateObject(id1, "3"); json1 = manager.GetObject(id1); - json1.ShouldBe("3"); - manager.HasObject(id1).ShouldBeTrue(); + json1.Should().Be("3"); + manager.HasObject(id1).Should().BeTrue(); var id2 = data[1].id; var json2 = manager.GetObject(id2); - json2.ShouldBe(data[1].json); - manager.HasObject(id2).ShouldBeTrue(); + json2.Should().Be(data[1].json); + manager.HasObject(id2).Should().BeTrue(); manager.DeleteObject(id2); json2 = manager.GetObject(id2); - json2.ShouldBeNull(); - manager.HasObject(id2).ShouldBeFalse(); + json2.Should().BeNull(); + manager.HasObject(id2).Should().BeFalse(); } } diff --git a/tests/Speckle.Sdk.Tests.Unit/SQLite/SQLiteJsonExceptionTests.cs b/tests/Speckle.Sdk.Tests.Unit/SQLite/SQLiteJsonExceptionTests.cs index a5d80fd6..91721a95 100644 --- a/tests/Speckle.Sdk.Tests.Unit/SQLite/SQLiteJsonExceptionTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/SQLite/SQLiteJsonExceptionTests.cs @@ -1,13 +1,12 @@ using Microsoft.Data.Sqlite; -using NUnit.Framework; using Speckle.Sdk.SQLite; +using Xunit; namespace Speckle.Sdk.Tests.Unit.SQLite; -[TestFixture] public class SqLiteJsonCacheExceptionTests { - [Test] + [Fact] public void ExpectedExceptionFires_Void() { using var pool = new CacheDbCommandPool("DataSource=:memory:", 1); @@ -16,7 +15,7 @@ public void ExpectedExceptionFires_Void() ); } - [Test] + [Fact] public void ExpectedExceptionFires_Return() { using var pool = new CacheDbCommandPool("DataSource=:memory:", 1); diff --git a/tests/Speckle.Sdk.Tests.Unit/Serialisation/BatchTests.cs b/tests/Speckle.Sdk.Tests.Unit/Serialisation/BatchTests.cs index a16b1937..9d8f2036 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Serialisation/BatchTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Serialisation/BatchTests.cs @@ -1,11 +1,10 @@ -using NUnit.Framework; -using Shouldly; +using FluentAssertions; using Speckle.Sdk.Dependencies; using Speckle.Sdk.Serialisation.V2.Send; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Serialisation; -[TestFixture] public class BatchTests { private class BatchItem : IHasSize @@ -18,28 +17,28 @@ public BatchItem(int size) public int Size { get; } } - [Test] + [Fact] public void TestBatchSize_Calc() { using var batch = new Batch(); batch.Add(new BatchItem(1)); - batch.Size.ShouldBe(1); + batch.Size.Should().Be(1); batch.Add(new BatchItem(2)); - batch.Size.ShouldBe(3); + batch.Size.Should().Be(3); } - [Test] + [Fact] public void TestBatchSize_Trim() { using var batch = new Batch(); batch.Add(new BatchItem(1)); batch.Add(new BatchItem(2)); - batch.Size.ShouldBe(3); + batch.Size.Should().Be(3); - batch.Items.Capacity.ShouldBe(Pools.DefaultCapacity); + batch.Items.Capacity.Should().Be(Pools.DefaultCapacity); batch.TrimExcess(); - batch.Items.Capacity.ShouldBe(2); - batch.Size.ShouldBe(3); + batch.Items.Capacity.Should().Be(2); + batch.Size.Should().Be(3); } } diff --git a/tests/Speckle.Sdk.Tests.Unit/Serialisation/ChunkingTests.cs b/tests/Speckle.Sdk.Tests.Unit/Serialisation/ChunkingTests.cs index 08a765b4..7eb55588 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Serialisation/ChunkingTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Serialisation/ChunkingTests.cs @@ -1,50 +1,56 @@ -using NUnit.Framework; +using FluentAssertions; using Speckle.Newtonsoft.Json; using Speckle.Sdk.Host; using Speckle.Sdk.Models; using Speckle.Sdk.Serialisation; using Speckle.Sdk.Transports; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Serialisation; public class ChunkingTests { - public static IEnumerable TestCases() + public static IEnumerable TestCases() { + // Initialize type loader TypeLoader.Reset(); TypeLoader.Initialize(typeof(Base).Assembly, typeof(IgnoreTest).Assembly); - yield return new TestCaseData(CreateDynamicTestCase(10, 100)).Returns(10); - yield return new TestCaseData(CreateDynamicTestCase(0.5, 100)).Returns(1); - yield return new TestCaseData(CreateDynamicTestCase(20.5, 100)).Returns(21); + // Return test data as a collection of objects for xUnit + yield return [CreateDynamicTestCase(10, 100), 10]; + yield return [CreateDynamicTestCase(0.5, 100), 1]; + yield return [CreateDynamicTestCase(20.5, 100), 21]; - yield return new TestCaseData(CreateDynamicTestCase(10, 1000)).Returns(10); - yield return new TestCaseData(CreateDynamicTestCase(0.5, 1000)).Returns(1); - yield return new TestCaseData(CreateDynamicTestCase(20.5, 1000)).Returns(21); + yield return [CreateDynamicTestCase(10, 1000), 10]; + yield return [CreateDynamicTestCase(0.5, 1000), 1]; + yield return [CreateDynamicTestCase(20.5, 1000), 21]; } - [TestCaseSource(nameof(TestCases))] - public int ChunkSerializationTest(Base testCase) + [Theory] + [MemberData(nameof(TestCases))] + public void ChunkSerializationTest(Base testCase, int expectedChunkCount) { - MemoryTransport transport = new(); + // Arrange + var transport = new MemoryTransport(); var sut = new SpeckleObjectSerializer([transport]); + // Act _ = sut.Serialize(testCase); - - var serailizedObjects = transport + var serializedObjects = transport .Objects.Values.Select(json => JsonConvert.DeserializeObject>(json)) .ToList(); - int numberOfChunks = serailizedObjects.Count(x => + var numberOfChunks = serializedObjects.Count(x => x!.TryGetValue("speckle_type", out var speckleType) && ((string)speckleType!) == "Speckle.Core.Models.DataChunk" ); - return numberOfChunks; + numberOfChunks.Should().Be(expectedChunkCount); } private static Base CreateDynamicTestCase(double numberOfChunks, int chunkSize) { - List value = Enumerable.Range(0, (int)Math.Floor(chunkSize * numberOfChunks)).ToList(); + // Helper method to create the dynamic test case + var value = Enumerable.Range(0, (int)Math.Floor(chunkSize * numberOfChunks)).ToList(); return new Base { [$"@({chunkSize})chunkedProperty"] = value }; } } diff --git a/tests/Speckle.Sdk.Tests.Unit/Serialisation/JsonIgnoreAttributeTests.cs b/tests/Speckle.Sdk.Tests.Unit/Serialisation/JsonIgnoreAttributeTests.cs index 288a0810..2b03a901 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Serialisation/JsonIgnoreAttributeTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Serialisation/JsonIgnoreAttributeTests.cs @@ -1,10 +1,11 @@ -using NUnit.Framework; +using FluentAssertions; using Speckle.Newtonsoft.Json; using Speckle.Sdk.Common; using Speckle.Sdk.Host; using Speckle.Sdk.Models; using Speckle.Sdk.Serialisation; using Speckle.Sdk.Transports; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Serialisation; @@ -12,74 +13,84 @@ namespace Speckle.Sdk.Tests.Unit.Serialisation; /// Tests that the leads to properties being ignored both from the final JSON output, /// But also from the id calculation /// -[TestOf(typeof(SpeckleObjectSerializer))] public sealed class JsonIgnoreRespected { - [SetUp] - public void Setup() + public JsonIgnoreRespected() { TypeLoader.Reset(); TypeLoader.Initialize(typeof(Base).Assembly, typeof(IgnoreTest).Assembly); } - public static IEnumerable IgnoredTestCases() + public static IEnumerable IgnoredTestCases() { const string EXPECTED_PAYLOAD = "this should have been included"; const string EXPECTED_HASH = "e1d9f0685266465c9bfe4e71f2eee6e9"; - yield return new TestCaseData("this should have been ignored", EXPECTED_PAYLOAD).Returns(EXPECTED_HASH); - yield return new TestCaseData("again, ignored!", EXPECTED_PAYLOAD).Returns(EXPECTED_HASH); - yield return new TestCaseData("this one is not", EXPECTED_PAYLOAD).Returns(EXPECTED_HASH); + yield return ["this should have been ignored", EXPECTED_PAYLOAD, EXPECTED_HASH]; + yield return ["again, ignored!", EXPECTED_PAYLOAD, EXPECTED_HASH]; + yield return ["this one is not", EXPECTED_PAYLOAD, EXPECTED_HASH]; } - public static IEnumerable IgnoredCompoundTestCases() + public static IEnumerable IgnoredCompoundTestCases() { const string EXPECTED_PAYLOAD = "this should have been included"; const string EXPECTED_HASH = "eeaeee4e61b04b313dd840cd63341eee"; - yield return new TestCaseData("this should have been ignored", EXPECTED_PAYLOAD).Returns(EXPECTED_HASH); - yield return new TestCaseData("again, ignored!", EXPECTED_PAYLOAD).Returns(EXPECTED_HASH); - yield return new TestCaseData("this one is not", EXPECTED_PAYLOAD).Returns(EXPECTED_HASH); + yield return ["this should have been ignored", EXPECTED_PAYLOAD, EXPECTED_HASH]; + yield return ["again, ignored!", EXPECTED_PAYLOAD, EXPECTED_HASH]; + yield return ["this one is not", EXPECTED_PAYLOAD, EXPECTED_HASH]; } - [TestCaseSource(nameof(IgnoredTestCases))] - public string? IgnoredProperties_NotIncludedInJson(string ignoredPayload, string expectedPayload) + [Theory] + [MemberData(nameof(IgnoredTestCases))] + public void IgnoredProperties_NotIncludedInJson(string ignoredPayload, string expectedPayload, string expectedHash) { IgnoreTest testData = new(ignoredPayload, expectedPayload); SpeckleObjectSerializer sut = new(); - var (json, id) = sut.SerializeBase(testData).NotNull(); + var result = sut.SerializeBase(testData); + result.Should().NotBeNull(); + result!.Value.Id.Should().NotBeNull(); - Assert.That(json.ToString(), Does.Not.Contain(nameof(testData.ShouldBeIgnored))); - Assert.That(json.ToString(), Does.Not.Contain(ignoredPayload)); + var jsonString = result.Value.Json.ToString(); + jsonString.Should().NotContain(nameof(testData.ShouldBeIgnored)); + jsonString.Should().NotContain(ignoredPayload); - Assert.That(json.ToString(), Does.Contain(nameof(testData.ShouldBeIncluded))); - Assert.That(json.ToString(), Does.Contain(expectedPayload)); + jsonString.Should().Contain(nameof(testData.ShouldBeIncluded)); + jsonString.Should().Contain(expectedPayload); - return id?.Value; + result.Value.Id!.Value.Value.Should().Be(expectedHash); } - [TestCaseSource(nameof(IgnoredCompoundTestCases))] - public string? IgnoredProperties_Compound_NotIncludedInJson(string ignoredPayload, string expectedPayload) + [Theory] + [MemberData(nameof(IgnoredCompoundTestCases))] + public void IgnoredProperties_Compound_NotIncludedInJson( + string ignoredPayload, + string expectedPayload, + string expectedHash + ) { IgnoredCompoundTest testData = new(ignoredPayload, expectedPayload); MemoryTransport savedObjects = new(); SpeckleObjectSerializer sut = new(writeTransports: [savedObjects]); - var (json, id) = sut.SerializeBase(testData).NotNull(); + var result = sut.SerializeBase(testData); + var (json, id) = result.NotNull(); + json.Value.Should().NotBeNull(); + id.Should().NotBeNull(); - savedObjects.SaveObject(id.NotNull().Value, json.Value); + savedObjects.SaveObject(id!.Value.Value.NotNull(), json.Value); foreach ((_, string childJson) in savedObjects.Objects) { - Assert.That(childJson, Does.Not.Contain(nameof(testData.ShouldBeIgnored))); - Assert.That(childJson, Does.Not.Contain(ignoredPayload)); + childJson.Should().NotContain(nameof(testData.ShouldBeIgnored)); + childJson.Should().NotContain(ignoredPayload); - Assert.That(childJson, Does.Contain(nameof(testData.ShouldBeIncluded))); - Assert.That(childJson, Does.Contain(expectedPayload)); + childJson.Should().Contain(nameof(testData.ShouldBeIncluded)); + childJson.Should().Contain(expectedPayload); } - return id.Value.Value; + id.Value.Value.Should().Be(expectedHash); } } @@ -88,6 +99,7 @@ public sealed class IgnoredCompoundTest(string ignoredPayload, string expectedPa { [JsonIgnore] public Base ShouldBeIgnored => new IgnoreTest(ignoredPayload, expectedPayload) { ["override"] = ignoredPayload }; + public Base ShouldBeIncluded => new IgnoreTest(ignoredPayload, expectedPayload); [JsonIgnore, DetachProperty] diff --git a/tests/Speckle.Sdk.Tests.Unit/Serialisation/ObjectModelDeprecationTests.cs b/tests/Speckle.Sdk.Tests.Unit/Serialisation/ObjectModelDeprecationTests.cs index 7c1437bc..eae0825c 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Serialisation/ObjectModelDeprecationTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Serialisation/ObjectModelDeprecationTests.cs @@ -1,41 +1,42 @@ -using NUnit.Framework; -using Shouldly; +using FluentAssertions; using Speckle.Sdk.Host; using Speckle.Sdk.Models; using Speckle.Sdk.Serialisation.Deprecated; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Serialisation { - [TestFixture] - [TestOf(typeof(TypeLoader))] public class TypeLoaderTests { - [SetUp] - public void Setup() + // Constructor replaces the [SetUp] functionality in NUnit + public TypeLoaderTests() { TypeLoader.Reset(); TypeLoader.Initialize(typeof(Base).Assembly, typeof(MySpeckleBase).Assembly); } - [Test] + [Fact] // Replaces [Test] public void TestThatTypeWithoutAttributeFails() { - var e = Assert.Throws(() => TypeLoader.ParseType(typeof(string))); - e.ShouldNotBeNull(); + // Record.Exception is the xUnit alternative of Assert.Throws + var exception = Record.Exception(() => TypeLoader.ParseType(typeof(string))); + + exception.Should().NotBeNull(); // Shouldly assertion + exception.Should().BeOfType(); // Ensure it's the correct exception type } - [Test] + [Fact] // Replaces [Test] public void TestThatTypeWithoutMultipleAttributes() { string destinationType = $"Speckle.Core.Serialisation.{nameof(MySpeckleBase)}"; var result = TypeLoader.GetAtomicType(destinationType); - Assert.That(result, Is.EqualTo(typeof(MySpeckleBase))); + result.Should().Be(typeof(MySpeckleBase)); // Shouldly assertion replaces Assert.That destinationType = $"Speckle.Core.Serialisation.Deprecated.{nameof(MySpeckleBase)}"; result = TypeLoader.GetAtomicType(destinationType); - Assert.That(result, Is.EqualTo(typeof(MySpeckleBase))); + result.Should().Be(typeof(MySpeckleBase)); // Shouldly assertion replaces Assert.That } } } diff --git a/tests/Speckle.Sdk.Tests.Unit/Serialisation/PrimitiveTestFixture.cs b/tests/Speckle.Sdk.Tests.Unit/Serialisation/PrimitiveTestFixture.cs new file mode 100644 index 00000000..75f63184 --- /dev/null +++ b/tests/Speckle.Sdk.Tests.Unit/Serialisation/PrimitiveTestFixture.cs @@ -0,0 +1,48 @@ +namespace Speckle.Sdk.Tests.Unit.Serialisation; + +public abstract class PrimitiveTestFixture +{ + public static IEnumerable Int8TestCases => + new sbyte[] { 0, sbyte.MaxValue, sbyte.MinValue }.Select(x => new object[] { x }); + public static readonly short[] Int16TestCases = [short.MaxValue, short.MinValue]; + public static IEnumerable Int32TestCases => + new int[] { int.MinValue, int.MaxValue }.Select(x => new object[] { x }); + + public static IEnumerable Int64TestCases => + new long[] { long.MinValue, long.MaxValue }.Select(x => new object[] { x }); + + public static IEnumerable UInt64TestCases => + new ulong[] { ulong.MinValue, ulong.MaxValue }.Select(x => new object[] { x }); + + public static IEnumerable Float64TestCases => + new[] + { + 0, + double.Epsilon, + double.MaxValue, + double.MinValue, + double.PositiveInfinity, + double.NegativeInfinity, + double.NaN, + }.Select(x => new object[] { x }); + + public static IEnumerable Float32TestCases => + new[] + { + default, + float.Epsilon, + float.MaxValue, + float.MinValue, + float.PositiveInfinity, + float.NegativeInfinity, + float.NaN, + }.Select(x => new object[] { x }); + + public static Half[] Float16TestCases { get; } = + [default, Half.Epsilon, Half.MaxValue, Half.MinValue, Half.PositiveInfinity, Half.NegativeInfinity, Half.NaN]; + + public static float[] FloatIntegralTestCases { get; } = [0, 1, int.MaxValue, int.MinValue]; + + public static IEnumerable MyEnums { get; } = + Enum.GetValues(typeof(MyEnum)).Cast().Select(x => new[] { x }); +} diff --git a/tests/Speckle.Sdk.Tests.Unit/Serialisation/SerializerBreakingChanges.cs b/tests/Speckle.Sdk.Tests.Unit/Serialisation/SerializerBreakingChanges.cs index 4c78302e..a2510abe 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Serialisation/SerializerBreakingChanges.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Serialisation/SerializerBreakingChanges.cs @@ -1,28 +1,26 @@ +using FluentAssertions; using Microsoft.Extensions.DependencyInjection; -using NUnit.Framework; using Speckle.Sdk.Api; using Speckle.Sdk.Host; using Speckle.Sdk.Models; using Speckle.Sdk.Serialisation; using Speckle.Sdk.Tests.Unit.Host; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Serialisation; /// -/// Test fixture that documents what property typing changes break backwards/cross/forwards compatibility, and are "breaking" changes. +/// Test class that documents what property typing changes break backwards/cross/forwards compatibility, +/// and are "breaking" changes. /// This doesn't guarantee things work this way for SpecklePy /// Nor does it encompass other tricks (like deserialize callback, or computed json ignored properties) /// -[TestFixture] -[Description( - "For certain types, changing property from one type to another is a breaking change, and not backwards/forwards compatible" -)] public class SerializerBreakingChanges : PrimitiveTestFixture { - private IOperations _operations; + private readonly IOperations _operations; - [SetUp] - public void Setup() + // xUnit does not support a Setup method; instead, you can use the constructor for initialization. + public SerializerBreakingChanges() { TypeLoader.Reset(); TypeLoader.Initialize(typeof(Base).Assembly, typeof(Point).Assembly); @@ -30,38 +28,39 @@ public void Setup() _operations = serviceProvider.GetRequiredService(); } - [Test] - public void StringToInt_ShouldThrow() + [Fact] + public async Task StringToInt_ShouldThrow() { var from = new StringValueMock { value = "testValue" }; - Assert.ThrowsAsync( - async () => await from.SerializeAsTAndDeserialize(_operations) - ); + await FluentActions + .Invoking(async () => await from.SerializeAsTAndDeserialize(_operations)) + .Should() + .ThrowAsync(); } - [Test, TestCaseSource(nameof(MyEnums))] - public void StringToEnum_ShouldThrow(MyEnum testCase) + [Theory] + [MemberData(nameof(MyEnums))] // Replaces [TestCaseSource(nameof(MyEnums))] + public async Task StringToEnum_ShouldThrow(MyEnum testCase) { var from = new StringValueMock { value = testCase.ToString() }; - Assert.ThrowsAsync(async () => - { - var res = await from.SerializeAsTAndDeserialize(_operations); - }); + await FluentActions + .Invoking(async () => await from.SerializeAsTAndDeserialize(_operations)) + .Should() + .ThrowAsync(); } - [ - Test, - Description("Deserialization of a JTokenType.Float to a .NET short/int/long should throw exception"), - TestCaseSource(nameof(Float64TestCases)), - TestCase(1e+30) - ] - public void DoubleToInt_ShouldThrow(double testCase) + [Theory(DisplayName = "Deserialization of a JTokenType.Float to a .NET short/int/long should throw exception")] + [MemberData(nameof(Float64TestCases))] + [InlineData(1e+30)] // Inline test case replaces [TestCase(1e+30)] + public async Task DoubleToInt_ShouldThrow(double testCase) { var from = new DoubleValueMock { value = testCase }; - Assert.ThrowsAsync( - async () => await from.SerializeAsTAndDeserialize(_operations) - ); + + await FluentActions + .Invoking(async () => await from.SerializeAsTAndDeserialize(_operations)) + .Should() + .ThrowAsync(); } } diff --git a/tests/Speckle.Sdk.Tests.Unit/Serialisation/SerializerNonBreakingChanges.cs b/tests/Speckle.Sdk.Tests.Unit/Serialisation/SerializerNonBreakingChanges.cs index e6893775..42397f93 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Serialisation/SerializerNonBreakingChanges.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Serialisation/SerializerNonBreakingChanges.cs @@ -1,29 +1,20 @@ using System.Drawing; +using FluentAssertions; using Microsoft.Extensions.DependencyInjection; -using NUnit.Framework; -using Shouldly; using Speckle.Sdk.Api; -using Speckle.Sdk.Helpers; using Speckle.Sdk.Host; using Speckle.Sdk.Models; using Speckle.Sdk.Serialisation; +using Xunit; using Matrix4x4 = Speckle.DoubleNumerics.Matrix4x4; namespace Speckle.Sdk.Tests.Unit.Serialisation; -/// -/// Test fixture that documents what property typing changes maintain backwards/cross/forwards compatibility, and are "non-breaking" changes. -/// This doesn't guarantee things work this way for SpecklePy -/// Nor does it encompass other tricks (like deserialize callback, or computed json ignored properties) -/// -[TestFixture] -[Description("For certain types, changing property from one type to another should be implicitly backwards compatible")] public class SerializerNonBreakingChanges : PrimitiveTestFixture { - private IOperations _operations; + private readonly IOperations _operations; - [SetUp] - public void Setup() + public SerializerNonBreakingChanges() { TypeLoader.Reset(); TypeLoader.Initialize(typeof(StringValueMock).Assembly); @@ -31,178 +22,163 @@ public void Setup() _operations = serviceProvider.GetRequiredService(); } - [Test, TestCaseSource(nameof(Int8TestCases)), TestCaseSource(nameof(Int32TestCases))] + [Theory, MemberData(nameof(Int8TestCases)), MemberData(nameof(Int32TestCases))] public async Task IntToColor(int argb) { var from = new IntValueMock { value = argb }; var res = await from.SerializeAsTAndDeserialize(_operations); - Assert.That(res.value.ToArgb(), Is.EqualTo(argb)); + res.value.ToArgb().Should().Be(argb); } - [Test, TestCaseSource(nameof(Int8TestCases)), TestCaseSource(nameof(Int32TestCases))] + [Theory, MemberData(nameof(Int8TestCases)), MemberData(nameof(Int32TestCases))] public async Task ColorToInt(int argb) { var from = new ColorValueMock { value = Color.FromArgb(argb) }; var res = await from.SerializeAsTAndDeserialize(_operations); - Assert.That(res.value, Is.EqualTo(argb)); + res.value.Should().Be(argb); } - [ - Test, - TestCaseSource(nameof(Int8TestCases)), - TestCaseSource(nameof(Int32TestCases)), - TestCaseSource(nameof(Int64TestCases)) - ] + [Theory, MemberData(nameof(Int8TestCases)), MemberData(nameof(Int32TestCases)), MemberData(nameof(Int64TestCases))] public async Task IntToDouble(long testCase) { var from = new IntValueMock { value = testCase }; var res = await from.SerializeAsTAndDeserialize(_operations); - Assert.That(res.value, Is.EqualTo(testCase)); + res.value.Should().Be(testCase); } - [Test] + [Fact] public async Task NullToInt() { var from = new ObjectValueMock { value = null }; var res = await from.SerializeAsTAndDeserialize(_operations); - Assert.That(res.value, Is.EqualTo(default(int))); + res.value.Should().Be(default(int)); } - [Test] + [Fact] public async Task NullToDouble() { var from = new ObjectValueMock { value = null }; var res = await from.SerializeAsTAndDeserialize(_operations); - Assert.That(res.value, Is.EqualTo(default(double))); + res.value.Should().Be(0); } - // IMPORTANT!!: This test mimics large numbers that we sometimes see from python - // This is behaviour our deserializer has, but not necessarily commited to keeping - // Numbers outside the range of a Long are not officially supported - [Test] - [TestCaseSource(nameof(UInt64TestCases))] - [DefaultFloatingPointTolerance(2048)] + [Theory] + [MemberData(nameof(UInt64TestCases))] public async Task UIntToDouble(ulong testCase) { var from = new UIntValueMock { value = testCase }; var res = await from.SerializeAsTAndDeserialize(_operations); - Assert.That(res.value, Is.EqualTo(testCase)); + res.value.Should().BeApproximately(testCase, 2048); } - [ - Test, - TestCaseSource(nameof(Int8TestCases)), - TestCaseSource(nameof(Int32TestCases)), - TestCaseSource(nameof(Int64TestCases)) - ] + [Theory, MemberData(nameof(Int8TestCases)), MemberData(nameof(Int32TestCases)), MemberData(nameof(Int64TestCases))] public async Task IntToString(long testCase) { var from = new IntValueMock { value = testCase }; var res = await from.SerializeAsTAndDeserialize(_operations); - Assert.That(res.value, Is.EqualTo(testCase.ToString())); + res.value.Should().Be(testCase.ToString()); } - private static readonly double[][] s_arrayTestCases = - { - Array.Empty(), - new double[] { 0, 1, int.MaxValue, int.MinValue }, - new[] { default, double.Epsilon, double.MaxValue, double.MinValue }, - }; + public static IEnumerable s_arrayTestCases => + new object[] + { + Array.Empty(), + new double[] { 0, 1, int.MaxValue, int.MinValue }, + new[] { default, double.Epsilon, double.MaxValue, double.MinValue }, + }.Select(x => new[] { x }); - [Test, TestCaseSource(nameof(s_arrayTestCases))] + [Theory, MemberData(nameof(s_arrayTestCases))] public async Task ArrayToList(double[] testCase) { var from = new ArrayDoubleValueMock { value = testCase }; var res = await from.SerializeAsTAndDeserialize(_operations); - Assert.That(res.value, Is.EquivalentTo(testCase)); + res.value.Should().BeEquivalentTo(testCase); } - [Test, TestCaseSource(nameof(s_arrayTestCases))] + [Theory, MemberData(nameof(s_arrayTestCases))] public async Task ListToArray(double[] testCase) { var from = new ListDoubleValueMock { value = testCase.ToList() }; var res = await from.SerializeAsTAndDeserialize(_operations); - Assert.That(res.value, Is.EquivalentTo(testCase)); + res.value.Should().BeEquivalentTo(testCase); } - [Test, TestCaseSource(nameof(s_arrayTestCases))] + [Theory, MemberData(nameof(s_arrayTestCases))] public async Task ListToIList(double[] testCase) { var from = new ListDoubleValueMock { value = testCase.ToList() }; var res = await from.SerializeAsTAndDeserialize(_operations); - Assert.That(res.value, Is.EquivalentTo(testCase)); + res.value.Should().BeEquivalentTo(testCase); } - [Test, TestCaseSource(nameof(s_arrayTestCases))] + [Theory, MemberData(nameof(s_arrayTestCases))] public async Task ListToIReadOnlyList(double[] testCase) { var from = new ListDoubleValueMock { value = testCase.ToList() }; var res = await from.SerializeAsTAndDeserialize(_operations); - Assert.That(res.value, Is.EquivalentTo(testCase)); + res.value.Should().BeEquivalentTo(testCase); } - [Test, TestCaseSource(nameof(s_arrayTestCases))] + [Theory, MemberData(nameof(s_arrayTestCases))] public async Task IListToList(double[] testCase) { var from = new IListDoubleValueMock { value = testCase.ToList() }; var res = await from.SerializeAsTAndDeserialize(_operations); - Assert.That(res.value, Is.EquivalentTo(testCase)); + res.value.Should().BeEquivalentTo(testCase); } - [Test, TestCaseSource(nameof(s_arrayTestCases))] + [Theory, MemberData(nameof(s_arrayTestCases))] public async Task IReadOnlyListToList(double[] testCase) { var from = new IReadOnlyListDoubleValueMock { value = testCase.ToList() }; var res = await from.SerializeAsTAndDeserialize(_operations); - Assert.That(res.value, Is.EquivalentTo(testCase)); + res.value.Should().BeEquivalentTo(testCase); } - [Test, TestCaseSource(nameof(MyEnums))] + [Theory, MemberData(nameof(MyEnums))] public async Task EnumToInt(MyEnum testCase) { var from = new EnumValueMock { value = testCase }; var res = await from.SerializeAsTAndDeserialize(_operations); - Assert.That(res.value, Is.EqualTo((int)testCase)); + res.value.Should().Be((int)testCase); } - [Test, TestCaseSource(nameof(MyEnums))] + [Theory, MemberData(nameof(MyEnums))] public async Task IntToEnum(MyEnum testCase) { var from = new IntValueMock { value = (int)testCase }; var res = await from.SerializeAsTAndDeserialize(_operations); - Assert.That(res.value, Is.EqualTo(testCase)); + res.value.Should().Be(testCase); } - [Test] - [TestCaseSource(nameof(Float64TestCases))] - [TestCaseSource(nameof(Float32TestCases))] + [Theory, MemberData(nameof(Float32TestCases)), MemberData(nameof(Float64TestCases))] public async Task DoubleToDouble(double testCase) { var from = new DoubleValueMock { value = testCase }; var res = await from.SerializeAsTAndDeserialize(_operations); - Assert.That(res.value, Is.EqualTo(testCase)); + res.value.Should().Be(testCase); } - [Test] - [TestCase(123, 255)] - [TestCase(256, 1)] - [TestCase(256, float.MinValue)] + [Theory] + [InlineData(123, 255)] + [InlineData(256, 1)] + [InlineData(256, float.MinValue)] public async Task ListToMatrix64(int seed, double scalar) { Random rand = new(seed); @@ -210,33 +186,29 @@ public async Task ListToMatrix64(int seed, double scalar) ListDoubleValueMock from = new() { value = testCase }; - //Test List -> Matrix var res = await from.SerializeAsTAndDeserialize(_operations); - Assert.That(res.value.M11, Is.EqualTo(testCase[0])); - Assert.That(res.value.M44, Is.EqualTo(testCase[testCase.Count - 1])); + res.value.M11.Should().Be(testCase[0]); + res.value.M44.Should().Be(testCase[^1]); - //Test Matrix -> List var backAgain = await res.SerializeAsTAndDeserialize(_operations); - Assert.That(backAgain.value, Is.Not.Null); - Assert.That(backAgain.value, Is.EquivalentTo(testCase)); + backAgain.value.Should().NotBeNull(); + backAgain.value.Should().BeEquivalentTo(testCase); } - [Test] - [TestCase(123, 255)] - [TestCase(256, 1)] - [DefaultFloatingPointTolerance(Constants.EPS)] - public void Matrix32ToMatrix64(int seed, float scalar) + [Theory] + [InlineData(123, 255)] + [InlineData(256, 1)] + public async Task Matrix32ToMatrix64(int seed, float scalar) { Random rand = new(seed); List testCase = Enumerable.Range(0, 16).Select(_ => rand.NextDouble() * scalar).ToList(); ListDoubleValueMock from = new() { value = testCase }; - //Test List -> Matrix - var exception = Assert.ThrowsAsync( - async () => await from.SerializeAsTAndDeserialize(_operations) - ); - exception.ShouldNotBeNull(); + await FluentActions + .Invoking(async () => await from.SerializeAsTAndDeserialize(_operations)) + .Should() + .ThrowAsync(); } } @@ -361,46 +333,8 @@ internal async Task SerializeAsTAndDeserialize(IOperations operations) var json = operations.Serialize(this); Base result = await operations.DeserializeAsync(json); - Assert.That(result, Is.Not.Null); - Assert.That(result, Is.TypeOf()); + result.Should().NotBeNull(); + result.Should().BeOfType(); return (TTo)result; } } - -public abstract class PrimitiveTestFixture -{ - public static readonly sbyte[] Int8TestCases = { default, sbyte.MaxValue, sbyte.MinValue }; - public static readonly short[] Int16TestCases = { short.MaxValue, short.MinValue }; - public static readonly int[] Int32TestCases = { int.MinValue, int.MaxValue }; - public static readonly long[] Int64TestCases = { long.MaxValue, long.MinValue }; - public static readonly ulong[] UInt64TestCases = { ulong.MaxValue, ulong.MinValue }; - - public static double[] Float64TestCases { get; } = - { - default, - double.Epsilon, - double.MaxValue, - double.MinValue, - double.PositiveInfinity, - double.NegativeInfinity, - double.NaN, - }; - - public static float[] Float32TestCases { get; } = - { - default, - float.Epsilon, - float.MaxValue, - float.MinValue, - float.PositiveInfinity, - float.NegativeInfinity, - float.NaN, - }; - - public static Half[] Float16TestCases { get; } = - { default, Half.Epsilon, Half.MaxValue, Half.MinValue, Half.PositiveInfinity, Half.NegativeInfinity, Half.NaN }; - - public static float[] FloatIntegralTestCases { get; } = { 0, 1, int.MaxValue, int.MinValue }; - - public static MyEnum[] MyEnums { get; } = Enum.GetValues(typeof(MyEnum)).Cast().ToArray(); -} diff --git a/tests/Speckle.Sdk.Tests.Unit/Serialisation/SimpleRoundTripTests.cs b/tests/Speckle.Sdk.Tests.Unit/Serialisation/SimpleRoundTripTests.cs index b9cd0183..dc6e8cb2 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Serialisation/SimpleRoundTripTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Serialisation/SimpleRoundTripTests.cs @@ -1,30 +1,29 @@ using System.Reflection; +using FluentAssertions; using Microsoft.Extensions.DependencyInjection; -using NUnit.Framework; using Speckle.Sdk.Api; using Speckle.Sdk.Host; using Speckle.Sdk.Models; using Speckle.Sdk.Tests.Unit.Host; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Serialisation; -[TestFixture] public class SimpleRoundTripTests { - private IOperations _operations; + private readonly IOperations _operations; - static SimpleRoundTripTests() - { - Reset(); - } - - private static void Reset() + public SimpleRoundTripTests() { TypeLoader.Reset(); TypeLoader.Initialize(typeof(Base).Assembly, Assembly.GetExecutingAssembly()); + var serviceProvider = TestServiceSetup.GetServiceProvider(); + _operations = serviceProvider.GetRequiredService(); } - public static IEnumerable TestData() + public static IEnumerable TestData() => TestDataInternal().Select(x => new object[] { x }); + + public static IEnumerable TestDataInternal() { yield return new DiningTable { ["@strangeVariable_NAme3"] = new TableLegFixture() }; @@ -36,21 +35,13 @@ public static IEnumerable TestData() yield return polyline; } - [SetUp] - public void Setup() - { - Reset(); - - var serviceProvider = TestServiceSetup.GetServiceProvider(); - _operations = serviceProvider.GetRequiredService(); - } - - [TestCaseSource(nameof(TestData))] + [Theory] + [MemberData(nameof(TestData))] public async Task SimpleSerialization(Base testData) { var result = _operations.Serialize(testData); var test = await _operations.DeserializeAsync(result); - Assert.That(testData.GetId(), Is.EqualTo(test.GetId())); + testData.GetId().Should().Be(test.GetId()); } } diff --git a/tests/Speckle.Sdk.Tests.Unit/Speckle.Sdk.Tests.Unit.csproj b/tests/Speckle.Sdk.Tests.Unit/Speckle.Sdk.Tests.Unit.csproj index 76529a53..5980c41d 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Speckle.Sdk.Tests.Unit.csproj +++ b/tests/Speckle.Sdk.Tests.Unit/Speckle.Sdk.Tests.Unit.csproj @@ -8,12 +8,12 @@ + - - - + + diff --git a/tests/Speckle.Sdk.Tests.Unit/Transports/DiskTransportTests.cs b/tests/Speckle.Sdk.Tests.Unit/Transports/DiskTransportTests.cs index 36aaf999..d903a902 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Transports/DiskTransportTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Transports/DiskTransportTests.cs @@ -1,37 +1,40 @@ -using NUnit.Framework; +using FluentAssertions; using Speckle.Sdk.Common; using Speckle.Sdk.Transports; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Transports; -[TestFixture] -[TestOf(nameof(DiskTransport))] -public sealed class DiskTransportTests : TransportTests +public sealed class DiskTransportTests : TransportTests, IDisposable { - protected override ITransport Sut => _diskTransport.NotNull(); - - private DiskTransport _diskTransport; + private readonly DiskTransport _diskTransport; + private readonly string _basePath = $"./temp_{Guid.NewGuid()}"; + private const string ApplicationName = "Speckle Integration Tests"; + private readonly string _fullPath; - private static readonly string s_basePath = $"./temp {Guid.NewGuid()}"; - private const string APPLICATION_NAME = "Speckle Integration Tests"; - private static readonly string s_fullPath = Path.Combine(s_basePath, APPLICATION_NAME); + protected override ITransport Sut => _diskTransport.NotNull(); - [SetUp] - public void Setup() + public DiskTransportTests() { - _diskTransport = new DiskTransport(s_fullPath); + _fullPath = Path.Combine(_basePath, ApplicationName); + _diskTransport = new DiskTransport(_fullPath); } - [TearDown] - public void TearDown() + [Fact] + public void DirectoryCreated_AfterInitialization() { - Directory.Delete(s_basePath, true); + // Act + var directoryExists = Directory.Exists(_fullPath); + + // Assert + directoryExists.Should().BeTrue(); } - [Test] - public void DirectoryCreated_AfterInitialization() + public void Dispose() { - bool fileExists = Directory.Exists(s_fullPath); - Assert.That(fileExists, Is.True); + if (Directory.Exists(_basePath)) + { + Directory.Delete(_basePath, true); + } } } diff --git a/tests/Speckle.Sdk.Tests.Unit/Transports/MemoryTransportTests.cs b/tests/Speckle.Sdk.Tests.Unit/Transports/MemoryTransportTests.cs index 2f41d623..e457c1a7 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Transports/MemoryTransportTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Transports/MemoryTransportTests.cs @@ -1,20 +1,23 @@ -using NUnit.Framework; +// MemoryTransportTests.cs + +using FluentAssertions; using Speckle.Sdk.Common; using Speckle.Sdk.Transports; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Transports; -[TestFixture] -[TestOf(nameof(MemoryTransport))] public sealed class MemoryTransportTests : TransportTests { protected override ITransport Sut => _memoryTransport.NotNull(); + private readonly MemoryTransport _memoryTransport; - private MemoryTransport _memoryTransport; - - [SetUp] - public void Setup() + // Constructor used for setup in xUnit + public MemoryTransportTests() { _memoryTransport = new MemoryTransport(); } + + [Fact] + public void TransportName_ShouldSetProperly() => _memoryTransport.TransportName.Should().Be("Memory"); } diff --git a/tests/Speckle.Sdk.Tests.Unit/Transports/SQLiteTransport2Tests.cs b/tests/Speckle.Sdk.Tests.Unit/Transports/SQLiteTransport2Tests.cs index f61009ab..e4d20cf4 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Transports/SQLiteTransport2Tests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Transports/SQLiteTransport2Tests.cs @@ -1,13 +1,12 @@ +using FluentAssertions; using Microsoft.Data.Sqlite; -using NUnit.Framework; using Speckle.Sdk.Common; using Speckle.Sdk.Serialisation.Utilities; using Speckle.Sdk.Transports; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Transports; -[TestFixture] -[TestOf(nameof(SQLiteTransport2))] public sealed class SQLiteTransport2Tests : TransportTests, IDisposable { protected override ITransport? Sut => _sqlite; @@ -17,30 +16,31 @@ public sealed class SQLiteTransport2Tests : TransportTests, IDisposable private static readonly string s_name = $"test-{Guid.NewGuid()}"; private static readonly string s_basePath = SqlitePaths.GetDBPath(s_name); - [SetUp] - public void Setup() + public SQLiteTransport2Tests() { _sqlite = new SQLiteTransport2(s_name); } - [TearDown] - public void TearDown() + public void Dispose() { _sqlite?.Dispose(); SqliteConnection.ClearAllPools(); - File.Delete(s_basePath); + if (File.Exists(s_basePath)) + { + File.Delete(s_basePath); + } + _sqlite = null; } - [Test] + [Fact] public void DbCreated_AfterInitialization() { bool fileExists = File.Exists(s_basePath); - Assert.That(fileExists, Is.True); + fileExists.Should().BeTrue(); } - [Test] - [Description("Tests that an object can be updated")] + [Fact(DisplayName = "Tests that an object can be updated")] public async Task UpdateObject_AfterAdd() { const string PAYLOAD_ID = "MyTestObjectId"; @@ -54,50 +54,45 @@ public async Task UpdateObject_AfterAdd() await _sqlite.WriteComplete(); var result = await _sqlite.GetObject(PAYLOAD_ID); - Assert.That(result, Is.EqualTo(NEW_PAYLOAD)); + result.Should().Be(NEW_PAYLOAD); } - [Test] - [Description("Tests that updating an object that hasn't been saved previously adds the object to the DB")] + [Fact(DisplayName = "Tests that updating an object that hasn't been saved previously adds the object to the DB")] public async Task UpdateObject_WhenMissing() { const string PAYLOAD_ID = "MyTestObjectId"; const string PAYLOAD_DATA = "MyTestObjectData"; var preUpdate = await _sqlite.NotNull().GetObject(PAYLOAD_ID); - Assert.That(preUpdate, Is.Null); + preUpdate.Should().BeNull(); _sqlite.UpdateObject(PAYLOAD_ID, PAYLOAD_DATA); await _sqlite.WriteComplete(); var postUpdate = await _sqlite.GetObject(PAYLOAD_ID); - Assert.That(postUpdate, Is.EqualTo(PAYLOAD_DATA)); + postUpdate.Should().Be(PAYLOAD_DATA); } - [Test] + [Fact] public async Task SaveAndRetrieveObject_Sync() { const string PAYLOAD_ID = "MyTestObjectId"; const string PAYLOAD_DATA = "MyTestObjectData"; var preAdd = await Sut.NotNull().GetObject(PAYLOAD_ID); - Assert.That(preAdd, Is.Null); + preAdd.Should().BeNull(); _sqlite.NotNull().SaveObjectSync(PAYLOAD_ID, PAYLOAD_DATA); { var postAdd = await Sut.GetObject(PAYLOAD_ID); - Assert.That(postAdd, Is.EqualTo(PAYLOAD_DATA)); + postAdd.Should().Be(PAYLOAD_DATA); } } - [Test( - Description = "Tests that it is possible to enumerate through all objects of the transport while updating them, without getting stuck in an infinite loop" - )] - [Timeout(1000)] + [Fact(DisplayName = "Tests enumerating through all objects while updating them without infinite loop")] public void UpdateObject_WhileEnumerating() { - //I question if this is the behaviour we want, but AccountManager.GetObjects is relying on being able to update objects while enumerating over them const string UPDATE_STRING = "_new"; Dictionary testData = new() { @@ -121,18 +116,14 @@ public void UpdateObject_WhileEnumerating() _sqlite.UpdateObject(key, newData); } - //Assert that objects were updated - Assert.That(_sqlite.GetAllObjects().ToList(), Has.All.Contains(UPDATE_STRING)); - //Assert that objects were only updated once - Assert.That(_sqlite.GetAllObjects().ToList(), Has.All.Length.EqualTo(length + UPDATE_STRING.Length)); + // Assert that objects were updated + _sqlite.GetAllObjects().ToList().Should().AllSatisfy(o => o.Should().Contain(UPDATE_STRING)); + // Assert that objects were only updated once + _sqlite.GetAllObjects().ToList().Should().AllSatisfy(o => o.Should().HaveLength(length + UPDATE_STRING.Length)); } - [Test] - [Repeat(10)] - [TestCase(6, 32)] - [Description( - $"Tests that the {nameof(SQLiteTransport2.GetAllObjects)} function can be called concurrently from multiple threads" - )] + [Theory(DisplayName = "Tests that GetAllObjects can be called concurrently from multiple threads")] + [InlineData(6, 32)] public void GetAllObjects_IsThreadSafe(int dataSize, int parallelism) { foreach (int i in Enumerable.Range(0, dataSize)) @@ -151,13 +142,8 @@ public void GetAllObjects_IsThreadSafe(int dataSize, int parallelism) foreach (var result in results) { - Assert.That(result, Is.EquivalentTo(results[0])); - Assert.That(result, Has.Count.EqualTo(dataSize)); + result.Should().BeEquivalentTo(results[0]); + result.Count.Should().Be(dataSize); } } - - public void Dispose() - { - _sqlite?.Dispose(); - } } diff --git a/tests/Speckle.Sdk.Tests.Unit/Transports/SQLiteTransportTests.cs b/tests/Speckle.Sdk.Tests.Unit/Transports/SQLiteTransportTests.cs index 5393730d..1a385466 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Transports/SQLiteTransportTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Transports/SQLiteTransportTests.cs @@ -1,51 +1,47 @@ +using FluentAssertions; using Microsoft.Data.Sqlite; -using NUnit.Framework; -using Speckle.Sdk.Common; using Speckle.Sdk.Transports; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Transports; -[TestFixture] -[TestOf(nameof(SQLiteTransport))] public sealed class SQLiteTransportTests : TransportTests, IDisposable { protected override ITransport? Sut => _sqlite; - private SQLiteTransport? _sqlite; + private readonly SQLiteTransport _sqlite; private static readonly string s_basePath = $"./temp {Guid.NewGuid()}"; private const string APPLICATION_NAME = "Speckle Integration Tests"; - [SetUp] - public void Setup() + // Constructor replaces [SetUp] + public SQLiteTransportTests() { _sqlite = new SQLiteTransport(s_basePath, APPLICATION_NAME); } - [TearDown] - public void TearDown() + // Disposal replaces [TearDown] for cleanup + public void Dispose() { _sqlite?.Dispose(); SqliteConnection.ClearAllPools(); Directory.Delete(s_basePath, true); - _sqlite = null; } - [Test] + [Fact] public void DbCreated_AfterInitialization() { bool fileExists = File.Exists($"{s_basePath}/{APPLICATION_NAME}/Data.db"); - Assert.That(fileExists, Is.True); + fileExists.Should().BeTrue(); } - [Test] - [Description("Tests that an object can be updated")] + [Fact] public async Task UpdateObject_AfterAdd() { const string PAYLOAD_ID = "MyTestObjectId"; const string PAYLOAD_DATA = "MyTestObjectData"; - _sqlite.NotNull().SaveObject(PAYLOAD_ID, PAYLOAD_DATA); + _sqlite!.SaveObject(PAYLOAD_ID, PAYLOAD_DATA); await _sqlite.WriteComplete(); const string NEW_PAYLOAD = "MyEvenBetterObjectData"; @@ -53,50 +49,45 @@ public async Task UpdateObject_AfterAdd() await _sqlite.WriteComplete(); var result = await _sqlite.GetObject(PAYLOAD_ID); - Assert.That(result, Is.EqualTo(NEW_PAYLOAD)); + result.Should().Be(NEW_PAYLOAD); } - [Test] - [Description("Tests that updating an object that hasn't been saved previously adds the object to the DB")] + [Fact] public async Task UpdateObject_WhenMissing() { const string PAYLOAD_ID = "MyTestObjectId"; const string PAYLOAD_DATA = "MyTestObjectData"; - var preUpdate = await _sqlite.NotNull().GetObject(PAYLOAD_ID); - Assert.That(preUpdate, Is.Null); + var preUpdate = await _sqlite!.GetObject(PAYLOAD_ID); + preUpdate.Should().BeNull(); _sqlite.UpdateObject(PAYLOAD_ID, PAYLOAD_DATA); await _sqlite.WriteComplete(); var postUpdate = await _sqlite.GetObject(PAYLOAD_ID); - Assert.That(postUpdate, Is.EqualTo(PAYLOAD_DATA)); + postUpdate.Should().Be(PAYLOAD_DATA); } - [Test] + [Fact] public async Task SaveAndRetrieveObject_Sync() { const string PAYLOAD_ID = "MyTestObjectId"; const string PAYLOAD_DATA = "MyTestObjectData"; - var preAdd = await Sut.NotNull().GetObject(PAYLOAD_ID); - Assert.That(preAdd, Is.Null); + var preAdd = await Sut!.GetObject(PAYLOAD_ID); + preAdd.Should().BeNull(); - _sqlite.NotNull().SaveObjectSync(PAYLOAD_ID, PAYLOAD_DATA); + _sqlite!.SaveObjectSync(PAYLOAD_ID, PAYLOAD_DATA); { var postAdd = await Sut.GetObject(PAYLOAD_ID); - Assert.That(postAdd, Is.EqualTo(PAYLOAD_DATA)); + postAdd.Should().Be(PAYLOAD_DATA); } } - [Test( - Description = "Tests that it is possible to enumerate through all objects of the transport while updating them, without getting stuck in an infinite loop" - )] - [Timeout(1000)] + [Fact] // No xUnit [Timeout], so this is purely indicative public void UpdateObject_WhileEnumerating() { - //I question if this is the behaviour we want, but AccountManager.GetObjects is relying on being able to update objects while enumerating over them const string UPDATE_STRING = "_new"; Dictionary testData = new() { @@ -109,10 +100,10 @@ public void UpdateObject_WhileEnumerating() foreach (var (key, data) in testData) { - _sqlite.NotNull().SaveObjectSync(key, data); + _sqlite!.SaveObjectSync(key, data); } - foreach (var o in _sqlite.NotNull().GetAllObjects()) + foreach (var o in _sqlite.GetAllObjects()) { string newData = o + UPDATE_STRING; string key = $"{o[length - 1]}"; @@ -120,23 +111,19 @@ public void UpdateObject_WhileEnumerating() _sqlite.UpdateObject(key, newData); } - //Assert that objects were updated - Assert.That(_sqlite.GetAllObjects().ToList(), Has.All.Contains(UPDATE_STRING)); - //Assert that objects were only updated once - Assert.That(_sqlite.GetAllObjects().ToList(), Has.All.Length.EqualTo(length + UPDATE_STRING.Length)); + // Assert that objects were updated + _sqlite.GetAllObjects().ToList().Should().AllSatisfy(o => o.Should().Contain(UPDATE_STRING)); + // Assert that objects were only updated once + _sqlite.GetAllObjects().ToList().Should().AllSatisfy(o => o.Should().HaveLength(length + UPDATE_STRING.Length)); } - [Test] - [Repeat(10)] - [TestCase(6, 32)] - [Description( - $"Tests that the {nameof(SQLiteTransport.GetAllObjects)} function can be called concurrently from multiple threads" - )] + [Theory] + [InlineData(6, 32)] public void GetAllObjects_IsThreadSafe(int dataSize, int parallelism) { foreach (int i in Enumerable.Range(0, dataSize)) { - _sqlite.NotNull().SaveObjectSync(i.ToString(), Guid.NewGuid().ToString()); + _sqlite!.SaveObjectSync(i.ToString(), Guid.NewGuid().ToString()); } List[] results = new List[parallelism]; @@ -144,19 +131,14 @@ public void GetAllObjects_IsThreadSafe(int dataSize, int parallelism) Enumerable.Range(0, parallelism), i => { - results[i] = _sqlite.NotNull().GetAllObjects().ToList(); + results[i] = _sqlite.GetAllObjects().ToList(); } ); foreach (var result in results) { - Assert.That(result, Is.EquivalentTo(results[0])); - Assert.That(result, Has.Count.EqualTo(dataSize)); + result.Should().BeEquivalentTo(results[0]); + result.Count.Should().Be(dataSize); } } - - public void Dispose() - { - _sqlite?.Dispose(); - } } diff --git a/tests/Speckle.Sdk.Tests.Unit/Transports/TransportTests.cs b/tests/Speckle.Sdk.Tests.Unit/Transports/TransportTests.cs index 77689518..2aa32fdf 100644 --- a/tests/Speckle.Sdk.Tests.Unit/Transports/TransportTests.cs +++ b/tests/Speckle.Sdk.Tests.Unit/Transports/TransportTests.cs @@ -1,16 +1,16 @@ -using NUnit.Framework; +using FluentAssertions; using Speckle.Newtonsoft.Json; using Speckle.Sdk.Common; using Speckle.Sdk.Transports; +using Xunit; namespace Speckle.Sdk.Tests.Unit.Transports; -[TestFixture] public abstract class TransportTests { protected abstract ITransport? Sut { get; } - [Test] + [Fact] public async Task SaveAndRetrieveObject() { const string PAYLOAD_ID = "MyTestObjectId"; @@ -18,7 +18,7 @@ public async Task SaveAndRetrieveObject() { var preAdd = await Sut.NotNull().GetObject(PAYLOAD_ID); - Assert.That(preAdd, Is.Null); + preAdd.Should().BeNull(); } Sut.SaveObject(PAYLOAD_ID, PAYLOAD_DATA); @@ -26,37 +26,35 @@ public async Task SaveAndRetrieveObject() { var postAdd = await Sut.GetObject(PAYLOAD_ID); - Assert.That(postAdd, Is.EqualTo(PAYLOAD_DATA)); + postAdd.Should().Be(PAYLOAD_DATA); } } - [Test] + [Fact] public async Task HasObject() { const string PAYLOAD_ID = "MyTestObjectId"; const string PAYLOAD_DATA = "MyTestObjectData"; { - var preAdd = await Sut.NotNull().HasObjects(new[] { PAYLOAD_ID }); - Assert.That(preAdd, Has.Exactly(1).Items); - Assert.That(preAdd, Has.No.ContainValue(true)); - Assert.That(preAdd, Contains.Key(PAYLOAD_ID)); + var preAdd = await Sut.NotNull().HasObjects([PAYLOAD_ID]); + preAdd.Count.Should().Be(1); + preAdd.Values.Should().NotContain(true); + preAdd.Keys.Should().Contain(PAYLOAD_ID); } Sut.SaveObject(PAYLOAD_ID, PAYLOAD_DATA); await Sut.WriteComplete(); { - var postAdd = await Sut.HasObjects(new[] { PAYLOAD_ID }); - - Assert.That(postAdd, Has.Exactly(1).Items); - Assert.That(postAdd, Has.No.ContainValue(false)); - Assert.That(postAdd, Contains.Key(PAYLOAD_ID)); + var postAdd = await Sut.HasObjects([PAYLOAD_ID]); + postAdd.Count.Should().Be(1); + postAdd.Values.Should().NotContain(false); + postAdd.Keys.Should().Contain(PAYLOAD_ID); } } - [Test] - [Description("Test that transports save objects when many threads are concurrently saving data")] + [Fact] public async Task SaveObject_ConcurrentWrites() { const int TEST_DATA_COUNT = 100; @@ -75,58 +73,54 @@ public async Task SaveObject_ConcurrentWrites() await Sut.NotNull().WriteComplete(); - //Test 1. SavedObjectCount //WARN: FAIL!!! seems this is not implemented for SQLite Transport - //Assert.That(transport.SavedObjectCount, Is.EqualTo(testDataCount)); - - //Test 2. HasObjects + //Test: HasObjects var ids = testData.Select(x => x.id).ToList(); var hasObjectsResult = await Sut.HasObjects(ids); - Assert.That(hasObjectsResult, Does.Not.ContainValue(false)); - Assert.That(hasObjectsResult.Keys, Is.EquivalentTo(ids)); + hasObjectsResult.Values.Should().NotContain(false); + hasObjectsResult.Keys.Should().BeEquivalentTo(ids); - //Test 3. GetObjects + //Test: GetObjects foreach (var x in testData) { var res = await Sut.GetObject(x.id); - Assert.That(res, Is.EqualTo(x.data)); + res.Should().Be(x.data); } } - [Test] + [Fact] public void ToString_IsNotEmpty() { var toString = Sut.NotNull().ToString(); - - Assert.That(toString, Is.Not.Null); - Assert.That(toString, Is.Not.Empty); + toString.Should().NotBeNullOrEmpty(); } - [Test] + [Fact] public void TransportName_IsNotEmpty() { var toString = Sut.NotNull().TransportName; - - Assert.That(toString, Is.Not.Null); - Assert.That(toString, Is.Not.Empty); + toString.Should().NotBeNullOrEmpty(); } - [Test] - public void SaveObject_ExceptionThrown_TaskIsCanceled() + [Fact] + public async Task SaveObject_ExceptionThrown_TaskIsCanceled() { using CancellationTokenSource tokenSource = new(); Sut.NotNull().CancellationToken = tokenSource.Token; - tokenSource.Cancel(); + await tokenSource.CancelAsync(); - Assert.CatchAsync(async () => - { - Sut.SaveObject("abcdef", "fake payload data"); - await Sut.WriteComplete(); - }); + await FluentActions + .Invoking(async () => + { + Sut.SaveObject("abcdef", "fake payload data"); + await Sut.WriteComplete(); + }) + .Should() + .ThrowAsync(); } - [Test] + [Fact] public async Task CopyObjectAndChildren() { //Assemble @@ -156,7 +150,7 @@ public async Task CopyObjectAndChildren() foreach (var (expectedId, expectedData) in testData) { var actual = await destination.GetObject(expectedId); - Assert.That(actual, Is.EqualTo(expectedData)); + actual.Should().Be(expectedData); } } } diff --git a/tests/Speckle.Sdk.Tests.Unit/packages.lock.json b/tests/Speckle.Sdk.Tests.Unit/packages.lock.json index 7c77fd93..f4ddc12b 100644 --- a/tests/Speckle.Sdk.Tests.Unit/packages.lock.json +++ b/tests/Speckle.Sdk.Tests.Unit/packages.lock.json @@ -4,9 +4,18 @@ "net8.0": { "altcover": { "type": "Direct", - "requested": "[8.9.3, )", - "resolved": "8.9.3", - "contentHash": "auKC+pDCkLjfhFkSRaAUBu25BOmlLSqucR7YBs/Lkbdc0XRuJoklWafs1KKp+M+VoJ1f0TeMS6B/FO5IeIcu7w==" + "requested": "[9.0.1, )", + "resolved": "9.0.1", + "contentHash": "aadciFNDT5bnylaYUkKal+s5hF7yU/lmZxImQWAlk1438iPqK1Uf79H5ylELpyLIU49HL5ql+tnWBihp3WVLCA==" + }, + "FluentAssertions": { + "type": "Direct", + "requested": "[7.0.0, )", + "resolved": "7.0.0", + "contentHash": "mTLbcU991EQ1SEmNbVBaGGGJy0YFzvGd1sYJGNZ07nlPKuyHSn1I22aeKzqQXgEiaKyRO6MSCto9eN9VxMwBdA==", + "dependencies": { + "System.Configuration.ConfigurationManager": "6.0.0" + } }, "GitVersion.MsBuild": { "type": "Direct", @@ -25,12 +34,12 @@ }, "Microsoft.NET.Test.Sdk": { "type": "Direct", - "requested": "[17.11.1, )", - "resolved": "17.11.1", - "contentHash": "U3Ty4BaGoEu+T2bwSko9tWqWUOU16WzSFkq6U8zve75oRBMSLTBdMAZrVNNz1Tq12aCdDom9fcOcM9QZaFHqFg==", + "requested": "[17.12.0, )", + "resolved": "17.12.0", + "contentHash": "kt/PKBZ91rFCWxVIJZSgVLk+YR+4KxTuHf799ho8WNiK5ZQpJNAEZCAWX86vcKrs+DiYjiibpYKdGZP6+/N17w==", "dependencies": { - "Microsoft.CodeCoverage": "17.11.1", - "Microsoft.TestPlatform.TestHost": "17.11.1" + "Microsoft.CodeCoverage": "17.12.0", + "Microsoft.TestPlatform.TestHost": "17.12.0" } }, "Microsoft.SourceLink.GitHub": { @@ -43,34 +52,12 @@ "Microsoft.SourceLink.Common": "8.0.0" } }, - "NUnit": { - "type": "Direct", - "requested": "[4.2.2, )", - "resolved": "4.2.2", - "contentHash": "mon0OPko28yZ/foVXrhiUvq1LReaGsBdziumyyYGxV/pOE4q92fuYeN+AF+gEU5pCjzykcdBt5l7xobTaiBjsg==" - }, - "NUnit3TestAdapter": { - "type": "Direct", - "requested": "[4.6.0, )", - "resolved": "4.6.0", - "contentHash": "R7e1+a4vuV/YS+ItfL7f//rG+JBvVeVLX4mHzFEZo4W1qEKl8Zz27AqvQSAqo+BtIzUCo4aAJMYa56VXS4hudw==" - }, "PolySharp": { "type": "Direct", "requested": "[1.15.0, )", "resolved": "1.15.0", "contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g==" }, - "Shouldly": { - "type": "Direct", - "requested": "[4.2.1, )", - "resolved": "4.2.1", - "contentHash": "dKAKiSuhLKqD2TXwLKtqNg1nwzJcIKOOMncZjk9LYe4W+h+SCftpWdxwR79YZUIHMH+3Vu9s0s0UHNrgICLwRQ==", - "dependencies": { - "DiffEngine": "11.3.0", - "EmptyFiles": "4.4.0" - } - }, "Speckle.DoubleNumerics": { "type": "Direct", "requested": "[4.0.1, )", @@ -83,19 +70,22 @@ "resolved": "0.9.6", "contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w==" }, - "DiffEngine": { - "type": "Transitive", - "resolved": "11.3.0", - "contentHash": "k0ZgZqd09jLZQjR8FyQbSQE86Q7QZnjEzq1LPHtj1R2AoWO8sjV5x+jlSisL7NZAbUOI4y+7Bog8gkr9WIRBGw==", + "xunit": { + "type": "Direct", + "requested": "[2.9.3, )", + "resolved": "2.9.3", + "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==", "dependencies": { - "EmptyFiles": "4.4.0", - "System.Management": "6.0.1" + "xunit.analyzers": "1.18.0", + "xunit.assert": "2.9.3", + "xunit.core": "[2.9.3]" } }, - "EmptyFiles": { - "type": "Transitive", - "resolved": "4.4.0", - "contentHash": "gwJEfIGS7FhykvtZoscwXj/XwW+mJY6UbAZk+qtLKFUGWC95kfKXnj8VkxsZQnWBxJemM/q664rGLN5nf+OHZw==" + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[3.0.0, )", + "resolved": "3.0.0", + "contentHash": "HggUqjQJe8PtDxcP25Q+CnR6Lz4oX3GElhD9V4oU2+75x9HI6A6sxbfKGS4UwU4t4yJaS9fBmAuriz8bQApNjw==" }, "GraphQL.Client.Abstractions": { "type": "Transitive", @@ -125,8 +115,8 @@ }, "Microsoft.CodeCoverage": { "type": "Transitive", - "resolved": "17.11.1", - "contentHash": "nPJqrcA5iX+Y0kqoT3a+pD/8lrW/V7ayqnEJQsTonSoPz59J8bmoQhcSN4G8+UJ64Hkuf0zuxnfuj2lkHOq4cA==" + "resolved": "17.12.0", + "contentHash": "4svMznBd5JM21JIG2xZKGNanAHNXplxf/kQDFfLHXQ3OnpJkayRK/TjacFjA+EYmoyuNXHo/sOETEfcYtAzIrA==" }, "Microsoft.Data.Sqlite.Core": { "type": "Transitive", @@ -191,21 +181,26 @@ }, "Microsoft.TestPlatform.ObjectModel": { "type": "Transitive", - "resolved": "17.11.1", - "contentHash": "E2jZqAU6JeWEVsyOEOrSW1o1bpHLgb25ypvKNB/moBXPVsFYBPd/Jwi7OrYahG50J83LfHzezYI+GaEkpAotiA==", + "resolved": "17.12.0", + "contentHash": "TDqkTKLfQuAaPcEb3pDDWnh7b3SyZF+/W9OZvWFp6eJCIiiYFdSB6taE2I6tWrFw5ywhzOb6sreoGJTI6m3rSQ==", "dependencies": { "System.Reflection.Metadata": "1.6.0" } }, "Microsoft.TestPlatform.TestHost": { "type": "Transitive", - "resolved": "17.11.1", - "contentHash": "DnG+GOqJXO/CkoqlJWeDFTgPhqD/V6VqUIL3vINizCWZ3X+HshCtbbyDdSHQQEjrc2Sl/K3yaxX6s+5LFEdYuw==", + "resolved": "17.12.0", + "contentHash": "MiPEJQNyADfwZ4pJNpQex+t9/jOClBGMiCiVVFuELCMSX2nmNfvUor3uFVxNNCg30uxDP8JDYfPnMXQzsfzYyg==", "dependencies": { - "Microsoft.TestPlatform.ObjectModel": "17.11.1", + "Microsoft.TestPlatform.ObjectModel": "17.12.0", "Newtonsoft.Json": "13.0.1" } }, + "Microsoft.Win32.SystemEvents": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "hqTM5628jSsQiv+HGpiq3WKBl2c8v1KZfby2J6Pr7pEPlK9waPdgEO6b8A/+/xn/yZ9ulv8HuqK71ONy2tg67A==" + }, "Newtonsoft.Json": { "type": "Transitive", "resolved": "13.0.1", @@ -241,22 +236,26 @@ "SQLitePCLRaw.core": "2.1.4" } }, - "System.CodeDom": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA==" - }, "System.ComponentModel.Annotations": { "type": "Transitive", "resolved": "4.5.0", "contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg==" }, - "System.Management": { + "System.Configuration.ConfigurationManager": { "type": "Transitive", - "resolved": "6.0.1", - "contentHash": "10J1D0h/lioojphfJ4Fuh5ZUThT/xOVHdV9roGBittKKNP2PMjrvibEdbVTGZcPra1399Ja3tqIJLyQrc5Wmhg==", + "resolved": "6.0.0", + "contentHash": "7T+m0kDSlIPTHIkPMIu6m6tV6qsMqJpvQWW2jIc2qi7sn40qxFo0q+7mEQAhMPXZHMKnWrnv47ntGlM/ejvw3g==", "dependencies": { - "System.CodeDom": "6.0.0" + "System.Security.Cryptography.ProtectedData": "6.0.0", + "System.Security.Permissions": "6.0.0" + } + }, + "System.Drawing.Common": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "NfuoKUiP2nUWwKZN6twGqXioIe1zVD0RIj2t976A+czLHr2nY454RwwXs6JU9Htc6mwqL6Dn/nEL3dpVf2jOhg==", + "dependencies": { + "Microsoft.Win32.SystemEvents": "6.0.0" } }, "System.Memory": { @@ -279,6 +278,73 @@ "resolved": "4.5.1", "contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw==" }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ==" + }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "rp1gMNEZpvx9vP0JW0oHLxlf8oSiQgtno77Y4PLUBjSiDYoD77Y8uXHr1Ea5XG4/pIKhqAdxZ8v8OTUtqo9PeQ==" + }, + "System.Security.Permissions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "T/uuc7AklkDoxmcJ7LGkyX1CcSviZuLCa4jg3PekfJ7SU0niF0IVTXwUiNVP9DSpzou2PpxJ+eNY2IfDM90ZCg==", + "dependencies": { + "System.Security.AccessControl": "6.0.0", + "System.Windows.Extensions": "6.0.0" + } + }, + "System.Windows.Extensions": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "IXoJOXIqc39AIe+CIR7koBtRGMiCt/LPM3lI+PELtDIy9XdyeSrwXFdWV9dzJ2Awl0paLWUaknLxFQ5HpHZUog==", + "dependencies": { + "System.Drawing.Common": "6.0.0" + } + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.18.0", + "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA==" + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]", + "xunit.extensibility.execution": "[2.9.3]" + } + }, + "xunit.extensibility.core": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==", + "dependencies": { + "xunit.abstractions": "2.0.3" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.9.3", + "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==", + "dependencies": { + "xunit.extensibility.core": "[2.9.3]" + } + }, "speckle.sdk": { "type": "Project", "dependencies": {