diff --git a/.github/workflows/net-ci.yml b/.github/workflows/net-ci.yml
index 7cac0ebc..333b4e7a 100644
--- a/.github/workflows/net-ci.yml
+++ b/.github/workflows/net-ci.yml
@@ -19,6 +19,8 @@ jobs:
build-test:
name: Build & Test
runs-on: ubuntu-latest
+ permissions:
+ packages: read
defaults:
run:
shell: bash
@@ -33,7 +35,14 @@ jobs:
dotnet-version: 8.0.x
- name: Restore Solution
- run: >-
+ run: |
+ NUGET_SOURCE_URL="https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json"
+ dotnet nuget remove source ${{ github.repository_owner }}-github || true
+ dotnet nuget add source $NUGET_SOURCE_URL \
+ --name ${{ github.repository_owner }}-github \
+ --username "${{ github.workflow }}" \
+ --password ${{ secrets.GITHUB_TOKEN }} \
+ --store-password-in-clear-text
dotnet restore
- name: Build Solution
diff --git a/Sails.Net.sln b/Sails.Net.sln
index ba363611..7cf30a1d 100644
--- a/Sails.Net.sln
+++ b/Sails.Net.sln
@@ -33,6 +33,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Substrate.Gear.Client.Tests
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sails.Client.Tests", "net\tests\Sails.Client.Tests\Sails.Client.Tests.csproj", "{BC248D44-14CD-4BB0-9AF9-4E0750574492}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{4D457454-ED79-4BC3-8B6B-AC6934AAF048}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sails.DemoClient", "net\examples\Sails.DemoClient\Sails.DemoClient.csproj", "{C9D87AD3-612B-43A0-89BD-9211FE2FB310}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sails.DemoClient.Tests", "net\tests\Sails.DemoClient.Tests\Sails.DemoClient.Tests.csproj", "{5ED8CDB7-F896-4ACE-8B7B-A128CCBBCFA4}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -83,6 +89,14 @@ Global
{BC248D44-14CD-4BB0-9AF9-4E0750574492}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BC248D44-14CD-4BB0-9AF9-4E0750574492}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BC248D44-14CD-4BB0-9AF9-4E0750574492}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C9D87AD3-612B-43A0-89BD-9211FE2FB310}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C9D87AD3-612B-43A0-89BD-9211FE2FB310}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C9D87AD3-612B-43A0-89BD-9211FE2FB310}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C9D87AD3-612B-43A0-89BD-9211FE2FB310}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5ED8CDB7-F896-4ACE-8B7B-A128CCBBCFA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5ED8CDB7-F896-4ACE-8B7B-A128CCBBCFA4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5ED8CDB7-F896-4ACE-8B7B-A128CCBBCFA4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5ED8CDB7-F896-4ACE-8B7B-A128CCBBCFA4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -93,6 +107,8 @@ Global
{A6A2172B-8F8F-4BDC-B519-E7299FFCCA5F} = {19594BCA-94DB-44AD-ACBC-2ACFED242E9F}
{42B621CE-C2B4-4911-961C-5B087A514AF5} = {19594BCA-94DB-44AD-ACBC-2ACFED242E9F}
{BC248D44-14CD-4BB0-9AF9-4E0750574492} = {19594BCA-94DB-44AD-ACBC-2ACFED242E9F}
+ {C9D87AD3-612B-43A0-89BD-9211FE2FB310} = {4D457454-ED79-4BC3-8B6B-AC6934AAF048}
+ {5ED8CDB7-F896-4ACE-8B7B-A128CCBBCFA4} = {19594BCA-94DB-44AD-ACBC-2ACFED242E9F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0894C08B-8BB9-401D-8471-26F5EB5A4EA2}
diff --git a/net/Directory.Packages.props b/net/Directory.Packages.props
index 17b57a68..c5b61101 100644
--- a/net/Directory.Packages.props
+++ b/net/Directory.Packages.props
@@ -11,7 +11,7 @@
-
+
@@ -19,17 +19,21 @@
+
-
+
-
+
+
+
+
diff --git a/net/examples/Sails.DemoClient/Sails.DemoClient.csproj b/net/examples/Sails.DemoClient/Sails.DemoClient.csproj
new file mode 100644
index 00000000..3e49ccb2
--- /dev/null
+++ b/net/examples/Sails.DemoClient/Sails.DemoClient.csproj
@@ -0,0 +1,16 @@
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/net/examples/Sails.DemoClient/demo.idl b/net/examples/Sails.DemoClient/demo.idl
new file mode 100644
index 00000000..d7cc6728
--- /dev/null
+++ b/net/examples/Sails.DemoClient/demo.idl
@@ -0,0 +1,91 @@
+type ReferenceCount = struct {
+ u32,
+};
+
+type DoThatParam = struct {
+ p1: nat32,
+ p2: actor_id,
+ p3: ManyVariants,
+};
+
+type ManyVariants = enum {
+ One,
+ Two: u32,
+ Three: opt u256,
+ Four: struct { a: u32, b: opt u16 },
+ Five: struct { str, h256 },
+ Six: struct { u32 },
+};
+
+type TupleStruct = struct {
+ bool,
+};
+
+constructor {
+ /// Program constructor (called once at the very beginning of the program lifetime)
+ Default : ();
+ /// Another program constructor (called once at the very beginning of the program lifetime)
+ New : (counter: opt u32, dog_position: opt struct { i32, i32 });
+};
+
+service Counter {
+ /// Add a value to the counter
+ Add : (value: u32) -> u32;
+ /// Substract a value from the counter
+ Sub : (value: u32) -> u32;
+ /// Get the current value
+ query Value : () -> u32;
+
+ events {
+ /// Emitted when a new value is added to the counter
+ Added: u32;
+ /// Emitted when a value is subtracted from the counter
+ Subtracted: u32;
+ }
+};
+
+service Dog {
+ MakeSound : () -> str;
+ Walk : (dx: i32, dy: i32) -> null;
+ query AvgWeight : () -> u32;
+ query Position : () -> struct { i32, i32 };
+
+ events {
+ Barked;
+ Walked: struct { from: struct { i32, i32 }, to: struct { i32, i32 } };
+ }
+};
+
+service PingPong {
+ Ping : (input: str) -> result (str, str);
+};
+
+service References {
+ Add : (v: u32) -> u32;
+ AddByte : (byte: u8) -> vec u8;
+ GuessNum : (number: u8) -> result (str, str);
+ Incr : () -> ReferenceCount;
+ SetNum : (number: u8) -> result (null, str);
+ query Baked : () -> str;
+ query LastByte : () -> opt u8;
+ query Message : () -> opt str;
+};
+
+service ThisThat {
+ DoThat : (param: DoThatParam) -> result (struct { actor_id, nat32 }, struct { str });
+ DoThis : (p1: u32, p2: str, p3: struct { opt h160, nat8 }, p4: TupleStruct) -> struct { str, u32 };
+ Noop : () -> null;
+ query That : () -> result (str, str);
+ query This : () -> u32;
+};
+
+service ValueFee {
+ /// Return flag if fee taken and remain value,
+ /// using special type `CommandReply`
+ DoSomethingAndTakeFee : () -> bool;
+
+ events {
+ Withheld: u128;
+ }
+};
+
diff --git a/net/tests/Sails.DemoClient.Tests/AssemblyAttributes.cs b/net/tests/Sails.DemoClient.Tests/AssemblyAttributes.cs
new file mode 100644
index 00000000..991f08f6
--- /dev/null
+++ b/net/tests/Sails.DemoClient.Tests/AssemblyAttributes.cs
@@ -0,0 +1,6 @@
+[assembly: TestFramework(
+ "Sails.Tests.Shared.XUnit.TestFramework",
+ "Sails.Tests.Shared")]
+
+[assembly: AssemblyFixture(
+ typeof(Sails.DemoClient.Tests._Infra.XUnit.Fixtures.SailsFixture))]
diff --git a/net/tests/Sails.DemoClient.Tests/DemoFactoryTests.cs b/net/tests/Sails.DemoClient.Tests/DemoFactoryTests.cs
new file mode 100644
index 00000000..60f4f229
--- /dev/null
+++ b/net/tests/Sails.DemoClient.Tests/DemoFactoryTests.cs
@@ -0,0 +1,21 @@
+using Sails.DemoClient.Tests._Infra.XUnit.Fixtures;
+
+namespace Sails.DemoClient.Tests;
+
+public sealed class DemoFactoryTests : IAssemblyFixture
+{
+ public DemoFactoryTests(SailsFixture fixture)
+ {
+ this.sailsFixture = fixture;
+ // Assert that IDL file from the Sails.DemoClient project is the same as the one
+ // from the SailsFixture
+ }
+
+ private readonly SailsFixture sailsFixture;
+
+ [Fact]
+ public async Task Test1()
+ {
+ var demoContractCodeId = await this.sailsFixture.GetDemoContractCodeIdAsync();
+ }
+}
diff --git a/net/tests/Sails.DemoClient.Tests/GlobalUsings.cs b/net/tests/Sails.DemoClient.Tests/GlobalUsings.cs
new file mode 100644
index 00000000..8f37676d
--- /dev/null
+++ b/net/tests/Sails.DemoClient.Tests/GlobalUsings.cs
@@ -0,0 +1,3 @@
+global using System.Threading.Tasks;
+global using Sails.Tests.Shared.XUnit;
+global using Xunit;
diff --git a/net/tests/Sails.DemoClient.Tests/Sails.DemoClient.Tests.csproj b/net/tests/Sails.DemoClient.Tests/Sails.DemoClient.Tests.csproj
new file mode 100644
index 00000000..eeb8d95e
--- /dev/null
+++ b/net/tests/Sails.DemoClient.Tests/Sails.DemoClient.Tests.csproj
@@ -0,0 +1,22 @@
+
+
+
+ net8.0
+ false
+ true
+ $(NoWarn);xUnit1041
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/net/tests/Sails.DemoClient.Tests/_Infra/XUnit/Fixtures/SailsFixture.cs b/net/tests/Sails.DemoClient.Tests/_Infra/XUnit/Fixtures/SailsFixture.cs
new file mode 100644
index 00000000..6ec86d5e
--- /dev/null
+++ b/net/tests/Sails.DemoClient.Tests/_Infra/XUnit/Fixtures/SailsFixture.cs
@@ -0,0 +1,9 @@
+namespace Sails.DemoClient.Tests._Infra.XUnit.Fixtures;
+
+public sealed class SailsFixture : Sails.Tests.Shared.XUnit.Fixtures.SailsFixture
+{
+ public SailsFixture()
+ : base("demo-client-tests")
+ {
+ }
+}
diff --git a/net/tests/Sails.Remoting.Tests/AssemblyAttributes.cs b/net/tests/Sails.Remoting.Tests/AssemblyAttributes.cs
new file mode 100644
index 00000000..32f26ce1
--- /dev/null
+++ b/net/tests/Sails.Remoting.Tests/AssemblyAttributes.cs
@@ -0,0 +1,6 @@
+[assembly: TestFramework(
+ "Sails.Tests.Shared.XUnit.TestFramework",
+ "Sails.Tests.Shared")]
+
+[assembly: AssemblyFixture(
+ typeof(Sails.Remoting.Tests._Infra.XUnit.Fixtures.SailsFixture))]
diff --git a/net/tests/Sails.Remoting.Tests/Core/RemotingViaNodeClientTests.cs b/net/tests/Sails.Remoting.Tests/Core/RemotingViaNodeClientTests.cs
index 3f603faf..1024a7c7 100644
--- a/net/tests/Sails.Remoting.Tests/Core/RemotingViaNodeClientTests.cs
+++ b/net/tests/Sails.Remoting.Tests/Core/RemotingViaNodeClientTests.cs
@@ -1,14 +1,6 @@
using Sails.Remoting.Tests._Infra.XUnit.Fixtures;
-using Substrate.Gear.Api.Generated;
-using Substrate.Gear.Client;
-using Substrate.Gear.Client.Extensions;
using Substrate.Gear.Client.GearApi.Model.gprimitives;
-using Substrate.NET.Schnorrkel.Keys;
-using Substrate.NetApi;
-using Substrate.NetApi.Model.Extrinsics;
-using Substrate.NetApi.Model.Types;
using Substrate.NetApi.Model.Types.Primitive;
-using CodeId = Substrate.Gear.Api.Generated.Model.gprimitives.CodeId;
namespace Sails.Remoting.Tests.Core;
@@ -25,18 +17,9 @@ public RemotingViaNodeClientTests(SailsFixture sailsFixture)
});
var serviceProvider = serviceCollection.BuildServiceProvider();
this.remotingProvider = serviceProvider.GetRequiredService();
- this.remoting = this.remotingProvider.CreateRemoting(AliceAccount);
+ this.remoting = this.remotingProvider.CreateRemoting(SailsFixture.AliceAccount);
}
- private static readonly MiniSecret AliceMiniSecret
- = new(
- Utils.HexToByteArray("0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a"),
- ExpandMode.Ed25519);
- private static readonly Account AliceAccount
- = Account.Build(
- KeyType.Sr25519,
- AliceMiniSecret.ExpandToSecret().ToEd25519Bytes(),
- AliceMiniSecret.GetPair().Public.Key);
private static readonly Random Random = new((int)DateTime.UtcNow.Ticks);
private readonly SailsFixture sailsFixture;
@@ -51,8 +34,7 @@ public void Service_Provider_Resolves_Expected_Implementation()
public async Task Program_Activation_Works()
{
// Arrange
- var codeBytes = await this.sailsFixture.GetNoSvcsProgContractWasmAsync();
- var codeId = await this.UploadCodeAsync(codeBytes.AsReadOnlyCollection());
+ var codeId = await this.sailsFixture.GetDemoContractCodeIdAsync();
// Act
var encodedPayload = new Str("Default").Encode();
@@ -63,25 +45,24 @@ public async Task Program_Activation_Works()
CancellationToken.None);
// Assert
- var activationResult = await activationReply.ReadAsync(CancellationToken.None);
+ var (programId, payload) = await activationReply.ReadAsync(CancellationToken.None);
- var programIdStr = activationResult.ProgramId.ToHexString(); // Should be asserted against logs produced by node
+ var programIdStr = programId.ToHexString(); // Should be asserted against logs produced by node
- activationResult.Payload.Should().BeEquivalentTo(encodedPayload, options => options.WithStrictOrdering());
+ payload.Should().BeEquivalentTo(encodedPayload, options => options.WithStrictOrdering());
}
[Fact]
public async Task Sending_Message_To_Program_Works()
{
// Arrange
- var codeBytes = await this.sailsFixture.GetDemoContractWasmAsync();
- var codeId = await this.UploadCodeAsync(codeBytes.AsReadOnlyCollection());
+ var codeId = await this.sailsFixture.GetDemoContractCodeIdAsync();
var activationReply = await this.remoting.ActivateAsync(
codeId,
salt: BitConverter.GetBytes(Random.NextInt64()),
new Str("Default").Encode(),
CancellationToken.None);
- var activationResult = await activationReply.ReadAsync(CancellationToken.None);
+ var (programId, _) = await activationReply.ReadAsync(CancellationToken.None);
// Act
var encodedPayload = new Str("Counter").Encode()
@@ -89,7 +70,7 @@ public async Task Sending_Message_To_Program_Works()
.Concat(new U32(42).Encode())
.ToArray();
var messageReply = await this.remoting.MessageAsync(
- activationResult.ProgramId,
+ programId,
encodedPayload,
CancellationToken.None);
@@ -105,16 +86,15 @@ public async Task Sending_Message_To_Program_Works()
public async Task Querying_Program_State_Works()
{
// Arrange
- var codeBytes = await this.sailsFixture.GetDemoContractWasmAsync();
- var codeId = await this.UploadCodeAsync(codeBytes.AsReadOnlyCollection());
+ var codeId = await this.sailsFixture.GetDemoContractCodeIdAsync();
var activationReply = await this.remoting.ActivateAsync(
codeId,
salt: BitConverter.GetBytes(Random.NextInt64()),
new Str("Default").Encode(),
CancellationToken.None);
- var activationResult = await activationReply.ReadAsync(CancellationToken.None);
+ var (programId, _) = await activationReply.ReadAsync(CancellationToken.None);
var messageReply = await this.remoting.MessageAsync(
- activationResult.ProgramId,
+ programId,
encodedPayload: new Str("Counter").Encode()
.Concat(new Str("Add").Encode())
.Concat(new U32(42).Encode())
@@ -127,28 +107,13 @@ public async Task Querying_Program_State_Works()
.Concat(new Str("Value").Encode())
.ToArray();
var queryResult = await this.remoting.QueryAsync(
- activationResult.ProgramId,
+ programId,
encodedPayload,
CancellationToken.None);
// Assert
queryResult.Should().BeEquivalentTo(
- encodedPayload.Concat(new U32(42).Encode()).ToArray(),
+ [.. encodedPayload, .. new U32(42).Encode()],
options => options.WithStrictOrdering());
}
-
- private async Task UploadCodeAsync(IReadOnlyCollection codeBytes)
- {
- using (var nodeClient = new SubstrateClientExt(
- this.sailsFixture.GearNodeWsUrl,
- ChargeTransactionPayment.Default()))
- {
- await nodeClient.ConnectAsync();
-
- return await nodeClient.UploadCodeAsync(
- AliceAccount,
- codeBytes,
- CancellationToken.None);
- }
- }
}
diff --git a/net/tests/Sails.Remoting.Tests/GlobalUsings.cs b/net/tests/Sails.Remoting.Tests/GlobalUsings.cs
index d22948a8..8310e6fc 100644
--- a/net/tests/Sails.Remoting.Tests/GlobalUsings.cs
+++ b/net/tests/Sails.Remoting.Tests/GlobalUsings.cs
@@ -1,19 +1,12 @@
global using System;
-global using System.Collections.Generic;
-global using System.IO;
global using System.Linq;
-global using System.Text.RegularExpressions;
global using System.Threading;
global using System.Threading.Tasks;
-global using EnsureThat;
global using FluentAssertions;
global using Microsoft.Extensions.DependencyInjection;
global using Sails.Remoting.Abstractions.Core;
global using Sails.Remoting.Core;
global using Sails.Remoting.DependencyInjection;
global using Sails.Remoting.Options;
-global using Sails.Tests.Shared.Containers;
-global using Sails.Tests.Shared.Git;
global using Sails.Tests.Shared.XUnit;
global using Xunit;
-global using Xunit.Abstractions;
diff --git a/net/tests/Sails.Remoting.Tests/Sails.Remoting.Tests.csproj b/net/tests/Sails.Remoting.Tests/Sails.Remoting.Tests.csproj
index bf619d93..a940f93f 100644
--- a/net/tests/Sails.Remoting.Tests/Sails.Remoting.Tests.csproj
+++ b/net/tests/Sails.Remoting.Tests/Sails.Remoting.Tests.csproj
@@ -15,7 +15,6 @@
-
all
@@ -24,11 +23,6 @@
-
-
diff --git a/net/tests/Sails.Remoting.Tests/_Infra/XUnit/Fixtures/SailsFixture.cs b/net/tests/Sails.Remoting.Tests/_Infra/XUnit/Fixtures/SailsFixture.cs
index 0fa13585..aba7cea8 100644
--- a/net/tests/Sails.Remoting.Tests/_Infra/XUnit/Fixtures/SailsFixture.cs
+++ b/net/tests/Sails.Remoting.Tests/_Infra/XUnit/Fixtures/SailsFixture.cs
@@ -1,138 +1,9 @@
-using Nito.AsyncEx;
-using Sails.Remoting.Tests._Infra.XUnit.Fixtures;
+namespace Sails.Remoting.Tests._Infra.XUnit.Fixtures;
-[assembly: AssemblyFixture(typeof(SailsFixture))]
-
-namespace Sails.Remoting.Tests._Infra.XUnit.Fixtures;
-
-public sealed partial class SailsFixture : IAsyncLifetime
+public sealed class SailsFixture : Sails.Tests.Shared.XUnit.Fixtures.SailsFixture
{
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(
- () => this.DownloadStringAsset("demo.idl"),
- AsyncLazyFlags.RetryOnFailure);
- this.demoContractWasm = new AsyncLazy(
- () => this.DownloadOctetAsset("demo.wasm"),
- AsyncLazyFlags.RetryOnFailure);
- this.noSvcsProgContractIdl = new AsyncLazy(
- () => this.DownloadStringAsset("no-svcs-prog.idl"),
- AsyncLazyFlags.RetryOnFailure);
- this.noSvcsProgContractWasm = new AsyncLazy(
- () => 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 demoContractIdl;
- private readonly AsyncLazy demoContractWasm;
- private readonly AsyncLazy noSvcsProgContractIdl;
- private readonly AsyncLazy noSvcsProgContractWasm;
- private GearNodeContainer? gearNodeContainer;
-
- public Uri GearNodeWsUrl => this.gearNodeContainer?.WsUrl
- ?? throw new InvalidOperationException("Gear node container is not initialized.");
-
- public async Task DisposeAsync()
+ : base("remoting-tests")
{
- 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 GetDemoContractIdlAsync()
- => this.demoContractIdl.Task;
-
- public async Task> GetDemoContractWasmAsync()
- {
- var byteStream = await this.demoContractWasm;
- Ensure.Comparable.IsLte(byteStream.Length, int.MaxValue);
- return new ReadOnlyMemory(byteStream.GetBuffer(), start: 0, length: (int)byteStream.Length);
- }
-
- public Task GetNoSvcsProgContractIdlAsync()
- => this.noSvcsProgContractIdl.Task;
-
- public async Task> GetNoSvcsProgContractWasmAsync()
- {
- var byteStream = await this.noSvcsProgContractWasm;
- Ensure.Comparable.IsLte(byteStream.Length, int.MaxValue);
- return new ReadOnlyMemory(byteStream.GetBuffer(), start: 0, length: (int)byteStream.Length);
- }
-
- private async Task 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 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 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();
}
diff --git a/net/tests/Sails.Remoting.Tests/_Infra/XUnit/TestFramework.cs b/net/tests/Sails.Remoting.Tests/_Infra/XUnit/TestFramework.cs
deleted file mode 100644
index 3580ce24..00000000
--- a/net/tests/Sails.Remoting.Tests/_Infra/XUnit/TestFramework.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-[assembly: 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)
- {
- }
-}
diff --git a/net/tests/Sails.Tests.Shared/Containers/GearNodeContainer.cs b/net/tests/Sails.Tests.Shared/Containers/GearNodeContainer.cs
index 8205d792..12fbc477 100644
--- a/net/tests/Sails.Tests.Shared/Containers/GearNodeContainer.cs
+++ b/net/tests/Sails.Tests.Shared/Containers/GearNodeContainer.cs
@@ -16,15 +16,16 @@ 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)
+ public GearNodeContainer(string consumerName, string gearNodeVersion, bool reuse)
{
+ EnsureArg.IsNotNullOrWhiteSpace(consumerName, nameof(consumerName));
EnsureArg.IsNotNullOrWhiteSpace(gearNodeVersion, nameof(gearNodeVersion));
this.nodeInitializationDetector = new NodeInitializationDetector();
this.container = new ContainerBuilder()
- .WithName("gear-node-for-tests")
+ .WithName($"gear-node-for-{consumerName.ToLower()}")
.WithImage($"ghcr.io/gear-tech/node:v{gearNodeVersion}")
- .WithPortBinding(RpcPort, RpcPort) // Use WithPortBinding(RpcPort, true) if random host port is required
+ .WithPortBinding(RpcPort, true)
.WithEntrypoint("gear")
.WithCommand(
"--rpc-external", // --rpc-external is required for listening on all interfaces
@@ -38,13 +39,13 @@ public GearNodeContainer(string gearNodeVersion, bool reuse)
}
private const ushort RpcPort = 9944;
- private static readonly TimeSpan NodeInitializationTimeout = TimeSpan.FromSeconds(10);
+ private static readonly TimeSpan NodeInitializationTimeout = TimeSpan.FromSeconds(30);
private readonly NodeInitializationDetector nodeInitializationDetector;
private readonly IContainer container;
private readonly bool reuse;
- public Uri WsUrl => new($"ws://localhost:{this.container.GetMappedPublicPort(9944)}");
+ public Uri WsUrl => new($"ws://localhost:{this.container.GetMappedPublicPort(RpcPort)}");
public ValueTask DisposeAsync()
// Do not dispose container if it is reused otherwise it will be stopped
@@ -57,8 +58,8 @@ public ValueTask DisposeAsync()
public async Task StartAsync()
{
- await this.container.StartAsync();
- await this.nodeInitializationDetector.IsInitializedAsync(NodeInitializationTimeout);
+ await this.container.StartAsync().ConfigureAwait(false);
+ await this.nodeInitializationDetector.IsInitializedAsync(NodeInitializationTimeout).ConfigureAwait(false);
}
private sealed class NodeInitializationDetector : IOutputConsumer
@@ -81,12 +82,12 @@ public NodeInitializationDetector()
public async Task IsInitializedAsync(TimeSpan maxWaitTime)
{
var timeoutTask = Task.Delay(maxWaitTime);
- var completedTask = await Task.WhenAny(this.isNodeInitialized.Task, timeoutTask);
+ var completedTask = await Task.WhenAny(this.isNodeInitialized.Task, timeoutTask).ConfigureAwait(false);
if (completedTask == timeoutTask)
{
this.isNodeInitialized.SetException(
new TimeoutException($"Node initialization timed out after {maxWaitTime}."));
- await this.isNodeInitialized.Task;
+ await this.isNodeInitialized.Task.ConfigureAwait(false);
}
}
diff --git a/net/tests/Sails.Tests.Shared/Sails.Tests.Shared.csproj b/net/tests/Sails.Tests.Shared/Sails.Tests.Shared.csproj
index bf5ab517..2df2bbc5 100644
--- a/net/tests/Sails.Tests.Shared/Sails.Tests.Shared.csproj
+++ b/net/tests/Sails.Tests.Shared/Sails.Tests.Shared.csproj
@@ -8,8 +8,14 @@
+
+
+
+
+
+
diff --git a/net/tests/Sails.Tests.Shared/XUnit/Fixtures/SailsFixture.cs b/net/tests/Sails.Tests.Shared/XUnit/Fixtures/SailsFixture.cs
new file mode 100644
index 00000000..c027a967
--- /dev/null
+++ b/net/tests/Sails.Tests.Shared/XUnit/Fixtures/SailsFixture.cs
@@ -0,0 +1,229 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using EnsureThat;
+using Nito.AsyncEx;
+using Polly;
+using Polly.Retry;
+using Sails.Tests.Shared.Containers;
+using Sails.Tests.Shared.Git;
+using Substrate.Gear.Api.Generated;
+using Substrate.Gear.Api.Generated.Model.gprimitives;
+using Substrate.Gear.Client;
+using Substrate.NET.Schnorrkel.Keys;
+using Substrate.NetApi;
+using Substrate.NetApi.Model.Extrinsics;
+using Substrate.NetApi.Model.Types;
+using Xunit;
+
+namespace Sails.Tests.Shared.XUnit.Fixtures;
+
+public partial class SailsFixture : IAsyncLifetime
+{
+ public SailsFixture(string consumerName)
+ : this(consumerName, sailsRsVersion: "0.6.3")
+ {
+ }
+
+ public SailsFixture(string consumerName, string sailsRsVersion)
+ {
+ EnsureArg.IsNotNullOrWhiteSpace(consumerName, nameof(consumerName));
+ EnsureArg.IsNotNullOrWhiteSpace(sailsRsVersion, nameof(sailsRsVersion));
+
+ this.consumerName = consumerName;
+ this.sailsRsReleaseTag = $"rs/v{sailsRsVersion}";
+ this.demoContractIdl = new AsyncLazy(
+ () => this.DownloadStringAssetAsync("demo.idl"),
+ AsyncLazyFlags.RetryOnFailure);
+ this.demoContractWasm = new AsyncLazy(
+ () => this.DownloadOctetAssetAsync("demo.wasm"),
+ AsyncLazyFlags.RetryOnFailure);
+ this.demoContractCodeId = new AsyncLazy(
+ async () =>
+ {
+ var codeBytes = await this.GetDemoContractWasmAsync().ConfigureAwait(false);
+ return await UploadCodeRetryPolicy.ExecuteAsync(
+ () => this.UploadCodeAsync(codeBytes.ToArray()))
+ .ConfigureAwait(false);
+ },
+ AsyncLazyFlags.RetryOnFailure);
+ this.noSvcsProgContractIdl = new AsyncLazy(
+ () => this.DownloadStringAssetAsync("no-svcs-prog.idl"),
+ AsyncLazyFlags.RetryOnFailure);
+ this.noSvcsProgContractWasm = new AsyncLazy(
+ () => this.DownloadOctetAssetAsync("no_svcs_prog.wasm"),
+ AsyncLazyFlags.RetryOnFailure);
+ this.noSvcsProgContractCodeId = new AsyncLazy(
+ async () =>
+ {
+ var codeBytes = await this.GetNoSvcsProgContractWasmAsync().ConfigureAwait(false);
+ return await UploadCodeRetryPolicy.ExecuteAsync(
+ () => this.UploadCodeAsync(codeBytes.ToArray()))
+ .ConfigureAwait(false);
+ },
+ AsyncLazyFlags.RetryOnFailure);
+ this.gearNodeContainer = null;
+ }
+
+ private static readonly GithubDownloader GithubDownloader = new("gear-tech", "sails");
+ private static readonly AsyncRetryPolicy UploadCodeRetryPolicy = Policy.Handle(
+ exception =>
+ exception.ErrorCode == 1014 && exception.Message.StartsWith("Priority is too low"))
+ .WaitAndRetryAsync(
+ 10,
+ retry => retry * TimeSpan.FromSeconds(1));
+
+ private readonly string consumerName;
+ private readonly string sailsRsReleaseTag;
+ private readonly AsyncLazy demoContractIdl;
+ private readonly AsyncLazy demoContractWasm;
+ private readonly AsyncLazy demoContractCodeId;
+ private readonly AsyncLazy noSvcsProgContractIdl;
+ private readonly AsyncLazy noSvcsProgContractWasm;
+ private readonly AsyncLazy noSvcsProgContractCodeId;
+ private GearNodeContainer? gearNodeContainer;
+
+ public static MiniSecret AliceMiniSecret { get; }
+ = new(
+ // Taken from 'gear key inspect //Alice' output
+ Utils.HexToByteArray("0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a"),
+ ExpandMode.Ed25519);
+ public static Account AliceAccount { get; }
+ = Account.Build(
+ KeyType.Sr25519,
+ AliceMiniSecret.ExpandToSecret().ToEd25519Bytes(),
+ AliceMiniSecret.GetPair().Public.Key);
+ public static MiniSecret BobMiniSecret { get; }
+ = new(
+ // Taken from 'gear key inspect //Bob' output
+ Utils.HexToByteArray("0x398f0c28f98885e046333d4a41c19cee4c37368a9832c6502f6cfd182e2aef89"),
+ ExpandMode.Ed25519);
+ public static Account BobAccount { get; }
+ = Account.Build(
+ KeyType.Sr25519,
+ BobMiniSecret.ExpandToSecret().ToEd25519Bytes(),
+ BobMiniSecret.GetPair().Public.Key);
+
+ 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().ConfigureAwait(false);
+ this.gearNodeContainer = null;
+ }
+ if (this.demoContractWasm.IsStarted)
+ {
+ await (await this.demoContractWasm).DisposeAsync().ConfigureAwait(false);
+ }
+ if (this.noSvcsProgContractWasm.IsStarted)
+ {
+ await (await this.noSvcsProgContractWasm).DisposeAsync().ConfigureAwait(false);
+ }
+ }
+
+ public async Task InitializeAsync()
+ {
+ var sailsRsCargoToml = await this.DownloadSailsRsCargoTomlAsync().ConfigureAwait(false);
+
+ 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(this.consumerName, gearNodeVersion, reuse: true);
+ await this.gearNodeContainer.StartAsync().ConfigureAwait(false);
+ }
+
+ public Task GetDemoContractIdlAsync()
+ => this.demoContractIdl.Task;
+
+ public async Task> GetDemoContractWasmAsync()
+ {
+ var byteStream = await this.demoContractWasm;
+ Ensure.Comparable.IsLte(byteStream.Length, int.MaxValue);
+ return new ReadOnlyMemory(byteStream.GetBuffer(), start: 0, length: (int)byteStream.Length);
+ }
+
+ public Task GetDemoContractCodeIdAsync()
+ => this.demoContractCodeId.Task;
+
+ public Task GetNoSvcsProgContractIdlAsync()
+ => this.noSvcsProgContractIdl.Task;
+
+ public async Task> GetNoSvcsProgContractWasmAsync()
+ {
+ var byteStream = await this.noSvcsProgContractWasm;
+ Ensure.Comparable.IsLte(byteStream.Length, int.MaxValue);
+ return new ReadOnlyMemory(byteStream.GetBuffer(), start: 0, length: (int)byteStream.Length);
+ }
+
+ public Task GetNoSvcsProgContractCodeIdAsync()
+ => this.noSvcsProgContractCodeId.Task;
+
+ private async Task DownloadStringAssetAsync(string assetName)
+ {
+ var downloadStream = await GithubDownloader.DownloadReleaseAssetAsync(
+ this.sailsRsReleaseTag,
+ assetName,
+ CancellationToken.None)
+ .ConfigureAwait(false);
+ using (var reader = new StreamReader(downloadStream, leaveOpen: false))
+ {
+ return await reader.ReadToEndAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+ }
+
+ private async Task DownloadOctetAssetAsync(string assetName)
+ {
+ var downloadStream = await GithubDownloader.DownloadReleaseAssetAsync(
+ this.sailsRsReleaseTag,
+ assetName,
+ CancellationToken.None)
+ .ConfigureAwait(false);
+ var memoryStream = new MemoryStream();
+ await downloadStream.CopyToAsync(memoryStream).ConfigureAwait(false);
+ return memoryStream;
+ }
+
+ private async Task DownloadSailsRsCargoTomlAsync()
+ {
+ var downloadStream = await GithubDownloader.DownloadFileFromTagAsync(
+ this.sailsRsReleaseTag,
+ "Cargo.toml",
+ CancellationToken.None)
+ .ConfigureAwait(false);
+ using (var reader = new StreamReader(downloadStream, leaveOpen: false))
+ {
+ return await reader.ReadToEndAsync(CancellationToken.None).ConfigureAwait(false);
+ }
+ }
+
+ private async Task UploadCodeAsync(IReadOnlyCollection codeBytes)
+ {
+ using (var nodeClient = new SubstrateClientExt(
+ this.GearNodeWsUrl,
+ ChargeTransactionPayment.Default()))
+ {
+ await nodeClient.ConnectAsync().ConfigureAwait(false);
+
+ return await nodeClient.UploadCodeAsync(
+ AliceAccount,
+ codeBytes,
+ CancellationToken.None)
+ .ConfigureAwait(false);
+ }
+ }
+
+ [GeneratedRegex(@"gstd\s*=\s*""=?(\d+\.\d+\.\d+)""")]
+ private static partial Regex GStdDependencyRegex();
+}
diff --git a/net/tests/Sails.Tests.Shared/XUnit/TestAssemblyRunner.cs b/net/tests/Sails.Tests.Shared/XUnit/TestAssemblyRunner.cs
index 90f4521a..90abc75b 100644
--- a/net/tests/Sails.Tests.Shared/XUnit/TestAssemblyRunner.cs
+++ b/net/tests/Sails.Tests.Shared/XUnit/TestAssemblyRunner.cs
@@ -30,7 +30,7 @@ public TestAssemblyRunner(
protected override async Task AfterTestAssemblyStartingAsync()
{
- await base.AfterTestAssemblyStartingAsync();
+ await base.AfterTestAssemblyStartingAsync().ConfigureAwait(false);
var requiredFixtureTypes = new HashSet();
@@ -71,7 +71,7 @@ protected override async Task AfterTestAssemblyStartingAsync()
foreach (var initializable in this.assemblyFixtureMappings.Values.OfType())
{
- await this.Aggregator.RunAsync(initializable.InitializeAsync);
+ await this.Aggregator.RunAsync(initializable.InitializeAsync).ConfigureAwait(false);
}
}
diff --git a/net/tests/Sails.Tests.Shared/XUnit/TestFramework.cs b/net/tests/Sails.Tests.Shared/XUnit/TestFramework.cs
index 18820c25..13956cfb 100644
--- a/net/tests/Sails.Tests.Shared/XUnit/TestFramework.cs
+++ b/net/tests/Sails.Tests.Shared/XUnit/TestFramework.cs
@@ -4,9 +4,9 @@
namespace Sails.Tests.Shared.XUnit;
-public abstract class TestFramework : XunitTestFramework
+public class TestFramework : XunitTestFramework
{
- protected TestFramework(IMessageSink messageSink)
+ public TestFramework(IMessageSink messageSink)
: base(messageSink)
{
}
diff --git a/net/tests/Sails.Tests.Shared/XUnit/TestFrameworkExecutor.cs b/net/tests/Sails.Tests.Shared/XUnit/TestFrameworkExecutor.cs
index be9079e6..55a424ce 100644
--- a/net/tests/Sails.Tests.Shared/XUnit/TestFrameworkExecutor.cs
+++ b/net/tests/Sails.Tests.Shared/XUnit/TestFrameworkExecutor.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using Xunit.Abstractions;
using Xunit.Sdk;
@@ -15,6 +16,9 @@ public TestFrameworkExecutor(
{
}
+ [SuppressMessage(
+ "Usage", "VSTHRD100:Avoid async void methods",
+ Justification = "All exceptions should be added into the aggregator")]
protected override async void RunTestCases(
IEnumerable testCases,
IMessageSink executionMessageSink,
@@ -27,7 +31,7 @@ protected override async void RunTestCases(
executionMessageSink,
executionOptions))
{
- await assemblyRunner.RunAsync();
+ await assemblyRunner.RunAsync().ConfigureAwait(false);
}
}
}
diff --git a/nuget.config b/nuget.config
new file mode 100644
index 00000000..67eb9d4d
--- /dev/null
+++ b/nuget.config
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+