Skip to content

Commit c154a46

Browse files
authored
Merge pull request #899 from Cysharp/feature/NativeAotTests
Add unit tests for Native AOT
2 parents 5aad6db + efb1e26 commit c154a46

File tree

9 files changed

+443
-1
lines changed

9 files changed

+443
-1
lines changed

.github/workflows/build-debug.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,16 @@ jobs:
4444
- run: dotnet retest -- -c Debug MagicOnion.sln
4545
- run: dotnet retest -- -c Release MagicOnion.sln
4646

47+
run-client-nativeaot-tests:
48+
name: "Run Client Native AOT tests"
49+
runs-on: ubuntu-latest
50+
timeout-minutes: 10
51+
steps:
52+
- uses: actions/checkout@v4
53+
- uses: Cysharp/Actions/.github/actions/setup-dotnet@main
54+
- run: dotnet publish -r linux-x64 tests/MagicOnion.Client.NativeAot.Tests/MagicOnion.Client.NativeAot.Tests.csproj
55+
- run: tests/MagicOnion.Client.NativeAot.Tests/bin/Release/net9.0/linux-x64/publish/MagicOnion.Client.NativeAot.Tests
56+
4757
build-unity:
4858
name: "Build Unity package"
4959
if: ${{ (github.event_name == 'push' && github.repository_owner == 'Cysharp') || startsWith(github.event.pull_request.head.label, 'Cysharp:') }}
@@ -111,6 +121,6 @@ jobs:
111121
# retention-days: 1
112122

113123
actions-timeline:
114-
needs: [build-dotnet, run-tests, build-unity]
124+
needs: [build-dotnet, run-tests, run-client-nativeaot-tests, build-unity]
115125
uses: Cysharp/Actions/.github/workflows/actions-timeline.yaml@main
116126
secrets: inherit

MagicOnion.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonTranscodingSample.Serve
130130
EndProject
131131
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonTranscodingSample.Shared", "samples\JsonTranscoding\JsonTranscodingSample.Shared\JsonTranscodingSample.Shared.csproj", "{50871ADE-4513-4AC1-8964-740AB6505B31}"
132132
EndProject
133+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MagicOnion.Client.NativeAot.Tests", "tests\MagicOnion.Client.NativeAot.Tests\MagicOnion.Client.NativeAot.Tests.csproj", "{9A70D36D-ACE2-4E6D-89D2-9AC0DEDE3A85}"
134+
EndProject
133135
Global
134136
GlobalSection(SolutionConfigurationPlatforms) = preSolution
135137
Debug|Any CPU = Debug|Any CPU
@@ -292,6 +294,10 @@ Global
292294
{50871ADE-4513-4AC1-8964-740AB6505B31}.Debug|Any CPU.Build.0 = Debug|Any CPU
293295
{50871ADE-4513-4AC1-8964-740AB6505B31}.Release|Any CPU.ActiveCfg = Release|Any CPU
294296
{50871ADE-4513-4AC1-8964-740AB6505B31}.Release|Any CPU.Build.0 = Release|Any CPU
297+
{9A70D36D-ACE2-4E6D-89D2-9AC0DEDE3A85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
298+
{9A70D36D-ACE2-4E6D-89D2-9AC0DEDE3A85}.Debug|Any CPU.Build.0 = Debug|Any CPU
299+
{9A70D36D-ACE2-4E6D-89D2-9AC0DEDE3A85}.Release|Any CPU.ActiveCfg = Release|Any CPU
300+
{9A70D36D-ACE2-4E6D-89D2-9AC0DEDE3A85}.Release|Any CPU.Build.0 = Release|Any CPU
295301
EndGlobalSection
296302
GlobalSection(SolutionProperties) = preSolution
297303
HideSolutionNode = FALSE
@@ -344,6 +350,7 @@ Global
344350
{57E6F117-1138-4899-81A2-CC87B20525E4} = {5A3F5158-7B17-4586-9885-9E60C1393185}
345351
{24A21CDA-C3A5-49F2-A1B8-1A93E2E64335} = {57E6F117-1138-4899-81A2-CC87B20525E4}
346352
{50871ADE-4513-4AC1-8964-740AB6505B31} = {57E6F117-1138-4899-81A2-CC87B20525E4}
353+
{9A70D36D-ACE2-4E6D-89D2-9AC0DEDE3A85} = {7ACC27E8-8FBE-4807-B91F-B89AF3CFF7E0}
347354
EndGlobalSection
348355
GlobalSection(ExtensibilityGlobals) = postSolution
349356
SolutionGuid = {D5B2E7E3-B727-40A1-BE68-7BAC9B9DE2FE}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using MessagePack;
2+
3+
namespace MagicOnion.Client.NativeAot.Tests;
4+
5+
public interface IUnaryTestService : IService<IUnaryTestService>
6+
{
7+
UnaryResult<int> TwoParametersReturnValueType(int arg1, string arg2);
8+
9+
UnaryResult Enum(MyEnumValue value);
10+
UnaryResult<MyEnumValue> EnumReturn();
11+
12+
UnaryResult BuiltInGeneric(List<MyObject> arg);
13+
UnaryResult<Dictionary<MyObject, string>> BuiltInGenericReturn();
14+
}
15+
16+
public enum MyEnumValue
17+
{
18+
A,
19+
B,
20+
C,
21+
}
22+
23+
[MessagePackObject]
24+
public record MyObject([property: Key(0)] int Value);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<Project Sdk="MSTest.Sdk/3.6.1">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<LangVersion>latest</LangVersion>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
9+
<OutputType>exe</OutputType>
10+
<PublishAot>true</PublishAot>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<Compile Include="..\MagicOnion.Client.Tests\ChannelAsyncStreamReader.cs" Link="Shared\ChannelAsyncStreamReader.cs" />
15+
<Compile Include="..\MagicOnion.Client.Tests\ChannelClientStreamWriter.cs" Link="Shared\ChannelClientStreamWriter.cs" />
16+
<Compile Include="..\MagicOnion.Client.Tests\MockAsyncStreamReader.cs" Link="Shared\MockAsyncStreamReader.cs" />
17+
<Compile Include="..\MagicOnion.Client.Tests\MockClientStreamWriter.cs" Link="Shared\MockClientStreamWriter.cs" />
18+
<Compile Include="..\MagicOnion.Client.Tests\MockSerializationContext.cs" Link="Shared\MockSerializationContext.cs" />
19+
</ItemGroup>
20+
21+
<ItemGroup>
22+
<PackageReference Include="coverlet.collector" />
23+
</ItemGroup>
24+
25+
<ItemGroup>
26+
<ProjectReference Include="..\..\src\MagicOnion.Client\MagicOnion.Client.csproj" />
27+
<ProjectReference Include="..\..\src\MagicOnion.Client.SourceGenerator\MagicOnion.Client.SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
28+
</ItemGroup>
29+
30+
<ItemGroup>
31+
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
32+
</ItemGroup>
33+
34+
</Project>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System.Buffers;
2+
using System.Threading.Channels;
3+
using Grpc.Core;
4+
using MagicOnion.Client.Tests;
5+
6+
namespace MagicOnion.Client.NativeAot.Tests;
7+
8+
public class MockCallInvoker : CallInvoker
9+
{
10+
public List<byte[]> RequestPayloads { get; } = new();
11+
public Channel<byte[]> ResponseChannel { get; } = Channel.CreateUnbounded<byte[]>();
12+
13+
public override TResponse BlockingUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string? host, CallOptions options, TRequest request)
14+
{
15+
throw new NotImplementedException();
16+
}
17+
18+
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string? host, CallOptions options, TRequest request)
19+
{
20+
var serializationContext = new MockSerializationContext();
21+
method.RequestMarshaller.ContextualSerializer(request, serializationContext);
22+
RequestPayloads.Add(serializationContext.ToMemory().ToArray());
23+
24+
return new AsyncUnaryCall<TResponse>(
25+
ResponseChannel.Reader.ReadAsync().AsTask().ContinueWith(x => method.ResponseMarshaller.ContextualDeserializer(new MockDeserializationContext(x.Result))),
26+
Task.FromResult(Metadata.Empty),
27+
() => Status.DefaultSuccess,
28+
() => Metadata.Empty,
29+
() => { });
30+
}
31+
32+
public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string? host, CallOptions options, TRequest request)
33+
{
34+
throw new NotImplementedException();
35+
}
36+
37+
public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string? host, CallOptions options)
38+
{
39+
throw new NotImplementedException();
40+
}
41+
42+
public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string? host, CallOptions options)
43+
{
44+
throw new NotImplementedException();
45+
}
46+
}
47+
48+
class MockDeserializationContext(byte[] payload) : DeserializationContext
49+
{
50+
public override int PayloadLength => payload.Length;
51+
public override byte[] PayloadAsNewBuffer() => payload.ToArray();
52+
public override ReadOnlySequence<byte> PayloadAsReadOnlySequence() => new ReadOnlySequence<byte>(payload);
53+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
using System.Buffers;
2+
using MessagePack.Resolvers;
3+
4+
namespace MagicOnion.Client.NativeAot.Tests;
5+
6+
// NOTE: This test uses a Resolver that contains code corresponding to the arguments and return values of IUnaryTestService.
7+
[TestClass]
8+
public sealed class ResolverTest
9+
{
10+
static ResolverTest()
11+
{
12+
// WORKAROUND: GeneratedAssemblyMessagePackResolverAttribute in MessagePack v3.1.1 does not consider trimming, causing type metadata to be removed.
13+
_ = typeof(MessagePack.GeneratedMessagePackResolver).GetFields();
14+
}
15+
16+
[TestMethod]
17+
public void MyObject()
18+
{
19+
// Arrange
20+
var options = MessagePackSerializerOptions.Standard.WithResolver(CompositeResolver.Create(MagicOnionClientGeneratedInitializer.Resolver, StandardResolver.Instance));
21+
var arrayBufferWriter = new ArrayBufferWriter<byte>();
22+
var writer = new MessagePackWriter(arrayBufferWriter);
23+
24+
// Act
25+
var formatter = SourceGeneratedFormatterResolver.Instance.GetFormatter<MyObject>();
26+
formatter?.Serialize(ref writer, new MyObject(12345), options);
27+
writer.Flush();
28+
29+
// Assert
30+
Assert.IsNotNull(formatter);
31+
Assert.AreEqual("0x91, 0xcd, 0x30, 0x39", string.Join(", ", arrayBufferWriter.WrittenMemory.ToArray().Select(x => $"0x{x:x2}")));
32+
}
33+
34+
[TestMethod]
35+
public void DynamicArgumentTuple()
36+
{
37+
// Arrange
38+
var options = MessagePackSerializerOptions.Standard.WithResolver(CompositeResolver.Create(MagicOnionClientGeneratedInitializer.Resolver, StandardResolver.Instance));
39+
var arrayBufferWriter = new ArrayBufferWriter<byte>();
40+
var writer = new MessagePackWriter(arrayBufferWriter);
41+
42+
// Act
43+
var formatter = options.Resolver.GetFormatter<DynamicArgumentTuple<int, string>>();
44+
formatter?.Serialize(ref writer, new DynamicArgumentTuple<int, string>(12345, "Hello world!"), options);
45+
writer.Flush();
46+
47+
// Assert
48+
Assert.IsNotNull(formatter);
49+
Assert.AreEqual("0x92, 0xcd, 0x30, 0x39, 0xac, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21", string.Join(", ", arrayBufferWriter.WrittenMemory.ToArray().Select(x => $"0x{x:x2}")));
50+
}
51+
52+
//[TestMethod]
53+
//public void DynamicArgumentTuple_Unknown()
54+
//{
55+
// // Arrange
56+
// var options = MessagePackSerializerOptions.Standard.WithResolver(CompositeResolver.Create(MagicOnionClientGeneratedInitializer.Resolver, StandardResolver.Instance));
57+
58+
// // Act
59+
// var formatter = options.Resolver.GetFormatter<DynamicArgumentTuple<int, string, object, bool, Uri, Guid, MyEnumValue>>();
60+
61+
// // Assert
62+
// Assert.IsNull(formatter);
63+
//}
64+
65+
[TestMethod]
66+
public void Enum()
67+
{
68+
// Arrange
69+
var options = MessagePackSerializerOptions.Standard.WithResolver(CompositeResolver.Create(MagicOnionClientGeneratedInitializer.Resolver, StandardResolver.Instance));
70+
var arrayBufferWriter = new ArrayBufferWriter<byte>();
71+
var writer = new MessagePackWriter(arrayBufferWriter);
72+
73+
// Act
74+
var formatter = options.Resolver.GetFormatter<MyEnumValue>();
75+
formatter?.Serialize(ref writer, MyEnumValue.C, options);
76+
writer.Flush();
77+
78+
// Assert
79+
Assert.IsNotNull(formatter);
80+
Assert.AreEqual("0x02", string.Join(", ", arrayBufferWriter.WrittenMemory.ToArray().Select(x => $"0x{x:x2}")));
81+
}
82+
83+
//[TestMethod]
84+
//public void Enum_Unknown()
85+
//{
86+
// // Arrange
87+
// var options = MessagePackSerializerOptions.Standard.WithResolver(CompositeResolver.Create(MagicOnionClientGeneratedInitializer.Resolver, StandardResolver.Instance));
88+
89+
// // Act
90+
// var formatter = options.Resolver.GetFormatter<UnknownEnumValue>();
91+
92+
// // Assert
93+
// Assert.IsNull(formatter);
94+
//}
95+
96+
[TestMethod]
97+
public void BuiltInGenerics_List()
98+
{
99+
// Arrange
100+
var options = MessagePackSerializerOptions.Standard.WithResolver(CompositeResolver.Create(MagicOnionClientGeneratedInitializer.Resolver, StandardResolver.Instance));
101+
var arrayBufferWriter = new ArrayBufferWriter<byte>();
102+
var writer = new MessagePackWriter(arrayBufferWriter);
103+
104+
// Act
105+
var formatter = options.Resolver.GetFormatter<List<MyObject>>();
106+
formatter?.Serialize(ref writer, [new MyObject(1), new MyObject(100), new MyObject(1000)], options);
107+
writer.Flush();
108+
109+
// Assert
110+
Assert.IsNotNull(formatter);
111+
Assert.AreEqual("0x93, 0x91, 0x01, 0x91, 0x64, 0x91, 0xcd, 0x03, 0xe8", string.Join(", ", arrayBufferWriter.WrittenMemory.ToArray().Select(x => $"0x{x:x2}")));
112+
}
113+
114+
[TestMethod]
115+
public void BuiltInGenerics_Dictionary()
116+
{
117+
// Arrange
118+
var options = MessagePackSerializerOptions.Standard.WithResolver(CompositeResolver.Create(MagicOnionClientGeneratedInitializer.Resolver, StandardResolver.Instance));
119+
var arrayBufferWriter = new ArrayBufferWriter<byte>();
120+
var writer = new MessagePackWriter(arrayBufferWriter);
121+
122+
// Act
123+
var formatter = options.Resolver.GetFormatter<Dictionary<MyObject, string>>();
124+
formatter?.Serialize(ref writer, new Dictionary<MyObject, string>()
125+
{
126+
[new MyObject(12345)] = "Hello",
127+
[new MyObject(67890)] = "World",
128+
}, options);
129+
writer.Flush();
130+
131+
// Assert
132+
Assert.IsNotNull(formatter);
133+
Assert.AreEqual("0x82, 0x91, 0xcd, 0x30, 0x39, 0xa5, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x91, 0xce, 0x00, 0x01, 0x09, 0x32, 0xa5, 0x57, 0x6f, 0x72, 0x6c, 0x64", string.Join(", ", arrayBufferWriter.WrittenMemory.ToArray().Select(x => $"0x{x:x2}")));
134+
}
135+
136+
//[TestMethod]
137+
//public void BuiltInGenerics_Unknown()
138+
//{
139+
// // Arrange
140+
// var options = MessagePackSerializerOptions.Standard.WithResolver(CompositeResolver.Create(MagicOnionClientGeneratedInitializer.Resolver, StandardResolver.Instance));
141+
142+
// // Act
143+
// var formatter1 = options.Resolver.GetFormatter<List<UnknownObject>>();
144+
// var formatter2 = options.Resolver.GetFormatter<Dictionary<UnknownObject, string>>();
145+
146+
// // Assert
147+
// Assert.IsNull(formatter1);
148+
// Assert.IsNull(formatter2);
149+
//}
150+
151+
class UnknownObject;
152+
153+
enum UnknownEnumValue
154+
{
155+
A,
156+
B,
157+
C,
158+
}
159+
}

0 commit comments

Comments
 (0)