private async Task PerformTrace(IHost host, TargetFrameworkMoniker tfm)
        {
            CollectTraceOptions options = ActionTestsHelper.GetActionOptions <CollectTraceOptions>(host, DefaultRuleName);

            ICollectionRuleActionFactoryProxy factory;

            Assert.True(host.Services.GetService <ICollectionRuleActionOperations>().TryCreateFactory(KnownCollectionRuleActions.CollectTrace, out factory));

            EndpointInfoSourceCallback callback = new(_outputHelper);

            await using ServerSourceHolder sourceHolder = await _endpointUtilities.StartServerAsync(callback);

            AppRunner runner = _endpointUtilities.CreateAppRunner(sourceHolder.TransportName, tfm);

            Task <IEndpointInfo> newEndpointInfoTask = callback.WaitAddedEndpointInfoAsync(runner, CommonTestTimeouts.StartProcess);

            await runner.ExecuteAsync(async() =>
            {
                IEndpointInfo endpointInfo = await newEndpointInfoTask;

                ICollectionRuleAction action = factory.Create(endpointInfo, options);

                CollectionRuleActionResult result = await ActionTestsHelper.ExecuteAndDisposeAsync(action, CommonTestTimeouts.TraceTimeout);

                string egressPath = ActionTestsHelper.ValidateEgressPath(result);

                using FileStream traceStream = new(egressPath, FileMode.Open, FileAccess.Read);
                Assert.NotNull(traceStream);

                await ValidateTrace(traceStream);

                await runner.SendCommandAsync(TestAppScenarios.AsyncWait.Commands.Continue);
            });
        }
Beispiel #2
0
        public async Task ServerSourceNoConnectionsTest()
        {
            await using ServerSourceHolder sourceHolder = await _endpointUtilities.StartServerAsync();

            var endpointInfos = await _endpointUtilities.GetEndpointInfoAsync(sourceHolder.Source);

            Assert.Empty(endpointInfos);
        }
        public async Task TestGetEnvVar(TargetFrameworkMoniker tfm)
        {
            const string VariableDoesNotExist = "SomeEnvVarThatIsNotSet";
            await TestHostHelper.CreateCollectionRulesHost(
                outputHelper : _outputHelper,
                setup : (Tools.Monitor.RootOptions rootOptions) =>
            {
                rootOptions.CreateCollectionRule(DefaultRuleName)
                .SetStartupTrigger()
                .AddGetEnvironmentVariableAction(TestAppScenarios.EnvironmentVariables.IncrementVariableName)
                .AddGetEnvironmentVariableAction(VariableDoesNotExist);
            },
                hostCallback : async(Extensions.Hosting.IHost host) =>
            {
                GetEnvironmentVariableOptions getOpts     = ActionTestsHelper.GetActionOptions <GetEnvironmentVariableOptions>(host, DefaultRuleName, 0);
                GetEnvironmentVariableOptions getFailOpts = ActionTestsHelper.GetActionOptions <GetEnvironmentVariableOptions>(host, DefaultRuleName, 1);

                ICollectionRuleActionOperations actionOperationsService = host.Services.GetService <ICollectionRuleActionOperations>();
                Assert.True(actionOperationsService.TryCreateFactory(KnownCollectionRuleActions.GetEnvironmentVariable, out ICollectionRuleActionFactoryProxy getFactory));

                EndpointInfoSourceCallback endpointInfoCallback = new(_outputHelper);
                await using ServerSourceHolder sourceHolder     = await _endpointUtilities.StartServerAsync(endpointInfoCallback);

                AppRunner runner    = _endpointUtilities.CreateAppRunner(sourceHolder.TransportName, tfm);
                runner.ScenarioName = TestAppScenarios.EnvironmentVariables.Name;

                Task <IEndpointInfo> newEndpointInfoTask = endpointInfoCallback.WaitAddedEndpointInfoAsync(runner, CommonTestTimeouts.StartProcess);

                await runner.ExecuteAsync(async() =>
                {
                    IEndpointInfo endpointInfo = await newEndpointInfoTask;

                    ICollectionRuleAction getAction = getFactory.Create(endpointInfo, getOpts);

                    await runner.SendCommandAsync(TestAppScenarios.EnvironmentVariables.Commands.IncVar);
                    Assert.Equal("1", await runner.GetEnvironmentVariable(TestAppScenarios.EnvironmentVariables.IncrementVariableName, CommonTestTimeouts.EnvVarsTimeout));

                    await runner.SendCommandAsync(TestAppScenarios.EnvironmentVariables.Commands.IncVar);
                    Assert.Equal("2", await runner.GetEnvironmentVariable(TestAppScenarios.EnvironmentVariables.IncrementVariableName, CommonTestTimeouts.EnvVarsTimeout));

                    CollectionRuleActionResult result = await ActionTestsHelper.ExecuteAndDisposeAsync(getAction, CommonTestTimeouts.EnvVarsTimeout);
                    Assert.Equal("2", result.OutputValues[CollectionRuleActionConstants.EnvironmentVariableValueName]);

                    await runner.SendCommandAsync(TestAppScenarios.EnvironmentVariables.Commands.IncVar);
                    Assert.Equal("3", await runner.GetEnvironmentVariable(TestAppScenarios.EnvironmentVariables.IncrementVariableName, CommonTestTimeouts.EnvVarsTimeout));

                    ICollectionRuleAction getActionFailure = getFactory.Create(endpointInfo, getFailOpts);
                    CollectionRuleActionException thrownEx = await Assert.ThrowsAsync <CollectionRuleActionException>(async() =>
                    {
                        await ActionTestsHelper.ExecuteAndDisposeAsync(getActionFailure, CommonTestTimeouts.EnvVarsTimeout);
                    });
                    Assert.Contains(VariableDoesNotExist, thrownEx.Message);

                    await runner.SendCommandAsync(TestAppScenarios.EnvironmentVariables.Commands.ShutdownScenario);
                });
            });
        }
Beispiel #4
0
        public async Task ServerSourceNoPruneDuringDumpTest(TargetFrameworkMoniker appTfm)
        {
            EndpointInfoSourceCallback callback     = new(_outputHelper);
            var             operationTrackerService = new OperationTrackerService();
            MockDumpService dumpService             = new(operationTrackerService);

            await using ServerSourceHolder sourceHolder = await _endpointUtilities.StartServerAsync(callback, dumpService, operationTrackerService);

            AppRunner runner = _endpointUtilities.CreateAppRunner(sourceHolder.TransportName, appTfm);

            Task <IEndpointInfo> addedEndpointTask   = callback.WaitAddedEndpointInfoAsync(runner, CommonTestTimeouts.StartProcess);
            Task <IEndpointInfo> removedEndpointTask = callback.WaitRemovedEndpointInfoAsync(runner, CommonTestTimeouts.StartProcess);

            Task <Stream> dumpTask     = null;
            IEndpointInfo endpointInfo = null;

            await runner.ExecuteAsync(async() =>
            {
                _outputHelper.WriteLine("Waiting for added endpoint notification.");
                endpointInfo = await addedEndpointTask;
                _outputHelper.WriteLine("Received added endpoint notifications.");

                // Start a dump operation; the process should not be pruned until the operation is completed.
                dumpTask = dumpService.DumpAsync(endpointInfo, DumpType.Triage, CancellationToken.None);

                await runner.SendCommandAsync(TestAppScenarios.AsyncWait.Commands.Continue);
            });

            // At this point, the process should no longer exist; but since the mock dump operation is
            // in progress, the ServiceEndpointInfoSource should not prune the process until the operation
            // is complete.

            int processId = await runner.ProcessIdTask;

            // Test that the process still exists.
            Assert.False(removedEndpointTask.IsCompleted);
            IEnumerable <IEndpointInfo> endpointInfos = await _endpointUtilities.GetEndpointInfoAsync(sourceHolder.Source);

            endpointInfo = Assert.Single(endpointInfos);
            Assert.Equal(processId, endpointInfo.ProcessId);

            // Signal and wait for mock dump operation to complete.
            dumpService.CompleteOperation();
            await dumpTask;

            // Process should no longer exist
            endpointInfos = await _endpointUtilities.GetEndpointInfoAsync(sourceHolder.Source);

            Assert.Empty(endpointInfos);

            // Test that process removal sent notification
            endpointInfo = await removedEndpointTask;
            Assert.Equal(processId, endpointInfo.ProcessId);
        }
Beispiel #5
0
        public async Task CollectDumpAction_Success(TargetFrameworkMoniker tfm, DumpType?dumpType)
        {
            // MacOS dumps inconsistently segfault the runtime on .NET 5: https://github.com/dotnet/dotnet-monitor/issues/174
            if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && tfm == TargetFrameworkMoniker.Net50)
            {
                return;
            }

            using TemporaryDirectory tempDirectory = new(_outputHelper);

            await TestHostHelper.CreateCollectionRulesHost(_outputHelper, rootOptions =>
            {
                rootOptions.AddFileSystemEgress(ActionTestsConstants.ExpectedEgressProvider, tempDirectory.FullName);

                rootOptions.CreateCollectionRule(DefaultRuleName)
                .AddCollectDumpAction(ActionTestsConstants.ExpectedEgressProvider, dumpType)
                .SetStartupTrigger();
            }, async host =>
            {
                CollectDumpOptions options = ActionTestsHelper.GetActionOptions <CollectDumpOptions>(host, DefaultRuleName);

                ICollectionRuleActionFactoryProxy factory;
                Assert.True(host.Services.GetService <ICollectionRuleActionOperations>().TryCreateFactory(KnownCollectionRuleActions.CollectDump, out factory));

                EndpointInfoSourceCallback callback         = new(_outputHelper);
                await using ServerSourceHolder sourceHolder = await _endpointUtilities.StartServerAsync(callback);

                AppRunner runner = _endpointUtilities.CreateAppRunner(sourceHolder.TransportName, tfm);

                Task <IEndpointInfo> newEndpointInfoTask = callback.WaitAddedEndpointInfoAsync(runner, CommonTestTimeouts.StartProcess);

                await runner.ExecuteAsync(async() =>
                {
                    IEndpointInfo endpointInfo = await newEndpointInfoTask;

                    ICollectionRuleAction action = factory.Create(endpointInfo, options);

                    CollectionRuleActionResult result = await ActionTestsHelper.ExecuteAndDisposeAsync(action, CommonTestTimeouts.DumpTimeout);

                    string egressPath = ActionTestsHelper.ValidateEgressPath(result);

                    using FileStream dumpStream = new(egressPath, FileMode.Open, FileAccess.Read);
                    Assert.NotNull(dumpStream);

                    await DumpTestUtilities.ValidateDump(runner.Environment.ContainsKey(DumpTestUtilities.EnableElfDumpOnMacOS), dumpStream);

                    await runner.SendCommandAsync(TestAppScenarios.AsyncWait.Commands.Continue);
                });
            });
        }
        public async Task TestEnvVarRoundTrip(TargetFrameworkMoniker tfm)
        {
            await TestHostHelper.CreateCollectionRulesHost(
                outputHelper : _outputHelper,
                setup : (Tools.Monitor.RootOptions rootOptions) =>
            {
                rootOptions.CreateCollectionRule(DefaultRuleName)
                .SetStartupTrigger()
                .AddSetEnvironmentVariableAction(DefaultVarName, DefaultVarValue)
                .AddGetEnvironmentVariableAction(DefaultVarName);
            },
                hostCallback : async(Extensions.Hosting.IHost host) =>
            {
                SetEnvironmentVariableOptions setOpts = ActionTestsHelper.GetActionOptions <SetEnvironmentVariableOptions>(host, DefaultRuleName, 0);
                GetEnvironmentVariableOptions getOpts = ActionTestsHelper.GetActionOptions <GetEnvironmentVariableOptions>(host, DefaultRuleName, 1);

                ICollectionRuleActionOperations actionOperationsService = host.Services.GetService <ICollectionRuleActionOperations>();
                Assert.True(actionOperationsService.TryCreateFactory(KnownCollectionRuleActions.SetEnvironmentVariable, out ICollectionRuleActionFactoryProxy setFactory));
                Assert.True(actionOperationsService.TryCreateFactory(KnownCollectionRuleActions.GetEnvironmentVariable, out ICollectionRuleActionFactoryProxy getFactory));

                EndpointInfoSourceCallback endpointInfoCallback = new(_outputHelper);
                await using ServerSourceHolder sourceHolder     = await _endpointUtilities.StartServerAsync(endpointInfoCallback);

                AppRunner runner    = _endpointUtilities.CreateAppRunner(sourceHolder.TransportName, tfm);
                runner.ScenarioName = TestAppScenarios.EnvironmentVariables.Name;

                Task <IEndpointInfo> newEndpointInfoTask = endpointInfoCallback.WaitAddedEndpointInfoAsync(runner, CommonTestTimeouts.StartProcess);

                await runner.ExecuteAsync(async() =>
                {
                    IEndpointInfo endpointInfo = await newEndpointInfoTask;

                    ICollectionRuleAction setAction = setFactory.Create(endpointInfo, setOpts);
                    ICollectionRuleAction getAction = getFactory.Create(endpointInfo, getOpts);

                    await ActionTestsHelper.ExecuteAndDisposeAsync(setAction, CommonTestTimeouts.EnvVarsTimeout);
                    CollectionRuleActionResult getResult = await ActionTestsHelper.ExecuteAndDisposeAsync(getAction, CommonTestTimeouts.EnvVarsTimeout);

                    await runner.SendCommandAsync(TestAppScenarios.EnvironmentVariables.Commands.ShutdownScenario);

                    Assert.True(getResult.OutputValues.TryGetValue(CollectionRuleActionConstants.EnvironmentVariableValueName, out string value));
                    Assert.Equal(DefaultVarValue, value);
                });
            });
        }
Beispiel #7
0
        public async Task CollectGCDumpAction_Success(TargetFrameworkMoniker tfm)
        {
            using TemporaryDirectory tempDirectory = new(_outputHelper);

            await TestHostHelper.CreateCollectionRulesHost(_outputHelper, rootOptions =>
            {
                rootOptions.AddFileSystemEgress(ActionTestsConstants.ExpectedEgressProvider, tempDirectory.FullName);

                rootOptions.CreateCollectionRule(DefaultRuleName)
                .AddCollectGCDumpAction(ActionTestsConstants.ExpectedEgressProvider)
                .SetStartupTrigger();
            }, async host =>
            {
                CollectGCDumpOptions options = ActionTestsHelper.GetActionOptions <CollectGCDumpOptions>(host, DefaultRuleName);

                ICollectionRuleActionFactoryProxy factory;
                Assert.True(host.Services.GetService <ICollectionRuleActionOperations>().TryCreateFactory(KnownCollectionRuleActions.CollectGCDump, out factory));

                EndpointInfoSourceCallback callback         = new(_outputHelper);
                await using ServerSourceHolder sourceHolder = await _endpointUtilities.StartServerAsync(callback);

                AppRunner runner = _endpointUtilities.CreateAppRunner(sourceHolder.TransportName, tfm);

                Task <IEndpointInfo> newEndpointInfoTask = callback.WaitAddedEndpointInfoAsync(runner, CommonTestTimeouts.StartProcess);

                await runner.ExecuteAsync(async() =>
                {
                    IEndpointInfo endpointInfo = await newEndpointInfoTask;

                    ICollectionRuleAction action = factory.Create(endpointInfo, options);

                    CollectionRuleActionResult result = await ActionTestsHelper.ExecuteAndDisposeAsync(action, CommonTestTimeouts.GCDumpTimeout);

                    string egressPath = ActionTestsHelper.ValidateEgressPath(result);

                    using FileStream gcdumpStream = new(egressPath, FileMode.Open, FileAccess.Read);
                    Assert.NotNull(gcdumpStream);

                    await ValidateGCDump(gcdumpStream);

                    await runner.SendCommandAsync(TestAppScenarios.AsyncWait.Commands.Continue);
                });
            });
        }
Beispiel #8
0
        public async Task ServerSourceAddRemoveSingleConnectionTest(TargetFrameworkMoniker appTfm)
        {
            EndpointInfoSourceCallback callback = new(_outputHelper);

            await using ServerSourceHolder sourceHolder = await _endpointUtilities.StartServerAsync(callback);

            var endpointInfos = await _endpointUtilities.GetEndpointInfoAsync(sourceHolder.Source);

            Assert.Empty(endpointInfos);

            AppRunner runner = _endpointUtilities.CreateAppRunner(sourceHolder.TransportName, appTfm);

            Task addedEndpointTask   = callback.WaitAddedEndpointInfoAsync(runner, CommonTestTimeouts.StartProcess);
            Task removedEndpointTask = callback.WaitRemovedEndpointInfoAsync(runner, CommonTestTimeouts.StartProcess);

            await runner.ExecuteAsync(async() =>
            {
                _outputHelper.WriteLine("Waiting for added endpoint notification.");
                await addedEndpointTask;
                _outputHelper.WriteLine("Received added endpoint notifications.");

                endpointInfos = await _endpointUtilities.GetEndpointInfoAsync(sourceHolder.Source);

                var endpointInfo = Assert.Single(endpointInfos);

                ValidateEndpointInfo(endpointInfo);

                await EndpointUtilities.VerifyConnectionAsync(runner, endpointInfo);

                await runner.SendCommandAsync(TestAppScenarios.AsyncWait.Commands.Continue);
            });

            _outputHelper.WriteLine("Waiting for removed endpoint notification.");
            await removedEndpointTask;

            _outputHelper.WriteLine("Received removed endpoint notifications.");

            endpointInfos = await _endpointUtilities.GetEndpointInfoAsync(sourceHolder.Source);

            Assert.Empty(endpointInfos);
        }
        private async Task ValidateLogsActionAsync(
            LogsConfiguration configuration,
            Func <ChannelReader <LogEntry>, Task> callback,
            LogFormat logFormat,
            TargetFrameworkMoniker tfm)
        {
            using TemporaryDirectory tempDirectory = new(_outputHelper);

            await TestHostHelper.CreateCollectionRulesHost(_outputHelper, rootOptions =>
            {
                rootOptions.AddFileSystemEgress(ActionTestsConstants.ExpectedEgressProvider, tempDirectory.FullName);

                rootOptions.CreateCollectionRule(DefaultRuleName)
                .AddCollectLogsAction(ActionTestsConstants.ExpectedEgressProvider, options =>
                {
                    options.Duration      = CommonTestTimeouts.LogsDuration;
                    options.FilterSpecs   = configuration.FilterSpecs;
                    options.DefaultLevel  = configuration.LogLevel;
                    options.Format        = logFormat;
                    options.UseAppFilters = configuration.UseAppFilters;
                })
                .SetStartupTrigger();
            }, async host =>
            {
                CollectLogsOptions options = ActionTestsHelper.GetActionOptions <CollectLogsOptions>(host, DefaultRuleName);

                // This is reassigned here due to a quirk in which a null value in the dictionary has its key removed, thus causing LogsDefaultLevelFallbackActionTest to fail. By reassigning here, any keys with null values are maintained.
                options.FilterSpecs = configuration.FilterSpecs;

                ICollectionRuleActionFactoryProxy factory;
                Assert.True(host.Services.GetService <ICollectionRuleActionOperations>().TryCreateFactory(KnownCollectionRuleActions.CollectLogs, out factory));

                using CancellationTokenSource endpointTokenSource = new CancellationTokenSource(CommonTestTimeouts.LogsTimeout);

                EndpointInfoSourceCallback endpointInfoCallback = new(_outputHelper);
                await using ServerSourceHolder sourceHolder     = await _endpointUtilities.StartServerAsync(endpointInfoCallback);

                AppRunner runner    = _endpointUtilities.CreateAppRunner(sourceHolder.TransportName, tfm);
                runner.ScenarioName = TestAppScenarios.Logger.Name;

                Task <IEndpointInfo> newEndpointInfoTask = endpointInfoCallback.WaitAddedEndpointInfoAsync(runner, CommonTestTimeouts.StartProcess);

                await runner.ExecuteAsync(async() =>
                {
                    IEndpointInfo endpointInfo = await newEndpointInfoTask;

                    ICollectionRuleAction action = factory.Create(endpointInfo, options);

                    using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(CommonTestTimeouts.LogsTimeout);

                    CollectionRuleActionResult result;
                    try
                    {
                        await action.StartAsync(cancellationTokenSource.Token);

                        await runner.SendCommandAsync(TestAppScenarios.Logger.Commands.StartLogging);

                        result = await action.WaitForCompletionAsync(cancellationTokenSource.Token);
                    }
                    finally
                    {
                        await Tools.Monitor.DisposableHelper.DisposeAsync(action);
                    }

                    string egressPath = ActionTestsHelper.ValidateEgressPath(result);

                    using FileStream logsStream = new(egressPath, FileMode.Open, FileAccess.Read);
                    Assert.NotNull(logsStream);

                    await LogsTestUtilities.ValidateLogsEquality(logsStream, callback, logFormat, _outputHelper);
                });
            });
        }
Beispiel #10
0
        public async Task ServerSourceAddRemoveMultipleConnectionTest(TargetFrameworkMoniker appTfm)
        {
            EndpointInfoSourceCallback callback = new(_outputHelper);

            await using ServerSourceHolder sourceHolder = await _endpointUtilities.StartServerAsync(callback);

            var endpointInfos = await _endpointUtilities.GetEndpointInfoAsync(sourceHolder.Source);

            Assert.Empty(endpointInfos);

            const int appCount = 5;

            AppRunner[] runners              = new AppRunner[appCount];
            Task[]      addedEndpointTasks   = new Task[appCount];
            Task[]      removedEndpointTasks = new Task[appCount];

            // Start all app instances
            for (int i = 0; i < appCount; i++)
            {
                runners[i]              = _endpointUtilities.CreateAppRunner(sourceHolder.TransportName, appTfm, appId: i + 1);
                addedEndpointTasks[i]   = callback.WaitAddedEndpointInfoAsync(runners[i], CommonTestTimeouts.StartProcess);
                removedEndpointTasks[i] = callback.WaitRemovedEndpointInfoAsync(runners[i], CommonTestTimeouts.StartProcess);
            }

            await runners.ExecuteAsync(async() =>
            {
                _outputHelper.WriteLine("Waiting for all added endpoint notifications.");
                await Task.WhenAll(addedEndpointTasks);
                _outputHelper.WriteLine("Received all added endpoint notifications.");

                endpointInfos = await _endpointUtilities.GetEndpointInfoAsync(sourceHolder.Source);

                Assert.Equal(appCount, endpointInfos.Count());

                for (int i = 0; i < appCount; i++)
                {
                    int processId = await runners[i].ProcessIdTask;

                    IEndpointInfo endpointInfo = endpointInfos.FirstOrDefault(info => info.ProcessId == processId);

                    ValidateEndpointInfo(endpointInfo);

                    await EndpointUtilities.VerifyConnectionAsync(runners[i], endpointInfo);

                    await runners[i].SendCommandAsync(TestAppScenarios.AsyncWait.Commands.Continue);
                }
            });

            _outputHelper.WriteLine("Waiting for all removed endpoint notifications.");
            await Task.WhenAll(removedEndpointTasks);

            _outputHelper.WriteLine("Received all removed endpoint notifications.");

            for (int i = 0; i < appCount; i++)
            {
                Assert.True(0 == runners[i].ExitCode, $"App {i} exit code is non-zero.");
            }

            await Task.Delay(TimeSpan.FromSeconds(1));

            endpointInfos = await _endpointUtilities.GetEndpointInfoAsync(sourceHolder.Source);

            Assert.Empty(endpointInfos);
        }
Beispiel #11
0
        private async Task ExecuteScenario(
            TargetFrameworkMoniker tfm,
            string scenarioName,
            string collectionRuleName,
            Action <Tools.Monitor.RootOptions> setup,
            Func <AppRunner, CollectionRulePipeline, PipelineCallbacks, Task> pipelineCallback,
            Action <IServiceCollection> servicesCallback = null)
        {
            EndpointInfoSourceCallback endpointInfoCallback = new(_outputHelper);
            EndpointUtilities          endpointUtilities    = new(_outputHelper);

            await using ServerSourceHolder sourceHolder = await endpointUtilities.StartServerAsync(endpointInfoCallback);

            AppRunner runner = new(_outputHelper, Assembly.GetExecutingAssembly(), tfm : tfm);

            runner.ConnectionMode     = DiagnosticPortConnectionMode.Connect;
            runner.DiagnosticPortPath = sourceHolder.TransportName;
            runner.ScenarioName       = scenarioName;

            Task <IEndpointInfo> endpointInfoTask = endpointInfoCallback.WaitAddedEndpointInfoAsync(runner, CommonTestTimeouts.StartProcess);

            await runner.ExecuteAsync(async() =>
            {
                IEndpointInfo endpointInfo = await endpointInfoTask;

                await TestHostHelper.CreateCollectionRulesHost(
                    _outputHelper,
                    setup,
                    async host =>
                {
                    ActionListExecutor actionListExecutor =
                        host.Services.GetRequiredService <ActionListExecutor>();
                    ICollectionRuleTriggerOperations triggerOperations =
                        host.Services.GetRequiredService <ICollectionRuleTriggerOperations>();
                    IOptionsMonitor <CollectionRuleOptions> optionsMonitor =
                        host.Services.GetRequiredService <IOptionsMonitor <CollectionRuleOptions> >();
                    ILogger <CollectionRuleService> logger =
                        host.Services.GetRequiredService <ILogger <CollectionRuleService> >();
                    ISystemClock clock =
                        host.Services.GetRequiredService <ISystemClock>();

                    PipelineCallbacks callbacks = new();

                    CollectionRuleContext context = new(
                        collectionRuleName,
                        optionsMonitor.Get(collectionRuleName),
                        endpointInfo,
                        logger,
                        clock,
                        callbacks.NotifyActionsThrottled);

                    await using CollectionRulePipeline pipeline = new(
                                    actionListExecutor,
                                    triggerOperations,
                                    context,
                                    callbacks.NotifyPipelineStarted);

                    await pipelineCallback(runner, pipeline, callbacks);

                    Assert.Equal(1, callbacks.StartedCount);
                },
                    servicesCallback);
            });
        }