Skip to content

Commit

Permalink
Merge pull request #899 from Cysharp/feature/NativeAotTests
Browse files Browse the repository at this point in the history
Add unit tests for Native AOT
  • Loading branch information
mayuki authored Jan 23, 2025
2 parents 5aad6db + efb1e26 commit c154a46
Show file tree
Hide file tree
Showing 9 changed files with 443 additions and 1 deletion.
12 changes: 11 additions & 1 deletion .github/workflows/build-debug.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ jobs:
- run: dotnet retest -- -c Debug MagicOnion.sln
- run: dotnet retest -- -c Release MagicOnion.sln

run-client-nativeaot-tests:
name: "Run Client Native AOT tests"
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: Cysharp/Actions/.github/actions/setup-dotnet@main
- run: dotnet publish -r linux-x64 tests/MagicOnion.Client.NativeAot.Tests/MagicOnion.Client.NativeAot.Tests.csproj
- run: tests/MagicOnion.Client.NativeAot.Tests/bin/Release/net9.0/linux-x64/publish/MagicOnion.Client.NativeAot.Tests

build-unity:
name: "Build Unity package"
if: ${{ (github.event_name == 'push' && github.repository_owner == 'Cysharp') || startsWith(github.event.pull_request.head.label, 'Cysharp:') }}
Expand Down Expand Up @@ -111,6 +121,6 @@ jobs:
# retention-days: 1

actions-timeline:
needs: [build-dotnet, run-tests, build-unity]
needs: [build-dotnet, run-tests, run-client-nativeaot-tests, build-unity]
uses: Cysharp/Actions/.github/workflows/actions-timeline.yaml@main
secrets: inherit
7 changes: 7 additions & 0 deletions MagicOnion.sln
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonTranscodingSample.Serve
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonTranscodingSample.Shared", "samples\JsonTranscoding\JsonTranscodingSample.Shared\JsonTranscodingSample.Shared.csproj", "{50871ADE-4513-4AC1-8964-740AB6505B31}"
EndProject
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}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -292,6 +294,10 @@ Global
{50871ADE-4513-4AC1-8964-740AB6505B31}.Debug|Any CPU.Build.0 = Debug|Any CPU
{50871ADE-4513-4AC1-8964-740AB6505B31}.Release|Any CPU.ActiveCfg = Release|Any CPU
{50871ADE-4513-4AC1-8964-740AB6505B31}.Release|Any CPU.Build.0 = Release|Any CPU
{9A70D36D-ACE2-4E6D-89D2-9AC0DEDE3A85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9A70D36D-ACE2-4E6D-89D2-9AC0DEDE3A85}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9A70D36D-ACE2-4E6D-89D2-9AC0DEDE3A85}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9A70D36D-ACE2-4E6D-89D2-9AC0DEDE3A85}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -344,6 +350,7 @@ Global
{57E6F117-1138-4899-81A2-CC87B20525E4} = {5A3F5158-7B17-4586-9885-9E60C1393185}
{24A21CDA-C3A5-49F2-A1B8-1A93E2E64335} = {57E6F117-1138-4899-81A2-CC87B20525E4}
{50871ADE-4513-4AC1-8964-740AB6505B31} = {57E6F117-1138-4899-81A2-CC87B20525E4}
{9A70D36D-ACE2-4E6D-89D2-9AC0DEDE3A85} = {7ACC27E8-8FBE-4807-B91F-B89AF3CFF7E0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D5B2E7E3-B727-40A1-BE68-7BAC9B9DE2FE}
Expand Down
24 changes: 24 additions & 0 deletions tests/MagicOnion.Client.NativeAot.Tests/IUnaryTestService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using MessagePack;

namespace MagicOnion.Client.NativeAot.Tests;

public interface IUnaryTestService : IService<IUnaryTestService>
{
UnaryResult<int> TwoParametersReturnValueType(int arg1, string arg2);

UnaryResult Enum(MyEnumValue value);
UnaryResult<MyEnumValue> EnumReturn();

UnaryResult BuiltInGeneric(List<MyObject> arg);
UnaryResult<Dictionary<MyObject, string>> BuiltInGenericReturn();
}

public enum MyEnumValue
{
A,
B,
C,
}

[MessagePackObject]
public record MyObject([property: Key(0)] int Value);
1 change: 1 addition & 0 deletions tests/MagicOnion.Client.NativeAot.Tests/MSTestSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<Project Sdk="MSTest.Sdk/3.6.1">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<OutputType>exe</OutputType>
<PublishAot>true</PublishAot>
</PropertyGroup>

<ItemGroup>
<Compile Include="..\MagicOnion.Client.Tests\ChannelAsyncStreamReader.cs" Link="Shared\ChannelAsyncStreamReader.cs" />
<Compile Include="..\MagicOnion.Client.Tests\ChannelClientStreamWriter.cs" Link="Shared\ChannelClientStreamWriter.cs" />
<Compile Include="..\MagicOnion.Client.Tests\MockAsyncStreamReader.cs" Link="Shared\MockAsyncStreamReader.cs" />
<Compile Include="..\MagicOnion.Client.Tests\MockClientStreamWriter.cs" Link="Shared\MockClientStreamWriter.cs" />
<Compile Include="..\MagicOnion.Client.Tests\MockSerializationContext.cs" Link="Shared\MockSerializationContext.cs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\MagicOnion.Client\MagicOnion.Client.csproj" />
<ProjectReference Include="..\..\src\MagicOnion.Client.SourceGenerator\MagicOnion.Client.SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>

<ItemGroup>
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
</ItemGroup>

</Project>
53 changes: 53 additions & 0 deletions tests/MagicOnion.Client.NativeAot.Tests/MockCallInvoker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.Buffers;
using System.Threading.Channels;
using Grpc.Core;
using MagicOnion.Client.Tests;

namespace MagicOnion.Client.NativeAot.Tests;

public class MockCallInvoker : CallInvoker
{
public List<byte[]> RequestPayloads { get; } = new();
public Channel<byte[]> ResponseChannel { get; } = Channel.CreateUnbounded<byte[]>();

public override TResponse BlockingUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string? host, CallOptions options, TRequest request)
{
throw new NotImplementedException();
}

public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string? host, CallOptions options, TRequest request)
{
var serializationContext = new MockSerializationContext();
method.RequestMarshaller.ContextualSerializer(request, serializationContext);
RequestPayloads.Add(serializationContext.ToMemory().ToArray());

return new AsyncUnaryCall<TResponse>(
ResponseChannel.Reader.ReadAsync().AsTask().ContinueWith(x => method.ResponseMarshaller.ContextualDeserializer(new MockDeserializationContext(x.Result))),
Task.FromResult(Metadata.Empty),
() => Status.DefaultSuccess,
() => Metadata.Empty,
() => { });
}

public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string? host, CallOptions options, TRequest request)
{
throw new NotImplementedException();
}

public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string? host, CallOptions options)
{
throw new NotImplementedException();
}

public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string? host, CallOptions options)
{
throw new NotImplementedException();
}
}

class MockDeserializationContext(byte[] payload) : DeserializationContext
{
public override int PayloadLength => payload.Length;
public override byte[] PayloadAsNewBuffer() => payload.ToArray();
public override ReadOnlySequence<byte> PayloadAsReadOnlySequence() => new ReadOnlySequence<byte>(payload);
}
159 changes: 159 additions & 0 deletions tests/MagicOnion.Client.NativeAot.Tests/ResolverTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
using System.Buffers;
using MessagePack.Resolvers;

namespace MagicOnion.Client.NativeAot.Tests;

// NOTE: This test uses a Resolver that contains code corresponding to the arguments and return values of IUnaryTestService.
[TestClass]
public sealed class ResolverTest
{
static ResolverTest()
{
// WORKAROUND: GeneratedAssemblyMessagePackResolverAttribute in MessagePack v3.1.1 does not consider trimming, causing type metadata to be removed.
_ = typeof(MessagePack.GeneratedMessagePackResolver).GetFields();
}

[TestMethod]
public void MyObject()
{
// Arrange
var options = MessagePackSerializerOptions.Standard.WithResolver(CompositeResolver.Create(MagicOnionClientGeneratedInitializer.Resolver, StandardResolver.Instance));
var arrayBufferWriter = new ArrayBufferWriter<byte>();
var writer = new MessagePackWriter(arrayBufferWriter);

// Act
var formatter = SourceGeneratedFormatterResolver.Instance.GetFormatter<MyObject>();
formatter?.Serialize(ref writer, new MyObject(12345), options);
writer.Flush();

// Assert
Assert.IsNotNull(formatter);
Assert.AreEqual("0x91, 0xcd, 0x30, 0x39", string.Join(", ", arrayBufferWriter.WrittenMemory.ToArray().Select(x => $"0x{x:x2}")));
}

[TestMethod]
public void DynamicArgumentTuple()
{
// Arrange
var options = MessagePackSerializerOptions.Standard.WithResolver(CompositeResolver.Create(MagicOnionClientGeneratedInitializer.Resolver, StandardResolver.Instance));
var arrayBufferWriter = new ArrayBufferWriter<byte>();
var writer = new MessagePackWriter(arrayBufferWriter);

// Act
var formatter = options.Resolver.GetFormatter<DynamicArgumentTuple<int, string>>();
formatter?.Serialize(ref writer, new DynamicArgumentTuple<int, string>(12345, "Hello world!"), options);
writer.Flush();

// Assert
Assert.IsNotNull(formatter);
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}")));
}

//[TestMethod]
//public void DynamicArgumentTuple_Unknown()
//{
// // Arrange
// var options = MessagePackSerializerOptions.Standard.WithResolver(CompositeResolver.Create(MagicOnionClientGeneratedInitializer.Resolver, StandardResolver.Instance));

// // Act
// var formatter = options.Resolver.GetFormatter<DynamicArgumentTuple<int, string, object, bool, Uri, Guid, MyEnumValue>>();

// // Assert
// Assert.IsNull(formatter);
//}

[TestMethod]
public void Enum()
{
// Arrange
var options = MessagePackSerializerOptions.Standard.WithResolver(CompositeResolver.Create(MagicOnionClientGeneratedInitializer.Resolver, StandardResolver.Instance));
var arrayBufferWriter = new ArrayBufferWriter<byte>();
var writer = new MessagePackWriter(arrayBufferWriter);

// Act
var formatter = options.Resolver.GetFormatter<MyEnumValue>();
formatter?.Serialize(ref writer, MyEnumValue.C, options);
writer.Flush();

// Assert
Assert.IsNotNull(formatter);
Assert.AreEqual("0x02", string.Join(", ", arrayBufferWriter.WrittenMemory.ToArray().Select(x => $"0x{x:x2}")));
}

//[TestMethod]
//public void Enum_Unknown()
//{
// // Arrange
// var options = MessagePackSerializerOptions.Standard.WithResolver(CompositeResolver.Create(MagicOnionClientGeneratedInitializer.Resolver, StandardResolver.Instance));

// // Act
// var formatter = options.Resolver.GetFormatter<UnknownEnumValue>();

// // Assert
// Assert.IsNull(formatter);
//}

[TestMethod]
public void BuiltInGenerics_List()
{
// Arrange
var options = MessagePackSerializerOptions.Standard.WithResolver(CompositeResolver.Create(MagicOnionClientGeneratedInitializer.Resolver, StandardResolver.Instance));
var arrayBufferWriter = new ArrayBufferWriter<byte>();
var writer = new MessagePackWriter(arrayBufferWriter);

// Act
var formatter = options.Resolver.GetFormatter<List<MyObject>>();
formatter?.Serialize(ref writer, [new MyObject(1), new MyObject(100), new MyObject(1000)], options);
writer.Flush();

// Assert
Assert.IsNotNull(formatter);
Assert.AreEqual("0x93, 0x91, 0x01, 0x91, 0x64, 0x91, 0xcd, 0x03, 0xe8", string.Join(", ", arrayBufferWriter.WrittenMemory.ToArray().Select(x => $"0x{x:x2}")));
}

[TestMethod]
public void BuiltInGenerics_Dictionary()
{
// Arrange
var options = MessagePackSerializerOptions.Standard.WithResolver(CompositeResolver.Create(MagicOnionClientGeneratedInitializer.Resolver, StandardResolver.Instance));
var arrayBufferWriter = new ArrayBufferWriter<byte>();
var writer = new MessagePackWriter(arrayBufferWriter);

// Act
var formatter = options.Resolver.GetFormatter<Dictionary<MyObject, string>>();
formatter?.Serialize(ref writer, new Dictionary<MyObject, string>()
{
[new MyObject(12345)] = "Hello",
[new MyObject(67890)] = "World",
}, options);
writer.Flush();

// Assert
Assert.IsNotNull(formatter);
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}")));
}

//[TestMethod]
//public void BuiltInGenerics_Unknown()
//{
// // Arrange
// var options = MessagePackSerializerOptions.Standard.WithResolver(CompositeResolver.Create(MagicOnionClientGeneratedInitializer.Resolver, StandardResolver.Instance));

// // Act
// var formatter1 = options.Resolver.GetFormatter<List<UnknownObject>>();
// var formatter2 = options.Resolver.GetFormatter<Dictionary<UnknownObject, string>>();

// // Assert
// Assert.IsNull(formatter1);
// Assert.IsNull(formatter2);
//}

class UnknownObject;

enum UnknownEnumValue
{
A,
B,
C,
}
}
Loading

0 comments on commit c154a46

Please sign in to comment.