Skip to content

Commit

Permalink
support test retries
Browse files Browse the repository at this point in the history
  • Loading branch information
fakefeik committed Feb 20, 2024
1 parent 73283ee commit 1e8732d
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ protected override void Configure(ISetupBuilder fixture, ISetupBuilder test)
{
fixture
.UseSetup<TestInvocationCounterSetup>()
.Use(t => t.Get<Counter>().InvocationsCount += i);
.Use(t => t.GetFromThisOrParentContext<Counter>().InvocationsCount += i);
}

[Test]
Expand Down
6 changes: 4 additions & 2 deletions NUnit.Middlewares.Tests/ParallelTestContextUsageTest.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Linq;
using System.Threading.Tasks;

using FluentAssertions;

using NUnit.Framework;
using NUnit.Framework.Interfaces;

Expand Down Expand Up @@ -69,10 +71,10 @@ public void TestSource(int i)
private static void TestInvocationCount()
{
var counter = SimpleTestContext.Current.Get<Counter>();
Assert.That(counter, Is.Not.Null);
counter.Should().NotBeNull();

counter.InvocationsCount++;
Assert.That(counter.InvocationsCount, Is.EqualTo(1));
counter.InvocationsCount.Should().Be(1);
}

private static readonly TestCaseData[] testCases = Enumerable.Range(0, 100).Select(x => new TestCaseData(x)).ToArray();
Expand Down
93 changes: 93 additions & 0 deletions NUnit.Middlewares.Tests/TestWithRetriesHasItsOwnSetup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System;

using FluentAssertions;

using NUnit.Framework;
using NUnit.Framework.Internal;

namespace SkbKontur.NUnit.Middlewares.Tests
{
public class DisposableCounter : IDisposable
{
public int InvocationsCount { get; set; }

private readonly object sync = new();
private bool disposed;

public void Dispose()
{
if (!disposed)
{
lock (sync)
{
if (!disposed)
{
disposed = true;
return;
}
}
}

throw new InvalidOperationException("Already disposed");
}
}

[Parallelizable(ParallelScope.Children)]
public class TestWithRetriesHasItsOwnSetup : SimpleTestBase
{
[Test]
[Retry(3)]
public void TestRepeatCounterWithRetries()
{
var repeatCount = TestExecutionContext.CurrentContext.CurrentRepeatCount;

var repeatCounter = SimpleTestContext.Current.Get<DisposableCounter>("repeat-counter");
repeatCounter.Should().NotBeNull();
repeatCounter.InvocationsCount.Should().Be(repeatCount);
repeatCounter.InvocationsCount++;

if (repeatCount is 0 or 1)
{
Assert.Fail("Third time's a Charm");
}

repeatCounter.InvocationsCount.Should().Be(repeatCount + 1);
}

[Test]
[Retry(3)]
public void TestCounterWithRetries()
{
var simpleCounter = SimpleTestContext.Current.Get<DisposableCounter>("simple-counter");
simpleCounter.Should().NotBeNull();
simpleCounter.InvocationsCount.Should().Be(0);
simpleCounter.InvocationsCount++;

if (TestExecutionContext.CurrentContext.CurrentRepeatCount is 0 or 1)
{
Assert.Fail("Third time's a Charm");
}

simpleCounter.InvocationsCount.Should().Be(1);
}

protected override void Configure(ISetupBuilder fixture, ISetupBuilder test)
{
test
.Use(t =>
{
var counter = new DisposableCounter();
counter.InvocationsCount = TestExecutionContext.CurrentContext.CurrentRepeatCount;
t.Properties.Set("repeat-counter", counter);
return () => counter.Dispose();
})
.Use(t =>
{
t.Properties.Set("simple-counter", new DisposableCounter());
return () => ((IDisposable)t.Properties.Get("simple-counter")).Dispose();
});
}
}
}
11 changes: 7 additions & 4 deletions NUnit.Middlewares/CompositeSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,28 @@ public CompositeSetup(List<SetUpAsync<TearDownAsync>> setups)
this.setups = setups;
}

private const string successfulSetupsKey = "SuccessfulSetups";
private const string teardownKey = "nunit-middlewares.teardown-key";

public async Task SetUpAsync(ITest test)
{
// note (p.vostretsov, 20.02.2024): clear teardown list in order to support test retries
test.Properties[teardownKey] = new List<object>();

foreach (var setup in setups)
{
var teardown = await setup(test).ConfigureAwait(false);
test.Properties.Add(successfulSetupsKey, teardown);
test.Properties.Add(teardownKey, teardown);
}
}

public async Task TearDownAsync(ITest test)
{
var exceptions = new List<Exception>();
for (var i = test.Properties[successfulSetupsKey].Count - 1; i >= 0; i--)
for (var i = test.Properties[teardownKey].Count - 1; i >= 0; i--)
{
try
{
var teardown = (TearDownAsync)test.Properties[successfulSetupsKey][i];
var teardown = (TearDownAsync)test.Properties[teardownKey][i];
await teardown().ConfigureAwait(false);
}
catch (Exception e)
Expand Down
8 changes: 4 additions & 4 deletions NUnit.Middlewares/SimpleTestContext.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Collections;
using System.Collections.Generic;

using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal;
Expand All @@ -14,9 +14,9 @@ private SimpleTestContext(ITest test)

public static SimpleTestContext Current => new(TestExecutionContext.CurrentContext.CurrentTest);

public object? Get(string key) => test.Get(key);
public bool ContainsKey(string key) => test.ContainsKey(key);
public IList? this[string key] => test.List(key);
public object? Get(string key) => test.GetRecursive(key);
public bool ContainsKey(string key) => test.ContainsKeyRecursive(key);
public IReadOnlyList<object>? this[string key] => (IReadOnlyList<object>?)test.ListRecursive(key);

private readonly ITest test;
}
Expand Down
40 changes: 32 additions & 8 deletions NUnit.Middlewares/SimpleTestContextExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,43 @@
using System;
using System.Collections;
using System.Collections.Generic;

using NUnit.Framework.Interfaces;

namespace SkbKontur.NUnit.Middlewares
{
public static class SimpleTestContextExtensions
{
public static T? Get<T>(this IPropertyBag properties)
public static T Get<T>(this IPropertyBag properties)
{
return (T)properties.Get(typeof(T).Name);
return properties.Get<T>(typeof(T).Name);
}

public static T Get<T>(this ITest test)
public static T Get<T>(this IPropertyBag properties, string key)
{
return (T?)test.Get(typeof(T).Name)!;
return (T?)properties.Get(key)
?? throw new KeyNotFoundException($"Cannot find item by key {key} in test properties");
}

public static T GetFromThisOrParentContext<T>(this ITest test)
{
return test.GetFromThisOrParentContext<T>(typeof(T).Name);
}

public static T GetFromThisOrParentContext<T>(this ITest test, string key)
{
return (T)test.GetRecursiveOrThrow(key);
}

public static T Get<T>(this SimpleTestContext context)
{
return (T)context.Get(typeof(T).Name)!;
return context.Get<T>(typeof(T).Name);
}

public static T Get<T>(this SimpleTestContext context, string key)
{
return (T?)context.Get(key)
?? throw new KeyNotFoundException($"Cannot find item by key {key} in test context");
}

public static void Set<T>(this IPropertyBag properties, T value)
Expand All @@ -28,13 +46,19 @@ public static void Set<T>(this IPropertyBag properties, T value)
properties.Set(typeof(T).Name, value);
}

public static object? Get(this ITest test, string key) =>
public static object GetRecursiveOrThrow(this ITest test, string key)
{
return test.GetRecursive(key)
?? throw new KeyNotFoundException($"Cannot find item by key {key} in test {test.Name} or its parents");
}

public static object? GetRecursive(this ITest test, string key) =>
GetRecursive(test, key, (p, k) => p.Get(k));

public static bool ContainsKey(this ITest test, string key) =>
public static bool ContainsKeyRecursive(this ITest test, string key) =>
GetRecursive(test, key, (p, k) => p.ContainsKey(k) ? true : (bool?)null) ?? false;

public static IList? List(this ITest test, string key) =>
public static IList? ListRecursive(this ITest test, string key) =>
GetRecursive(test, key, (p, k) => p[k]);

private static T? GetRecursive<T>(ITest leaf, string key, Func<IPropertyBag, string, T?> getValue)
Expand Down

0 comments on commit 1e8732d

Please sign in to comment.