Skip to content

Commit

Permalink
Add Xunit TestFrameworkAttribute support (#116)
Browse files Browse the repository at this point in the history
* Add Xunit TestFrameworkAttribute support

* Update specs
  • Loading branch information
Arkatufus committed Dec 23, 2021
1 parent 7ff1f5d commit 350d87c
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 45 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Xunit;

[assembly: CollectionBehavior(DisableTestParallelization = true)]
[assembly: TestFramework("Akka.MultiNode.TestAdapter.MultiNodeTestFramework", "Akka.MultiNode.TestAdapter")]
namespace Akka.MultiNode.TestAdapter.SampleTests.Metadata
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// </copyright>
//-----------------------------------------------------------------------

using System.Linq;
using Akka.Remote.TestKit;

namespace Akka.MultiNode.TestAdapter.Tests.Internal.MultiNodeTestRunnerDiscovery
Expand Down Expand Up @@ -139,6 +140,31 @@ public FloodyChildSpec3(FloodyConfig config) : base(config)
}
}

public class NoReflectionConfig : MultiNodeConfig
{
public NoReflectionConfig()
{
foreach(var i in Enumerable.Range(1, 10))
{
Role("node-" + i);
}
}
}

public class NoReflectionSpec : MultiNodeSpec
{
public NoReflectionSpec(NoReflectionConfig config): base(config, typeof(NoReflectionSpec))
{
}

[MultiNodeFact(Skip = "Only for discovery tests")]
public void Dummy()
{
}

protected override int InitialParticipantsValueFactory { get; }
}

public class DiverseConfig : MultiNodeConfig
{
public RoleName RoleProp { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class DiscoverySpec
public void No_abstract_classes()
{
var discoveredSpecs = DiscoverSpecs();
Assert.DoesNotContain(discoveredSpecs, s => s.TypeName == nameof(DiscoveryCases.NoAbstractClassesSpec));
Assert.False(discoveredSpecs.ContainsKey(KeyFromSpecName(nameof(DiscoveryCases.NoAbstractClassesSpec))));
}

[Fact(DisplayName = "Deeply inherited classes are discoverable")]
Expand All @@ -30,9 +30,7 @@ public void Deeply_inherited_are_ok()
var discoveredSpecs = DiscoverSpecs();
Assert.Equal(
"DeeplyInheritedChildRole",
discoveredSpecs
.FirstOrDefault(s => s.TypeName == KeyFromSpecName(nameof(DiscoveryCases.DeeplyInheritedChildSpec)))?.Nodes
.First().Role);
discoveredSpecs[KeyFromSpecName(nameof(DiscoveryCases.DeeplyInheritedChildSpec))].First().Role);
}

[Fact(DisplayName = "Child test class with default constructors are ok")]
Expand All @@ -52,25 +50,19 @@ public void Child_class_with_default_constructor_are_ok()
public void Discovered_count_equals_number_of_roles_mult_specs()
{
var discoveredSpecs = DiscoverSpecs();
Assert.Equal(5, discoveredSpecs
.FirstOrDefault(s => s.TypeName == KeyFromSpecName(nameof(DiscoveryCases.FloodyChildSpec1)))?.Nodes.Count);
Assert.Equal(5, discoveredSpecs
.FirstOrDefault(s => s.TypeName == KeyFromSpecName(nameof(DiscoveryCases.FloodyChildSpec2)))?.Nodes.Count);
Assert.Equal(5, discoveredSpecs
.FirstOrDefault(s => s.TypeName == KeyFromSpecName(nameof(DiscoveryCases.FloodyChildSpec3)))?.Nodes.Count);
Assert.Equal(5, discoveredSpecs[KeyFromSpecName(nameof(DiscoveryCases.FloodyChildSpec1))].Count);
Assert.Equal(5, discoveredSpecs[KeyFromSpecName(nameof(DiscoveryCases.FloodyChildSpec2))].Count);
Assert.Equal(5, discoveredSpecs[KeyFromSpecName(nameof(DiscoveryCases.FloodyChildSpec3))].Count);
}

[Fact(DisplayName = "Only public props and fields are considered when looking for RoleNames")]
public void Public_props_and_fields_are_considered()
[Fact(DisplayName = "Only the MultiNodeConfig.Roles property is used to compute the number of Roles in MultiNodeFact")]
public void Only_MultiNodeConfig_role_count_used()
{
var discoveredSpecs = DiscoverSpecs();
Assert.Equal(
discoveredSpecs
.FirstOrDefault(test => test.TypeName == KeyFromSpecName(nameof(DiscoveryCases.DiverseSpec)))?.Nodes
.Select(n => n.Role), new[] {"RoleProp", "RoleField"});
Assert.Equal(10, discoveredSpecs[KeyFromSpecName(nameof(DiscoveryCases.NoReflectionSpec))].Select(c => c.Role).Count());
}

private static List<MultiNodeTestCase> DiscoverSpecs()
private static Dictionary<string, List<NodeTest>> DiscoverSpecs()
{
var assemblyPath = new Uri(typeof(DiscoveryCases).GetTypeInfo().Assembly.Location).LocalPath;

Expand All @@ -80,7 +72,9 @@ private static List<MultiNodeTestCase> DiscoverSpecs()
{
controller.Find(false, discovery, TestFrameworkOptions.ForDiscovery());
discovery.Finished.WaitOne();
return discovery.TestCases;
return discovery
.TestCases
.ToDictionary(t => t.TypeName, t => t.Nodes);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.IO;
using Xunit.Abstractions;
using Xunit.Sdk;

namespace Akka.MultiNode.TestAdapter.Internal
{
internal class CollectionPerSessionTestCollectionFactory : IXunitTestCollectionFactory
{
private readonly Dictionary<IAssemblyInfo, TestCollection> _collectionCache =
new Dictionary<IAssemblyInfo, TestCollection>();

public ITestCollection Get(ITypeInfo testClass)
{
if (_collectionCache.TryGetValue(testClass.Assembly, out var collection))
return collection;

collection = new TestCollection(
new TestAssembly(testClass.Assembly),
null,
$"MultiNode test collection for {Path.GetFileName(testClass.Assembly.AssemblyPath)}");
_collectionCache[testClass.Assembly] = collection;
return collection;
}

public string DisplayName => "collection-per-session";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Xunit.Abstractions;
using Xunit.Sdk;

namespace Akka.MultiNode.TestAdapter.Internal
{
internal class MultiNodeTestAssemblyRunner : XunitTestAssemblyRunner
{
public MultiNodeTestAssemblyRunner(
ITestAssembly testAssembly,
IEnumerable<IXunitTestCase> testCases,
IMessageSink diagnosticMessageSink,
IMessageSink executionMessageSink,
ITestFrameworkExecutionOptions executionOptions)
: base(testAssembly, testCases, diagnosticMessageSink, executionMessageSink, executionOptions)
{
}

protected override async Task<RunSummary> RunTestCollectionsAsync(IMessageBus messageBus, CancellationTokenSource cancellationTokenSource)
{
var summary = new RunSummary();

foreach (var (testCollection, testCases) in OrderTestCollections())
{
summary.Aggregate(await RunTestCollectionAsync(messageBus, testCollection, testCases, cancellationTokenSource));
if (cancellationTokenSource.IsCancellationRequested)
break;
}

return summary;
}
}
}
12 changes: 2 additions & 10 deletions src/Akka.MultiNode.TestAdapter/Internal/MultiNodeTestCase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,16 +143,8 @@ private IEnumerable<RoleName> RoleNames(Type specType)
{
var configType = ctorWithConfig.GetParameters().First().ParameterType;
var args = ConfigConstructorParamValues(configType);
var configInstance = Activator.CreateInstance(configType, args);
var roleType = typeof(RoleName);
var configProps = configType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
var roleProps = configProps.Where(p => p.PropertyType == roleType && p.Name != "Myself")
.Select(p => (RoleName) p.GetValue(configInstance));
var configFields = configType.GetFields(BindingFlags.Instance | BindingFlags.Public);
var roleFields = configFields.Where(f => f.FieldType == roleType && f.Name != "Myself")
.Select(f => (RoleName) f.GetValue(configInstance));
var roles = roleProps.Concat(roleFields).Distinct();
return roles;
var configInstance = (MultiNodeConfig) Activator.CreateInstance(configType, args);
return configInstance.Roles;
}
catch (Exception e)
{
Expand Down
20 changes: 9 additions & 11 deletions src/Akka.MultiNode.TestAdapter/Internal/MultiNodeTestCaseRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -225,16 +225,12 @@ protected override async Task<RunSummary> RunTestAsync()

private async Task DumpAggregatedSpecLogs(RunSummary summary, IActorRef timelineCollector)
{
var dumpPath = Path.GetFullPath(Path.Combine(Path.Combine(Options.OutputDirectory, TestCase.DisplayName), "aggregated.txt"));
var failedSpecPath = Path.GetFullPath(Path.Combine(Options.OutputDirectory, Options.FailedSpecsDirectory, $"{TestCase.DisplayName}.txt"));

if (!Options.AppendLogOutput)
{
if(File.Exists(dumpPath))
File.Delete(dumpPath);
if(File.Exists(failedSpecPath))
File.Delete(failedSpecPath);
}
var dumpFolder = Path.GetFullPath(Path.Combine(Options.OutputDirectory, TestCase.DisplayName));
var dumpPath = Path.Combine(dumpFolder, "aggregated.txt");

Directory.CreateDirectory(dumpFolder);
if (!Options.AppendLogOutput && File.Exists(dumpPath))
File.Delete(dumpPath);

var logLines = await timelineCollector.Ask<string[]>(new TimelineLogCollectorActor.GetLog());

Expand All @@ -243,8 +239,10 @@ private async Task DumpAggregatedSpecLogs(RunSummary summary, IActorRef timeline

if (summary.Failed > 0)
{
Directory.CreateDirectory(Path.GetDirectoryName(failedSpecPath));
var failedSpecFolder = Path.GetFullPath(Path.Combine(Options.OutputDirectory, Options.FailedSpecsDirectory));
var failedSpecPath = Path.Combine(failedSpecFolder, $"{TestCase.DisplayName}.txt");

Directory.CreateDirectory(failedSpecFolder);
if(!Options.AppendLogOutput && File.Exists(failedSpecPath))
File.Delete(failedSpecPath);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Collections.Generic;
using System.Reflection;
using Xunit.Abstractions;
using Xunit.Sdk;

namespace Akka.MultiNode.TestAdapter.Internal
{
internal class MultiNodeTestFrameworkExecutor : XunitTestFrameworkExecutor
{
public MultiNodeTestFrameworkExecutor(
AssemblyName assemblyName,
ISourceInformationProvider sourceInformationProvider,
IMessageSink diagnosticMessageSink)
: base(assemblyName, sourceInformationProvider, diagnosticMessageSink)
{
}

protected override async void RunTestCases(IEnumerable<IXunitTestCase> testCases, IMessageSink executionMessageSink,
ITestFrameworkExecutionOptions executionOptions)
{
using (var assemblyRunner = new MultiNodeTestAssemblyRunner(TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink, executionOptions))
await assemblyRunner.RunAsync();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,11 @@ private void SetReceive()
if (_currentSpecRunActor == null) return;
_currentSpecRunActor.Forward(message);
});
Receive<BeginNewSpec>(spec => ReceiveBeginSpecRun(spec));
ReceiveAsync<EndSpec>(spec => ReceiveEndSpecRun(spec));
Receive<BeginNewSpec>(ReceiveBeginSpecRun);
ReceiveAsync<EndSpec>(spec => _currentSpecRunActor != null ? ReceiveEndSpecRun(spec) : Task.CompletedTask);
Receive<RequestTestRunState>(state => Sender.Tell(TestRunData.Copy(TestRunPassed(TestRunData))));
Receive<SubscribeFactCompletionMessages>(messages => AddSubscriber(messages));
Receive<UnsubscribeFactCompletionMessages>(messages => RemoveSubscriber(messages));
Receive<SubscribeFactCompletionMessages>(AddSubscriber);
Receive<UnsubscribeFactCompletionMessages>(RemoveSubscriber);
ReceiveAsync<EndTestRun>(async run =>
{
//clean up the current spec, if it hasn't been done already
Expand Down
29 changes: 29 additions & 0 deletions src/Akka.MultiNode.TestAdapter/MultiNodeTestFramework.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Reflection;
using Akka.MultiNode.TestAdapter.Internal;
using Xunit.Abstractions;
using Xunit.Sdk;

namespace Akka.MultiNode.TestAdapter
{
public class MultiNodeTestFramework : TestFramework
{
public MultiNodeTestFramework(IMessageSink diagnosticMessageSink) : base(diagnosticMessageSink)
{
}

protected override ITestFrameworkDiscoverer CreateDiscoverer(IAssemblyInfo assemblyInfo)
{
return new XunitTestFrameworkDiscoverer(
assemblyInfo: assemblyInfo,
sourceProvider: SourceInformationProvider,
diagnosticMessageSink: DiagnosticMessageSink,
collectionFactory: new CollectionPerSessionTestCollectionFactory());
}

protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName)
{
return new MultiNodeTestFrameworkExecutor(assemblyName, SourceInformationProvider, DiagnosticMessageSink);
}
}
}

0 comments on commit 350d87c

Please sign in to comment.