Skip to content

Commit 78d23cb

Browse files
cjhillbrandCJ Hillbrand
andauthored
Filter Child Components dictated by the User-Provided Scenario Options. (#528)
* Filter child components logic. * Unit tests and up version. * Additional Unit tests exercsing a non-duplicated scenario * One last test to exercise exclusion scenario. --------- Co-authored-by: CJ Hillbrand <[email protected]>
1 parent 5815fc9 commit 78d23cb

File tree

5 files changed

+217
-15
lines changed

5 files changed

+217
-15
lines changed

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.0.25
1+
2.0.26

src/VirtualClient/VirtualClient.Contracts/ComponentFactory.cs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,22 @@ public static class ComponentFactory
3030
/// (vs. terminal).
3131
/// </param>
3232
/// <param name="logToFile">True to instruct the application to log output to files on the file system.</param>
33+
/// <param name="includedScenarios">
34+
/// When evaluating child components evaluate whether or not the child component should be
35+
/// included dictated by the scenarios provided on the command line. These scenarios must be included.
36+
/// </param>
37+
/// <param name="excludedScenarios">
38+
/// When evaluating child components evaluate whether or not the child component should be
39+
/// included dictated by the scenarios provided on the command line. These scenarios must be excluded.
40+
/// </param>
3341
public static VirtualClientComponent CreateComponent(
3442
ExecutionProfileElement componentDescription,
3543
IServiceCollection dependencies,
3644
int? randomizationSeed = null,
3745
bool? failFast = null,
38-
bool? logToFile = null)
46+
bool? logToFile = null,
47+
IEnumerable<string> includedScenarios = null,
48+
IEnumerable<string> excludedScenarios = null)
3949
{
4050
componentDescription.ThrowIfNull(nameof(componentDescription));
4151
dependencies.ThrowIfNull(nameof(dependencies));
@@ -55,7 +65,9 @@ public static VirtualClientComponent CreateComponent(
5565
new Dictionary<string, JToken>(StringComparer.OrdinalIgnoreCase),
5666
randomizationSeed,
5767
failFast,
58-
logToFile);
68+
logToFile,
69+
includedScenarios,
70+
excludedScenarios);
5971

6072
return component;
6173
}
@@ -99,7 +111,9 @@ private static VirtualClientComponent CreateComponent(
99111
IDictionary<string, JToken> extensions,
100112
int? randomizationSeed = null,
101113
bool? failFast = null,
102-
bool? logToFile = null)
114+
bool? logToFile = null,
115+
IEnumerable<string> includeScenarios = null,
116+
IEnumerable<string> excludeScenarios = null)
103117
{
104118
VirtualClientComponent component = component = (VirtualClientComponent)Activator.CreateInstance(type, dependencies, componentDescription.Parameters);
105119
component.ComponentType = componentDescription.ComponentType;
@@ -157,6 +171,22 @@ private static VirtualClientComponent CreateComponent(
157171
throw new TypeLoadException($"Type '{subComponent.Type}' does not exist.");
158172
}
159173

174+
bool scenarioIncluded = includeScenarios?.Any() == true;
175+
if (includeScenarios?.Any() == true)
176+
{
177+
scenarioIncluded = subComponent.IsTargetedScenario(includeScenarios);
178+
if (!scenarioIncluded)
179+
{
180+
continue;
181+
}
182+
}
183+
184+
// Included scenarios take precedence over excluded (e.g. Scenario1,-Scenario1 -> Scenario1 will be included).
185+
if (!scenarioIncluded && excludeScenarios?.Any() == true && subComponent.IsExcludedScenario(excludeScenarios))
186+
{
187+
continue;
188+
}
189+
160190
VirtualClientComponent childComponent = ComponentFactory.CreateComponent(
161191
subComponent,
162192
subcomponentType,

src/VirtualClient/VirtualClient.Core.UnitTests/ProfileExecutorTests.cs

Lines changed: 131 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,23 @@ public void SetupDefaults()
5252
"VirtualClient.TestExecutor",
5353
new Dictionary<string, IConvertible> { ["Scenario"] = "Scenario2" }),
5454
new ExecutionProfileElement(
55-
"VirtualClient.TestExecutor",
56-
new Dictionary<string, IConvertible> { ["Scenario"] = "Scenario3" })
55+
"VirtualClient.TestCollectionExecutor",
56+
new Dictionary<string, IConvertible> { ["Scenario"] = "Scenario3" },
57+
components: new List<ExecutionProfileElement>()
58+
{
59+
new ExecutionProfileElement(
60+
"VirtualClient.TestExecutor",
61+
new Dictionary<string, IConvertible> { ["Scenario"] = "Scenario1" }),
62+
new ExecutionProfileElement(
63+
"VirtualClient.TestExecutor",
64+
new Dictionary<string, IConvertible> { ["Scenario"] = "Scenario2" }),
65+
new ExecutionProfileElement(
66+
"VirtualClient.TestExecutor",
67+
new Dictionary<string, IConvertible> { ["Scenario"] = "Scenario3" }),
68+
new ExecutionProfileElement(
69+
"VirtualClient.TestExecutor",
70+
new Dictionary<string, IConvertible> { ["Scenario"] = "Scenario4" })
71+
})
5772
},
5873
dependencies: new List<ExecutionProfileElement>
5974
{
@@ -94,7 +109,8 @@ public void ProfileExecutorCreatesTheExpectedWorkloadActionExecutorsAsDefinedInT
94109

95110
Assert.IsNotEmpty(executor.ProfileActions);
96111
Assert.IsTrue(executor.ProfileActions.Count() == 3);
97-
Assert.IsTrue(executor.ProfileActions.All(action => action.GetType() == typeof(TestExecutor)));
112+
Assert.IsTrue(executor.ProfileActions.Where(action => action.GetType() == typeof(TestExecutor)).Count() == 2);
113+
Assert.IsTrue(executor.ProfileActions.Where(action => action.GetType() == typeof(TestCollectionExecutor)).Count() == 1);
98114
Assert.AreEqual("Scenario1", executor.ProfileActions.ElementAt(0).Parameters["Scenario"]);
99115
Assert.AreEqual("Scenario2", executor.ProfileActions.ElementAt(1).Parameters["Scenario"]);
100116
Assert.AreEqual("Scenario3", executor.ProfileActions.ElementAt(2).Parameters["Scenario"]);
@@ -208,6 +224,12 @@ public void ProfileExecutorSupportsUserSpecifiedScenarios_Subsets_Of_Scenarios()
208224
CollectionAssert.AreEquivalent(
209225
this.mockProfile.Actions.Skip(1).Select(a => a.Parameters["Scenario"]),
210226
executor.ProfileActions.Select(a => a.Parameters["Scenario"]));
227+
228+
// Assert child components honor the scenario values.
229+
VirtualClientComponentCollection collectionComponent = executor.ProfileActions.First(action => action.GetType() == typeof(TestCollectionExecutor)) as VirtualClientComponentCollection;
230+
Assert.IsNotNull(collectionComponent);
231+
Assert.AreEqual(2, collectionComponent.Count);
232+
Assert.IsTrue(collectionComponent.All(component => targetScenarios.Contains(component.Parameters["Scenario"])));
211233
}
212234
}
213235

@@ -220,18 +242,25 @@ public void ProfileExecutorSupportsUserSpecifiedScenarios_Components_Have_Duplic
220242
};
221243

222244
// Ensure we have components that share the same scenario name.
223-
this.mockProfile.Actions.Take(2).ToList().ForEach(a => a.Parameters["Scenario"] = "Scenario1");
245+
this.mockProfile.Actions.Take(3).ToList().ForEach(a => a.Parameters["Scenario"] = "Scenario1");
246+
this.mockProfile.Actions.Last().Components.ToList().ForEach(a => a.Parameters["Scenario"] = "Scenario1");
224247

225248
using (TestProfileExecutor executor = new TestProfileExecutor(this.mockProfile, this.mockFixture.Dependencies, targetScenarios))
226249
{
227250
executor.Initialize();
228251

229252
Assert.IsNotEmpty(executor.ProfileActions);
230-
Assert.IsTrue(executor.ProfileActions.Count() == 2);
253+
Assert.IsTrue(executor.ProfileActions.Count() == 3);
231254

232255
CollectionAssert.AreEquivalent(
233-
this.mockProfile.Actions.Take(2).Select(a => a.Parameters["Scenario"]),
256+
this.mockProfile.Actions.Take(3).Select(a => a.Parameters["Scenario"]),
234257
executor.ProfileActions.Select(a => a.Parameters["Scenario"]));
258+
259+
// Assert child components honor the scenario values.
260+
VirtualClientComponentCollection collectionComponent = executor.ProfileActions.First(action => action.GetType() == typeof(TestCollectionExecutor)) as VirtualClientComponentCollection;
261+
Assert.IsNotNull(collectionComponent);
262+
Assert.AreEqual(4, collectionComponent.Count);
263+
Assert.IsTrue(collectionComponent.All(component => targetScenarios.Contains(component.Parameters["Scenario"])));
235264
}
236265
}
237266

@@ -273,6 +302,92 @@ public void ProfileExecutorSupportsUserSpecifiedScenarioExclusionForActions()
273302
}
274303
}
275304

305+
[Test]
306+
public void ProfileExecutorSupportsUserSpecifiedScenarioExclusionForActionsAndChildComponents()
307+
{
308+
List<string> excludedScenarios = new List<string>()
309+
{
310+
"-Scenario1",
311+
"-Scenario2"
312+
};
313+
314+
using (TestProfileExecutor executor = new TestProfileExecutor(this.mockProfile, this.mockFixture.Dependencies, excludedScenarios))
315+
{
316+
executor.Initialize();
317+
318+
Assert.IsNotEmpty(executor.ProfileActions);
319+
Assert.IsTrue(executor.ProfileActions.Count() == 1);
320+
321+
CollectionAssert.AreEquivalent(
322+
this.mockProfile.Actions.Skip(2).Take(1).Select(a => a.Parameters["Scenario"]),
323+
executor.ProfileActions.Select(a => a.Parameters["Scenario"]));
324+
325+
// Assert child components honor the scenario values.
326+
VirtualClientComponentCollection collectionComponent = executor.ProfileActions.First(action => action.GetType() == typeof(TestCollectionExecutor)) as VirtualClientComponentCollection;
327+
Assert.IsNotNull(collectionComponent);
328+
Assert.AreEqual(2, collectionComponent.Count);
329+
330+
Assert.IsTrue(collectionComponent.First().Parameters["Scenario"].ToString() == "Scenario3");
331+
Assert.IsTrue(collectionComponent.Last().Parameters["Scenario"].ToString() == "Scenario4");
332+
}
333+
}
334+
335+
[Test]
336+
public void ProfileExecutorSupportsUsingSpecifiedInclusionsForSpecificChildComponents()
337+
{
338+
List<string> includedScenarios = new List<string>()
339+
{
340+
"Scenario3",
341+
"Scenario4"
342+
};
343+
344+
using (TestProfileExecutor executor = new TestProfileExecutor(this.mockProfile, this.mockFixture.Dependencies, includedScenarios))
345+
{
346+
executor.Initialize();
347+
348+
Assert.IsNotEmpty(executor.ProfileActions);
349+
Assert.IsTrue(executor.ProfileActions.Count() == 1);
350+
351+
CollectionAssert.AreEquivalent(
352+
this.mockProfile.Actions.Skip(2).Take(1).Select(a => a.Parameters["Scenario"]),
353+
executor.ProfileActions.Select(a => a.Parameters["Scenario"]));
354+
355+
// Assert child components honor the scenario values.
356+
VirtualClientComponentCollection collectionComponent = executor.ProfileActions.First(action => action.GetType() == typeof(TestCollectionExecutor)) as VirtualClientComponentCollection;
357+
Assert.IsNotNull(collectionComponent);
358+
Assert.AreEqual(2, collectionComponent.Count);
359+
360+
Assert.IsTrue(collectionComponent.First().Parameters["Scenario"].ToString() == "Scenario3");
361+
Assert.IsTrue(collectionComponent.Last().Parameters["Scenario"].ToString() == "Scenario4");
362+
}
363+
}
364+
365+
[Test]
366+
public void ProfileExecutorSupportsUsingSpecifiedExclusionsForSpecificChildComponents()
367+
{
368+
List<string> includedScenarios = new List<string>()
369+
{
370+
"-Scenario4"
371+
};
372+
373+
this.mockProfile.Actions.Last().Parameters.Remove("Scenario");
374+
375+
using (TestProfileExecutor executor = new TestProfileExecutor(this.mockProfile, this.mockFixture.Dependencies, includedScenarios))
376+
{
377+
executor.Initialize();
378+
379+
Assert.IsNotEmpty(executor.ProfileActions);
380+
Assert.IsTrue(executor.ProfileActions.Count() == 3);
381+
382+
// Assert child components honor the scenario values.
383+
VirtualClientComponentCollection collectionComponent = executor.ProfileActions.First(action => action.GetType() == typeof(TestCollectionExecutor)) as VirtualClientComponentCollection;
384+
Assert.IsNotNull(collectionComponent);
385+
Assert.AreEqual(3, collectionComponent.Count);
386+
387+
Assert.IsTrue(collectionComponent.All(component => component.Parameters["Scenario"].ToString() != "Scenario4"));
388+
}
389+
}
390+
276391
[Test]
277392
public void ProfileExecutorSupportsUserSpecifiedScenarioExclusionForDependencies()
278393
{
@@ -554,11 +669,17 @@ public async Task ProfileExecutorCorrelationIdentifiersAreCorrectForActionsExecu
554669
Assert.IsNotEmpty(iterations);
555670
Assert.IsTrue(iterations.Count() == 2);
556671

557-
var actions = this.mockFixture.Logger.Where(log => log.Item2.Name == "TestExecutor.ExecuteStart").Select(a => a.Item3 as EventContext);
558-
Assert.IsNotNull(actions);
559-
Assert.IsNotEmpty(actions);
560-
Assert.IsTrue(actions.Count() == 6);
672+
var singleActions = this.mockFixture.Logger.Where(log => log.Item2.Name == "TestExecutor.ExecuteStart").Select(a => a.Item3 as EventContext);
673+
Assert.IsNotNull(singleActions);
674+
Assert.IsNotEmpty(singleActions);
675+
Assert.AreEqual(4, singleActions.Count());
676+
677+
var collectionActions = this.mockFixture.Logger.Where(log => log.Item2.Name == "TestCollectionExecutor.ExecuteStart").Select(a => a.Item3 as EventContext);
678+
Assert.IsNotNull(collectionActions);
679+
Assert.IsNotEmpty(collectionActions);
680+
Assert.AreEqual(2, collectionActions.Count());
561681

682+
var actions = singleActions.Union(collectionActions);
562683
// First round of actions should have the same parent ID but each action should have its
563684
// own unique activity ID.
564685
var iteration1Actions = actions.Where(a => a.ParentActivityId == iterations.ElementAt(0).ActivityId);

src/VirtualClient/VirtualClient.Core/ProfileExecutor.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,9 @@ private List<VirtualClientComponent> CreateComponents(
712712
this.Dependencies,
713713
this.RandomizationSeed,
714714
this.FailFast,
715-
this.LogToFile);
715+
this.LogToFile,
716+
includeScenarios,
717+
excludeScenarios);
716718

717719
if (!VirtualClientComponent.IsSupported(runtimeComponent))
718720
{
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace VirtualClient
5+
{
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using Microsoft.Extensions.DependencyInjection;
11+
using VirtualClient.Common.Telemetry;
12+
using VirtualClient.Contracts;
13+
14+
/// <summary>
15+
/// A test/fake executor that can be used in unit and functional testing scenarios.
16+
/// </summary>
17+
public class TestCollectionExecutor : VirtualClientComponentCollection
18+
{
19+
/// <summary>
20+
/// Initializes a new instance of the <see cref="TestExecutor"/> class.
21+
/// </summary>
22+
public TestCollectionExecutor(MockFixture fixture)
23+
: base(fixture?.Dependencies, fixture?.Parameters)
24+
{
25+
this.LogToFile = true;
26+
}
27+
28+
/// <summary>
29+
/// Initializes a new instance of the <see cref="TestExecutor"/> class.
30+
/// </summary>
31+
public TestCollectionExecutor(IServiceCollection dependencies, IDictionary<string, IConvertible> parameters)
32+
: base(dependencies, parameters)
33+
{
34+
this.LogToFile = true;
35+
}
36+
37+
/// <summary>
38+
/// Delegate/anonymous function can be used to inject behaviors into the executor when
39+
/// the <see cref="ExecuteAsync(EventContext, CancellationToken)"/> method is called.
40+
/// </summary>
41+
public Action<EventContext, CancellationToken> OnExecute { get; set; }
42+
43+
protected override Task ExecuteAsync(EventContext telemetryContext, CancellationToken cancellationToken)
44+
{
45+
this.OnExecute?.Invoke(telemetryContext, cancellationToken);
46+
return Task.CompletedTask;
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)