Skip to content

Commit

Permalink
tests(net): introduce helpers and fixtures for tests (#644)
Browse files Browse the repository at this point in the history
  • Loading branch information
DennisInSky authored Nov 14, 2024
1 parent 0756f12 commit fb07939
Show file tree
Hide file tree
Showing 18 changed files with 828 additions and 7 deletions.
26 changes: 20 additions & 6 deletions .github/workflows/net-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,26 @@ on:
branches:
- master
paths:
- '.github/workflows/ci-net.yml'
- '.github/workflows/net-ci.yml'
- 'net/**'
- 'Sales.Net.sln'

pull_request:
paths:
- '.github/workflows/ci-net.yml'
- '.github/workflows/net-ci.yml'
- 'net/**'
- 'Sales.Net.sln'

jobs:
check:
name: Check Code
build-test:
name: Build & Test
runs-on: ubuntu-latest
defaults:
run:
shell: bash

steps:
- name: Check Out Source
- name: Check Code
uses: actions/checkout@v4

- name: Set Up .NET
Expand All @@ -38,4 +38,18 @@ jobs:
- name: Build Solution
run: >-
dotnet build --no-restore
dotnet build --no-restore --configuration Debug
- name: Test Solution
run: >-
dotnet test --no-build --configuration Debug
--logger "trx;LogFileName=TestResults.trx"
- name: Upload Test Results
if: ${{ success() || failure() }}
uses: actions/upload-artifact@v4
with:
name: test-results
path: |
net/**/*.trx
!net/**/Sails.Tests.Shared/**/*.trx
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ target/
# C#
net/**/[Bb]in/
net/**/[Oo]bj/
net/**/*.trx

# js
node_modules/
Expand Down
14 changes: 13 additions & 1 deletion Sails.Net.sln
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Substrate.Gear.Client", "net\src\Substrate.Gear.Client\Substrate.Gear.Client.csproj", "{1589D5A4-0CC4-4855-89E0-2E61BBC5E0B0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Substrate.Gear.Client.Tests", "net\tests\Substrate.Gear.Client.Tests\Substrate.Gear.Client.Tests.csproj", "{42B621CE-C2B4-4911-961C-5B087A514AF5}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Substrate.Gear.Client.Tests", "net\tests\Substrate.Gear.Client.Tests\Substrate.Gear.Client.Tests.csproj", "{42B621CE-C2B4-4911-961C-5B087A514AF5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sails.Tests.Shared", "net\tests\Sails.Tests.Shared\Sails.Tests.Shared.csproj", "{382EBE9F-C60C-4D20-A585-B3272113F944}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sails.Remoting.Tests", "net\tests\Sails.Remoting.Tests\Sails.Remoting.Tests.csproj", "{DEEACFC0-EE4D-42E5-9616-232242913A39}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -51,6 +55,14 @@ Global
{42B621CE-C2B4-4911-961C-5B087A514AF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42B621CE-C2B4-4911-961C-5B087A514AF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42B621CE-C2B4-4911-961C-5B087A514AF5}.Release|Any CPU.Build.0 = Release|Any CPU
{382EBE9F-C60C-4D20-A585-B3272113F944}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{382EBE9F-C60C-4D20-A585-B3272113F944}.Debug|Any CPU.Build.0 = Debug|Any CPU
{382EBE9F-C60C-4D20-A585-B3272113F944}.Release|Any CPU.ActiveCfg = Release|Any CPU
{382EBE9F-C60C-4D20-A585-B3272113F944}.Release|Any CPU.Build.0 = Release|Any CPU
{DEEACFC0-EE4D-42E5-9616-232242913A39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DEEACFC0-EE4D-42E5-9616-232242913A39}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DEEACFC0-EE4D-42E5-9616-232242913A39}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DEEACFC0-EE4D-42E5-9616-232242913A39}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
2 changes: 2 additions & 0 deletions net/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="FluentAssertions" Version="6.12.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageVersion Include="Nito.AsyncEx" Version="5.1.2" />
<PackageVersion Include="Testcontainers" Version="4.0.0" />
<PackageVersion Include="xunit" Version="2.9.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
<!-- For Code Analysis -->
Expand Down
25 changes: 25 additions & 0 deletions net/tests/Sails.Remoting.Tests/Core/RemotingViaNodeClientTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Threading.Tasks;
using Sails.Remoting.Tests._Infra.XUnit.Fixtures;
using Sails.Tests.Shared.XUnit;
using Xunit;

namespace Sails.Remoting.Tests.Core;

public sealed class RemotingViaNodeClientTests : IAssemblyFixture<SailsFixture>
{
public RemotingViaNodeClientTests(SailsFixture sailsFixture)
{
this.sailsFixture = sailsFixture;
}

private readonly SailsFixture sailsFixture;

[Fact]
public async Task Test()
{
var demoIdl = await this.sailsFixture.GetDemoContractIdlAsync();
var demoContractWasm = await this.sailsFixture.GetDemoContractWasmAsync();
var noSvcsProgIdl = await this.sailsFixture.GetNoSvcsProgContractIdlAsync();
var gearNodeWsUrl = this.sailsFixture.GearNodeWsUrl;
}
}
28 changes: 28 additions & 0 deletions net/tests/Sails.Remoting.Tests/Sails.Remoting.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<NoWarn>$(NoWarn);xUnit1041</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Nito.AsyncEx" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Sails.Tests.Shared\Sails.Tests.Shared.csproj" />
</ItemGroup>

</Project>
148 changes: 148 additions & 0 deletions net/tests/Sails.Remoting.Tests/_Infra/XUnit/Fixtures/SailsFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
using EnsureThat;
using Nito.AsyncEx;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Threading;
using System;
using Sails.Remoting.Tests._Infra.XUnit.Fixtures;
using Sails.Tests.Shared.Containers;
using Sails.Tests.Shared.Git;
using Sails.Tests.Shared.XUnit;
using Xunit;

[assembly: AssemblyFixture(typeof(SailsFixture))]

namespace Sails.Remoting.Tests._Infra.XUnit.Fixtures;

public sealed partial class SailsFixture : IAsyncLifetime
{
public SailsFixture()
: this(sailsRsVersion: "0.6.3")
{
}

public SailsFixture(string sailsRsVersion)
{
EnsureArg.IsNotNullOrWhiteSpace(sailsRsVersion, nameof(sailsRsVersion));

this.sailsRsReleaseTag = $"rs/v{sailsRsVersion}";
this.demoContractIdl = new AsyncLazy<string>(
() => this.DownloadStringAsset("demo.idl"),
AsyncLazyFlags.RetryOnFailure);
this.demoContractWasm = new AsyncLazy<MemoryStream>(
() => this.DownloadOctetAsset("demo.wasm"),
AsyncLazyFlags.RetryOnFailure);
this.noSvcsProgContractIdl = new AsyncLazy<string>(
() => this.DownloadStringAsset("no-svcs-prog.idl"),
AsyncLazyFlags.RetryOnFailure);
this.noSvcsProgContractWasm = new AsyncLazy<MemoryStream>(
() => this.DownloadOctetAsset("no_svcs_prog.wasm"),
AsyncLazyFlags.RetryOnFailure);
this.gearNodeContainer = null;
}

private static readonly GithubDownloader GithubDownloader = new("gear-tech", "sails");

private readonly string sailsRsReleaseTag;
private readonly AsyncLazy<string> demoContractIdl;
private readonly AsyncLazy<MemoryStream> demoContractWasm;
private readonly AsyncLazy<string> noSvcsProgContractIdl;
private readonly AsyncLazy<MemoryStream> noSvcsProgContractWasm;
private GearNodeContainer? gearNodeContainer;

public Uri GearNodeWsUrl => this.gearNodeContainer?.WsUrl
?? throw new InvalidOperationException("Gear node container is not initialized.");

public async Task DisposeAsync()
{
if (this.gearNodeContainer is not null)
{
await this.gearNodeContainer.DisposeAsync();
this.gearNodeContainer = null;
}
if (this.demoContractWasm.IsStarted)
{
await (await this.demoContractWasm).DisposeAsync();
}
if (this.noSvcsProgContractWasm.IsStarted)
{
await (await this.noSvcsProgContractWasm).DisposeAsync();
}
}

public async Task InitializeAsync()
{
var sailsRsCargoToml = await this.DownloadSailsRsCargoTomlAsync();

var matchResult = GStdDependencyRegex().Match(sailsRsCargoToml);
if (!matchResult.Success)
{
throw new InvalidOperationException(
$"Failed to find gstd dependency in Cargo.toml by the '{this.sailsRsReleaseTag}' tag.");
}
var gearNodeVersion = matchResult.Groups[1].Value;

// The `reuse` parameter can be made configurable if needed
this.gearNodeContainer = new GearNodeContainer(gearNodeVersion, reuse: true);
await this.gearNodeContainer.StartAsync();
}

public Task<string> GetDemoContractIdlAsync()
=> this.demoContractIdl.Task;

public async Task<ReadOnlyMemory<byte>> GetDemoContractWasmAsync()
{
var byteStream = await this.demoContractWasm;
Ensure.Comparable.IsLte(byteStream.Length, int.MaxValue);
return new ReadOnlyMemory<byte>(byteStream.GetBuffer(), start: 0, length: (int)byteStream.Length);
}

public Task<string> GetNoSvcsProgContractIdlAsync()
=> this.noSvcsProgContractIdl.Task;

public async Task<ReadOnlyMemory<byte>> GetNoSvcsProgContractWasmAsync()
{
var byteStream = await this.noSvcsProgContractWasm;
Ensure.Comparable.IsLte(byteStream.Length, int.MaxValue);
return new ReadOnlyMemory<byte>(byteStream.GetBuffer(), start: 0, length: (int)byteStream.Length);
}

private async Task<string> DownloadStringAsset(string assetName)
{
var downloadStream = await GithubDownloader.DownloadReleaseAssetAsync(
this.sailsRsReleaseTag,
assetName,
CancellationToken.None);
using (var reader = new StreamReader(downloadStream, leaveOpen: false))
{
return await reader.ReadToEndAsync(CancellationToken.None);
}
}

private async Task<MemoryStream> DownloadOctetAsset(string assetName)
{
var downloadStream = await GithubDownloader.DownloadReleaseAssetAsync(
this.sailsRsReleaseTag,
assetName,
CancellationToken.None);
var memoryStream = new MemoryStream();
await downloadStream.CopyToAsync(memoryStream);
return memoryStream;
}

private async Task<string> DownloadSailsRsCargoTomlAsync()
{
var downloadStream = await GithubDownloader.DownloadFileFromTagAsync(
this.sailsRsReleaseTag,
"Cargo.toml",
CancellationToken.None);
using (var reader = new StreamReader(downloadStream, leaveOpen: false))
{
return await reader.ReadToEndAsync(CancellationToken.None);
}
}

[GeneratedRegex(@"gstd\s*=\s*""=?(\d+\.\d+\.\d+)""")]
private static partial Regex GStdDependencyRegex();
}
15 changes: 15 additions & 0 deletions net/tests/Sails.Remoting.Tests/_Infra/XUnit/TestFramework.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Xunit.Abstractions;

[assembly: Xunit.TestFramework(
"Sails.Remoting.Tests._Infra.XUnit.TestFramework",
"Sails.Remoting.Tests")]

namespace Sails.Remoting.Tests._Infra.XUnit;

internal sealed class TestFramework : Sails.Tests.Shared.XUnit.TestFramework
{
public TestFramework(IMessageSink messageSink)
: base(messageSink)
{
}
}
53 changes: 53 additions & 0 deletions net/tests/Sails.Tests.Shared/Containers/GearNodeContainer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Threading.Tasks;
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Containers;
using EnsureThat;

namespace Sails.Tests.Shared.Containers;

// TODO: Consider introducing ContainerBuilder similar to how it is done
// for modules shipped along with Testcontainers library.
public sealed class GearNodeContainer : IAsyncDisposable
{
// TODO: Consider making 'Version' as an optional parameter.
// By default the latest version should be taken which can be determined
// from the downloaded 'Cargo.toml' file.
public GearNodeContainer(string gearNodeVersion, bool reuse)
{
EnsureArg.IsNotNullOrWhiteSpace(gearNodeVersion, nameof(gearNodeVersion));

this.container = new ContainerBuilder()
.WithName("gear-node-for-tests")
.WithImage($"ghcr.io/gear-tech/node:v{gearNodeVersion}")
.WithPortBinding(RpcPort, RpcPort) // Use WithPortBinding(RpcPort, true) if random host port is required
.WithEntrypoint("gear")
.WithCommand(
"--rpc-external", // --rpc-external is required for listening on all interfaces
"--dev",
"--tmp")
.WithEnvironment("RUST_LOG", "gear=debug,pallet_gear=debug,gwasm=debug")
.WithReuse(reuse)
.Build();
this.reuse = reuse;
}

private const ushort RpcPort = 9944;

private readonly IContainer container;
private readonly bool reuse;

public Uri WsUrl => new($"ws://localhost:{this.container.GetMappedPublicPort(9944)}");

public ValueTask DisposeAsync()
// Do not dispose container if it is reused otherwise it will be stopped
// which we don't want for this particular container. For another one we might
// choose the opposite behavior though.
// https://dotnet.testcontainers.org/api/resource_reuse/
=> this.reuse
? ValueTask.CompletedTask
: this.container.DisposeAsync();

public Task StartAsync()
=> this.container.StartAsync();
}
Loading

0 comments on commit fb07939

Please sign in to comment.