mirror of
https://github.com/esiur/esiur-dotnet.git
synced 2026-03-31 10:28:21 +00:00
comparision test brought here
This commit is contained in:
10
Esiur.AI.Annotations/Esiur.AI.Annotations.csproj
Normal file
10
Esiur.AI.Annotations/Esiur.AI.Annotations.csproj
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
1
Esiur.AI.Annotations/Program.cs
Normal file
1
Esiur.AI.Annotations/Program.cs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Console.WriteLine("Hello, World!");
|
||||||
@@ -47,7 +47,7 @@ if (args.Length > 0)
|
|||||||
{
|
{
|
||||||
try
|
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}");
|
Console.WriteLine($"Generated successfully: {path}");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
18
Esiur.sln
18
Esiur.sln
@@ -1,6 +1,6 @@
|
|||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 18
|
||||||
VisualStudioVersion = 17.0.31919.166
|
VisualStudioVersion = 18.4.11612.150
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Esiur", "Esiur\Esiur.csproj", "{4F74A8C1-D38F-4CC0-ACD1-24459BA0EAFC}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Esiur", "Esiur\Esiur.csproj", "{4F74A8C1-D38F-4CC0-ACD1-24459BA0EAFC}"
|
||||||
EndProject
|
EndProject
|
||||||
@@ -8,7 +8,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Esiur.Stores.MongoDB", "Esi
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Esiur.Stores.EntityCore", "Esiur.Stores.EntityCore\Esiur.Stores.EntityCore.csproj", "{53DE5A30-CFA9-4DE7-A840-77CFF519D31B}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Esiur.Stores.EntityCore", "Esiur.Stores.EntityCore\Esiur.Stores.EntityCore.csproj", "{53DE5A30-CFA9-4DE7-A840-77CFF519D31B}"
|
||||||
EndProject
|
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
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Esiur.Security.Cryptography", "Esiur.Security.Cryptography\Esiur.Security.Cryptography.csproj", "{C0C55C1A-7C48-41EB-9A65-27BC99D82F6D}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Esiur.Security.Cryptography", "Esiur.Security.Cryptography\Esiur.Security.Cryptography.csproj", "{C0C55C1A-7C48-41EB-9A65-27BC99D82F6D}"
|
||||||
EndProject
|
EndProject
|
||||||
@@ -20,6 +20,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Esiur.AspNetCore.Example",
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Esiur.AspNetCore", "Esiur.AspNetCore\Esiur.AspNetCore.csproj", "{7B0C521F-8B13-4F2A-BD78-7C692620C831}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Esiur.AspNetCore", "Esiur.AspNetCore\Esiur.AspNetCore.csproj", "{7B0C521F-8B13-4F2A-BD78-7C692620C831}"
|
||||||
EndProject
|
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
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{7B0C521F-8B13-4F2A-BD78-7C692620C831}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
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
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {C584421D-5EC0-4821-B7D8-2633D8D405F2}
|
SolutionGuid = {C584421D-5EC0-4821-B7D8-2633D8D405F2}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
|||||||
@@ -406,9 +406,9 @@ public static class TypeDefGenerator
|
|||||||
rt.AppendLine($"[Export] public {ptTypeName} {p.Name} {{");
|
rt.AppendLine($"[Export] public {ptTypeName} {p.Name} {{");
|
||||||
rt.AppendLine($"get => ({ptTypeName})properties[{p.Index}];");
|
rt.AppendLine($"get => ({ptTypeName})properties[{p.Index}];");
|
||||||
if (asyncSetters)
|
if (asyncSetters)
|
||||||
rt.AppendLine($"set => _Set({p.Index}, value);");
|
rt.AppendLine($"set => SetResourcePropertyAsync({p.Index}, value);");
|
||||||
else
|
else
|
||||||
rt.AppendLine($"set => _SetSync({p.Index}, value);");
|
rt.AppendLine($"set => SetResourceProperty({p.Index}, value);");
|
||||||
rt.AppendLine("}");
|
rt.AppendLine("}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
357
Test/Program.cs
357
Test/Program.cs
@@ -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<string, byte?>
|
|
||||||
//{
|
|
||||||
// ["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<object>
|
|
||||||
static AsyncReply<object> Authenticator(AuthorizationRequest x)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Authenticator: {x.Clue}");
|
|
||||||
|
|
||||||
var format = x.RequiredFormat;
|
|
||||||
|
|
||||||
if (format == EpAuthPacketIAuthFormat.Number)
|
|
||||||
return new AsyncReply<object>(Convert.ToInt32(10));
|
|
||||||
else if (format == EpAuthPacketIAuthFormat.Text)
|
|
||||||
return new AsyncReply<object>(Console.ReadLine().Trim());
|
|
||||||
|
|
||||||
throw new NotImplementedException("Not supported format.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async void TestClient(IResource local)
|
|
||||||
{
|
|
||||||
|
|
||||||
var con = await new Warehouse().Get<EpConnection>("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();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Esiur.Security.Cryptography\Esiur.Security.Cryptography.csproj" />
|
|
||||||
<ProjectReference Include="..\Esiur\Esiur.csproj" OutputItemType="Analyzer" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
13
Tests/Distribution/Esiur.Tests.Distribution.csproj
Normal file
13
Tests/Distribution/Esiur.Tests.Distribution.csproj
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Esiur.Security.Cryptography\Esiur.Security.Cryptography.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Esiur\Esiur.csproj" OutputItemType="Analyzer" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -5,9 +5,8 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Test
|
namespace Esiur.Tests.Distribution;
|
||||||
{
|
|
||||||
public interface IMyRecord:IRecord
|
public interface IMyRecord:IRecord
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
|
||||||
@@ -5,12 +5,11 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Test
|
namespace Esiur.Tests.Distribution;
|
||||||
{
|
|
||||||
[Export]
|
[Export]
|
||||||
public class MyChildRecord : MyRecord
|
public class MyChildRecord : MyRecord
|
||||||
{
|
{
|
||||||
public string ChildName { get; set; }
|
public string ChildName { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
@@ -5,8 +5,8 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Test
|
namespace Esiur.Tests.Distribution;
|
||||||
{
|
|
||||||
[Resource]
|
[Resource]
|
||||||
public partial class MyChildResource : MyResource
|
public partial class MyChildResource : MyResource
|
||||||
{
|
{
|
||||||
@@ -17,4 +17,3 @@ namespace Test
|
|||||||
[Export] public string HelloChild() => "Hi from Child";
|
[Export] public string HelloChild() => "Hi from Child";
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
@@ -6,8 +6,8 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Test
|
namespace Esiur.Tests.Distribution;
|
||||||
{
|
|
||||||
public class MyGenericRecord<T> : IRecord where T : IResource
|
public class MyGenericRecord<T> : IRecord where T : IResource
|
||||||
{
|
{
|
||||||
[Export] public int Start { get; set; }
|
[Export] public int Start { get; set; }
|
||||||
@@ -16,4 +16,3 @@ namespace Test
|
|||||||
[Export] public T[] Results { get; set; }
|
[Export] public T[] Results { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
@@ -6,8 +6,8 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Test
|
namespace Esiur.Tests.Distribution;
|
||||||
{
|
|
||||||
[Export]
|
[Export]
|
||||||
public class MyRecord:IRecord
|
public class MyRecord:IRecord
|
||||||
{
|
{
|
||||||
@@ -17,4 +17,3 @@ namespace Test
|
|||||||
public double Score { get; set; }
|
public double Score { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
31
Tests/Distribution/MyResource.cs
Normal file
31
Tests/Distribution/MyResource.cs
Normal file
@@ -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)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ using Esiur.Protocol;
|
|||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
namespace Test;
|
namespace Esiur.Tests.Distribution;
|
||||||
|
|
||||||
|
|
||||||
public enum SizeEnum:short
|
public enum SizeEnum:short
|
||||||
348
Tests/Distribution/Program.cs
Normal file
348
Tests/Distribution/Program.cs
Normal file
@@ -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<string, byte?>
|
||||||
|
//{
|
||||||
|
// ["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<object>
|
||||||
|
static AsyncReply<object> Authenticator(AuthorizationRequest x)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Authenticator: {x.Clue}");
|
||||||
|
|
||||||
|
var format = x.RequiredFormat;
|
||||||
|
|
||||||
|
if (format == EpAuthPacketIAuthFormat.Number)
|
||||||
|
return new AsyncReply<object>(Convert.ToInt32(10));
|
||||||
|
else if (format == EpAuthPacketIAuthFormat.Text)
|
||||||
|
return new AsyncReply<object>(Console.ReadLine().Trim());
|
||||||
|
|
||||||
|
throw new NotImplementedException("Not supported format.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async void TestClient(IResource local)
|
||||||
|
{
|
||||||
|
|
||||||
|
var con = await new Warehouse().Get<EpConnection>("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();
|
||||||
|
}
|
||||||
|
}
|
||||||
25
Tests/Serialization/Esiur.Tests.Serialization.csproj
Normal file
25
Tests/Serialization/Esiur.Tests.Serialization.csproj
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Apache.Avro" Version="1.12.1" />
|
||||||
|
<PackageReference Include="AvroConvert" Version="3.4.16" />
|
||||||
|
<PackageReference Include="FlatSharp" Version="6.3.5" />
|
||||||
|
<PackageReference Include="Google.Protobuf" Version="3.34.0" />
|
||||||
|
<PackageReference Include="MessagePack" Version="3.1.4" />
|
||||||
|
<PackageReference Include="MongoDB.Bson" Version="3.7.1" />
|
||||||
|
<PackageReference Include="PeterO.Cbor" Version="4.5.5" />
|
||||||
|
<PackageReference Include="protobuf-net" Version="3.2.56" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Esiur\Esiur.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
353
Tests/Serialization/IntArrayGenerator.cs
Normal file
353
Tests/Serialization/IntArrayGenerator.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
335
Tests/Serialization/IntArrayRunner.cs
Normal file
335
Tests/Serialization/IntArrayRunner.cs
Normal file
@@ -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<T>
|
||||||
|
{
|
||||||
|
// Field index must be stable; start at 0
|
||||||
|
[FlatBufferItem(0)]
|
||||||
|
public virtual IList<T>? 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<long>() { 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<int>() { 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<short>() { 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<uint>() { 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<int>() { 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<ulong>() { 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<ushort>() { 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<T>(ArrayRoot<T> array)
|
||||||
|
{
|
||||||
|
var buffer = new byte[1000000000];
|
||||||
|
var len = FlatBufferSerializer.Default.Serialize(array, buffer);
|
||||||
|
return buffer.Take(len).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
597
Tests/Serialization/Model.cs
Normal file
597
Tests/Serialization/Model.cs
Normal file
@@ -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<string, Variant>? 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<byte>();
|
||||||
|
|
||||||
|
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<string, Variant>? 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<T>
|
||||||
|
{
|
||||||
|
// Field index must be stable; start at 0
|
||||||
|
[FlatBufferItem(0)]
|
||||||
|
public virtual IList<T>? Values { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
372
Tests/Serialization/ModelGenerator.cs
Normal file
372
Tests/Serialization/ModelGenerator.cs
Normal file
@@ -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<string, Variant>
|
||||||
|
{
|
||||||
|
["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> { 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
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<string, Variant>();
|
||||||
|
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<string, Variant>() : 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<byte>();
|
||||||
|
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);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Simple deep clone via manual copy to stay serializer-agnostic.
|
||||||
|
/// (Good enough for benchmarks; switch to a fast serializer if you like.)
|
||||||
|
/// </summary>
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
484
Tests/Serialization/ModelRunner.cs
Normal file
484
Tests/Serialization/ModelRunner.cs
Normal file
@@ -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<BusinessDocument>(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<BusinessDocument>(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<BusinessDocument>(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<BusinessDocument>(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<BusinessDocument>(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<BusinessDocument>(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<BusinessDocument>(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<BusinessDocument>(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[]? Serialize(BusinessDocument obj)
|
||||||
|
{
|
||||||
|
return AvroConvert.Serialize(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------- Stat helpers -------------------------
|
||||||
|
|
||||||
|
public static class Stats
|
||||||
|
{
|
||||||
|
public static double Mean(IReadOnlyList<long> xs) =>
|
||||||
|
xs.Count == 0 ? double.NaN : xs.Average();
|
||||||
|
|
||||||
|
public static double Median(IReadOnlyList<long> 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<long> 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<string, WorkloadResult>();
|
||||||
|
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<WorkItem>)> BuildWorkloads()
|
||||||
|
{
|
||||||
|
var result = new List<(string, List<WorkItem>)>();
|
||||||
|
|
||||||
|
// Small
|
||||||
|
{
|
||||||
|
var items = new List<WorkItem>();
|
||||||
|
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<WorkItem>();
|
||||||
|
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<WorkItem>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
15
Tests/Serialization/Program.cs
Normal file
15
Tests/Serialization/Program.cs
Normal file
@@ -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();
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user