Add AssemblyFixture to our test infra

This is a feature that we're using in Templates and Blazor E2E tests to manage selenium.

It's a general purpose kind of thing, so it makes sense to make it more general. This requires using the
`[assembly: TestFramework()]`.
Also fixed a bug here where this feature broke collection fixtures.
\n\nCommit migrated from 208d44a985
This commit is contained in:
Ryan Nowak 2019-09-11 16:31:38 -07:00
parent b1987c75cb
commit aadc979baf
9 changed files with 286 additions and 0 deletions

View File

@ -0,0 +1,83 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Testing
{
public class AspNetTestAssemblyRunner : XunitTestAssemblyRunner
{
private readonly Dictionary<Type, object> _assemblyFixtureMappings = new Dictionary<Type, object>();
public AspNetTestAssemblyRunner(
ITestAssembly testAssembly,
IEnumerable<IXunitTestCase> testCases,
IMessageSink diagnosticMessageSink,
IMessageSink executionMessageSink,
ITestFrameworkExecutionOptions executionOptions)
: base(testAssembly, testCases, diagnosticMessageSink, executionMessageSink, executionOptions)
{
}
protected override async Task AfterTestAssemblyStartingAsync()
{
await base.AfterTestAssemblyStartingAsync();
// Find all the AssemblyFixtureAttributes on the test assembly
Aggregator.Run(() =>
{
var fixturesAttributes = ((IReflectionAssemblyInfo)TestAssembly.Assembly)
.Assembly
.GetCustomAttributes(typeof(AssemblyFixtureAttribute), false)
.Cast<AssemblyFixtureAttribute>()
.ToList();
// Instantiate all the fixtures
foreach (var fixtureAttribute in fixturesAttributes)
{
var ctorWithDiagnostics = fixtureAttribute.FixtureType.GetConstructor(new[] { typeof(IMessageSink) });
if (ctorWithDiagnostics != null)
{
_assemblyFixtureMappings[fixtureAttribute.FixtureType] = Activator.CreateInstance(fixtureAttribute.FixtureType, DiagnosticMessageSink);
}
else
{
_assemblyFixtureMappings[fixtureAttribute.FixtureType] = Activator.CreateInstance(fixtureAttribute.FixtureType);
}
}
});
}
protected override Task BeforeTestAssemblyFinishedAsync()
{
// Dispose fixtures
foreach (var disposable in _assemblyFixtureMappings.Values.OfType<IDisposable>())
{
Aggregator.Run(disposable.Dispose);
}
return base.BeforeTestAssemblyFinishedAsync();
}
protected override Task<RunSummary> RunTestCollectionAsync(
IMessageBus messageBus,
ITestCollection testCollection,
IEnumerable<IXunitTestCase> testCases,
CancellationTokenSource cancellationTokenSource)
=> new AspNetTestCollectionRunner(
_assemblyFixtureMappings,
testCollection,
testCases,
DiagnosticMessageSink,
messageBus,
TestCaseOrderer,
new ExceptionAggregator(Aggregator),
cancellationTokenSource).RunAsync();
}
}

View File

@ -0,0 +1,67 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Testing
{
public class AspNetTestCollectionRunner : XunitTestCollectionRunner
{
private readonly IDictionary<Type, object> _assemblyFixtureMappings;
private readonly IMessageSink _diagnosticMessageSink;
public AspNetTestCollectionRunner(
Dictionary<Type, object> assemblyFixtureMappings,
ITestCollection testCollection,
IEnumerable<IXunitTestCase> testCases,
IMessageSink diagnosticMessageSink,
IMessageBus messageBus,
ITestCaseOrderer testCaseOrderer,
ExceptionAggregator aggregator,
CancellationTokenSource cancellationTokenSource)
: base(testCollection, testCases, diagnosticMessageSink, messageBus, testCaseOrderer, aggregator, cancellationTokenSource)
{
_assemblyFixtureMappings = assemblyFixtureMappings;
_diagnosticMessageSink = diagnosticMessageSink;
}
protected override async Task AfterTestCollectionStartingAsync()
{
await base.AfterTestCollectionStartingAsync();
// note: We pass the assembly fixtures into the runner as ICollectionFixture<> - this seems to work OK without any
// drawbacks. It's reasonable that we could add IAssemblyFixture<> and related plumbing if it ever became required.
//
// The reason for assembly fixture is when we want to start/stop something as the project scope - tests can only be
// in one test collection at a time.
foreach (var mapping in _assemblyFixtureMappings)
{
CollectionFixtureMappings.Add(mapping.Key, mapping.Value);
}
}
protected override Task BeforeTestCollectionFinishedAsync()
{
// We need to remove the assembly fixtures so they won't get disposed.
foreach (var mapping in _assemblyFixtureMappings)
{
CollectionFixtureMappings.Remove(mapping.Key);
}
return base.BeforeTestCollectionFinishedAsync();
}
protected override Task<RunSummary> RunTestClassAsync(ITestClass testClass, IReflectionTypeInfo @class, IEnumerable<IXunitTestCase> testCases)
{
var caste = testCases.ToArray();
var type = caste.First().GetType();
return base.RunTestClassAsync(testClass, @class, testCases);
}
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Reflection;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Testing
{
public class AspNetTestFramework : XunitTestFramework
{
public AspNetTestFramework(IMessageSink messageSink)
: base(messageSink)
{
}
protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName)
=> new AspNetTestFrameworkExecutor(assemblyName, SourceInformationProvider, DiagnosticMessageSink);
}
}

View File

@ -0,0 +1,26 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Reflection;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Testing
{
public class AspNetTestFrameworkExecutor : XunitTestFrameworkExecutor
{
public AspNetTestFrameworkExecutor(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 AspNetTestAssemblyRunner(TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink, executionOptions))
{
await assemblyRunner.RunAsync();
}
}
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNetCore.Testing
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public class AssemblyFixtureAttribute : Attribute
{
public AssemblyFixtureAttribute(Type fixtureType)
{
FixtureType = fixtureType;
}
public Type FixtureType { get; private set; }
}
}

View File

@ -0,0 +1,47 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Xunit;
namespace Microsoft.AspNetCore.Testing
{
// We include a collection and assembly fixture to verify that they both still work.
[Collection("MyCollection")]
[TestCaseOrderer("Microsoft.AspNetCore.Testing.AlphabeticalOrderer", "Microsoft.AspNetCore.Testing.Tests")]
public class AssemblyFixtureTest
{
public AssemblyFixtureTest(TestAssemblyFixture assemblyFixture, TestCollectionFixture collectionFixture)
{
AssemblyFixture = assemblyFixture;
CollectionFixture = collectionFixture;
}
public TestAssemblyFixture AssemblyFixture { get; }
public TestCollectionFixture CollectionFixture { get; }
[Fact]
public void A()
{
Assert.NotNull(AssemblyFixture);
Assert.Equal(0, AssemblyFixture.Count);
Assert.NotNull(CollectionFixture);
Assert.Equal(0, CollectionFixture.Count);
AssemblyFixture.Count++;
CollectionFixture.Count++;
}
[Fact]
public void B()
{
Assert.Equal(1, AssemblyFixture.Count);
Assert.Equal(1, CollectionFixture.Count);
}
}
[CollectionDefinition("MyCollection", DisableParallelization = true)]
public class MyCollection : ICollectionFixture<TestCollectionFixture>
{
}
}

View File

@ -0,0 +1,5 @@
using Microsoft.AspNetCore.Testing;
using Xunit;
[assembly: AssemblyFixture(typeof(TestAssemblyFixture))]
[assembly: TestFramework("Microsoft.AspNetCore.Testing.AspNetTestFramework", "Microsoft.AspNetCore.Testing")]

View File

@ -0,0 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Testing
{
public class TestAssemblyFixture
{
public int Count { get; set; }
}
}

View File

@ -0,0 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNetCore.Testing
{
public class TestCollectionFixture
{
public int Count { get; set; }
}
}