Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions TestStack.BDDfy.Tests/Configuration/EmptyScenario.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace TestStack.BDDfy.Tests.Configuration
{
public class EmptyScenario
{
public void GivenSomething() { }
public void WhenSomething() { }
public void ThenSomething() { }
}
}
67 changes: 67 additions & 0 deletions TestStack.BDDfy.Tests/Configuration/StepExecutorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System;
using System.Text;
using NUnit.Framework;
using TestStack.BDDfy.Configuration;

namespace TestStack.BDDfy.Tests.Configuration
{
[TestFixture]
public class StepExecutorTests
{
private class TestStepExecutor : StepExecutor
{
readonly StringBuilder _builder = new StringBuilder();

public string Results
{
get { return _builder.ToString(); }
}

public override object Execute(Step step, object testObject)
{
try
{
_builder.AppendLine(string.Format("About to run step '{0}'", step.Title));
return base.Execute(step, testObject);
}
finally
{
_builder.AppendLine(string.Format("Finished running step '{0}'", step.Title));
}
}
}

[Test]
public void CustomizingStepExecutionByOverridingStepExecutor()
{
try
{
var testStepExecutor = new TestStepExecutor();

Configurator.StepExecutor = testStepExecutor;

new EmptyScenario()
.Given(s => s.GivenSomething())
.When(s => s.WhenSomething())
.Then(s => s.ThenSomething())
.BDDfy();

string expected =
@"About to run step 'Given something'
Finished running step 'Given something'
About to run step 'When something'
Finished running step 'When something'
About to run step 'Then something'
Finished running step 'Then something'
";

Assert.AreEqual(expected, testStepExecutor.Results);

}
finally
{
Configurator.StepExecutor = new StepExecutor();
}
}
}
}
4 changes: 2 additions & 2 deletions TestStack.BDDfy.Tests/Processors/TestRunnerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class TestRunnerTests
public int ExampleValue { get; set; }

[Test]
public void InitialisesScenarioWithExampleBeforeRunning()
public void InitializesScenarioWithExampleBeforeRunning()
{
const int expectedValue = 1;
var actualValue = 0;
Expand All @@ -22,7 +22,7 @@ public void InitialisesScenarioWithExampleBeforeRunning()
}.Single();

var sut = new TestRunner();
Action<object> action = o => actualValue = ExampleValue;
Func<object, object> action = o => actualValue = ExampleValue;
var steps = new List<Step> { new Step(action, new StepTitle("A Step"), true, ExecutionOrder.Initialize, true, new List<StepArgument>()) };

var scenarioWithExample = new Scenario("id", this, steps, "Scenario Text", exampleTable, new List<string>());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ public void LongStepName()
var textReporter = new TextReporter();
var scenario = new Scenario(typeof(TextReporterTests), new List<Step>
{
new Step(o =>{ }, new StepTitle("Given a normal length title"), false, ExecutionOrder.SetupState, true, new List<StepArgument>()),
new Step(o =>{ }, new StepTitle("When something of normal length happens"), false, ExecutionOrder.Transition, true, new List<StepArgument>()),
new Step(o =>{ }, new StepTitle("Then some long state should be: #Title\r\n\r\nSome more stuff which is quite long on the second line\r\n\r\nAnd finally another really long line"),
new Step(o => null, new StepTitle("Given a normal length title"), false, ExecutionOrder.SetupState, true, new List<StepArgument>()),
new Step(o => null, new StepTitle("When something of normal length happens"), false, ExecutionOrder.Transition, true, new List<StepArgument>()),
new Step(o => null, new StepTitle("Then some long state should be: #Title\r\n\r\nSome more stuff which is quite long on the second line\r\n\r\nAnd finally another really long line"),
true, ExecutionOrder.Assertion, true, new List<StepArgument>()),
new Step(o =>{ }, new StepTitle("And a normal length assertion"), true, ExecutionOrder.ConsecutiveAssertion, true, new List<StepArgument>())
new Step(o => null, new StepTitle("And a normal length assertion"), true, ExecutionOrder.ConsecutiveAssertion, true, new List<StepArgument>())
}, "Scenario Text", new List<string>());
textReporter.Process(new Story(new StoryMetadata(typeof(TextReporterTests), new StoryNarrativeAttribute()),
scenario));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Reflection;
using System.Threading.Tasks;
using NUnit.Framework;
using TestStack.BDDfy.Processors;

namespace TestStack.BDDfy.Tests.Scanner.ReflectiveScanner
{
Expand All @@ -11,17 +12,16 @@ public class WhenStepScannerFactoryAsyncMethods
[Test]
public void CallingAsyncTaskWhichThrowsIsObservedAndRethrown()
{
var stepAction = StepActionFactory.GetStepAction<SomeScenario>(o => AsyncTaskMethod(o));

Assert.Throws<ArgumentException>(()=>stepAction(new SomeScenario()));
var stepAction = StepActionFactory.GetStepAction<SomeScenario>(o => AsyncTaskMethod(o));
Assert.Throws<ArgumentException>(()=> AsyncTestRunner.Run(() => stepAction(new SomeScenario())));
}

[Test]
public void CallingAsyncVoidWhichThrowsIsObservedAndRethrown()
{
var stepAction = StepActionFactory.GetStepAction<SomeScenario>(s=>AsyncVoidMethod(s));

Assert.Throws<ArgumentException>(() => stepAction(new SomeScenario()));
Assert.Throws<ArgumentException>(()=> AsyncTestRunner.Run(() => stepAction(new SomeScenario())));
}

[Test]
Expand All @@ -30,7 +30,7 @@ public void InvokingAsyncTaskWhichThrowsIsObservedAndRethrown()
var methodInfo = typeof(WhenStepScannerFactoryAsyncMethods).GetMethod("AsyncVoidMethod", BindingFlags.Instance | BindingFlags.NonPublic);
var stepAction = StepActionFactory.GetStepAction(methodInfo, new object[] { new SomeScenario() });

Assert.Throws<ArgumentException>(() => stepAction(this));
Assert.Throws<ArgumentException>(()=> AsyncTestRunner.Run(() => stepAction(this)));
}

[Test]
Expand All @@ -39,7 +39,7 @@ public void InvokingAsyncVoidWhichThrowsIsObservedAndRethrown()
var methodInfo = typeof(WhenStepScannerFactoryAsyncMethods).GetMethod("AsyncTaskMethod", BindingFlags.Instance | BindingFlags.NonPublic);
var stepAction = StepActionFactory.GetStepAction(methodInfo, new object[] { new SomeScenario() });

Assert.Throws<ArgumentException>(() => stepAction(this));
Assert.Throws<ArgumentException>(()=> AsyncTestRunner.Run(() => stepAction(this)));
}

private async void AsyncVoidMethod(SomeScenario someScenario)
Expand Down
2 changes: 2 additions & 0 deletions TestStack.BDDfy.Tests/TestStack.BDDfy.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@
<Compile Include="Arguments\ArgumentsProvidedForGiven.cs" />
<Compile Include="Arguments\ArgumentsProvidedForThen.cs" />
<Compile Include="Arguments\MultipleArgumentsProvidedForTheSameStep.cs" />
<Compile Include="Configuration\EmptyScenario.cs" />
<Compile Include="Configuration\SequentialKeyGeneratorTests.cs" />
<Compile Include="Configuration\StepExecutorTests.cs" />
<Compile Include="Configuration\TestRunnerTests.cs" />
<Compile Include="Reporters\TextReporter\TextReporterTests.cs" />
<Compile Include="Reporters\Html\MetroReportBuilderTests.cs" />
Expand Down
9 changes: 9 additions & 0 deletions TestStack.BDDfy/Configuration/Configurator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,14 @@ public static IKeyGenerator IdGenerator
get { return _idGenerator; }
set { _idGenerator = value; }
}


private static IStepExecutor _stepExecutor = new StepExecutor();
public static IStepExecutor StepExecutor
{
get { return _stepExecutor; }
set { _stepExecutor = value; }
}

}
}
7 changes: 7 additions & 0 deletions TestStack.BDDfy/IStepExecutor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace TestStack.BDDfy
{
public interface IStepExecutor
{
object Execute(Step step, object testObject);
}
}
54 changes: 54 additions & 0 deletions TestStack.BDDfy/Processors/AsyncTestRunner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Security;
using System.Threading;
using System.Threading.Tasks;

namespace TestStack.BDDfy.Processors
{
public static class AsyncTestRunner
{
public static void Run(Func<object> performStep)
{
var oldSyncContext = SynchronizationContext.Current;
try
{
var asyncSyncContext = new AsyncTestSyncContext();
SetSynchronizationContext(asyncSyncContext);
var result = performStep();
var task = result as Task;
if (task != null)
{
try
{
task.Wait();
}
catch (AggregateException ae)
{
var innerException = ae.InnerException;
ExceptionProcessor.PreserveStackTrace(innerException);
throw innerException;
}
}
else
{
var ex = asyncSyncContext.WaitForCompletion();
if (ex != null)
{
ExceptionProcessor.PreserveStackTrace(ex);
throw ex;
}
}
}
finally
{
SetSynchronizationContext(oldSyncContext);
}
}

[SecuritySafeCritical]
private static void SetSynchronizationContext(SynchronizationContext context)
{
SynchronizationContext.SetSynchronizationContext(context);
}
}
}
3 changes: 2 additions & 1 deletion TestStack.BDDfy/Processors/ScenarioExecutor.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Reflection;
using TestStack.BDDfy.Configuration;

namespace TestStack.BDDfy.Processors
{
Expand Down Expand Up @@ -52,7 +53,7 @@ public Result ExecuteStep(Step step)
{
try
{
step.Execute(_scenario.TestObject);
AsyncTestRunner.Run(() => Configurator.StepExecutor.Execute(step, _scenario.TestObject));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this cause your deadlock again? I thought the idea was to swap out the async run with the custom one

Haven't fully thought through this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JakeGinnivan No actually, it's the reverse - the deadlock only happens if we were to call the AsyncTestRunner from the UI thread (meaning, if it was called from within the StepExecutor itself). This way, the StepExecutor can choose to marshal the actual execution to whatever thread they like, and the AsyncTestRunner successfully waits for it to finish.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah understand. Possibly it's a good idea to turn off captured sync context stuff as well as this change.

i.e

Configurator.AsyncVoidSupportEnabled = false;

Not needed for this PR but do you think that could be useful?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sounds like that would be a good idea, but to be honest I'm not sure I understand the way the async void use-case works well enough to comment. If it helps, I've uploaded an initial sample of the Visual Studio integration Test assembly that uses the code in this pull request. Here is a sample test and the aforementioned BDDfy configuration. You'll need VS2013SDK if you want to run this locally.

step.Result = Result.Passed;
}
catch (Exception ex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ private static string GetStepTitleFromMethod(MethodInfo method, RunStepWithArgsA
}
}

static Action<object> GetStepAction(MethodInfo method, object[] inputs, bool returnsItsText)
static Func<object,object> GetStepAction(MethodInfo method, object[] inputs, bool returnsItsText)
{
if (returnsItsText)
{
Expand Down
58 changes: 7 additions & 51 deletions TestStack.BDDfy/Scanners/StepScanners/StepActionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,69 +9,25 @@ namespace TestStack.BDDfy
{
public class StepActionFactory
{
public static Action<object> GetStepAction(MethodInfo method, object[] inputs)
public static Func<object,object> GetStepAction(MethodInfo method, object[] inputs)
{
return o => Run(() => method.Invoke(o, inputs));
return o => method.Invoke(o, inputs);
}

public static Action<object> GetStepAction<TScenario>(Action<TScenario> action)
public static Func<object,object> GetStepAction<TScenario>(Action<TScenario> action)
where TScenario : class
{
return o => Run(() =>
return o =>
{
action((TScenario)o);
return null;
});
};
}

public static Action<object> GetStepAction<TScenario>(Func<TScenario, Task> action)
public static Func<object,object> GetStepAction<TScenario>(Func<TScenario, Task> action)
where TScenario : class
{
return o => Run(() => action((TScenario)o));
}

private static void Run(Func<object> func)
{
var oldSyncContext = SynchronizationContext.Current;
try
{
var asyncSyncContext = new AsyncTestSyncContext();
SetSynchronizationContext(asyncSyncContext);
var result = func();
var task = result as Task;
if (task != null)
{
try
{
task.Wait();
}
catch (AggregateException ae)
{
var innerException = ae.InnerException;
ExceptionProcessor.PreserveStackTrace(innerException);
throw innerException;
}
}
else
{
var ex = asyncSyncContext.WaitForCompletion();
if (ex != null)
{
ExceptionProcessor.PreserveStackTrace(ex);
throw ex;
}
}
}
finally
{
SetSynchronizationContext(oldSyncContext);
}
}

[SecuritySafeCritical]
static void SetSynchronizationContext(SynchronizationContext context)
{
SynchronizationContext.SetSynchronizationContext(context);
return o => action((TScenario)o);
}
}
}
20 changes: 2 additions & 18 deletions TestStack.BDDfy/Step.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class Step
private readonly StepTitle _title;

public Step(
Action<object> action,
Func<object, object> action,
StepTitle title,
bool asserts,
ExecutionOrder executionOrder,
Expand Down Expand Up @@ -40,7 +40,7 @@ public Step(Step step)
}

public string Id { get; private set; }
internal Action<object> Action { get; set; }
internal Func<object, object> Action { get; set; }
public bool Asserts { get; private set; }
public bool ShouldReport { get; private set; }
public string Title
Expand All @@ -58,21 +58,5 @@ public string Title
public int ExecutionSubOrder { get; set; }
public TimeSpan Duration { get; set; }
public List<StepArgument> Arguments { get; private set; }

public void Execute(object testObject)
{
Stopwatch sw = Stopwatch.StartNew();
try
{
Action(testObject);
sw.Stop();
Duration = sw.Elapsed;
}
finally
{
sw.Stop();
Duration = sw.Elapsed;
}
}
}
}
Loading