From ed5f8696f48ffcf306fe5167c7e237c92c1115e3 Mon Sep 17 00:00:00 2001 From: Chad Lee Date: Thu, 26 Apr 2018 09:29:47 -0500 Subject: [PATCH] Revert to v2.0.0 This reverts most of the changes made in v3 & v4. Mainly, this gets rid of the custom test runner and relies on xunit's built-in test-running logic. This will eliminate flakiness in some of our builds. Also removing the ISpecification interface and instead relying on AsyncSpecification & Specification base classes. Updating documentation to reflect the new state of things. +semver:major --- LICENSE | 2 +- README.md | 13 +-- src/AsyncSpecification.cs | 80 +++++++++++++-- src/CommonTasks.cs | 9 ++ src/ISpecification.cs | 12 --- src/ObservationAssemblyRunner.cs | 34 ------- src/ObservationAttribute.cs | 16 +-- src/ObservationDiscoverer.cs | 61 ----------- src/ObservationExecutor.cs | 42 -------- src/ObservationTest.cs | 19 ---- src/ObservationTestCase.cs | 45 --------- src/ObservationTestCaseOrderer.cs | 18 ---- src/ObservationTestCaseRunner.cs | 32 ------ src/ObservationTestClassRunner.cs | 29 ------ src/ObservationTestCollectionRunner.cs | 135 ------------------------- src/ObservationTestFramework.cs | 22 ---- src/ObservationTestInvoker.cs | 43 -------- src/ObservationTestMethodRunner.cs | 31 ------ src/ObservationTestRunner.cs | 29 ------ src/Specification.cs | 27 +++-- src/bdd.csproj | 13 ++- test/TestAsyncSpecification.cs | 55 ---------- test/TestException.cs | 6 -- test/TestISpecification.cs | 123 ---------------------- test/TestSpecification.cs | 107 +++++--------------- test/test.csproj | 12 +-- 26 files changed, 132 insertions(+), 883 deletions(-) create mode 100644 src/CommonTasks.cs delete mode 100644 src/ISpecification.cs delete mode 100644 src/ObservationAssemblyRunner.cs delete mode 100644 src/ObservationDiscoverer.cs delete mode 100644 src/ObservationExecutor.cs delete mode 100644 src/ObservationTest.cs delete mode 100644 src/ObservationTestCase.cs delete mode 100644 src/ObservationTestCaseOrderer.cs delete mode 100644 src/ObservationTestCaseRunner.cs delete mode 100644 src/ObservationTestClassRunner.cs delete mode 100644 src/ObservationTestCollectionRunner.cs delete mode 100644 src/ObservationTestFramework.cs delete mode 100644 src/ObservationTestInvoker.cs delete mode 100644 src/ObservationTestMethodRunner.cs delete mode 100644 src/ObservationTestRunner.cs delete mode 100644 test/TestAsyncSpecification.cs delete mode 100644 test/TestException.cs delete mode 100644 test/TestISpecification.cs diff --git a/LICENSE b/LICENSE index 673e383..e2a308e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013 Chad Lee +Copyright (c) 2018 Archon Information Systems, LLC. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index aeb9db8..b7f73e6 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Extends xUnit.Net with Behavior Driven Development style fixtures. Some of the design goals include: * Use natural C# constructs to control things such as BDD contexts and concerns. For example, the context of the specification is defined in the class constructor and the concern for the fixture is defined by its namespace. - * Don't force me to inherit from any base class to get BDD style tests working. There is an interface `ISpecification` that one can implement to accomplish this. A `Specification` & `AsyncSpecification` base classes are also provided for convenience. + * Async tests are a first class citizen. See here for a [full introduction](https://www.chadly.net/bdd-with-xunit-net/) @@ -19,15 +19,12 @@ dotnet add package xUnit.BDD * [Write a Scenario](#write-a-scenario) * [Async Tests](#async-tests) * [Handling Exceptions](#handling-exceptions) -* [Shared Context](#shared-context) ### Write a Scenario ```cs using Xunit.Extensions; -[assembly: TestFramework("Xunit.Extensions.ObservationTestFramework", "Xunit.Bdd")] - public class Calculator { public int Add(int x, int y) => x + y; @@ -177,14 +174,6 @@ public class when_adding_an_inappropriate_number : Specification The `HandleExceptionsAttribute` will cause the test harness to handle any exceptions thrown by the `Observe` method. You can then make assertions on the thrown exception via the `ThrownException` property. If you leave off the `HandleExceptions` on the test class, it will not handle any exceptions from `Observe`. Therefore, you should only add the attribute if you are expecting an exception so as not to hide test failures. -### Shared Context - -When writing test scenarios like this, you "observe" one thing in the `Observe` method and make one or more `Observation`s on the results. Due to this, the test framework overrides [Xunit's default handling of shared test context](https://xunit.github.io/docs/shared-context.html). Instead of creating a new instance of the class for each `Observation` and rerunning the test setup & `Observe` method, the test harness will create the class once, run the setup & `Observe` once, and then run all of the `Observation`s in sequence. - -In other words, it treats all `ISpecification` tests as [class fixtures](https://xunit.github.io/docs/shared-context.html#class-fixture) (e.g. shared object instance across tests in a single class). - -If you write BDD scenarios as prescribed, this should make no difference to you. It is simply a performance optimization that you should be aware of. - ## Building Locally After cloning, run: diff --git a/src/AsyncSpecification.cs b/src/AsyncSpecification.cs index 4f41400..af4562b 100644 --- a/src/AsyncSpecification.cs +++ b/src/AsyncSpecification.cs @@ -1,4 +1,8 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading; using System.Threading.Tasks; namespace Xunit.Extensions @@ -6,26 +10,84 @@ namespace Xunit.Extensions /// /// The base async specification class /// - public abstract class AsyncSpecification : ISpecification, IAsyncLifetime + public abstract class AsyncSpecification : IAsyncLifetime { + Exception exception; + static readonly ReaderWriterLockSlim sync = new ReaderWriterLockSlim(); + static readonly Dictionary typeCache = new Dictionary(); + /// /// The exception that was thrown when Observe was run; null if no exception was thrown. /// - public Exception ThrownException { get; set; } + protected Exception ThrownException => exception; - public virtual Task InitializeAsync() - { - return Task.FromResult(0); - } + /// + /// Initialize the test class all async-like. + /// + public virtual Task InitializeAsync() => CommonTasks.Completed; /// - /// Performs an action, the outcome of which will be observed to validate the specification. + /// Performs the action to observe the outcome of to validate the specification. /// public abstract Task ObserveAsync(); - public virtual Task DisposeAsync() + /// + /// Cleanup the test class all async-like. + /// + public virtual Task DisposeAsync() => CommonTasks.Completed; + + async Task IAsyncLifetime.InitializeAsync() { - return Task.FromResult(0); + await InitializeAsync(); + + try + { + await ObserveAsync(); + } + catch (Exception ex) + { + if (!HandleException(ex)) + throw; + } + } + + bool HandleException(Exception ex) + { + exception = ex; + return ShouldHandleException(); + } + + bool ShouldHandleException() + { + Type type = GetType(); + + try + { + sync.EnterReadLock(); + + if (typeCache.ContainsKey(type)) + return typeCache[type]; + } + finally + { + sync.ExitReadLock(); + } + + try + { + sync.EnterWriteLock(); + + if (typeCache.ContainsKey(type)) + return typeCache[type]; + + var attrs = type.GetTypeInfo().GetCustomAttributes(typeof(HandleExceptionsAttribute), true).OfType(); + + return typeCache[type] = attrs.Any(); + } + finally + { + sync.ExitWriteLock(); + } } } } \ No newline at end of file diff --git a/src/CommonTasks.cs b/src/CommonTasks.cs new file mode 100644 index 0000000..7c6cdcf --- /dev/null +++ b/src/CommonTasks.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Xunit.Extensions +{ + static class CommonTasks + { + public static readonly Task Completed = Task.FromResult(0); + } +} \ No newline at end of file diff --git a/src/ISpecification.cs b/src/ISpecification.cs deleted file mode 100644 index 43ec58c..0000000 --- a/src/ISpecification.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace Xunit.Extensions -{ - public interface ISpecification - { - Exception ThrownException { get; set; } - - Task ObserveAsync(); - } -} diff --git a/src/ObservationAssemblyRunner.cs b/src/ObservationAssemblyRunner.cs deleted file mode 100644 index bb8da64..0000000 --- a/src/ObservationAssemblyRunner.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Xunit.Extensions -{ - public class ObservationAssemblyRunner : TestAssemblyRunner - { - public ObservationAssemblyRunner(ITestAssembly testAssembly, - IEnumerable testCases, - IMessageSink diagnosticMessageSink, - IMessageSink executionMessageSink, - ITestFrameworkExecutionOptions executionOptions) - : base(testAssembly, testCases, diagnosticMessageSink, executionMessageSink, executionOptions) - { - TestCaseOrderer = new ObservationTestCaseOrderer(); - } - - protected override string GetTestFrameworkDisplayName() - { - return "Observation Framework"; - } - - protected override Task RunTestCollectionAsync(IMessageBus messageBus, - ITestCollection testCollection, - IEnumerable testCases, - CancellationTokenSource cancellationTokenSource) - { - return new ObservationTestCollectionRunner(testCollection, testCases, DiagnosticMessageSink, messageBus, TestCaseOrderer, new ExceptionAggregator(Aggregator), cancellationTokenSource).RunAsync(); - } - } -} diff --git a/src/ObservationAttribute.cs b/src/ObservationAttribute.cs index 0c17613..8d53b87 100644 --- a/src/ObservationAttribute.cs +++ b/src/ObservationAttribute.cs @@ -1,19 +1,9 @@ -using System; -using Xunit; - -[assembly: TestFramework("Xunit.Extensions.ObservationTestFramework", "Xunit.Bdd")] - -namespace Xunit.Extensions +namespace Xunit.Extensions { /// /// Identifies a method as an observation which asserts the specification /// - [AttributeUsage(AttributeTargets.Method)] - public class ObservationAttribute : Attribute + public class ObservationAttribute : FactAttribute { - /// - /// Marks the test so that it will not be run, and gets or sets the skip reason - /// - public string Skip { get; set; } } -} +} \ No newline at end of file diff --git a/src/ObservationDiscoverer.cs b/src/ObservationDiscoverer.cs deleted file mode 100644 index 2f922c5..0000000 --- a/src/ObservationDiscoverer.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Linq; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Xunit.Extensions -{ - public class ObservationDiscoverer : TestFrameworkDiscoverer - { - readonly CollectionPerClassTestCollectionFactory testCollectionFactory; - - public ObservationDiscoverer(IAssemblyInfo assemblyInfo, - ISourceInformationProvider sourceProvider, - IMessageSink diagnosticMessageSink) - : base(assemblyInfo, sourceProvider, diagnosticMessageSink) - { - string config = null; -#if NET452 - config = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; -#endif - - var testAssembly = new TestAssembly(assemblyInfo, config); - testCollectionFactory = new CollectionPerClassTestCollectionFactory(testAssembly, diagnosticMessageSink); - } - - protected override ITestClass CreateTestClass(ITypeInfo @class) - { - return new TestClass(testCollectionFactory.Get(@class), @class); - } - - bool FindTestsForMethod(ITestMethod testMethod, - TestMethodDisplay defaultMethodDisplay, - bool includeSourceInformation, - IMessageBus messageBus) - { - var observationAttribute = testMethod.Method.GetCustomAttributes(typeof(ObservationAttribute)).FirstOrDefault(); - if (observationAttribute == null) - return true; - - var testCase = new ObservationTestCase(defaultMethodDisplay, testMethod); - if (!ReportDiscoveredTestCase(testCase, includeSourceInformation, messageBus)) - return false; - - return true; - } - - protected override bool FindTestsForType(ITestClass testClass, - bool includeSourceInformation, - IMessageBus messageBus, - ITestFrameworkDiscoveryOptions discoveryOptions) - { - var methodDisplay = discoveryOptions.MethodDisplayOrDefault(); - - foreach (var method in testClass.Class.GetMethods(includePrivateMethods: true)) - if (!FindTestsForMethod(new TestMethod(testClass, method), methodDisplay, includeSourceInformation, messageBus)) - return false; - - return true; - } - } -} diff --git a/src/ObservationExecutor.cs b/src/ObservationExecutor.cs deleted file mode 100644 index 35ee152..0000000 --- a/src/ObservationExecutor.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Xunit.Extensions -{ - public class ObservationExecutor : TestFrameworkExecutor - { - public ObservationExecutor(AssemblyName assemblyName, - ISourceInformationProvider sourceInformationProvider, - IMessageSink diagnosticMessageSink) - : base(assemblyName, sourceInformationProvider, diagnosticMessageSink) - { - string config = null; -#if NET452 - config = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; -#endif - TestAssembly = new TestAssembly(AssemblyInfo, config, assemblyName.Version); - } - - /// - /// Gets the test assembly that contains the test. - /// - protected TestAssembly TestAssembly { get; set; } - - - protected override ITestFrameworkDiscoverer CreateDiscoverer() - { - return new ObservationDiscoverer(AssemblyInfo, SourceInformationProvider, DiagnosticMessageSink); - } - - protected override async void RunTestCases(IEnumerable testCases, - IMessageSink executionMessageSink, - ITestFrameworkExecutionOptions executionOptions) - { - using (var assemblyRunner = new ObservationAssemblyRunner(TestAssembly, testCases, DiagnosticMessageSink, executionMessageSink, executionOptions)) - await assemblyRunner.RunAsync(); - } - } -} diff --git a/src/ObservationTest.cs b/src/ObservationTest.cs deleted file mode 100644 index c3975d4..0000000 --- a/src/ObservationTest.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Xunit.Abstractions; - -namespace Xunit.Extensions -{ - public class ObservationTest : LongLivedMarshalByRefObject, ITest - { - public ObservationTest(ObservationTestCase testCase, string displayName) - { - TestCase = testCase; - DisplayName = displayName; - } - - public string DisplayName { get; private set; } - - public ObservationTestCase TestCase { get; private set; } - - ITestCase ITest.TestCase { get { return TestCase; } } - } -} diff --git a/src/ObservationTestCase.cs b/src/ObservationTestCase.cs deleted file mode 100644 index bdc34a7..0000000 --- a/src/ObservationTestCase.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Xunit.Abstractions; -using Xunit.Sdk; -using System.Linq; - -namespace Xunit.Extensions -{ - public class ObservationTestCase : TestMethodTestCase - { - [Obsolete("For de-serialization purposes only", error: true)] - public ObservationTestCase() { } - - public ObservationTestCase(TestMethodDisplay defaultMethodDisplay, ITestMethod testMethod) - : base(defaultMethodDisplay, testMethod) { } - - protected override void Initialize() - { - base.Initialize(); - - IAttributeInfo observationAttribute = TestMethod.Method.GetCustomAttributes(typeof(ObservationAttribute)).First(); - - DisplayName = $"{TestMethod.TestClass.Class.Name}; it {TestMethod.Method.Name}".Replace('_', ' '); - SkipReason = GetSkipReason(observationAttribute); - } - - /// - /// Gets the skip reason for the test case. By default, pulls the skip reason from the - /// property. - /// - /// The observation attribute that decorated the test case. - /// The skip reason, if skipped; null, otherwise. - protected virtual string GetSkipReason(IAttributeInfo observationAttribute) - => observationAttribute.GetNamedArgument("Skip"); - - public Task RunAsync(ISpecification specification, - IMessageBus messageBus, - ExceptionAggregator aggregator, - CancellationTokenSource cancellationTokenSource) - { - return new ObservationTestCaseRunner(specification, this, DisplayName, SkipReason, messageBus, aggregator, cancellationTokenSource).RunAsync(); - } - } -} \ No newline at end of file diff --git a/src/ObservationTestCaseOrderer.cs b/src/ObservationTestCaseOrderer.cs deleted file mode 100644 index 5186567..0000000 --- a/src/ObservationTestCaseOrderer.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Xunit.Extensions -{ - public class ObservationTestCaseOrderer : ITestCaseOrderer - { - public IEnumerable OrderTestCases(IEnumerable testCases) where TTestCase : ITestCase - { - return testCases - .OrderBy(c => c.TestMethod.TestClass.Class.Name) - .ThenBy(c => c.TestMethod.Method.Name) - .ToList(); - } - } -} diff --git a/src/ObservationTestCaseRunner.cs b/src/ObservationTestCaseRunner.cs deleted file mode 100644 index 05af444..0000000 --- a/src/ObservationTestCaseRunner.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using Xunit.Sdk; - -namespace Xunit.Extensions -{ - public class ObservationTestCaseRunner : TestCaseRunner - { - readonly string displayName; - private readonly string skipReason; - readonly ISpecification specification; - - public ObservationTestCaseRunner(ISpecification specification, ObservationTestCase testCase, string displayName, string skipReason, IMessageBus messageBus, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) - : base(testCase, messageBus, aggregator, cancellationTokenSource) - { - this.specification = specification; - this.displayName = displayName; - this.skipReason = skipReason; - } - - protected override Task RunTestAsync() - { - var timer = new ExecutionTimer(); - var TestClass = TestCase.TestMethod.TestClass.Class.ToRuntimeType(); - var TestMethod = TestCase.TestMethod.Method.ToRuntimeMethod(); - var test = new ObservationTest(TestCase, displayName); - - return new ObservationTestRunner(specification, test, MessageBus, timer, TestClass, TestMethod, skipReason, Aggregator, CancellationTokenSource).RunAsync(); - } - } -} - diff --git a/src/ObservationTestClassRunner.cs b/src/ObservationTestClassRunner.cs deleted file mode 100644 index 7d8333c..0000000 --- a/src/ObservationTestClassRunner.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Xunit.Extensions -{ - public class ObservationTestClassRunner : TestClassRunner - { - private bool exceptionDuringInitialization { get; } - readonly ISpecification specification; - - public ObservationTestClassRunner(ISpecification specification, ITestClass testClass, IReflectionTypeInfo @class, IEnumerable testCases, IMessageSink diagnosticMessageSink, IMessageBus messageBus, ITestCaseOrderer testCaseOrderer, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) - : base(testClass, @class, testCases, diagnosticMessageSink, messageBus, testCaseOrderer, aggregator, cancellationTokenSource) - { - this.specification = specification; - } - - protected override Task RunTestMethodAsync(ITestMethod testMethod, - IReflectionMethodInfo method, - IEnumerable testCases, - object[] constructorArguments) - { - return new ObservationTestMethodRunner(specification, testMethod, Class, method, testCases, MessageBus, new ExceptionAggregator(Aggregator), CancellationTokenSource).RunAsync(); - } - } -} diff --git a/src/ObservationTestCollectionRunner.cs b/src/ObservationTestCollectionRunner.cs deleted file mode 100644 index f20e60a..0000000 --- a/src/ObservationTestCollectionRunner.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Xunit.Extensions -{ - public class ObservationTestCollectionRunner : TestCollectionRunner - { - private static readonly RunSummary FailedSummary = new RunSummary { Total = 1, Failed = 1 }; - private static readonly ReaderWriterLockSlim Sync = new ReaderWriterLockSlim(); - private static readonly Dictionary TypeCache = new Dictionary(); - - private readonly IMessageSink diagnosticMessageSink; - - public ObservationTestCollectionRunner(ITestCollection testCollection, - IEnumerable testCases, - IMessageSink diagnosticMessageSink, - IMessageBus messageBus, - ITestCaseOrderer testCaseOrderer, - ExceptionAggregator aggregator, - CancellationTokenSource cancellationTokenSource) - : base(testCollection, testCases, messageBus, testCaseOrderer, aggregator, cancellationTokenSource) - { - this.diagnosticMessageSink = diagnosticMessageSink; - } - - protected override async Task RunTestClassAsync(ITestClass testClass, - IReflectionTypeInfo @class, - IEnumerable testCases) - { - var timer = new ExecutionTimer(); - object testClassInstance = null; - - Aggregator.Run(() => testClassInstance = Activator.CreateInstance(testClass.Class.ToRuntimeType())); - - if (Aggregator.HasExceptions) - return FailEntireClass(testCases, timer, "Exception was thrown in class constructor"); - - var specification = testClassInstance as ISpecification; - if (specification == null) - { - Aggregator.Add(new InvalidOperationException($"Test class {testClass.Class.Name} cannot be static, and must implement ISpecification.")); - return FailEntireClass(testCases, timer, "[Observation] tests must be on an instantiable class, which must implement ISpecification."); - } - - var asynclife = specification as IAsyncLifetime; - if (asynclife != null) - await timer.AggregateAsync(asynclife.InitializeAsync); - - async Task ObserveAndCatchIfSpecified() - { - try - { - await specification.ObserveAsync(); - } - catch (Exception ex) - { - specification.ThrownException = ex; - if (!ShouldHandleException(specification.GetType())) throw; - } - } - - await Aggregator.RunAsync(ObserveAndCatchIfSpecified); - if (Aggregator.HasExceptions) - return FailEntireClass(testCases, timer, "Observe() threw an exception that the Specification did not expect"); - - var result = await new ObservationTestClassRunner(specification, testClass, @class, testCases, diagnosticMessageSink, MessageBus, TestCaseOrderer, new ExceptionAggregator(Aggregator), CancellationTokenSource).RunAsync(); - - if (asynclife != null) - await timer.AggregateAsync(asynclife.DisposeAsync); - - if (specification is IDisposable disposable) - timer.Aggregate(disposable.Dispose); - - return result; - } - - private RunSummary FailEntireClass(IEnumerable testCases, ExecutionTimer timer, string failureReason) - { - RunSummary summary = new RunSummary { Time = timer.Total }; - foreach (var testCase in testCases) - { - if (testCase.SkipReason != null) - { - MessageBus.QueueMessage(new TestSkipped(new ObservationTest(testCase, testCase.DisplayName), testCase.SkipReason)); - summary.Skipped++; - } - else - { - MessageBus.QueueMessage(new TestFailed(new ObservationTest(testCase, testCase.DisplayName), timer.Total, - failureReason, Aggregator.ToException())); - summary.Failed++; - } - summary.Total++; - } - return summary; - } - - private static bool ShouldHandleException(Type type) - { - try - { - Sync.EnterReadLock(); - - if (TypeCache.ContainsKey(type)) - return TypeCache[type]; - } - finally - { - Sync.ExitReadLock(); - } - - try - { - Sync.EnterWriteLock(); - - if (TypeCache.ContainsKey(type)) - return TypeCache[type]; - - var attrs = type.GetTypeInfo().GetCustomAttributes(typeof(HandleExceptionsAttribute), true).OfType(); - - return TypeCache[type] = attrs.Any(); - } - finally - { - Sync.ExitWriteLock(); - } - } - } -} \ No newline at end of file diff --git a/src/ObservationTestFramework.cs b/src/ObservationTestFramework.cs deleted file mode 100644 index 5825ea6..0000000 --- a/src/ObservationTestFramework.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Reflection; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Xunit.Extensions -{ - public class ObservationTestFramework : TestFramework - { - public ObservationTestFramework(IMessageSink diagnosticMessageSink) - : base(diagnosticMessageSink) { } - - protected override ITestFrameworkDiscoverer CreateDiscoverer(IAssemblyInfo assemblyInfo) - { - return new ObservationDiscoverer(assemblyInfo, SourceInformationProvider, DiagnosticMessageSink); - } - - protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName) - { - return new ObservationExecutor(assemblyName, SourceInformationProvider, DiagnosticMessageSink); - } - } -} diff --git a/src/ObservationTestInvoker.cs b/src/ObservationTestInvoker.cs deleted file mode 100644 index ff56cb0..0000000 --- a/src/ObservationTestInvoker.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Xunit.Extensions -{ - public class ObservationTestInvoker : TestInvoker - { - private readonly ISpecification specification; - - public ObservationTestInvoker(ISpecification specification, - ITest test, - IMessageBus messageBus, - Type testClass, - MethodInfo testMethod, - ExceptionAggregator aggregator, - CancellationTokenSource cancellationTokenSource) - : base(test, messageBus, testClass, null, testMethod, null, aggregator, cancellationTokenSource) - { - this.specification = specification; - } - - public new Task RunAsync() - { - return Aggregator.RunAsync(async () => - { - if (!CancellationTokenSource.IsCancellationRequested) - { - if (!CancellationTokenSource.IsCancellationRequested) - { - if (!Aggregator.HasExceptions) - await Timer.AggregateAsync(() => InvokeTestMethodAsync(specification)); - } - } - - return Timer.Total; - }); - } - } -} diff --git a/src/ObservationTestMethodRunner.cs b/src/ObservationTestMethodRunner.cs deleted file mode 100644 index c060851..0000000 --- a/src/ObservationTestMethodRunner.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Xunit.Extensions -{ - public class ObservationTestMethodRunner : TestMethodRunner - { - readonly ISpecification specification; - - public ObservationTestMethodRunner(ISpecification specification, - ITestMethod testMethod, - IReflectionTypeInfo @class, - IReflectionMethodInfo method, - IEnumerable testCases, - IMessageBus messageBus, - ExceptionAggregator aggregator, - CancellationTokenSource cancellationTokenSource) - : base(testMethod, @class, method, testCases, messageBus, aggregator, cancellationTokenSource) - { - this.specification = specification; - } - - protected override Task RunTestCaseAsync(ObservationTestCase testCase) - { - return testCase.RunAsync(specification, MessageBus, new ExceptionAggregator(Aggregator), CancellationTokenSource); - } - } -} diff --git a/src/ObservationTestRunner.cs b/src/ObservationTestRunner.cs deleted file mode 100644 index 0faa646..0000000 --- a/src/ObservationTestRunner.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; -using Xunit.Abstractions; -using Xunit.Sdk; - -namespace Xunit.Extensions -{ - public class ObservationTestRunner : TestRunner - { - readonly ISpecification specification; - readonly ExecutionTimer timer; - - public ObservationTestRunner(ISpecification specification, ITest test, IMessageBus messageBus, ExecutionTimer timer, Type testClass, MethodInfo testMethod, string skipReason, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) - : base(test, messageBus, testClass, null, testMethod, null, skipReason, aggregator, cancellationTokenSource) - { - this.specification = specification; - this.timer = timer; - } - - protected override async Task> InvokeTestAsync(ExceptionAggregator aggregator) - { - var duration = await new ObservationTestInvoker(specification, Test, MessageBus, TestClass, TestMethod, aggregator, CancellationTokenSource).RunAsync(); - return Tuple.Create(duration, String.Empty); - } - } -} - diff --git a/src/Specification.cs b/src/Specification.cs index 041454a..4a2b55e 100644 --- a/src/Specification.cs +++ b/src/Specification.cs @@ -1,27 +1,24 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; namespace Xunit.Extensions { /// - /// The base specification class + /// The base specification class for non-async scenarios /// - public abstract class Specification : ISpecification + public abstract class Specification : AsyncSpecification { - /// - /// The exception that was thrown when Observe was run; null if no exception was thrown. - /// - public Exception ThrownException { get; set; } - - /// - /// Performs an action, the outcome of which will be observed to validate the specification. - /// - public abstract void Observe(); + public sealed override Task InitializeAsync() => base.InitializeAsync(); + public sealed override Task DisposeAsync() => base.DisposeAsync(); - Task ISpecification.ObserveAsync() + public sealed override Task ObserveAsync() { Observe(); - return Task.FromResult(0); + return CommonTasks.Completed; } + + /// + /// Performs the action to observe the outcome of to validate the specification. + /// + public abstract void Observe(); } } \ No newline at end of file diff --git a/src/bdd.csproj b/src/bdd.csproj index 7b7b3cc..f7dff2f 100644 --- a/src/bdd.csproj +++ b/src/bdd.csproj @@ -2,7 +2,7 @@ net452;netstandard1.1 - 4.0.0-beta.2 + 1.0.0 Xunit BDD Extensions Chad Lee Xunit.Extensions @@ -11,20 +11,19 @@ xUnit.BDD Xunit.Bdd - https://github.com/chadly/xUnit-BDD-Extensions - https://github.com/chadly/xUnit-BDD-Extensions/blob/master/LICENSE - Copyright © 2017 Chad Lee - https://github.com/chadly/xUnit-BDD-Extensions.git + https://github.com/civicsource/xunit-bdd + https://github.com/civicsource/xunit-bdd/blob/master/LICENSE + Copyright © 2018 Archon Information Systems, LLC. + https://github.com/civicsource/xunit-bdd.git git bdd xunit behavior-driven-development should - https://github.com/chadly/xUnit-BDD-Extensions/releases + https://github.com/civicsource/xunit-bdd/releases - diff --git a/test/TestAsyncSpecification.cs b/test/TestAsyncSpecification.cs deleted file mode 100644 index 458a780..0000000 --- a/test/TestAsyncSpecification.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Threading.Tasks; -using Xunit.Extensions; - -namespace Xunit.Bdd.Test -{ - public class behaves_like_an_async_specification : AsyncSpecification - { - protected static int constructionCount = 0; - protected static int initCount = 0; - protected static int disposeCount = 0; - - int observedCount = 0; - - public behaves_like_an_async_specification() - { - constructionCount++; - } - - public override Task InitializeAsync() - { - initCount++; - return Task.CompletedTask; - } - - public override Task DisposeAsync() - { - disposeCount++; - return Task.CompletedTask; - } - - public override Task ObserveAsync() - { - observedCount++; - return Task.CompletedTask; - } - - [Observation] - public void should_call_observe_once() - { - observedCount.ShouldEqual(1); - } - - [Observation] - public void should_call_init_only_once() - { - initCount.ShouldEqual(1); - } - - [Observation] - public void should_construct_only_once() - { - constructionCount.ShouldEqual(1); - } - } -} diff --git a/test/TestException.cs b/test/TestException.cs deleted file mode 100644 index 7f707e4..0000000 --- a/test/TestException.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System; - -namespace Xunit.Bdd.Test -{ - public class TestException : Exception { } -} \ No newline at end of file diff --git a/test/TestISpecification.cs b/test/TestISpecification.cs deleted file mode 100644 index 3fbd4b5..0000000 --- a/test/TestISpecification.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System; -using System.Threading.Tasks; -using Xunit.Extensions; - -namespace Xunit.Bdd.Test -{ - public class behaves_like_an_ispecification : ISpecification - { - public Exception ThrownException { get; set; } - - protected static int constructionCount = 0; - - public behaves_like_an_ispecification() - { - constructionCount++; - } - - private bool observedInBase = false; - - public virtual Task ObserveAsync() - { - observedInBase = true; - return Task.FromResult(0); - } - - [Observation] - public void should_call_base_observe() - { - observedInBase.ShouldBeTrue("Observe should be called in the base class"); - } - - [Observation] - public void should_be_constructed_either_once_or_twice() - { - Assert.InRange(constructionCount, 1, 2); - } - - [Observation(Skip = "Skipped this observation")] - public void should_skip_this_observation() - { - Assert.True(false); - } - - [Observation] - public void should_have_no_exception() - { - ThrownException.ShouldBeNull(); - } - } - - public class behaves_like_a_polymorphic_ispecification : behaves_like_an_ispecification - { - protected bool observedInDerived = false; - - public override async Task ObserveAsync() - { - await base.ObserveAsync(); - observedInDerived = true; - } - - [Observation] - public void should_call_derived_observe() - { - observedInDerived.ShouldBeTrue("Observe should be called in the derived class"); - } - } - - [HandleExceptions] - public class behaves_like_an_ispecification_that_throws_during_setup : ISpecification - { - public Exception ThrownException { get; set; } - - public Task ObserveAsync() - { - throw new TestException(); - } - - [Observation] - public void should_handle_exception() - { - ThrownException.ShouldNotBeNull(); - ThrownException.ShouldBeType(); - } - } - - public class behaves_like_an_ispecification_that_unexpectedly_throws_during_setup : ISpecification - { - public Exception ThrownException { get; set; } - - public Task ObserveAsync() - { - throw new TestException(); - } - - [Observation] - public void should_fail() - { } - - [Observation(Skip = "This test should never fail")] - public void should_skip_even_if_constructor_throws() - { } - } - - public class behaves_like_an_ispecification_that_unexpectedly_throws_during_construction : ISpecification - { - public Exception ThrownException { get; set; } - - public behaves_like_an_ispecification_that_unexpectedly_throws_during_construction() - { - throw new TestException(); - } - - public Task ObserveAsync() => Task.CompletedTask; - - [Observation] - public void should_fail() - { } - - [Observation(Skip = "This test should never fail")] - public void should_skip_even_if_constructor_throws() - { } - } -} diff --git a/test/TestSpecification.cs b/test/TestSpecification.cs index 62a442a..bcb334b 100644 --- a/test/TestSpecification.cs +++ b/test/TestSpecification.cs @@ -1,24 +1,16 @@ -using Xunit; -using Xunit.Extensions; +using System; +using System.Threading.Tasks; -[assembly: TestFramework("Xunit.Extensions.ObservationTestFramework", "Xunit.Bdd")] - -namespace Xunit.Bdd.Test +namespace Xunit.Extensions.Test { - public class behaves_like_a_specification : Specification + public class behaves_like_async_specification : AsyncSpecification { - protected static int constructionCount = 0; + bool observedInBase = false; - public behaves_like_a_specification() - { - constructionCount++; - } - - private bool observedInBase = false; - - public override void Observe() + public override Task ObserveAsync() { observedInBase = true; + return Task.CompletedTask; } [Observation] @@ -26,33 +18,31 @@ public void should_call_base_observe() { observedInBase.ShouldBeTrue("Observe should be called in the base class"); } + } - [Observation] - public void should_be_constructed_either_once_or_twice() - { - Assert.InRange(constructionCount, 1, 2); - } + public class behaves_like_specification : Specification + { + bool observedInBase = false; - [Observation(Skip = "Skipped this observation")] - public void should_skip_this_observation() + public override void Observe() { - Assert.True(false); + observedInBase = true; } [Observation] - public void should_have_no_exception() + public void should_call_base_observe() { - ThrownException.ShouldBeNull(); + observedInBase.ShouldBeTrue("Observe should be called in the base class"); } } - public class behaves_like_a_polymorphic_specification : behaves_like_a_specification + public class behaves_like_a_polymorphic_specification : behaves_like_async_specification { protected bool observedInDerived = false; - public override void Observe() + public override async Task ObserveAsync() { - base.Observe(); + await base.ObserveAsync(); observedInDerived = true; } @@ -63,10 +53,12 @@ public void should_call_derived_observe() } } + public class TestException : Exception { } + [HandleExceptions] - public class behaves_like_a_specification_that_throws_during_setup : Specification + public class behaves_like_a_specification_that_throws_when_observed : AsyncSpecification { - public override void Observe() + public override Task ObserveAsync() { throw new TestException(); } @@ -79,64 +71,17 @@ public void should_handle_exception() } } - public class behaves_like_a_specification_that_unexpectedly_throws_during_setup : Specification + public class behaves_like_a_specification_that_unexpectedly_throws_when_observed : AsyncSpecification { - public override void Observe() + public override Task ObserveAsync() { throw new TestException(); } - [Observation] + [Observation(Skip = "This test should fail")] public void should_fail() - { } - - [Observation(Skip = "This test should never fail")] - public void should_skip_even_if_Observe_throws() - { } - } - - public class behaves_like_a_base_specification_that_doesnt_throw : Specification - { - public override void Observe() - { } - - [Observation] - public void should_run_test() - { } - } - - [HandleExceptions] - public class behaves_like_a_derived_specification_that_throws : behaves_like_a_base_specification_that_doesnt_throw - { - public override void Observe() - { - throw new TestException(); - } - - [Observation] - public void should_handle_exception() - { - ThrownException.ShouldNotBeNull(); - ThrownException.ShouldBeType(); - } - } - - public class behaves_like_a_specification_that_unexpectedly_throws_during_construction : Specification - { - public behaves_like_a_specification_that_unexpectedly_throws_during_construction() { - throw new TestException(); + // This test will fail because of the exception thrown in Observe(). } - - public override void Observe() - { } - - [Observation] - public void should_fail() - { } - - [Observation(Skip = "This test should never fail")] - public void should_skip_even_if_constructor_throws() - { } } } diff --git a/test/test.csproj b/test/test.csproj index 7e22251..4b991ea 100644 --- a/test/test.csproj +++ b/test/test.csproj @@ -5,17 +5,11 @@ false - Xunit.Bdd.Test + Xunit.Extensions.Test - Xunit.Bdd.Test + Xunit.Extensions.Test - 3.0.0 - - - - Library - - + 1.0.0