From e300173bdd201cec9cedb541c183e2eec3b56b67 Mon Sep 17 00:00:00 2001 From: ahmed Date: Thu, 19 Mar 2026 15:26:42 +0300 Subject: [PATCH] comparision test brought here --- .../Esiur.AI.Annotations.csproj | 10 + Esiur.AI.Annotations/Program.cs | 1 + Esiur.CLI/Program.cs | 2 +- Esiur.sln | 18 +- Esiur/Proxy/TypeDefGenerator.cs | 4 +- Test/MyResource.cs | 32 - Test/Program.cs | 357 ----------- Test/Test.csproj | 13 - .../Esiur.Tests.Distribution.csproj | 13 + {Test => Tests/Distribution}/IMyRecord.cs | 5 +- {Test => Tests/Distribution}/MyChildRecord.cs | 5 +- .../Distribution}/MyChildResource.cs | 5 +- .../Distribution}/MyGenericRecord.cs | 5 +- {Test => Tests/Distribution}/MyRecord.cs | 5 +- Tests/Distribution/MyResource.cs | 31 + {Test => Tests/Distribution}/MyService.cs | 2 +- Tests/Distribution/Program.cs | 348 ++++++++++ .../Esiur.Tests.Serialization.csproj | 25 + Tests/Serialization/IntArrayGenerator.cs | 353 +++++++++++ Tests/Serialization/IntArrayRunner.cs | 335 ++++++++++ Tests/Serialization/Model.cs | 597 ++++++++++++++++++ Tests/Serialization/ModelGenerator.cs | 372 +++++++++++ Tests/Serialization/ModelRunner.cs | 484 ++++++++++++++ Tests/Serialization/Program.cs | 15 + 24 files changed, 2613 insertions(+), 424 deletions(-) create mode 100644 Esiur.AI.Annotations/Esiur.AI.Annotations.csproj create mode 100644 Esiur.AI.Annotations/Program.cs delete mode 100644 Test/MyResource.cs delete mode 100644 Test/Program.cs delete mode 100644 Test/Test.csproj create mode 100644 Tests/Distribution/Esiur.Tests.Distribution.csproj rename {Test => Tests/Distribution}/IMyRecord.cs (83%) rename {Test => Tests/Distribution}/MyChildRecord.cs (87%) rename {Test => Tests/Distribution}/MyChildResource.cs (92%) rename {Test => Tests/Distribution}/MyGenericRecord.cs (92%) rename {Test => Tests/Distribution}/MyRecord.cs (90%) create mode 100644 Tests/Distribution/MyResource.cs rename {Test => Tests/Distribution}/MyService.cs (99%) create mode 100644 Tests/Distribution/Program.cs create mode 100644 Tests/Serialization/Esiur.Tests.Serialization.csproj create mode 100644 Tests/Serialization/IntArrayGenerator.cs create mode 100644 Tests/Serialization/IntArrayRunner.cs create mode 100644 Tests/Serialization/Model.cs create mode 100644 Tests/Serialization/ModelGenerator.cs create mode 100644 Tests/Serialization/ModelRunner.cs create mode 100644 Tests/Serialization/Program.cs diff --git a/Esiur.AI.Annotations/Esiur.AI.Annotations.csproj b/Esiur.AI.Annotations/Esiur.AI.Annotations.csproj new file mode 100644 index 0000000..ed9781c --- /dev/null +++ b/Esiur.AI.Annotations/Esiur.AI.Annotations.csproj @@ -0,0 +1,10 @@ + + + + Exe + net10.0 + enable + enable + + + diff --git a/Esiur.AI.Annotations/Program.cs b/Esiur.AI.Annotations/Program.cs new file mode 100644 index 0000000..1bc52a6 --- /dev/null +++ b/Esiur.AI.Annotations/Program.cs @@ -0,0 +1 @@ +Console.WriteLine("Hello, World!"); diff --git a/Esiur.CLI/Program.cs b/Esiur.CLI/Program.cs index 6863153..3b972aa 100644 --- a/Esiur.CLI/Program.cs +++ b/Esiur.CLI/Program.cs @@ -47,7 +47,7 @@ if (args.Length > 0) { try { - var path = Esiur.Proxy.TypeDefGenerator.GetTemplate(url, o.Dir, false, o.Username, o.Password, o.AsyncSetters); + var path = Esiur.Proxy.TypeDefGenerator.GetTypes(url, o.Dir, false, o.Username, o.Password, o.AsyncSetters); Console.WriteLine($"Generated successfully: {path}"); } catch (Exception ex) diff --git a/Esiur.sln b/Esiur.sln index febbab7..552a6f3 100644 --- a/Esiur.sln +++ b/Esiur.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31919.166 +# Visual Studio Version 18 +VisualStudioVersion = 18.4.11612.150 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Esiur", "Esiur\Esiur.csproj", "{4F74A8C1-D38F-4CC0-ACD1-24459BA0EAFC}" EndProject @@ -8,7 +8,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Esiur.Stores.MongoDB", "Esi EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Esiur.Stores.EntityCore", "Esiur.Stores.EntityCore\Esiur.Stores.EntityCore.csproj", "{53DE5A30-CFA9-4DE7-A840-77CFF519D31B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test", "Test\Test.csproj", "{331F82B6-6B90-4533-9718-F7C8090D8F19}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Esiur.Tests.Distribution", "Tests\Distribution\Esiur.Tests.Distribution.csproj", "{331F82B6-6B90-4533-9718-F7C8090D8F19}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Esiur.Security.Cryptography", "Esiur.Security.Cryptography\Esiur.Security.Cryptography.csproj", "{C0C55C1A-7C48-41EB-9A65-27BC99D82F6D}" EndProject @@ -20,6 +20,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Esiur.AspNetCore.Example", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Esiur.AspNetCore", "Esiur.AspNetCore\Esiur.AspNetCore.csproj", "{7B0C521F-8B13-4F2A-BD78-7C692620C831}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{2769C4C3-2595-413B-B7FE-5903826770C1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Esiur.Tests.Serialization", "Tests\Serialization\Esiur.Tests.Serialization.csproj", "{58A49FFF-7A2C-2EE8-B184-9E89A168C403}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -62,10 +66,18 @@ Global {7B0C521F-8B13-4F2A-BD78-7C692620C831}.Debug|Any CPU.Build.0 = Debug|Any CPU {7B0C521F-8B13-4F2A-BD78-7C692620C831}.Release|Any CPU.ActiveCfg = Release|Any CPU {7B0C521F-8B13-4F2A-BD78-7C692620C831}.Release|Any CPU.Build.0 = Release|Any CPU + {58A49FFF-7A2C-2EE8-B184-9E89A168C403}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58A49FFF-7A2C-2EE8-B184-9E89A168C403}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58A49FFF-7A2C-2EE8-B184-9E89A168C403}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58A49FFF-7A2C-2EE8-B184-9E89A168C403}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {331F82B6-6B90-4533-9718-F7C8090D8F19} = {2769C4C3-2595-413B-B7FE-5903826770C1} + {58A49FFF-7A2C-2EE8-B184-9E89A168C403} = {2769C4C3-2595-413B-B7FE-5903826770C1} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C584421D-5EC0-4821-B7D8-2633D8D405F2} EndGlobalSection diff --git a/Esiur/Proxy/TypeDefGenerator.cs b/Esiur/Proxy/TypeDefGenerator.cs index 9cf83a6..091478b 100644 --- a/Esiur/Proxy/TypeDefGenerator.cs +++ b/Esiur/Proxy/TypeDefGenerator.cs @@ -406,9 +406,9 @@ public static class TypeDefGenerator rt.AppendLine($"[Export] public {ptTypeName} {p.Name} {{"); rt.AppendLine($"get => ({ptTypeName})properties[{p.Index}];"); if (asyncSetters) - rt.AppendLine($"set => _Set({p.Index}, value);"); + rt.AppendLine($"set => SetResourcePropertyAsync({p.Index}, value);"); else - rt.AppendLine($"set => _SetSync({p.Index}, value);"); + rt.AppendLine($"set => SetResourceProperty({p.Index}, value);"); rt.AppendLine("}"); } diff --git a/Test/MyResource.cs b/Test/MyResource.cs deleted file mode 100644 index 35318b2..0000000 --- a/Test/MyResource.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Esiur.Core; -using Esiur.Resource; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using static System.Runtime.InteropServices.JavaScript.JSType; - -namespace Test -{ - [Resource] - [Annotation("A", "B")] - public partial class MyResource - { - [Export][Annotation("Comment")] string description; - [Export] int categoryId; - - [Export] public string Hello() => "Hi"; - - [Export] public string HelloParent() => "Hi from Parent"; - - [Export] - [Annotation("This function computes the standard deviation of a list")] - public double StDev(double[] values) - { - double avg = values.Average(); - return Math.Sqrt(values.Average(v => Math.Pow(v - avg, 2))); - } - - } -} diff --git a/Test/Program.cs b/Test/Program.cs deleted file mode 100644 index aef3759..0000000 --- a/Test/Program.cs +++ /dev/null @@ -1,357 +0,0 @@ -/* - -Copyright (c) 2017 Ahmed Kh. Zamil - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -*/ - -using Esiur.Data; -using Esiur.Core; -using Esiur.Net.HTTP; -using Esiur.Net.Sockets; -using Esiur.Resource; -using Esiur.Security.Permissions; -using Esiur.Stores; -using System; -using System.Threading; -using System.Threading.Tasks; - -using Esiur.Security.Integrity; -using System.Linq; -using Esiur.Data.Types; -using System.Collections; -using System.Runtime.CompilerServices; -using Esiur.Proxy; -using System.Text.RegularExpressions; -using System.Collections.Generic; -using System.Reflection; -using System.Security.Cryptography; -using System.Text; -using Esiur.Security.Cryptography; -using Esiur.Security.Membership; -using Esiur.Net.Packets; -using System.Numerics; -using Esiur.Protocol; - -namespace Test -{ - - class Program - { - - static void TestSerialization(object x, EpConnection connection = null) - { - - var d = Codec.Compose(x, Warehouse.Default, connection); - // var rr = DC.ToHex(y); - - var y = Codec.ParseSync(d, 0, Warehouse.Default); - Console.WriteLine($"{x.GetType().Name}: {x} == {y}, {d.ToHex()}"); - } - - - [Export] - public class StudentRecord : IRecord - { - public string Name { get; set; } - public byte Grade { get; set; } - } - public enum LogLevel : int - { - Debug, - Warning, - Error, - } - - static async Task Main(string[] args) - { - //TestSerialization("Hello"); - //TestSerialization(10); - //TestSerialization(10.1); - //TestSerialization(10.1d); - //TestSerialization((byte)1); - //TestSerialization((byte)2); - TestSerialization(new int[] { 1, 2, 3, 4 }); - //var x = LogLevel.Warning; - - //TestSerialization(LogLevel.Warning); - - //TestSerialization(new Map - //{ - // ["C++"] = 1, - // ["C#"] = 2, - // ["JS"] = null - //}); - - - - //TestSerialization(new StudentRecord() { Name = "Ali", Grade = 90 }); - - //var tn = Encoding.UTF8.GetBytes("Test.StudentRecord"); - //var hash = System.Security.Cryptography.SHA256.Create().ComputeHash(tn).Clip(0, 16); - //hash[6] = (byte)((hash[6] & 0xF) | 0x80); - //hash[8] = (byte)((hash[8] & 0xF) | 0x80); - - //var g = new UUID(hash); - - //Console.WriteLine(g); - - - - var a = new ECDH(); - var b = new ECDH(); - - var apk = a.GetPublicKey(); - var bpk = b.GetPublicKey(); - - var ska = a.ComputeSharedKey(bpk); - var skb = b.ComputeSharedKey(apk); - - Console.WriteLine(ska.ToHex()); - Console.WriteLine(skb.ToHex()); - - // Simple membership provider - var membership = new SimpleMembership() { GuestsAllowed = true }; - - membership.AddUser("user", "123456", new SimpleMembership.QuestionAnswer[0]); - membership.AddUser("admin", "admin", new SimpleMembership.QuestionAnswer[] - { - new SimpleMembership.QuestionAnswer() - { - Question = "What is 5+5", - Answer = 10, - Hashed = true, - } - }); - - var wh = new Warehouse(); - - // Create stores to keep objects. - var system = await wh.Put("sys", new MemoryStore()); - var server = await wh.Put("sys/server", new EpServer() { Membership = membership }); - - - var web = await wh.Put("sys/web", new HTTPServer() { Port = 8088 }); - - var service = await wh.Put("sys/service", new MyService()); - var res1 = await wh.Put("sys/service/r1", new MyResource() { Description = "Testing 1", CategoryId = 10 }); - var res2 = await wh.Put("sys/service/r2", new MyResource() { Description = "Testing 2", CategoryId = 11 }); - var res3 = await wh.Put("sys/service/c1", new MyChildResource() { ChildName = "Child 1", Description = "Child Testing 3", CategoryId = 12 }); - var res4 = await wh.Put("sys/service/c2", new MyChildResource() { ChildName = "Child 2 Destroy", Description = "Testing Destroy Handler", CategoryId = 12 }); - - //TestSerialization(res1); - - server.MapCall("Hello", (string msg, DateTime time, EpConnection sender) => - { - Console.WriteLine(msg); - return "Hi " + DateTime.UtcNow; - }).MapCall("temp", () => res4); - - service.Resource = res1; - service.ChildResource = res3; - service.Resources = new MyResource[] { res1, res2, res1, res3 }; - service.MyResources = new MyResource[] { res1, res2, res3, res4 }; - - //web.MapGet("/{action}/{age}", (int age, string action, HTTPConnection sender) => - //{ - // Console.WriteLine($"AGE: {age} ACTION: {action}"); - - // sender.Response.Number = Esiur.Net.Packets.HTTPResponsePacket.ResponseCode.NotFound; - // sender.Send("Not found"); - //}); - - web.MapGet("/", (HTTPConnection sender) => - { - sender.Send("Hello"); - }); - - await wh.Open(); - - - //var sc = service.GetGenericRecord(); - //var d = Codec.Compose(sc, Warehouse.Default, null); - - // Start testing - TestClient(service); - } - - - // AuthorizationRequest, AsyncReply - static AsyncReply Authenticator(AuthorizationRequest x) - { - Console.WriteLine($"Authenticator: {x.Clue}"); - - var format = x.RequiredFormat; - - if (format == EpAuthPacketIAuthFormat.Number) - return new AsyncReply(Convert.ToInt32(10)); - else if (format == EpAuthPacketIAuthFormat.Text) - return new AsyncReply(Console.ReadLine().Trim()); - - throw new NotImplementedException("Not supported format."); - } - - private static async void TestClient(IResource local) - { - - var con = await new Warehouse().Get("EP://localhost", new EpConnectionConfig - { - AutoReconnect = true, - Username = "admin", - Password = "admin", - Authenticator = Authenticator - }); - - - dynamic remote = await con.Get("sys/service"); - var gr = await remote.GetGenericRecord(); - Console.WriteLine(gr); - //return; - - Console.WriteLine("OK"); - - perodicTimer = new Timer(new TimerCallback(perodicTimerElapsed), remote, 0, 1000); - - var pcall = await con.Call("Hello", "whats up ?", DateTime.UtcNow); - - var temp = await con.Call("temp"); - Console.WriteLine("Temp: " + temp.GetHashCode()); - - //var template = await con.GetTemplateByClassName("Test.MyResource"); - - - TestObjectProps(local, remote); - - - var opt = await remote.Optional(new { a1 = 22, a2 = 33, a4 = "What?" }); - Console.WriteLine(opt); - - var hello = await remote.AsyncHello(); - - await remote.Void(); - await remote.Connection("ss", 33); - await remote.ConnectionOptional("Test 2", 88); - var rt = await remote.Optional("Optiona", 311); - Console.WriteLine(rt); - - var t2 = await remote.GetTuple2(1, "A"); - Console.WriteLine(t2); - var t3 = await remote.GetTuple3(1, "A", 1.3); - Console.WriteLine(t3); - var t4 = await remote.GetTuple4(1, "A", 1.3, true); - Console.WriteLine(t4); - - remote.StringEvent += new EpResourceEvent((sender, args) => - Console.WriteLine($"StringEvent {args}") - ); - - remote.ArrayEvent += new EpResourceEvent((sender, args) => - Console.WriteLine($"ArrayEvent {args}") - ); - - await remote.InvokeEvents("Hello"); - - - - //var path = TemplateGenerator.GetTemplate("EP://localhost/sys/service", "Generated"); - - //Console.WriteLine(path); - - - } - - static async void perodicTimerElapsed(object state) - { - GC.Collect(); - try - { - dynamic remote = state; - await remote.InvokeEvents("Hello"); - - Console.WriteLine("Perodic : " + await remote.AsyncHello()); - } - catch (Exception ex) - { - Console.WriteLine("Perodic : " + ex.ToString()); - } - } - - static Timer perodicTimer; - - static void TestObjectProps(IResource local, EpResource remote) - { - - foreach (var pt in local.Instance.Definition.Properties) - { - - var lv = pt.PropertyInfo.GetValue(local); - object v; - var rv = remote.TryGetPropertyValue(pt.Index, out v); - if (!rv) - Console.WriteLine($" ** {pt.Name} Failed"); - else - Console.WriteLine($"{pt.Name} {GetString(lv)} == {GetString(v)}"); - } - - } - - static string GetString(object value) - { - if (value == null) - return "NULL"; - - var t = value.GetType(); - var nt = Nullable.GetUnderlyingType(t); - if (nt != null) - t = nt; - if (t.IsArray) - { - var ar = (Array)value; - if (ar.Length == 0) - return "[]"; - var rt = "["; - for (var i = 0; i < ar.Length - 1; i++) - rt += GetString(ar.GetValue(i)) + ","; - rt += GetString(ar.GetValue(ar.Length - 1)) + "]"; - - return rt; - } - else if (value is Record) - { - return value.ToString(); - } - else if (value is IRecord) - { - return "{" + String.Join(", ", t.GetProperties().Select(x => x.Name + ": " + x.GetValue(value))) + "}"; - } - - else - return value.ToString(); - } - - - - } - - - - -} - diff --git a/Test/Test.csproj b/Test/Test.csproj deleted file mode 100644 index 4cefa57..0000000 --- a/Test/Test.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - Exe - net10.0 - - - - - - - - \ No newline at end of file diff --git a/Tests/Distribution/Esiur.Tests.Distribution.csproj b/Tests/Distribution/Esiur.Tests.Distribution.csproj new file mode 100644 index 0000000..9f9b8d9 --- /dev/null +++ b/Tests/Distribution/Esiur.Tests.Distribution.csproj @@ -0,0 +1,13 @@ + + + + Exe + net10.0 + + + + + + + + \ No newline at end of file diff --git a/Test/IMyRecord.cs b/Tests/Distribution/IMyRecord.cs similarity index 83% rename from Test/IMyRecord.cs rename to Tests/Distribution/IMyRecord.cs index 77e8174..77395c7 100644 --- a/Test/IMyRecord.cs +++ b/Tests/Distribution/IMyRecord.cs @@ -5,9 +5,8 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Test -{ +namespace Esiur.Tests.Distribution; + public interface IMyRecord:IRecord { } -} diff --git a/Test/MyChildRecord.cs b/Tests/Distribution/MyChildRecord.cs similarity index 87% rename from Test/MyChildRecord.cs rename to Tests/Distribution/MyChildRecord.cs index 9596436..0a8b179 100644 --- a/Test/MyChildRecord.cs +++ b/Tests/Distribution/MyChildRecord.cs @@ -5,12 +5,11 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Test -{ +namespace Esiur.Tests.Distribution; + [Export] public class MyChildRecord : MyRecord { public string ChildName { get; set; } } -} diff --git a/Test/MyChildResource.cs b/Tests/Distribution/MyChildResource.cs similarity index 92% rename from Test/MyChildResource.cs rename to Tests/Distribution/MyChildResource.cs index f655da0..3fcf77b 100644 --- a/Test/MyChildResource.cs +++ b/Tests/Distribution/MyChildResource.cs @@ -5,8 +5,8 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Test -{ +namespace Esiur.Tests.Distribution; + [Resource] public partial class MyChildResource : MyResource { @@ -17,4 +17,3 @@ namespace Test [Export] public string HelloChild() => "Hi from Child"; } -} diff --git a/Test/MyGenericRecord.cs b/Tests/Distribution/MyGenericRecord.cs similarity index 92% rename from Test/MyGenericRecord.cs rename to Tests/Distribution/MyGenericRecord.cs index f354aa6..30886d9 100644 --- a/Test/MyGenericRecord.cs +++ b/Tests/Distribution/MyGenericRecord.cs @@ -6,8 +6,8 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Test -{ +namespace Esiur.Tests.Distribution; + public class MyGenericRecord : IRecord where T : IResource { [Export] public int Start { get; set; } @@ -16,4 +16,3 @@ namespace Test [Export] public T[] Results { get; set; } } -} diff --git a/Test/MyRecord.cs b/Tests/Distribution/MyRecord.cs similarity index 90% rename from Test/MyRecord.cs rename to Tests/Distribution/MyRecord.cs index 0b7206b..af6c6b7 100644 --- a/Test/MyRecord.cs +++ b/Tests/Distribution/MyRecord.cs @@ -6,8 +6,8 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Test -{ +namespace Esiur.Tests.Distribution; + [Export] public class MyRecord:IRecord { @@ -17,4 +17,3 @@ namespace Test public double Score { get; set; } } -} diff --git a/Tests/Distribution/MyResource.cs b/Tests/Distribution/MyResource.cs new file mode 100644 index 0000000..fb9ad7d --- /dev/null +++ b/Tests/Distribution/MyResource.cs @@ -0,0 +1,31 @@ +using Esiur.Core; +using Esiur.Resource; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static System.Runtime.InteropServices.JavaScript.JSType; + +namespace Esiur.Tests.Distribution; + +[Resource] +[Annotation("A", "B")] +public partial class MyResource +{ + [Export][Annotation("Comment")] string description; + [Export] int categoryId; + + [Export] public string Hello() => "Hi"; + + [Export] public string HelloParent() => "Hi from Parent"; + + [Export] + [Annotation("This function computes the standard deviation of a list")] + public double StDev(double[] values) + { + double avg = values.Average(); + return Math.Sqrt(values.Average(v => Math.Pow(v - avg, 2))); + } + +} diff --git a/Test/MyService.cs b/Tests/Distribution/MyService.cs similarity index 99% rename from Test/MyService.cs rename to Tests/Distribution/MyService.cs index be0468e..ae268ad 100644 --- a/Test/MyService.cs +++ b/Tests/Distribution/MyService.cs @@ -9,7 +9,7 @@ using Esiur.Protocol; #nullable enable -namespace Test; +namespace Esiur.Tests.Distribution; public enum SizeEnum:short diff --git a/Tests/Distribution/Program.cs b/Tests/Distribution/Program.cs new file mode 100644 index 0000000..6b5b940 --- /dev/null +++ b/Tests/Distribution/Program.cs @@ -0,0 +1,348 @@ +/* + +Copyright (c) 2017 Ahmed Kh. Zamil + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +using Esiur.Data; +using Esiur.Core; +using Esiur.Net.HTTP; +using Esiur.Net.Sockets; +using Esiur.Resource; +using Esiur.Security.Permissions; +using Esiur.Stores; +using System; +using System.Threading; +using System.Threading.Tasks; + +using Esiur.Security.Integrity; +using System.Linq; +using Esiur.Data.Types; +using System.Collections; +using System.Runtime.CompilerServices; +using Esiur.Proxy; +using System.Text.RegularExpressions; +using System.Collections.Generic; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using Esiur.Security.Cryptography; +using Esiur.Security.Membership; +using Esiur.Net.Packets; +using System.Numerics; +using Esiur.Protocol; + +namespace Esiur.Tests.Distribution; + + +class Program +{ + + static void TestSerialization(object x, EpConnection connection = null) + { + + var d = Codec.Compose(x, Warehouse.Default, connection); + // var rr = DC.ToHex(y); + + var y = Codec.ParseSync(d, 0, Warehouse.Default); + Console.WriteLine($"{x.GetType().Name}: {x} == {y}, {d.ToHex()}"); + } + + + [Export] + public class StudentRecord : IRecord + { + public string Name { get; set; } + public byte Grade { get; set; } + } + public enum LogLevel : int + { + Debug, + Warning, + Error, + } + + static async Task Main(string[] args) + { + //TestSerialization("Hello"); + //TestSerialization(10); + //TestSerialization(10.1); + //TestSerialization(10.1d); + //TestSerialization((byte)1); + //TestSerialization((byte)2); + TestSerialization(new int[] { 1, 2, 3, 4 }); + //var x = LogLevel.Warning; + + //TestSerialization(LogLevel.Warning); + + //TestSerialization(new Map + //{ + // ["C++"] = 1, + // ["C#"] = 2, + // ["JS"] = null + //}); + + + + //TestSerialization(new StudentRecord() { Name = "Ali", Grade = 90 }); + + //var tn = Encoding.UTF8.GetBytes("Test.StudentRecord"); + //var hash = System.Security.Cryptography.SHA256.Create().ComputeHash(tn).Clip(0, 16); + //hash[6] = (byte)((hash[6] & 0xF) | 0x80); + //hash[8] = (byte)((hash[8] & 0xF) | 0x80); + + //var g = new UUID(hash); + + //Console.WriteLine(g); + + + + var a = new ECDH(); + var b = new ECDH(); + + var apk = a.GetPublicKey(); + var bpk = b.GetPublicKey(); + + var ska = a.ComputeSharedKey(bpk); + var skb = b.ComputeSharedKey(apk); + + Console.WriteLine(ska.ToHex()); + Console.WriteLine(skb.ToHex()); + + // Simple membership provider + var membership = new SimpleMembership() { GuestsAllowed = true }; + + membership.AddUser("user", "123456", new SimpleMembership.QuestionAnswer[0]); + membership.AddUser("admin", "admin", new SimpleMembership.QuestionAnswer[] + { + new SimpleMembership.QuestionAnswer() + { + Question = "What is 5+5", + Answer = 10, + Hashed = true, + } + }); + + var wh = new Warehouse(); + + // Create stores to keep objects. + var system = await wh.Put("sys", new MemoryStore()); + var server = await wh.Put("sys/server", new EpServer() { Membership = membership }); + + + var web = await wh.Put("sys/web", new HTTPServer() { Port = 8088 }); + + var service = await wh.Put("sys/service", new MyService()); + var res1 = await wh.Put("sys/service/r1", new MyResource() { Description = "Testing 1", CategoryId = 10 }); + var res2 = await wh.Put("sys/service/r2", new MyResource() { Description = "Testing 2", CategoryId = 11 }); + var res3 = await wh.Put("sys/service/c1", new MyChildResource() { ChildName = "Child 1", Description = "Child Testing 3", CategoryId = 12 }); + var res4 = await wh.Put("sys/service/c2", new MyChildResource() { ChildName = "Child 2 Destroy", Description = "Testing Destroy Handler", CategoryId = 12 }); + + //TestSerialization(res1); + + server.MapCall("Hello", (string msg, DateTime time, EpConnection sender) => + { + Console.WriteLine(msg); + return "Hi " + DateTime.UtcNow; + }).MapCall("temp", () => res4); + + service.Resource = res1; + service.ChildResource = res3; + service.Resources = new MyResource[] { res1, res2, res1, res3 }; + service.MyResources = new MyResource[] { res1, res2, res3, res4 }; + + //web.MapGet("/{action}/{age}", (int age, string action, HTTPConnection sender) => + //{ + // Console.WriteLine($"AGE: {age} ACTION: {action}"); + + // sender.Response.Number = Esiur.Net.Packets.HTTPResponsePacket.ResponseCode.NotFound; + // sender.Send("Not found"); + //}); + + web.MapGet("/", (HTTPConnection sender) => + { + sender.Send("Hello"); + }); + + await wh.Open(); + + + //var sc = service.GetGenericRecord(); + //var d = Codec.Compose(sc, Warehouse.Default, null); + + // Start testing + TestClient(service); + } + + + // AuthorizationRequest, AsyncReply + static AsyncReply Authenticator(AuthorizationRequest x) + { + Console.WriteLine($"Authenticator: {x.Clue}"); + + var format = x.RequiredFormat; + + if (format == EpAuthPacketIAuthFormat.Number) + return new AsyncReply(Convert.ToInt32(10)); + else if (format == EpAuthPacketIAuthFormat.Text) + return new AsyncReply(Console.ReadLine().Trim()); + + throw new NotImplementedException("Not supported format."); + } + + private static async void TestClient(IResource local) + { + + var con = await new Warehouse().Get("EP://localhost", new EpConnectionConfig + { + AutoReconnect = true, + Username = "admin", + Password = "admin", + Authenticator = Authenticator + }); + + + dynamic remote = await con.Get("sys/service"); + var gr = await remote.GetGenericRecord(); + Console.WriteLine(gr); + //return; + + Console.WriteLine("OK"); + + perodicTimer = new Timer(new TimerCallback(perodicTimerElapsed), remote, 0, 1000); + + var pcall = await con.Call("Hello", "whats up ?", DateTime.UtcNow); + + var temp = await con.Call("temp"); + Console.WriteLine("Temp: " + temp.GetHashCode()); + + //var template = await con.GetTemplateByClassName("Test.MyResource"); + + + TestObjectProps(local, remote); + + + var opt = await remote.Optional(new { a1 = 22, a2 = 33, a4 = "What?" }); + Console.WriteLine(opt); + + var hello = await remote.AsyncHello(); + + await remote.Void(); + await remote.Connection("ss", 33); + await remote.ConnectionOptional("Test 2", 88); + var rt = await remote.Optional("Optiona", 311); + Console.WriteLine(rt); + + var t2 = await remote.GetTuple2(1, "A"); + Console.WriteLine(t2); + var t3 = await remote.GetTuple3(1, "A", 1.3); + Console.WriteLine(t3); + var t4 = await remote.GetTuple4(1, "A", 1.3, true); + Console.WriteLine(t4); + + remote.StringEvent += new EpResourceEvent((sender, args) => + Console.WriteLine($"StringEvent {args}") + ); + + remote.ArrayEvent += new EpResourceEvent((sender, args) => + Console.WriteLine($"ArrayEvent {args}") + ); + + await remote.InvokeEvents("Hello"); + + + + //var path = TemplateGenerator.GetTemplate("EP://localhost/sys/service", "Generated"); + + //Console.WriteLine(path); + + + } + + static async void perodicTimerElapsed(object state) + { + GC.Collect(); + try + { + dynamic remote = state; + await remote.InvokeEvents("Hello"); + + Console.WriteLine("Perodic : " + await remote.AsyncHello()); + } + catch (Exception ex) + { + Console.WriteLine("Perodic : " + ex.ToString()); + } + } + + static Timer perodicTimer; + + static void TestObjectProps(IResource local, EpResource remote) + { + + foreach (var pt in local.Instance.Definition.Properties) + { + + var lv = pt.PropertyInfo.GetValue(local); + object v; + var rv = remote.TryGetPropertyValue(pt.Index, out v); + if (!rv) + Console.WriteLine($" ** {pt.Name} Failed"); + else + Console.WriteLine($"{pt.Name} {GetString(lv)} == {GetString(v)}"); + } + + } + + static string GetString(object value) + { + if (value == null) + return "NULL"; + + var t = value.GetType(); + var nt = Nullable.GetUnderlyingType(t); + if (nt != null) + t = nt; + if (t.IsArray) + { + var ar = (Array)value; + if (ar.Length == 0) + return "[]"; + var rt = "["; + for (var i = 0; i < ar.Length - 1; i++) + rt += GetString(ar.GetValue(i)) + ","; + rt += GetString(ar.GetValue(ar.Length - 1)) + "]"; + + return rt; + } + else if (value is Record) + { + return value.ToString(); + } + else if (value is IRecord) + { + return "{" + String.Join(", ", t.GetProperties().Select(x => x.Name + ": " + x.GetValue(value))) + "}"; + } + + else + return value.ToString(); + } +} \ No newline at end of file diff --git a/Tests/Serialization/Esiur.Tests.Serialization.csproj b/Tests/Serialization/Esiur.Tests.Serialization.csproj new file mode 100644 index 0000000..0b51025 --- /dev/null +++ b/Tests/Serialization/Esiur.Tests.Serialization.csproj @@ -0,0 +1,25 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + + + + + + + + + + + + diff --git a/Tests/Serialization/IntArrayGenerator.cs b/Tests/Serialization/IntArrayGenerator.cs new file mode 100644 index 0000000..e31f608 --- /dev/null +++ b/Tests/Serialization/IntArrayGenerator.cs @@ -0,0 +1,353 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Esiur.Tests.Serialization; + +public static class IntArrayGenerator +{ + private static readonly Random rng = new Random(24241564); + + + public static long[] GenerateInt32Run(int length) + { + var data = new long[length]; + + int i = 0; + var inSmallRange = true; + var inShortRange = false; + var inLargeRange = false; + var inLongRange = false; + + long range = 30; + + while (i < length) + { + // stay same range + if (rng.NextDouble() < 0.9) + { + if (inSmallRange) + data[i++] = rng.Next(-64, 65); + else if (inShortRange) + data[i++] = rng.NextInt64(range - 100, range + 100); + else if (inLargeRange) + data[i++] = rng.NextInt64(range - 1000, range + 1000); + else if (inLongRange) + data[i++] = rng.NextInt64(range - 10000, range + 10000); + } + else + { + // switch range + var rand = rng.NextDouble(); + if (rand < 0.25) + { + inSmallRange = true; + inShortRange = false; + inLargeRange = false; + inLongRange = false; + data[i++] = rng.Next(-64, 65); + } + else if (rand < 0.50) + { + inSmallRange = false; + inShortRange = true; + inLargeRange = false; + inLongRange = false; + range = rng.NextInt64(1000, short.MaxValue); + data[i++] = rng.NextInt64(range - 100, range + 100); + } + else if (rand < 0.75) + { + inSmallRange = false; + inShortRange = false; + inLargeRange = true; + inLongRange = false; + range = rng.NextInt64(1000, int.MaxValue); + data[i++] = rng.NextInt64(range - 1000, range + 1000); + } + else + { + inSmallRange = false; + inShortRange = false; + inLargeRange = false; + inLongRange = true; + range = rng.NextInt64(10000, long.MaxValue); + data[i++] = rng.NextInt64(range - 10000, range + 10000); + + } + } + + } + + return data; + } + + // Generate random int array of given length and distribution + public static int[] GenerateInt32(int length, string pattern = "uniform", + int range = int.MaxValue) + { + var data = new int[length]; + + switch (pattern.ToLower()) + { + case "uniform": + // Random values in [-range, range] + for (int i = 0; i < length; i++) + data[i] = rng.Next(-range, range); + break; + + case "positive": + for (int i = 0; i < length; i++) + data[i] = rng.Next(0, range); + break; + + case "negative": + for (int i = 0; i < length; i++) + data[i] = -rng.Next(0, range); + break; + + case "alternating": + for (int i = 0; i < length; i++) + { + int val = rng.Next(0, range); + data[i] = (i % 2 == 0) ? val : -val; + } + break; + + case "small": + // Focused on small magnitudes to test ZigZag fast path + for (int i = 0; i < length; i++) + data[i] = rng.Next(-64, 65); + break; + + + case "ascending": + { + int start = rng.Next(-range, range); + for (int i = 0; i < length; i++) + data[i] = start + i; + } + break; + + default: + throw new ArgumentException($"Unknown pattern: {pattern}"); + } + + return data; + } + + + // Generate random int array of given length and distribution + public static uint[] GenerateUInt32(int length, string pattern = "uniform", + uint range = uint.MaxValue) + { + + var data = new uint[length]; + + switch (pattern.ToLower()) + { + case "uniform": + // Random values in [-range, range] + for (int i = 0; i < length; i++) + data[i] = (uint)rng.NextInt64(0, (long)range); + break; + + case "small": + // Focused on small magnitudes to test ZigZag fast path + for (int i = 0; i < length; i++) + data[i] = (uint)rng.Next(0, 127); + break; + + + case "ascending": + { + uint start = (uint)rng.NextInt64(0, (long)range); + for (uint i = 0; i < length; i++) + data[i] = start + i; + } + break; + + default: + throw new ArgumentException($"Unknown pattern: {pattern}"); + } + + return data; + } + + // Generate random int array of given length and distribution + public static ulong[] GenerateUInt64(int length, string pattern = "uniform") + { + var data = new ulong[length]; + + switch (pattern.ToLower()) + { + case "uniform": + // Random values in [-range, range] + for (int i = 0; i < length; i++) + data[i] = (ulong)rng.NextInt64(); + break; + + case "small": + // Focused on small magnitudes to test ZigZag fast path + for (int i = 0; i < length; i++) + data[i] = (uint)rng.Next(0, 127); + break; + + + case "ascending": + { + uint start = (uint)rng.NextInt64(); + for (uint i = 0; i < length; i++) + data[i] = start + i; + } + break; + + default: + throw new ArgumentException($"Unknown pattern: {pattern}"); + } + + return data; + } + + public static uint[] GenerateUInt16(int length, string pattern = "uniform", + ushort range = ushort.MaxValue) + { + var data = new uint[length]; + + switch (pattern.ToLower()) + { + case "uniform": + // Random values in [-range, range] + for (int i = 0; i < length; i++) + data[i] = (ushort)rng.Next(0, range); + break; + + case "small": + // Focused on small magnitudes to test ZigZag fast path + for (int i = 0; i < length; i++) + data[i] = (uint)rng.Next(0, 127); + break; + + + case "ascending": + { + var start = (ushort)rng.Next(0, range); + for (uint i = 0; i < length; i++) + data[i] = start + i; + } + break; + + default: + throw new ArgumentException($"Unknown pattern: {pattern}"); + } + + return data; + } + + // Generate random int array of given length and distribution + public static long[] GenerateInt64(int length, string pattern = "uniform", + long range = long.MaxValue) + { + var data = new long[length]; + + switch (pattern.ToLower()) + { + case "uniform": + // Random values in [-range, range] + for (int i = 0; i < length; i++) + data[i] = rng.NextInt64(-range, range); + break; + + case "positive": + for (int i = 0; i < length; i++) + data[i] = rng.NextInt64(0, range); + break; + + case "negative": + for (int i = 0; i < length; i++) + data[i] = -rng.NextInt64(0, range); + break; + + case "alternating": + for (int i = 0; i < length; i++) + { + var val = rng.NextInt64(0, range); + data[i] = (i % 2 == 0) ? val : -val; + } + break; + + case "small": + // Focused on small magnitudes to test ZigZag fast path + for (int i = 0; i < length; i++) + data[i] = rng.NextInt64(-64, 65); + break; + + + case "ascending": + { + var start = rng.NextInt64(-range, range); + for (int i = 0; i < length; i++) + data[i] = start + i; + } + break; + + default: + throw new ArgumentException($"Unknown pattern: {pattern}"); + } + + return data; + } + + public static short[] GenerateInt16(int length, string pattern = "uniform", + short range = short.MaxValue) + { + var data = new short[length]; + + switch (pattern.ToLower()) + { + case "uniform": + for (int i = 0; i < length; i++) + data[i] = (short)rng.Next(-range, range + 1); + break; + + case "positive": + for (int i = 0; i < length; i++) + data[i] = (short)rng.Next(0, range + 1); + break; + + case "negative": + for (int i = 0; i < length; i++) + data[i] = (short)(-rng.Next(0, range + 1)); + break; + + case "alternating": + for (int i = 0; i < length; i++) + { + short val = (short)rng.Next(0, range + 1); + data[i] = (i % 2 == 0) ? val : (short)-val; + } + break; + + case "small": + for (int i = 0; i < length; i++) + data[i] = (short)rng.Next(-64, 65); + break; + + + case "ascending": + { + short start = (short)rng.Next(-range, range); + for (int i = 0; i < length; i++) + data[i] = (short)(start + i); + } + break; + + default: + throw new ArgumentException($"Unknown pattern: {pattern}"); + } + + return data; + } +} \ No newline at end of file diff --git a/Tests/Serialization/IntArrayRunner.cs b/Tests/Serialization/IntArrayRunner.cs new file mode 100644 index 0000000..cc97139 --- /dev/null +++ b/Tests/Serialization/IntArrayRunner.cs @@ -0,0 +1,335 @@ +using Esiur.Data.GVWIE; +using FlatSharp; +using FlatSharp.Attributes; +using MessagePack; +using MongoDB.Bson; +using PeterO.Cbor; +using ProtoBuf; +using SolTechnology.Avro; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Text; + +namespace Esiur.Tests.Serialization +{ + [FlatBufferTable] + public class ArrayRoot + { + // Field index must be stable; start at 0 + [FlatBufferItem(0)] + public virtual IList? Values { get; set; } + } + + internal class IntArrayRunner + { + public void Run() + { + Console.WriteLine(";Esiur;FlatBuffer;ProtoBuffer;MessagePack;BSON;CBOR;Avro,Optimal"); + + + + //Console.Write("Cluster (Int32);"); + ////CompareInt(int32cluster); + //Average(() => CompareInt(IntArrayGenerator.GenerateInt32Run(1000)), 1000); + + + Console.Write("Positive (Int32);"); + Average(() => CompareInt(IntArrayGenerator.GenerateInt32(1000, "positive")), 1000); + + Console.Write("Negative (Int32);"); + Average(() => CompareInt(IntArrayGenerator.GenerateInt32(1000, "negative")), 1000); + + + Console.Write("Small (Int32);"); + Average(() => CompareInt(IntArrayGenerator.GenerateInt32(1000, "small")), 1000); + + // CompareInt(int32small); + + Console.Write("Alternating (Int32);"); + //CompareInt(int32alter); + + Average(() => CompareInt(IntArrayGenerator.GenerateInt32(1000, "alternating")), 1000); + + + Console.Write("Ascending (Int32);"); + //CompareInt(int32asc); + Average(() => CompareInt(IntArrayGenerator.GenerateInt32(1000, "ascending")), 1000); + + + Console.Write("Int64;"); + Average(() => CompareInt(IntArrayGenerator.GenerateInt64(1000, "uniform")), 1000); + //CompareInt(int64Uni); + + Console.Write("Int32;"); + //CompareInt(int32Uni); + Average(() => CompareInt(IntArrayGenerator.GenerateInt32(1000, "uniform")), 1000); + + Console.Write("Int16;"); + //CompareInt(int16Uni); + + Average(() => CompareInt(IntArrayGenerator.GenerateInt16(1000, "uniform")), 1000); + + + Console.Write("UInt64;"); + //CompareInt(uint64Uni); + Average(() => CompareInt(IntArrayGenerator.GenerateUInt64(1000, "uniform")), 1000); + + + Console.Write("UInt32;"); + //CompareInt(uint32Uni); + Average(() => CompareInt(IntArrayGenerator.GenerateUInt32(1000, "uniform")), 1000); + + Console.Write("UInt16;"); + //CompareInt(uint16Uni); + Average(() => CompareInt(IntArrayGenerator.GenerateUInt16(1000, "uniform")), 1000); + + } + + public static (int, int, int, int, int, int, int, int) CompareInt(long[] sample) + { + var intRoot = new ArrayRoot() { Values = sample }; + + var esiur = GroupInt64Codec.Encode(sample); + var messagePack = MessagePackSerializer.Serialize(sample); + var flatBuffer = SerializeFlatBuffers(intRoot); + + using var ms = new MemoryStream(); + Serializer.Serialize(ms, sample); + var protoBuffer = ms.ToArray(); + + var bson = intRoot.ToBson(); + + var cbor = CBORObject.FromObject(intRoot).EncodeToBytes(); + //var seq = new DerSequence(sample.Select(v => new DerInteger(v)).ToArray()); + //var ans1 = seq.GetDerEncoded(); + + + var avro = AvroConvert.Serialize(sample); + + var optimal = OptimalSignedEnocding(sample); + //Console.WriteLine($"{esiur.Length};{flatBuffer.Length};{protoBuffer.Length};{messagePack.Length};{bson.Length};{cbor.Length};{avro.Length};{optimal}"); + return (esiur.Length, flatBuffer.Length, protoBuffer.Length, messagePack.Length, bson.Length, cbor.Length, avro.Length, optimal); + + } + + public static (int, int, int, int, int, int, int, int) CompareInt(int[] sample) + { + var intRoot = new ArrayRoot() { Values = sample }; + + var esiur = GroupInt32Codec.Encode(sample); + var messagePack = MessagePackSerializer.Serialize(sample); + var flatBuffer = SerializeFlatBuffers(intRoot); + + using var ms = new MemoryStream(); + Serializer.Serialize(ms, sample); + var protoBuffer = ms.ToArray(); + + var bson = intRoot.ToBson(); + + var cbor = CBORObject.FromObject(intRoot).EncodeToBytes(); + //var seq = new DerSequence(sample.Select(v => new DerInteger(v)).ToArray()); + //var ans1 = seq.GetDerEncoded(); + + + var avro = AvroConvert.Serialize(sample); + + var optimal = OptimalSignedEnocding(sample.Select(x => (long)x).ToArray()); + //Console.WriteLine($"{esiur.Length};{flatBuffer.Length};{protoBuffer.Length};{messagePack.Length};{bson.Length};{cbor.Length};{avro.Length};{optimal}"); + return (esiur.Length, flatBuffer.Length, protoBuffer.Length, messagePack.Length, bson.Length, cbor.Length, avro.Length, optimal); + + } + + + public static (int, int, int, int, int, int, int, int) CompareInt(short[] sample) + { + var intRoot = new ArrayRoot() { Values = sample }; + + var esiur = GroupInt16Codec.Encode(sample); + var messagePack = MessagePackSerializer.Serialize(sample); + var flatBuffer = SerializeFlatBuffers(intRoot); + + using var ms = new MemoryStream(); + Serializer.Serialize(ms, sample); + var protoBuffer = ms.ToArray(); + + var bson = intRoot.ToBson(); + + var cbor = CBORObject.FromObject(intRoot).EncodeToBytes(); + //var seq = new DerSequence(sample.Select(v => new DerInteger(v)).ToArray()); + //var ans1 = seq.GetDerEncoded(); + + var avro = AvroConvert.Serialize(sample); + + var optimal = OptimalSignedEnocding(sample.Select(x => (long)x).ToArray()); + //Console.WriteLine($"{esiur.Length};{flatBuffer.Length};{protoBuffer.Length};{messagePack.Length};{bson.Length};{cbor.Length};{avro.Length};{optimal}"); + return (esiur.Length, flatBuffer.Length, protoBuffer.Length, messagePack.Length, bson.Length, cbor.Length, avro.Length, optimal); + + } + + public static (int, int, int, int, int, int, int, int) CompareInt(uint[] sample) + { + var intRoot = new ArrayRoot() { Values = sample }; + + var esiur = GroupUInt32Codec.Encode(sample); + var messagePack = MessagePackSerializer.Serialize(sample); + var flatBuffer = SerializeFlatBuffers(intRoot); + + using var ms = new MemoryStream(); + Serializer.Serialize(ms, sample); + var protoBuffer = ms.ToArray(); + + var intRoot2 = new ArrayRoot() { Values = sample.Select(x => (int)x).ToArray() }; + + var bson = intRoot2.ToBson(); + + var cbor = CBORObject.FromObject(intRoot).EncodeToBytes(); + + + var avro = AvroConvert.Serialize(sample.Select(x => (int)x).ToArray()); + + //var seq = new DerSequence(sample.Select(v => new DerInteger(v)).ToArray()); + //var avro = seq.GetDerEncoded(); + + var optimal = OptimalUnsignedEnocding(sample.Select(x => (ulong)x).ToArray()); + //Console.WriteLine($"{esiur.Length};{flatBuffer.Length};{protoBuffer.Length};{messagePack.Length};{bson.Length};{cbor.Length};{avro.Length};{optimal}"); + + return (esiur.Length, flatBuffer.Length, protoBuffer.Length, messagePack.Length, bson.Length, cbor.Length, avro.Length, optimal); + + } + + public static (int, int, int, int, int, int, int, int) CompareInt(ulong[] sample) + { + var intRoot = new ArrayRoot() { Values = sample }; + + var esiur = GroupUInt64Codec.Encode(sample); + var messagePack = MessagePackSerializer.Serialize(sample); + var flatBuffer = SerializeFlatBuffers(intRoot); + + using var ms = new MemoryStream(); + Serializer.Serialize(ms, sample); + var protoBuffer = ms.ToArray(); + + var bson = intRoot.ToBson(); + + var cbor = CBORObject.FromObject(intRoot).EncodeToBytes(); + //var seq = new DerSequence(sample.Select(v => new DerInteger((long)v)).ToArray()); + //var ans1 = seq.GetDerEncoded(); + + var avro = AvroConvert.Serialize(sample); + + + var optimal = OptimalUnsignedEnocding(sample); + //Console.WriteLine($"{esiur.Length};{flatBuffer.Length};{protoBuffer.Length};{messagePack.Length};{bson.Length};{cbor.Length};{avro.Length};{optimal}"); + + return (esiur.Length, flatBuffer.Length, protoBuffer.Length, messagePack.Length, bson.Length, cbor.Length, avro.Length, optimal); + } + + public static (int, int, int, int, int, int, int, int) CompareInt(ushort[] sample) + { + var intRoot = new ArrayRoot() { Values = sample }; + + var esiur = GroupUInt16Codec.Encode(sample); + var messagePack = MessagePackSerializer.Serialize(sample); + var flatBuffer = SerializeFlatBuffers(intRoot); + + using var ms = new MemoryStream(); + Serializer.Serialize(ms, sample); + var protoBuffer = ms.ToArray(); + + var bson = intRoot.ToBson(); + + var cbor = CBORObject.FromObject(intRoot).EncodeToBytes(); + //var seq = new DerSequence(sample.Select(v => new DerInteger(v)).ToArray()); + //var ans1 = seq.GetDerEncoded(); + + var avro = AvroConvert.Serialize(sample); + + var optimal = OptimalUnsignedEnocding(sample.Select(x => (ulong)x).ToArray()); + //Console.WriteLine($"{esiur.Length};{flatBuffer.Length};{protoBuffer.Length};{messagePack.Length};{bson.Length};{cbor.Length};{avro.Length};{optimal}"); + return (esiur.Length, flatBuffer.Length, protoBuffer.Length, messagePack.Length, bson.Length, cbor.Length, avro.Length, optimal); + + } + + public static int OptimalSignedEnocding(long[] data) + { + var sum = 0; + + foreach (var i in data) + if (i >= sbyte.MinValue && i <= sbyte.MaxValue) + sum += 1; + else if (i >= short.MinValue && i <= short.MaxValue) + sum += 2; + else if (i >= -8_388_608 && i <= 8_388_607) + sum += 3; + else if (i >= int.MinValue && i <= int.MaxValue) + sum += 4; + else if (i >= -549_755_813_888 && i <= 549_755_813_887) + sum += 5; + else if (i >= -140_737_488_355_328 && i <= 140_737_488_355_327) + sum += 6; + else if (i >= -36_028_797_018_963_968 && i <= 36_028_797_018_963_967) + sum += 7; + else if (i >= long.MinValue && i <= long.MaxValue) + sum += 8; + + return sum; + } + + public static int OptimalUnsignedEnocding(ulong[] data) + { + var sum = 0; + + foreach (var i in data) + if (i <= byte.MaxValue) + sum += 1; + else if (i <= ushort.MaxValue) + sum += 2; + else if (i <= uint.MaxValue) + sum += 4; + else if (i <= 0xFF_FF_FF_FF_FF) + sum += 5; + else if (i <= 0xFF_FF_FF_FF_FF_FF) + sum += 6; + else if (i <= 0xFF_FF_FF_FF_FF_FF_FF) + sum += 7; + else if (i <= ulong.MaxValue) + sum += 8; + + return sum; + } + + + static (double, double, double, double, double, double, double, double) Average(Func<(int, int, int, int, int, int, int, int)> call, int count) + { + var sum = new List<(int, int, int, int, int, int, int, int)>(); + + for (var i = 0; i < count; i++) + sum.Add(call()); + + + var rt = (sum.Average(x => x.Item1), + sum.Average(x => x.Item2), + sum.Average(x => x.Item3), + sum.Average(x => x.Item4), + sum.Average(x => x.Item5), + sum.Average(x => x.Item6), + sum.Average(x => x.Item7), + sum.Average(x => x.Item8) + ); + + Console.WriteLine($"{rt.Item1};{rt.Item2};{rt.Item3};{rt.Item4};{rt.Item5};{rt.Item6};{rt.Item7};{rt.Item8}"); + + return rt; + } + + + public static byte[] SerializeFlatBuffers(ArrayRoot array) + { + var buffer = new byte[1000000000]; + var len = FlatBufferSerializer.Default.Serialize(array, buffer); + return buffer.Take(len).ToArray(); + } + + } +} diff --git a/Tests/Serialization/Model.cs b/Tests/Serialization/Model.cs new file mode 100644 index 0000000..a7f9372 --- /dev/null +++ b/Tests/Serialization/Model.cs @@ -0,0 +1,597 @@ +using System; +using System.Collections.Generic; +using ProtoBuf; +using MessagePack; +using FlatSharp.Attributes; +using Esiur.Data; +using Esiur.Resource; + +namespace Esiur.Tests.Serialization; + +#nullable enable + +// ========================= Enums ========================= +// (Optional) You can add [ProtoContract]/[ProtoEnum] if you want explicit enum numbering. +// FlatSharp works fine with standard C# enums. + +[FlatBufferEnum(typeof(int))] +public enum Currency { USD, EUR, IQD, JPY, GBP } +[FlatBufferEnum(typeof(int))] +public enum DocType { Quote, Order, Invoice, CreditNote } +[FlatBufferEnum(typeof(int))] +public enum PaymentMethod { Cash, Card, Wire, Crypto, Other } +[FlatBufferEnum(typeof(int))] +public enum LineType { Product, Service, Discount, Shipping } + +// ========================= Variant ========================= +// NOTE (FlatBuffers): a structured union in .fbs is preferable. +// Here we annotate as requested; FlatSharp will compile but you’d typically replace this with a union/table family. + +[ProtoContract] +[MessagePackObject(true)] // keyAsPropertyName = true, to avoid manual [Key] on every field here +[FlatBufferTable] +[Export] +public class Variant : IRecord +{ + [ProtoMember(1)] + [FlatBufferItem(0)] + public Kind Tag { get; set; } + + [ProtoMember(2)] + [FlatBufferItem(1)] + public bool? Bool { get; set; } + + [ProtoMember(3)] + [FlatBufferItem(2)] + public long? I64 { get; set; } + + [ProtoMember(4)] + [FlatBufferItem(3)] + public ulong? U64 { get; set; } + + [ProtoMember(5)] + [FlatBufferItem(4)] + public double? F64 { get; set; } + + //[ProtoMember(6)] + //[FlatBufferItem(5)] + //public double? Dec { get; set; } + + [ProtoMember(7)] + [FlatBufferItem(6)] + public string? Str { get; set; } + + [ProtoMember(8)] + [FlatBufferItem(7)] + public byte[]? Bytes { get; set; } + + [ProtoMember(9)] + public DateTime? Dt { get; set; } + + [FlatBufferItem(8)] + [Ignore, IgnoreMember] + public long DtAsLong { get; set; } + + [ProtoMember(10)] + [FlatBufferItem(9)] + public byte[]? Guid { get; set; } + + [FlatBufferEnum(typeof(int))] + public enum Kind { Null, Bool, Int64, UInt64, Double, Decimal, String, Bytes, DateTime, Guid } + + public override bool Equals(object? obj) + { + var other = obj as Variant; + if (other == null) return false; + + if (other.I64 != I64) return false; + if (other.U64 != U64) return false; + if (other.Bool != Bool) return false; + //if (other.Dec != Dec) return false; + if (other.Str != Str) return false; + if (Guid != null) + if (!other.Guid.SequenceEqual(Guid)) return false; + if (other.F64 != F64) return false; + if (other.Tag != Tag) return false; + if (Bytes != null) + if (!other.Bytes.SequenceEqual(Bytes)) return false; + + if (other.DtAsLong != DtAsLong) + return false; + if (other.Dt != Dt) + return false; + + return true; + } +} + +// ========================= Address ========================= + +[ProtoContract] +[MessagePackObject] +[FlatBufferTable] +[Export] +public class Address : IRecord +{ + [ProtoMember(1)] + [Key(0)] + [FlatBufferItem(0)] + public string Line1 { get; set; } = ""; + + [ProtoMember(2)] + [Key(1)] + [FlatBufferItem(1)] + public string? Line2 { get; set; } + + [ProtoMember(3)] + [Key(2)] + [FlatBufferItem(2)] + public string City { get; set; } = ""; + + [ProtoMember(4)] + [Key(3)] + [FlatBufferItem(3)] + public string Region { get; set; } = ""; + + [ProtoMember(5)] + [Key(4)] + [FlatBufferItem(4)] + public string Country { get; set; } = "IQ"; + + [ProtoMember(6)] + [Key(5)] + [FlatBufferItem(5)] + public string? PostalCode { get; set; } + + public override bool Equals(object? obj) + { + var other = obj as Address; + if (other == null) return false; + if (other.Line1 != Line1) return false; + if (other.Line2 != Line2) return false; + if (other.PostalCode != PostalCode) return false; + if (other.City != City) return false; + if (other.Country != Country) return false; + if (other.Region != Region) return false; + + return true; + } +} + +// ========================= Party ========================= + +[ProtoContract] +[MessagePackObject] +[FlatBufferTable] +[Export] +public class Party : IRecord +{ + [ProtoMember(1)] + [Key(0)] + [FlatBufferItem(0)] + public ulong Id { get; set; } + + [ProtoMember(2)] + [Key(1)] + [FlatBufferItem(1)] + public string Name { get; set; } = ""; + + [ProtoMember(3)] + [Key(2)] + [FlatBufferItem(2)] + public string? TaxId { get; set; } + + [ProtoMember(4)] + [Key(3)] + [FlatBufferItem(3)] + public string? Email { get; set; } + + [ProtoMember(5)] + [Key(4)] + [FlatBufferItem(4)] + public string? Phone { get; set; } + + [ProtoMember(6)] + [Key(5)] + [FlatBufferItem(5)] + public Address? Address { get; set; } + + // v2 field + [ProtoMember(7)] + [Key(6)] + [FlatBufferItem(6)] + public string? PreferredLanguage { get; set; } + + public override bool Equals(object? obj) + { + var other = obj as Party; + if (other == null) return false; + + if (other.Id != Id) return false; + + if (other.TaxId != TaxId) return false; + if (!other.Address.Equals(Address)) return false; + if (other.Email != Email) return false; + if (other.Name != Name) return false; + if (other.Phone != Phone) return false; + if (other.PreferredLanguage != PreferredLanguage) return false; + + return true; + } +} + +// ========================= LineItem ========================= + +[ProtoContract] +[MessagePackObject] +[FlatBufferTable] +[Export] +public class LineItem : IRecord +{ + [ProtoMember(1)] + [Key(0)] + [FlatBufferItem(0)] + public int LineNo { get; set; } + + [ProtoMember(2)] + [Key(1)] + [FlatBufferItem(1)] + public LineType Type { get; set; } + + [ProtoMember(3)] + [Key(2)] + [FlatBufferItem(2)] + public string SKU { get; set; } = ""; + + [ProtoMember(4)] + [Key(3)] + [FlatBufferItem(3)] + public string Description { get; set; } = ""; + + [ProtoMember(5)] + [Key(4)] + [FlatBufferItem(4)] + + public double Qty { get; set; } + + //[Ignore, IgnoreMember] + //public double QtyAsDouble { get; set; } + + [ProtoMember(6)] + [Key(5)] + [FlatBufferItem(5)] + public string QtyUnit { get; set; } = "pcs"; + + [ProtoMember(7)] + [Key(6)] + [FlatBufferItem(6)] + public double UnitPrice { get; set; } + + [ProtoMember(8)] + [Key(7)] + [FlatBufferItem(7)] + public double? VatRate { get; set; } + + // NOTE (FlatBuffers): Dictionary is not native. Consider mapping to a vector of {Key, Value(Variant)} entries for real FlatBuffers use. + [ProtoMember(9)] + [Key(8)] + public Dictionary? Ext { get; set; } + + // v2 field + [ProtoMember(10)] + [Key(9)] + [FlatBufferItem(8)] + public double? Discount { get; set; } + + [FlatBufferItem(9), Ignore, IgnoreMember] + public string[]? ExtKeys { get; set; } + + [FlatBufferItem(10), Ignore, IgnoreMember] + public Variant[]? ExtValues { get; set; } + + public override bool Equals(object? obj) + { + var other = obj as LineItem; + if (other == null) return false; + if (other.LineNo != LineNo) return false; + if (other.SKU != SKU) return false; + if (other.Description != Description) return false; + if (other.Discount != Discount) return false; + if (other.QtyUnit != QtyUnit) return false; + if (other.Type != Type) return false; + if (other.VatRate != VatRate) return false; + if (other.UnitPrice != UnitPrice) return false; + + if (other.ExtKeys == null) + other.ExtKeys = other.Ext.Keys.ToArray(); + + if (other.ExtValues == null) + other.ExtValues = other.Ext.Values.ToArray(); + + + if (!other.ExtKeys.SequenceEqual(ExtKeys)) return false; + if (!other.ExtValues.SequenceEqual(ExtValues)) return false; + + return true; + } +} + +// ========================= Payment ========================= + +[ProtoContract] +[MessagePackObject] +[FlatBufferTable] +[Export] +public class Payment : IRecord +{ + [ProtoMember(1)] + [Key(0)] + [FlatBufferItem(0)] + public PaymentMethod Method { get; set; } + + [ProtoMember(2)] + [Key(1)] + [FlatBufferItem(1)] + public double Amount { get; set; } + + [ProtoMember(3)] + [Key(2)] + [FlatBufferItem(2)] + public string? Reference { get; set; } + + [ProtoMember(4)] + [Key(3)] + public DateTime Timestamp { get; set; } + + [FlatBufferItem(3), Ignore, IgnoreMember] + public long TimestampAsLong { get; set; } + + // v2 fields + [ProtoMember(5)] + [Key(4)] + [FlatBufferItem(4)] + public double? Fee { get; set; } + + //[ProtoMember(6)] + //[Key(5)] + //[FlatBufferItem(5)] + //public Currency Currency { get; set; } + + public override bool Equals(object? obj) + { + var other = obj as Payment; + + if (other == null) return false; + + if (Method != other.Method) return false; + if (Amount != other.Amount) return false; + if (Reference != other.Reference) return false; + //if (Timestamp != other.Timestamp) return false; + //if (TimestampAsLong != other.TimestampAsLong) return false; + if (Fee != other.Fee) return false; + //if (CurrencyOverride != other.CurrencyOverride) return false; + + return true; + } +} + +// ========================= Attachment ========================= + +[ProtoContract] +[MessagePackObject] +[FlatBufferTable] +[Export] +public class Attachment : IRecord +{ + [ProtoMember(1)] + [Key(0)] + [FlatBufferItem(0)] + public string Name { get; set; } = ""; + + [ProtoMember(2)] + [Key(1)] + [FlatBufferItem(1)] + public string MimeType { get; set; } = "application/pdf"; + + [ProtoMember(3)] + [Key(2)] + [FlatBufferItem(2)] + public byte[] Data { get; set; } = Array.Empty(); + + public override bool Equals(object? obj) + { + var other = obj as Attachment; + if (Name != other.Name) return false; + if (MimeType != other.MimeType) return false; + if (!(Data.SequenceEqual(other.Data))) return false; + + return true; + } +} + +// ========================= DocumentHeader ========================= + +[ProtoContract] +[MessagePackObject] +[FlatBufferTable] +[Export] +public class DocumentHeader : IRecord +{ + [ProtoMember(1)] + [Key(0)] + [FlatBufferItem(0)] + public byte[] DocId { get; set; } + + [ProtoMember(2)] + [Key(1)] + [FlatBufferItem(1)] + public DocType Type { get; set; } + + [ProtoMember(3)] + [Key(2)] + [FlatBufferItem(2)] + public int Version { get; set; } + + [ProtoMember(4)] + [Key(3)] + public DateTime CreatedAt { get; set; } + + [FlatBufferItem(3), Ignore, IgnoreMember] + public long CreatedAtAsLong + { + get => CreatedAt.Ticks; + set => CreatedAt = new DateTime(value); + } + + [ProtoMember(5)] + [Key(4)] + public DateTime? UpdatedAt { get; set; } + + [FlatBufferItem(4), Ignore, IgnoreMember] + public long? UpdatedAtAsLong + { + get => UpdatedAt?.Ticks ?? 0; + set => UpdatedAt = value == null || value == 0 ? null : new DateTime(value.Value); + } + + + [ProtoMember(6)] + [Key(5)] + [FlatBufferItem(5)] + public Currency Currency { get; set; } + + [ProtoMember(7)] + [Key(6)] + [FlatBufferItem(6)] + public string? Notes { get; set; } + + [ProtoMember(8)] + [Key(7)] + public Dictionary? Meta { get; set; } + + // FlatBuffers: don't support dictionary. + [FlatBufferItem(7), Ignore, IgnoreMember] + public string[] MetaKeys { get; set; } + [FlatBufferItem(8), Ignore, IgnoreMember] + public Variant[] MetaValues { get; set; } + + public override bool Equals(object? obj) + { + var other = obj as DocumentHeader; + + if (other == null) return false; + if (!DocId.SequenceEqual(other.DocId)) return false; + if (Type != other.Type) return false; + if (Version != other.Version) return false; + //if (CreatedAtAsLong != other.CreatedAtAsLong) return false; + //if (UpdatedAtAsLong != other.UpdatedAtAsLong) return false; + if (CreatedAt != other.CreatedAt) return false; + if (UpdatedAt != other.UpdatedAt) return false; + + if (Currency != other.Currency) return false; + if (Notes != other.Notes) return false; + + if (other.MetaKeys == null) + other.MetaKeys = other.Meta.Keys.ToArray(); + + if (other.MetaValues == null) + other.MetaValues = other.Meta.Values.ToArray(); + + if (!MetaKeys.SequenceEqual(other.MetaKeys)) return false; + if (!MetaValues.SequenceEqual(other.MetaValues)) return false; + + + return true; + } +} + +// ========================= BusinessDocument (root) ========================= + +[ProtoContract] +[MessagePackObject] +[FlatBufferTable] +[Export] +public class BusinessDocument : IRecord +{ + + [ProtoMember(1)] + [Key(0)] + [FlatBufferItem(0)] + public DocumentHeader? Header { get; set; } + + [ProtoMember(2)] + [Key(1)] + [FlatBufferItem(1)] + public Party? Seller { get; set; } + + [ProtoMember(3)] + [Key(2)] + [FlatBufferItem(2)] + public Party? Buyer { get; set; } + + [ProtoMember(4)] + [Key(3)] + [FlatBufferItem(3)] + public LineItem[]? Items { get; set; } + + [ProtoMember(5)] + [Key(4)] + [FlatBufferItem(4)] + public Payment[]? Payments { get; set; } + + [ProtoMember(6)] + [Key(5)] + [FlatBufferItem(5)] + public Attachment[]? Attachments { get; set; } + + [ProtoMember(7)] + [Key(6)] + [FlatBufferItem(6)] + public int[]? RiskScores { get; set; } + + public override bool Equals(object? obj) + { + var other = obj as BusinessDocument; + if (other == null) + return false; + + + if (!Header.Equals(other.Header)) + return false; + if (!Seller.Equals(other.Seller)) + return false; + if (!Buyer.Equals(other.Buyer)) + return false; + + if (Items != null) + for (var i = 0; i < Items.Length; i++) + if (!Items[i].Equals(other.Items[i])) + return false; + + if (Payments != null) + for (var i = 0; i < Payments.Length; i++) + if (!Payments[i].Equals(other.Payments[i])) + return false; + + if (Attachments != null) + for (var i = 0; i < Attachments.Length; i++) + if (!Attachments[i].Equals(other.Attachments[i])) + return false; + + if (!RiskScores.SequenceEqual(other.RiskScores)) + return false; + + return true; + } + + + [FlatBufferTable] + public class ArrayRoot + { + // Field index must be stable; start at 0 + [FlatBufferItem(0)] + public virtual IList? Values { get; set; } + } + + +} diff --git a/Tests/Serialization/ModelGenerator.cs b/Tests/Serialization/ModelGenerator.cs new file mode 100644 index 0000000..777c6cc --- /dev/null +++ b/Tests/Serialization/ModelGenerator.cs @@ -0,0 +1,372 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +#nullable enable +namespace Esiur.Tests.Serialization; + +public static class ModelGenerator +{ + public sealed class GenOptions + { + public int Lines { get; init; } = 20; // items count + public int Attachments { get; init; } = 0; // 0..N + public int AttachmentBytes { get; init; } = 0;// per attachment + public int Payments { get; init; } = 1; // 0..N + public bool IncludeV2Fields { get; init; } = false; + public bool IncludeUnicode { get; init; } = true; // Arabic/emoji sprinkled in + public int VariantPerLine { get; init; } = 1; // ad-hoc KV per line + public Currency Currency { get; init; } = Currency.USD; + + public int RiskScores { get; set; } = 20; + public int Seed { get; init; } = 12345; + } + + public static BusinessDocument MakeBusinessDocument(GenOptions? options = null) + { + var opt = options ?? new GenOptions(); + var rng = new Random(opt.Seed); + + var seller = MakeParty(rng, opt.IncludeV2Fields, isSeller: true, opt.IncludeUnicode); + var buyer = MakeParty(rng, opt.IncludeV2Fields, isSeller: false, opt.IncludeUnicode); + + var createdAt = DateTime.UtcNow.AddMinutes(-rng.Next(0, 60 * 24)); + var doc = new BusinessDocument + { + Header = new DocumentHeader + { + DocId = Guid.NewGuid().ToByteArray(), + Type = (DocType)rng.Next(0, 4), + Version = 1, + CreatedAt = createdAt, + UpdatedAt = null, + Currency = opt.Currency, + Notes = opt.IncludeUnicode ? SampleNoteUnicode(rng) : SampleNoteAscii(rng), + Meta = new Dictionary + { + ["source"] = VStr("benchmark"), + ["region"] = VStr("ME"), + ["channel"] = VStr(rng.Next(0, 2) == 0 ? "online" : "pos"), + } + }, + Seller = seller, + Buyer = buyer, + Items = new LineItem[opt.Lines], + Payments = opt.Payments > 0 ? new Payment[opt.Payments] : null, + Attachments = opt.Attachments > 0 ? new Attachment[opt.Attachments] : null, + + RiskScores = RandomRiskScores(rng, opt.RiskScores), + //RelatedDocs_v2 = opt.IncludeV2Fields ? new List { Guid.NewGuid(), Guid.NewGuid() } : null + }; + + doc.Header.MetaValues = doc.Header.Meta.Values.ToArray(); + doc.Header.MetaKeys = doc.Header.Meta.Keys.ToArray(); + + // Items + for (int i = 0; i < opt.Lines; i++) + doc.Items[i] = MakeLineItem(rng, i + 1, opt.IncludeV2Fields, opt.VariantPerLine, opt.IncludeUnicode); + + // Payments + if (doc.Payments != null) + { + var grand = doc.Items.Sum(L => L.UnitPrice * L.Qty * (double)(1.0 - (L.Discount ?? 0.0) / 100.0)); + var remain = grand; + for (int i = 0; i < opt.Payments; i++) + { + var last = (i == opt.Payments - 1); + var amt = last ? remain : RoundMoney((double)rng.NextDouble() * remain * 0.7 + 1.0); + remain = Math.Max(0.0, remain - amt); + doc.Payments[i] = MakePayment(rng, amt, opt.IncludeV2Fields); + } + } + + // Attachments + if (doc.Attachments != null) + { + for (int i = 0; i < opt.Attachments; i++) + doc.Attachments[i] = MakeAttachment(rng, i, opt.AttachmentBytes); + } + + return doc; + } + + /// + /// Create a slightly modified copy of an existing document to test delta/partial updates. + /// Changes: tweak 5–10% of line items (qty/price), add or edit a payment, and bump UpdatedAt. + /// + public static BusinessDocument MakeDelta(BusinessDocument v1, int seed, double changeRatio = 0.07) + { + var rng = new Random(seed); + var v2 = DeepClone(v1); + + v2.Header.UpdatedAt = DateTime.UtcNow; + var toChange = Math.Max(1, (int)Math.Round(v2.Items.Length * changeRatio)); + + // change random lines + for (int i = 0; i < toChange; i++) + { + var idx = rng.Next(0, v2.Items.Length); + var li = v2.Items[idx]; + li.Qty = RoundQty(li.Qty + (double)(rng.NextDouble() * 2.0 - 1.0)); // ±1 + li.UnitPrice = RoundMoney(li.UnitPrice * (double)(0.95 + rng.NextDouble() * 0.1)); // ±5% + if (li.Ext == null) li.Ext = new Dictionary(); + li.Ext["lastEdit"] = VDate(DateTime.UtcNow); + li.ExtKeys = li.Ext.Keys.ToArray(); + li.ExtValues = li.Ext.Values.ToArray(); + } + + + if (v2.Payments == null || rng.Next(0, 3) == 0) + { + if (v2.Payments == null) + { + if (v2.Payments == null) v2.Payments = new Payment[1]; + v2.Payments[0] = (MakePayment(rng, RoundMoney((double)rng.NextDouble() * 50.0 + 10.0), includeV2: true)); + + } + else + { + v2.Payments = v2.Payments.Append((MakePayment(rng, RoundMoney((double)rng.NextDouble() * 50.0 + 10.0), includeV2: true))).ToArray(); + } + } + else + { + var p = v2.Payments[rng.Next(0, v2.Payments.Length)]; + p.Fee = (p.Fee ?? 0.0) + 0.25; + p.Reference = "ADJ-" + rng.Next(10000, 99999).ToString(CultureInfo.InvariantCulture); + } + + return v2; + } + + // -------------------------- Builders -------------------------- + + private static int[] RandomRiskScores(Random rng, int count) + { + + var rt = new int[count];// rng.Next(100, 1000)]; + for (var i = 0; i < rt.Length; i++) + rt[i] = (int)rng.Next(); + return rt; + } + + private static Party MakeParty(Random rng, bool includeV2, bool isSeller, bool unicode) + { + return new Party + { + Id = (ulong)rng.NextInt64(), //Guid.NewGuid().ToByteArray(), + Name = unicode ? (isSeller ? "Delta Systems — دلتا" : "Client التجربة ✅") : (isSeller ? "Delta Systems" : "Client Demo"), + TaxId = isSeller ? $"TX-{rng.Next(100000, 999999)}" : null, + Email = (isSeller ? "sales" : "contact") + "@example.com", + Phone = "+964-7" + rng.Next(100000000, 999999999).ToString(CultureInfo.InvariantCulture), + Address = new Address + { + Line1 = rng.Next(0, 2) == 0 ? "Street 14" : "Tech Park", + City = "Baghdad", + Region = "BG", + Country = "IQ", + PostalCode = rng.Next(0, 2) == 0 ? "10001" : null + }, + PreferredLanguage = includeV2 ? (rng.Next(0, 2) == 0 ? "ar-IQ" : "en-US") : null + }; + } + + private static LineItem MakeLineItem(Random rng, int lineNo, bool includeV2, int variantKvp, bool unicode) + { + var isProduct = rng.Next(0, 100) < 80; + var desc = unicode + ? (isProduct ? $"وحدة قياس — Item {lineNo} 🔧" : $"خدمة دعم — Service {lineNo} ✨") + : (isProduct ? $"Item {lineNo}" : $"Service {lineNo}"); + + var li = new LineItem + { + LineNo = lineNo, + Type = isProduct ? LineType.Product : LineType.Service, + SKU = isProduct ? ("SKU-" + rng.Next(1000, 9999)) : "", + Description = desc, + Qty = RoundQty((double)(rng.NextDouble() * 9.0 + 1.0)), // 1..10 + QtyUnit = isProduct ? "pcs" : "h", + UnitPrice = RoundMoney((double)(rng.NextDouble() * 90.0 + 10.0)), + VatRate = rng.Next(0, 100) < 80 ? 5.0 : (double?)null, + Ext = variantKvp > 0 ? new Dictionary() : null, + Discount= includeV2 && rng.Next(0, 3) == 0 ? Math.Round(rng.NextDouble() * 10.0, 2) : (double?)null + }; + + if (li.Ext != null) + { + li.ExtKeys = li.Ext.Keys.ToArray(); + li.ExtValues = li.Ext.Values.ToArray(); + } + + + for (int i = 0; i < variantKvp; i++) + { + var key = i switch { 0 => "color", 1 => "size", 2 => "batch", _ => "attr" + i }; + li.Ext!.TryAdd(key, i switch + { + 0 => VStr(rng.Next(0, 3) switch { 0 => "red", 1 => "blue", _ => "green" }), + 1 => VStr(rng.Next(0, 3) switch { 0 => "S", 1 => "M", _ => "L" }), + 2 => VGuid(Guid.NewGuid()), + _ => VInt(rng.Next(0, 1000)) + }); + } + + li.ExtValues = li.Ext.Values.ToArray(); + li.ExtKeys = li.Ext.Keys.ToArray(); + + return li; + } + + private static Payment MakePayment(Random rng, double amount, bool includeV2) + { + var p = new Payment + { + Method = (PaymentMethod)rng.Next(0, 5), + Amount = RoundMoney(amount), + Reference = "REF-" + rng.Next(100_000, 999_999), + Timestamp = DateTime.UtcNow.AddMinutes(-rng.Next(0, 60 * 24)), + Fee = includeV2 && rng.Next(0, 2) == 0 ? RoundMoney((double)rng.NextDouble() * 2.0) : null, + //CurrencyOverride = includeV2 && rng.Next(0, 2) == 0 ? Currency.IQD : Currency.USD + }; + + p.TimestampAsLong = p.Timestamp.Ticks; + + return p; + } + + private static Attachment MakeAttachment(Random rng, int index, int bytes) + { + var arr = bytes > 0 ? new byte[bytes] : Array.Empty(); + if (arr.Length > 0) rng.NextBytes(arr); + return new Attachment + { + Name = $"att-{index + 1}.bin", + MimeType = "application/octet-stream", + Data = arr + }; + } + + private static string SampleNoteUnicode(Random rng) + => rng.Next(0, 2) == 0 + ? "ملاحظة: تم إنشاء هذا المستند لأغراض الاختبار 📦" + : "Note: synthetic benchmark document 🧪"; + + private static string SampleNoteAscii(Random rng) + => rng.Next(0, 2) == 0 ? "Note: synthetic benchmark document" : "Internal use only"; + + // -------------------------- Variant helpers -------------------------- + private static Variant VStr(string s) => new() { Tag = Variant.Kind.String, Str = s }; + private static Variant VInt(int v) => new() { Tag = Variant.Kind.Int64, I64 = v }; + private static Variant VGuid(Guid g) => new() { Tag = Variant.Kind.Guid, Guid = g.ToByteArray() }; + private static Variant VDate(DateTime d) => new() { Tag = Variant.Kind.DateTime, Dt = d, DtAsLong = d.Ticks }; + + // -------------------------- Utils -------------------------- + private static double RoundMoney(double v) => Math.Round(v, 2, MidpointRounding.AwayFromZero); + private static double RoundQty(double v) => Math.Round(v, 3, MidpointRounding.AwayFromZero); + + /// + /// Simple deep clone via manual copy to stay serializer-agnostic. + /// (Good enough for benchmarks; switch to a fast serializer if you like.) + /// + private static BusinessDocument DeepClone(BusinessDocument s) + { + var d = new BusinessDocument + { + Header = new DocumentHeader + { + DocId = s.Header.DocId, + Type = s.Header.Type, + Version = s.Header.Version, + CreatedAt = s.Header.CreatedAt, + UpdatedAt = s.Header.UpdatedAt, + //CreatedAtAsLong = s.Header.CreatedAtAsLong, + //UpdatedAtAsLong = s.Header.UpdatedAtAsLong, + Currency = s.Header.Currency, + Notes = s.Header.Notes, + Meta = s.Header.Meta?.ToDictionary(kv => kv.Key, kv => CloneVariant(kv.Value)), + MetaKeys = s.Header.MetaKeys.ToArray(), + MetaValues = s.Header.MetaValues.ToArray(), + }, + Seller = CloneParty(s.Seller), + Buyer = CloneParty(s.Buyer), + Items = s.Items.Select(CloneLine).ToArray(), + Payments = s.Payments?.Select(ClonePayment).ToArray(), + Attachments = s.Attachments?.Select(CloneAttachment).ToArray(), + RiskScores = s.RiskScores, + //RelatedDocs_v2 = s.RelatedDocs_v2?.ToList() + }; + + + return d; + } + + private static Party CloneParty(Party p) => new() + { + Id = p.Id, + Name = p.Name, + TaxId = p.TaxId, + Email = p.Email, + Phone = p.Phone, + Address = p.Address is null ? null : new Address + { + Line1 = p.Address.Line1, + Line2 = p.Address.Line2, + City = p.Address.City, + Region = p.Address.Region, + Country = p.Address.Country, + PostalCode = p.Address.PostalCode + }, + PreferredLanguage = p.PreferredLanguage + }; + + private static LineItem CloneLine(LineItem s) => new() + { + LineNo = s.LineNo, + Type = s.Type, + SKU = s.SKU, + Description = s.Description, + Qty = s.Qty, + QtyUnit = s.QtyUnit, + UnitPrice = s.UnitPrice, + VatRate = s.VatRate, + Ext = s.Ext?.ToDictionary(kv => kv.Key, kv => CloneVariant(kv.Value)), + Discount = s.Discount, + ExtKeys = s.Ext?.Keys.ToArray(), + ExtValues = s.Ext?.Values.ToArray(), + }; + + private static Payment ClonePayment(Payment s) => new() + { + Method = s.Method, + Amount = s.Amount, + Reference = s.Reference, + Timestamp = s.Timestamp, + TimestampAsLong = s.TimestampAsLong, + Fee = s.Fee, + //CurrencyOverride= s.CurrencyOverride + }; + + private static Attachment CloneAttachment(Attachment s) => new() + { + Name = s.Name, + MimeType = s.MimeType, + Data = s.Data.ToArray() + }; + + private static Variant CloneVariant(Variant v) => new() + { + Tag = v.Tag, + Bool = v.Bool, + I64 = v.I64, + U64 = v.U64, + F64 = v.F64, + //Dec = v.Dec, + Str = v.Str, + Bytes = v.Bytes?.ToArray(), + Dt = v.Dt, + DtAsLong = v.DtAsLong, + Guid = v.Guid + }; +} diff --git a/Tests/Serialization/ModelRunner.cs b/Tests/Serialization/ModelRunner.cs new file mode 100644 index 0000000..4883d66 --- /dev/null +++ b/Tests/Serialization/ModelRunner.cs @@ -0,0 +1,484 @@ +using Avro.Generic; +using Esiur.Resource; +using FlatSharp; +using MessagePack; +using MongoDB.Bson; +using MongoDB.Bson.Serialization; +using PeterO.Cbor; +using ProtoBuf; +using SolTechnology.Avro; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +#nullable enable + +namespace Esiur.Tests.Serialization; + +public interface ICodec +{ + string Name { get; } + byte[]? Serialize(BusinessDocument obj); // returns null on failure + BusinessDocument Deserialize(byte[] data); +} + +public sealed class JsonCodec : ICodec +{ + static readonly JsonSerializerOptions Opt = new() + { + WriteIndented = false, + DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.Never + }; + public string Name => "JSON"; + public byte[]? Serialize(BusinessDocument obj) + { + var data = JsonSerializer.SerializeToUtf8Bytes(obj, obj.GetType(), Opt); + return data; + } + + public BusinessDocument Deserialize(byte[] data) + { + return JsonSerializer.Deserialize(data)!; + } +} + +public sealed class EsiurCodec : ICodec +{ + public string Name => "Esiur"; + + public BusinessDocument Deserialize(byte[] data) + { + var (_, y) = Esiur.Data.Codec.ParseSync(data, 0, Warehouse.Default); + return (BusinessDocument)y!; + } + + public byte[]? Serialize(BusinessDocument obj) + { + var rt = Esiur.Data.Codec.Compose(obj, Warehouse.Default, null); + return rt; + } +} + +public sealed class MessagePackCodec : ICodec +{ + public string Name => "MessagePack"; + + public BusinessDocument Deserialize(byte[] data) + { + return MessagePackSerializer.Deserialize(data); + } + + public byte[]? Serialize(BusinessDocument obj) + { + return MessagePackSerializer.Serialize(obj.GetType(), obj); + } +} + +public sealed class ProtobufCodec : ICodec +{ + public string Name => "Protobuf"; + + public BusinessDocument Deserialize(byte[] data) + { + var dst = Serializer.Deserialize(new MemoryStream(data)); + return dst; + } + + public byte[]? Serialize(BusinessDocument obj) + { + using var ms = new MemoryStream(); + Serializer.Serialize(ms, obj); + var rt = ms.ToArray(); + + // Single correctness check (outside timing loops) + var dst = Serializer.Deserialize(new MemoryStream(rt)); + if (!obj.Equals(dst)) + throw new NotSupportedException("Protobuf roundtrip mismatch."); + + return rt; + } +} + +public sealed class FlatBuffersCodec : ICodec +{ + public string Name => "FlatBuffers"; + + public BusinessDocument Deserialize(byte[] data) + { + var m = FlatBufferSerializer.Default.Parse(data); + return m; + } + + public byte[]? Serialize(BusinessDocument obj) + { + var buffer = new byte[1_000_000]; + var count = FlatBufferSerializer.Default.Serialize(obj, buffer); + var msg = buffer.AsSpan(0, count).ToArray(); + + // Single correctness check (outside timing loops) + var m = FlatBufferSerializer.Default.Parse(msg); + if (!m!.Equals(obj)) + throw new Exception("FlatBuffers roundtrip mismatch."); + + return msg; + } +} + +public sealed class CborCodec : ICodec +{ + public string Name => "CBOR"; + + public BusinessDocument Deserialize(byte[] data) + { + return CBORObject.DecodeObjectFromBytes(data)!; + } + + public byte[]? Serialize(BusinessDocument obj) + { + var c = CBORObject.FromObject(obj); + return c.EncodeToBytes(); + } +} + +public sealed class BsonCodec : ICodec +{ + private static bool _init; + private static void EnsureMaps() + { + if (_init) return; + _init = true; + // Register class maps if needed; defaults usually work for POCOs. + } + + public string Name => "BSON"; + public byte[]? Serialize(BusinessDocument obj) + { + try + { + EnsureMaps(); + using var ms = new MemoryStream(); + using var writer = new MongoDB.Bson.IO.BsonBinaryWriter(ms); + var context = MongoDB.Bson.Serialization.BsonSerializationContext.CreateRoot(writer); + var args = new MongoDB.Bson.Serialization.BsonSerializationArgs(obj.GetType(), true, false); + var serializer = BsonSerializer.LookupSerializer(obj.GetType()); + serializer.Serialize(context, args, obj); + return ms.ToArray(); + } + catch { return null; } + } + + public BusinessDocument Deserialize(byte[] data) + { + using var ms = new MemoryStream(data); + using var reader = new MongoDB.Bson.IO.BsonBinaryReader(ms); + var args = new MongoDB.Bson.Serialization.BsonSerializationArgs(typeof(BusinessDocument), true, false); + var context = MongoDB.Bson.Serialization.BsonDeserializationContext.CreateRoot(reader); + var serializer = BsonSerializer.LookupSerializer(typeof(BusinessDocument)); + return (BusinessDocument)serializer.Deserialize(context)!; + } +} + +public sealed class AvroCodec : ICodec +{ + public string Name => "Avro"; + + public BusinessDocument Deserialize(byte[] data) + { + return AvroConvert.Deserialize(data); + } + + public byte[]? Serialize(BusinessDocument obj) + { + return AvroConvert.Serialize(obj); + } +} + +// ------------------------- Stat helpers ------------------------- + +public static class Stats +{ + public static double Mean(IReadOnlyList xs) => + xs.Count == 0 ? double.NaN : xs.Average(); + + public static double Median(IReadOnlyList xs) + { + if (xs.Count == 0) return double.NaN; + var arr = xs.OrderBy(v => v).ToArray(); + int n = arr.Length; + return n % 2 == 1 ? arr[n / 2] : (arr[n / 2 - 1] + arr[n / 2]) / 2.0; + } + + public static string ClassifyVsJson(double ratio) + { + if (double.IsNaN(ratio)) return "N/A"; + if (ratio <= 0.75) return "Smaller (≤0.75× JSON)"; + if (ratio <= 1.25) return "Similar (~0.75–1.25×)"; + return "Larger (≥1.25× JSON)"; + } +} + +// ------------------------- Workload config ------------------------- + +public enum Workload +{ + Small, // ~5 lines, no attachments + Medium, // ~20 lines, 1 small attachment + Large, // ~100 lines, 3 x 64KB attachments +} + +public sealed class WorkItem +{ + public required string Name { get; init; } + public required BusinessDocument Payload { get; init; } +} + +// ------------------------- CPU timing helpers ------------------------- + +public static class CpuTimer +{ + // small warm-up to reduce JIT/first-use bias + public static void WarmUp(Action action, int rounds = 5) + { + for (int i = 0; i < rounds; i++) action(); + } + + // Measures process CPU time consumed by running `action` N times. + // Returns average microseconds per operation. + public static double MeasureAverageMicros(Action action, int rounds) + { + var proc = Process.GetCurrentProcess(); + + // GC before timing to reduce random interference (still CPU, but more stable) + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + var start = proc.TotalProcessorTime; + for (int i = 0; i < rounds; i++) action(); + var end = proc.TotalProcessorTime; + + var delta = end - start; + var micros = delta.TotalMilliseconds * 1000.0; + return micros / rounds; + } +} + +// ------------------------- Runner ------------------------- + +public sealed class ModelRunner +{ + private readonly ICodec[] _codecs; + + public ModelRunner() + { + _codecs = new ICodec[] + { + new JsonCodec(), + new EsiurCodec(), + new MessagePackCodec(), + new ProtobufCodec(), + new FlatBuffersCodec(), + new CborCodec(), + new BsonCodec(), + new AvroCodec() + }; + } + + record WorkloadResult + { + public string Name { get; init; } = ""; + public List Sizes { get; init; } = new(); + public double EncodeCpuUsSum { get; set; } // sum of per-item avg CPU us/op + public double DecodeCpuUsSum { get; set; } + public int Samples { get; set; } // count of successful samples + } + + // volatile sink to avoid aggressive JIT elimination in tight loops + private static volatile byte[]? _sinkBytes; + private static volatile BusinessDocument? _sinkDoc; + + public void Run() + { + const int Rounds = 100; + + var workloads = BuildWorkloads(); + + Console.WriteLine("=== Serialization Size & CPU (process) Benchmark ==="); + Console.WriteLine($"Date (UTC): {DateTime.UtcNow:O}"); + Console.WriteLine($"Rounds per op (CPU): {Rounds}"); + Console.WriteLine(); + + foreach (var (wName, items) in workloads) + { + Console.WriteLine($"--- Workload: {wName} ---"); + + // Collect results: Codec -> result container + var results = new Dictionary(); + foreach (var c in _codecs) results[c.Name] = new WorkloadResult { Name = c.Name }; + + foreach (var item in items) + { + foreach (var c in _codecs) + { + try + { + // Single functional serialize to get bytes & verify equality ONCE (not in timed loop) + var bytes = c.Serialize(item.Payload); + if (bytes == null) + { + results[c.Name].Sizes.Add(long.MinValue); + continue; + } + + var back = c.Deserialize(bytes); + if (!item.Payload.Equals(back)) + throw new InvalidOperationException($"{c.Name} roundtrip inequality."); + + results[c.Name].Sizes.Add(bytes.LongLength); + + // ---- CPU timing ---- + + // Warm-up (tiny) + CpuTimer.WarmUp(() => { _sinkBytes = c.Serialize(item.Payload); }, 3); + CpuTimer.WarmUp(() => { _sinkDoc = c.Deserialize(bytes); }, 3); + + // Measure serialize CPU (average µs/op over Rounds) + var encUs = CpuTimer.MeasureAverageMicros(() => + { + _sinkBytes = c.Serialize(item.Payload); + }, Rounds); + + // Measure deserialize CPU + var decUs = CpuTimer.MeasureAverageMicros(() => + { + _sinkDoc = c.Deserialize(bytes); + }, Rounds); + + results[c.Name].EncodeCpuUsSum += encUs; + results[c.Name].DecodeCpuUsSum += decUs; + results[c.Name].Samples += 1; + } + catch + { + // mark size failure for this sample if not already added + results[c.Name].Sizes.Add(long.MinValue); + } + } + } + + // Compute stats, using only successful size samples + var jsonSizes = results["JSON"].Sizes.Where(v => v != long.MinValue).ToList(); + var jsonMean = Stats.Mean(jsonSizes); + var jsonMed = Stats.Median(jsonSizes); + + Console.WriteLine($"JSON mean: {jsonMean:F1} B, median: {jsonMed:F1} B"); + Console.WriteLine(); + + Console.WriteLine("{0,-14} {1,12} {2,12} {3,10} {4,26} {5,18} {6,18}", + "Codec", "Mean(B)", "Median(B)", "Ratio", "Class vs JSON", "Enc CPU (µs)", "Dec CPU (µs)"); + Console.WriteLine(new string('-', 118)); + + foreach (var c in _codecs) + { + var r = results[c.Name]; + var okSizes = r.Sizes.Where(v => v != long.MinValue).ToList(); + var mean = Stats.Mean(okSizes); + var med = Stats.Median(okSizes); + + double ratio = double.NaN; + if (!double.IsNaN(mean) && !double.IsNaN(jsonMean) && jsonMean > 0) ratio = mean / jsonMean; + + string cls = Stats.ClassifyVsJson(ratio); + string meanS = double.IsNaN(mean) ? "N/A" : mean.ToString("F1"); + string medS = double.IsNaN(med) ? "N/A" : med.ToString("F1"); + string ratioS = double.IsNaN(ratio) ? "N/A" : ratio.ToString("F3"); + + // average CPU µs/op across samples where serialization succeeded + string encCpuS = (r.Samples == 0) ? "N/A" : (r.EncodeCpuUsSum / r.Samples).ToString("F1"); + string decCpuS = (r.Samples == 0) ? "N/A" : (r.DecodeCpuUsSum / r.Samples).ToString("F1"); + + Console.WriteLine("{0,-14} {1,12} {2,12} {3,10} {4,26} {5,18} {6,18}", + c.Name, meanS, medS, ratioS, cls, encCpuS, decCpuS); + } + + Console.WriteLine(); + + Console.ReadLine(); + } + } + + private static List<(string, List)> BuildWorkloads() + { + var result = new List<(string, List)>(); + + // Small + { + var items = new List(); + for (int i = 0; i < 16; i++) + { + var doc = ModelGenerator.MakeBusinessDocument(new ModelGenerator.GenOptions + { + Lines = 5, + Payments = 3, + Attachments = 0, + IncludeV2Fields = (i % 2 == 0), + IncludeUnicode = true, + RiskScores = 500, + Seed = 1000 + i + }); + + items.Add(new WorkItem { Name = $"S-{i}", Payload = doc }); + } + result.Add(("Small", items)); + } + + // Medium + { + var items = new List(); + for (int i = 0; i < 16; i++) + { + var doc = ModelGenerator.MakeBusinessDocument(new ModelGenerator.GenOptions + { + Lines = 20, + Payments = 5, + Attachments = 1, + AttachmentBytes = 8 * 1024, + IncludeV2Fields = (i % 3 == 0), + IncludeUnicode = true, + RiskScores = 1000, + Seed = 2000 + i + }); + items.Add(new WorkItem { Name = $"M-{i}", Payload = doc }); + } + result.Add(("Medium", items)); + } + + // Large + { + var items = new List(); + for (int i = 0; i < 12; i++) + { + var doc = ModelGenerator.MakeBusinessDocument(new ModelGenerator.GenOptions + { + Lines = 100, + Payments = 20, + Attachments = 3, + AttachmentBytes = 64 * 1024, + IncludeV2Fields = (i % 2 == 1), + IncludeUnicode = true, + RiskScores = 3000, + Seed = 3000 + i + }); + items.Add(new WorkItem { Name = $"L-{i}", Payload = doc }); + } + result.Add(("Large", items)); + } + + return result; + } +} diff --git a/Tests/Serialization/Program.cs b/Tests/Serialization/Program.cs new file mode 100644 index 0000000..9daca1e --- /dev/null +++ b/Tests/Serialization/Program.cs @@ -0,0 +1,15 @@ + +using Esiur.Tests.Serialization; +using MessagePack; + +MessagePack.MessagePackSerializer.DefaultOptions = MessagePackSerializerOptions.Standard + .WithCompression(MessagePackCompression.None); // optional; remove if you want raw size + + +var ints = new IntArrayRunner(); +ints.Run(); + +var models = new ModelRunner(); +models.Run(); + +