Skip to content

Commit 1e12bac

Browse files
Add AutoDilate metadata to TestKit timeouts (#8441)
1 parent 0ec2139 commit 1e12bac

17 files changed

Lines changed: 536 additions & 315 deletions

src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveTestKit.DotNet.verified.txt

Lines changed: 104 additions & 99 deletions
Large diffs are not rendered by default.

src/core/Akka.API.Tests/verify/CoreAPISpec.ApproveTestKit.Net.verified.txt

Lines changed: 104 additions & 99 deletions
Large diffs are not rendered by default.

src/core/Akka.Cluster.TestKit/MultiNodeClusterSpec.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ public async Task AwaitClusterUpAsync(CancellationToken cancellationToken, param
328328
public Task AwaitClusterUpAsync(params RoleName[] roles)
329329
=> AwaitClusterUpAsync(CancellationToken.None, roles);
330330

331-
public void JoinWithin(RoleName joinNode, TimeSpan? max = null, TimeSpan? interval = null)
331+
public void JoinWithin(RoleName joinNode, [AutoDilate] TimeSpan? max = null, TimeSpan? interval = null)
332332
{
333333
if (max == null) max = RemainingOrDefault;
334334
if (interval == null) interval = TimeSpan.FromSeconds(1);
@@ -407,7 +407,7 @@ public void AssertLeaderIn(ImmutableList<RoleName> nodesInCluster)
407407
public void AwaitMembersUp(
408408
int numbersOfMembers,
409409
ImmutableHashSet<Address> canNotBePartOfMemberRing = null,
410-
TimeSpan? timeout = null)
410+
[AutoDilate] TimeSpan? timeout = null)
411411
{
412412
if (canNotBePartOfMemberRing == null)
413413
canNotBePartOfMemberRing = ImmutableHashSet.Create<Address>();
@@ -432,7 +432,7 @@ public void AwaitMembersUp(
432432
public async Task AwaitMembersUpAsync(
433433
int numbersOfMembers,
434434
ImmutableHashSet<Address> canNotBePartOfMemberRing = null,
435-
TimeSpan? timeout = null,
435+
[AutoDilate] TimeSpan? timeout = null,
436436
CancellationToken cancellationToken = default)
437437
{
438438
canNotBePartOfMemberRing ??= ImmutableHashSet.Create<Address>();
@@ -558,4 +558,3 @@ public FailureDetectorPuppet FailureDetectorPuppet(Address address)
558558
}
559559
}
560560
}
561-
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="AutoDilateMetadataSpec.cs" company="Akka.NET Project">
3+
// Copyright (C) 2009-2022 Lightbend Inc. <http://www.lightbend.com>
4+
// Copyright (C) 2013-2026 .NET Foundation <https://github.com/akkadotnet/akka.net>
5+
// </copyright>
6+
//-----------------------------------------------------------------------
7+
8+
using System.Linq;
9+
using System.Reflection;
10+
using Akka.Cluster.TestKit;
11+
using Akka.TestKit;
12+
using Xunit;
13+
14+
namespace Akka.Cluster.Tests.MultiNode;
15+
16+
public class AutoDilateMetadataSpec
17+
{
18+
[Fact]
19+
public void MultiNodeClusterSpec_should_mark_automatically_dilated_parameters()
20+
{
21+
AssertAutoDilated(nameof(MultiNodeClusterSpec.JoinWithin), "max",
22+
"joinNode", "max", "interval");
23+
AssertAutoDilated(nameof(MultiNodeClusterSpec.AwaitMembersUp), "timeout",
24+
"numbersOfMembers", "canNotBePartOfMemberRing", "timeout");
25+
AssertAutoDilated(nameof(MultiNodeClusterSpec.AwaitMembersUpAsync), "timeout",
26+
"numbersOfMembers", "canNotBePartOfMemberRing", "timeout", "cancellationToken");
27+
}
28+
29+
[Fact]
30+
public void MultiNodeClusterSpec_should_not_mark_raw_interval_parameters()
31+
{
32+
var parameter = FindParameter(nameof(MultiNodeClusterSpec.JoinWithin), "interval",
33+
"joinNode", "max", "interval");
34+
35+
Assert.False(parameter.IsDefined(typeof(AutoDilateAttribute), inherit: false));
36+
}
37+
38+
private static void AssertAutoDilated(
39+
string methodName,
40+
string parameterName,
41+
params string[] parameterNames)
42+
{
43+
var parameter = FindParameter(methodName, parameterName, parameterNames);
44+
Assert.True(parameter.IsDefined(typeof(AutoDilateAttribute), inherit: false));
45+
}
46+
47+
private static ParameterInfo FindParameter(
48+
string methodName,
49+
string parameterName,
50+
params string[] parameterNames)
51+
{
52+
var method = typeof(MultiNodeClusterSpec)
53+
.GetMethods(BindingFlags.Instance | BindingFlags.Public)
54+
.Single(candidate =>
55+
candidate.Name == methodName &&
56+
candidate.GetParameters().Select(parameter => parameter.Name).SequenceEqual(parameterNames));
57+
58+
return method.GetParameters().Single(parameter => parameter.Name == parameterName);
59+
}
60+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="AutoDilateMetadataTests.cs" company="Akka.NET Project">
3+
// Copyright (C) 2009-2022 Lightbend Inc. <http://www.lightbend.com>
4+
// Copyright (C) 2013-2026 .NET Foundation <https://github.com/akkadotnet/akka.net>
5+
// </copyright>
6+
//-----------------------------------------------------------------------
7+
8+
using System;
9+
using System.Linq;
10+
using System.Reflection;
11+
using Akka.TestKit.Internal;
12+
using Xunit;
13+
14+
namespace Akka.TestKit.Tests.TestKitBaseTests;
15+
16+
public class AutoDilateMetadataTests
17+
{
18+
[Fact]
19+
public void AutoDilateAttribute_should_be_parameter_only_metadata()
20+
{
21+
var usage = typeof(AutoDilateAttribute).GetCustomAttribute<AttributeUsageAttribute>();
22+
23+
Assert.NotNull(usage);
24+
Assert.Equal(AttributeTargets.Parameter, usage.ValidOn);
25+
Assert.False(usage.AllowMultiple);
26+
Assert.False(usage.Inherited);
27+
}
28+
29+
[Fact]
30+
public void AutoDilateAttribute_should_only_mark_duration_parameters()
31+
{
32+
var annotatedParameters = typeof(TestKitBase).Assembly
33+
.GetTypes()
34+
.SelectMany(type => type
35+
.GetMembers(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public |
36+
BindingFlags.NonPublic)
37+
.OfType<MethodBase>())
38+
.SelectMany(method => method.GetParameters())
39+
.Where(HasAutoDilateAttribute)
40+
.ToArray();
41+
42+
Assert.NotEmpty(annotatedParameters);
43+
Assert.All(annotatedParameters, parameter =>
44+
Assert.True(
45+
parameter.ParameterType == typeof(TimeSpan) ||
46+
parameter.ParameterType == typeof(TimeSpan?),
47+
$"{parameter.Member.DeclaringType?.FullName}.{parameter.Member.Name} parameter " +
48+
$"'{parameter.Name}' has unexpected type {parameter.ParameterType}."));
49+
}
50+
51+
[Fact]
52+
public void Automatically_dilated_TestKit_parameters_should_be_marked()
53+
{
54+
AssertAutoDilated(typeof(TestKitBase), nameof(TestKitBase.Dilated), "duration", "duration");
55+
AssertAutoDilated(typeof(TestKitBase), nameof(TestKitBase.RemainingOrDilated), "duration", "duration");
56+
AssertAutoDilated(typeof(TestKitBase), nameof(TestKitBase.AwaitAssert), "duration",
57+
"assertion", "duration", "interval", "cancellationToken");
58+
AssertAutoDilated(typeof(TestKitBase), nameof(TestKitBase.ReceiveWhile), "max",
59+
"filter", "max", "idle", "msgs", "cancellationToken");
60+
AssertAutoDilated(typeof(TestKitBase), nameof(TestKitBase.Within), "max",
61+
"min", "max", "action", "hint", "epsilonValue", "cancellationToken");
62+
AssertAutoDilated(typeof(TestKitBase), nameof(TestKitBase.FishUntilMessageAsync), "max",
63+
"max", "cancellationToken");
64+
AssertAutoDilated(typeof(IEventFilterApplier), nameof(IEventFilterApplier.ExpectOne), "timeout",
65+
"timeout", "action", "cancellationToken");
66+
AssertAutoDilated(typeof(InternalEventFilterApplier), nameof(InternalEventFilterApplier.ExpectOne),
67+
"timeout", "timeout", "action", "cancellationToken");
68+
AssertAutoDilated(typeof(TestBarrier), nameof(TestBarrier.Await), "timeout", "timeout");
69+
70+
var constructor = typeof(TestBarrier).GetConstructors()
71+
.Single(ctor => HasParameterNames(ctor, "testKit", "count", "defaultTimeout"));
72+
Assert.True(HasAutoDilateAttribute(constructor.GetParameters().Single(p => p.Name == "defaultTimeout")));
73+
}
74+
75+
[Fact]
76+
public void Raw_or_conditionally_dilated_TestKit_parameters_should_not_be_marked()
77+
{
78+
AssertNotAutoDilated(typeof(TestKitBase), nameof(TestKitBase.AwaitAssert), "interval",
79+
"assertion", "duration", "interval", "cancellationToken");
80+
AssertNotAutoDilated(typeof(TestKitBase), nameof(TestKitBase.ReceiveWhile), "idle",
81+
"filter", "max", "idle", "msgs", "cancellationToken");
82+
AssertNotAutoDilated(typeof(TestKitBase), nameof(TestKitBase.Within), "min",
83+
"min", "max", "action", "hint", "epsilonValue", "cancellationToken");
84+
AssertNotAutoDilated(typeof(TestKitBase), nameof(TestKitBase.Within), "epsilonValue",
85+
"min", "max", "action", "hint", "epsilonValue", "cancellationToken");
86+
AssertNotAutoDilated(typeof(TestKitBase), nameof(TestKitBase.ReceiveOne), "max",
87+
"max", "cancellationToken");
88+
AssertNotAutoDilated(typeof(TestKitBase), nameof(TestKitBase.AwaitConditionNoThrow), "max",
89+
"conditionIsFulfilled", "max", "interval", "cancellationToken");
90+
AssertNotAutoDilated(typeof(TestLatch), nameof(TestLatch.Ready), "timeout", "timeout");
91+
}
92+
93+
private static void AssertAutoDilated(
94+
Type declaringType,
95+
string methodName,
96+
string parameterName,
97+
params string[] parameterNames)
98+
{
99+
var parameter = FindParameter(declaringType, methodName, parameterName, parameterNames);
100+
Assert.True(HasAutoDilateAttribute(parameter));
101+
}
102+
103+
private static void AssertNotAutoDilated(
104+
Type declaringType,
105+
string methodName,
106+
string parameterName,
107+
params string[] parameterNames)
108+
{
109+
var parameter = FindParameter(declaringType, methodName, parameterName, parameterNames);
110+
Assert.False(HasAutoDilateAttribute(parameter));
111+
}
112+
113+
private static ParameterInfo FindParameter(
114+
Type declaringType,
115+
string methodName,
116+
string parameterName,
117+
params string[] parameterNames)
118+
{
119+
var method = declaringType
120+
.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public |
121+
BindingFlags.NonPublic)
122+
.Single(candidate =>
123+
candidate.Name == methodName &&
124+
HasParameterNames(candidate, parameterNames));
125+
126+
return method.GetParameters().Single(parameter => parameter.Name == parameterName);
127+
}
128+
129+
private static bool HasParameterNames(MethodBase method, params string[] parameterNames)
130+
=> method.GetParameters().Select(parameter => parameter.Name).SequenceEqual(parameterNames);
131+
132+
private static bool HasAutoDilateAttribute(ParameterInfo parameter)
133+
=> parameter.IsDefined(typeof(AutoDilateAttribute), inherit: false);
134+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="AutoDilateAttribute.cs" company="Akka.NET Project">
3+
// Copyright (C) 2009-2022 Lightbend Inc. <http://www.lightbend.com>
4+
// Copyright (C) 2013-2026 .NET Foundation <https://github.com/akkadotnet/akka.net>
5+
// </copyright>
6+
//-----------------------------------------------------------------------
7+
8+
#nullable enable
9+
10+
using System;
11+
12+
namespace Akka.TestKit;
13+
14+
/// <summary>
15+
/// Marks a duration parameter whose value is automatically scaled by
16+
/// <see cref="TestKitSettings.TestTimeFactor"/>.
17+
/// </summary>
18+
/// <remarks>
19+
/// Callers should pass an undilated duration to parameters marked with this attribute.
20+
/// Passing a value that has already been scaled by <see cref="TestKitBase.Dilated(TimeSpan)"/>
21+
/// applies the configured time factor twice.
22+
/// </remarks>
23+
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
24+
public sealed class AutoDilateAttribute : Attribute
25+
{
26+
}

src/core/Akka.TestKit/EventFilter/IEventFilterApplier.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public interface IEventFilterApplier
4949
/// <param name="timeout">The time to wait for a log event after executing <paramref name="action"/></param>
5050
/// <param name="action">The action.</param>
5151
/// <param name="cancellationToken"></param>
52-
void ExpectOne(TimeSpan timeout, Action action, CancellationToken cancellationToken = default);
52+
void ExpectOne([AutoDilate] TimeSpan timeout, Action action, CancellationToken cancellationToken = default);
5353

5454
/// <summary>
5555
/// Executes <paramref name="action"/> and
@@ -60,7 +60,7 @@ public interface IEventFilterApplier
6060
/// <param name="timeout">The time to wait for a log event after executing <paramref name="action"/></param>
6161
/// <param name="action">The action.</param>
6262
/// <param name="cancellationToken"></param>
63-
Task ExpectOneAsync(TimeSpan timeout, Func<Task> action, CancellationToken cancellationToken = default);
63+
Task ExpectOneAsync([AutoDilate] TimeSpan timeout, Func<Task> action, CancellationToken cancellationToken = default);
6464

6565
[Obsolete(
6666
"Only for backwards compat. Use ExpectOneAsync(Func<Task>, CancellationToken) instead beginning in Akka.NET v1.5")]
@@ -101,7 +101,7 @@ public interface IEventFilterApplier
101101
/// <param name="actionAsync">The async action.</param>
102102
/// <param name="timeout"></param>
103103
/// <param name="cancellationToken"></param>
104-
Task ExpectAsync(int expectedCount, Func<Task> actionAsync, TimeSpan? timeout, CancellationToken cancellationToken = default);
104+
Task ExpectAsync(int expectedCount, Func<Task> actionAsync, [AutoDilate] TimeSpan? timeout, CancellationToken cancellationToken = default);
105105

106106
/// <summary>
107107
/// Executes <paramref name="action"/> and expects the specified number
@@ -114,7 +114,7 @@ public interface IEventFilterApplier
114114
/// <param name="expectedCount">The expected number of events</param>
115115
/// <param name="action">The action.</param>
116116
/// <param name="cancellationToken"></param>
117-
void Expect(int expectedCount, TimeSpan timeout, Action action, CancellationToken cancellationToken = default);
117+
void Expect(int expectedCount, [AutoDilate] TimeSpan timeout, Action action, CancellationToken cancellationToken = default);
118118

119119
/// <summary>
120120
/// Executes <paramref name="action"/> and expects the specified number
@@ -127,10 +127,10 @@ public interface IEventFilterApplier
127127
/// <param name="expectedCount">The expected number of events</param>
128128
/// <param name="action">The action.</param>
129129
/// <param name="cancellationToken"></param>
130-
Task ExpectAsync(int expectedCount, TimeSpan timeout, Func<Task> action, CancellationToken cancellationToken = default);
130+
Task ExpectAsync(int expectedCount, [AutoDilate] TimeSpan timeout, Func<Task> action, CancellationToken cancellationToken = default);
131131

132132
[Obsolete("Use ExpectAsync<T>(expectedCount, TimeSpan, Func<Task<T>>) instead. This method only exists to support backwards compatibility as of Akka.NET v1.5.")]
133-
Task ExpectAsync(int expectedCount, TimeSpan timeout, Action action, CancellationToken cancellationToken = default);
133+
Task ExpectAsync(int expectedCount, [AutoDilate] TimeSpan timeout, Action action, CancellationToken cancellationToken = default);
134134

135135
/// <summary>
136136
/// Executes <paramref name="func"/> and
@@ -169,7 +169,7 @@ public interface IEventFilterApplier
169169
/// <param name="func">The function.</param>
170170
/// <param name="cancellationToken"></param>
171171
/// <returns>The returned value from <paramref name="func"/>.</returns>
172-
T ExpectOne<T>(TimeSpan timeout, Func<T> func, CancellationToken cancellationToken = default);
172+
T ExpectOne<T>([AutoDilate] TimeSpan timeout, Func<T> func, CancellationToken cancellationToken = default);
173173

174174
/// <summary>
175175
/// Executes <paramref name="func"/> and
@@ -182,7 +182,7 @@ public interface IEventFilterApplier
182182
/// <param name="func">The function.</param>
183183
/// <param name="cancellationToken"></param>
184184
/// <returns>The returned value from <paramref name="func"/>.</returns>
185-
Task<T> ExpectOneAsync<T>(TimeSpan timeout, Func<Task<T>> func, CancellationToken cancellationToken = default);
185+
Task<T> ExpectOneAsync<T>([AutoDilate] TimeSpan timeout, Func<Task<T>> func, CancellationToken cancellationToken = default);
186186

187187
/// <summary>
188188
/// Executes <paramref name="func"/> and expects the specified number
@@ -226,7 +226,7 @@ public interface IEventFilterApplier
226226
/// <param name="func">The function.</param>
227227
/// <param name="cancellationToken"></param>
228228
/// <returns>The returned value from <paramref name="func"/>.</returns>
229-
T Expect<T>(int expectedCount, TimeSpan timeout, Func<T> func, CancellationToken cancellationToken = default);
229+
T Expect<T>(int expectedCount, [AutoDilate] TimeSpan timeout, Func<T> func, CancellationToken cancellationToken = default);
230230

231231
/// <summary>
232232
/// Executes <paramref name="func"/> and expects the specified number
@@ -241,7 +241,7 @@ public interface IEventFilterApplier
241241
/// <param name="func">The function.</param>
242242
/// <param name="cancellationToken"></param>
243243
/// <returns>The returned value from <paramref name="func"/>.</returns>
244-
Task<T> ExpectAsync<T>(int expectedCount, TimeSpan timeout, Func<Task<T>> func, CancellationToken cancellationToken = default);
244+
Task<T> ExpectAsync<T>(int expectedCount, [AutoDilate] TimeSpan timeout, Func<Task<T>> func, CancellationToken cancellationToken = default);
245245

246246
/// <summary>
247247
/// Executes <paramref name="func"/> and prevent events from being logged during the execution.
@@ -307,4 +307,4 @@ public interface IEventFilterApplier
307307
/// Let's you chain more filters together. Similar to Akka JVM's filterEvents
308308
/// </summary>
309309
EventFilterFactory And { get; }
310-
}
310+
}

0 commit comments

Comments
 (0)