public async Task InvalidTokenReferenceTest() { string a2input1 = "$(Actions. badly formed"; string a2input2 = "$(Actions.a15.MissingAction)"; string a2input3 = "$(Actions.a1.MissingResult)"; LogRecord record = new LogRecord(); PassThroughOptions settings = null; await TestHostHelper.CreateCollectionRulesHost(_outputHelper, rootOptions => { CollectionRuleOptions options = rootOptions.CreateCollectionRule(DefaultRuleName) .AddPassThroughAction("a1", "a1input1", "a1input2", "a1input3") .AddPassThroughAction("a2", a2input1, a2input2, a2input3) .SetStartupTrigger(); settings = (PassThroughOptions)options.Actions.Last().Settings; }, host => { using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TimeoutMs); CollectionRuleOptions ruleOptions = host.Services.GetRequiredService <IOptionsMonitor <CollectionRuleOptions> >().Get(DefaultRuleName); ILogger <CollectionRuleService> logger = host.Services.GetRequiredService <ILogger <CollectionRuleService> >(); ISystemClock clock = host.Services.GetRequiredService <ISystemClock>(); CollectionRuleContext context = new(DefaultRuleName, ruleOptions, null, logger, clock); ActionOptionsDependencyAnalyzer analyzer = ActionOptionsDependencyAnalyzer.Create(context); analyzer.GetActionDependencies(1); analyzer.SubstituteOptionValues(new Dictionary <string, CollectionRuleActionResult>(), 1, settings); Assert.Equal(3, record.Events.Count); Assert.Equal(LoggingEventIds.InvalidActionReferenceToken.Id(), record.Events[0].EventId.Id); Assert.Equal(LoggingEventIds.InvalidActionReference.Id(), record.Events[1].EventId.Id); Assert.Equal(LoggingEventIds.InvalidActionResultReference.Id(), record.Events[2].EventId.Id); }, serviceCollection =>
//CONSIDER Only named rules currently return results since only named results can be referenced public async Task <IDictionary <string, CollectionRuleActionResult> > ExecuteActions( CollectionRuleContext context, Action startCallback, CancellationToken cancellationToken) { if (context == null) { throw new ArgumentNullException(nameof(context)); } bool started = false; Action wrappedStartCallback = () => { if (!started) { started = true; startCallback?.Invoke(); } }; int actionIndex = 0; List <ActionCompletionEntry> deferredCompletions = new(context.Options.Actions.Count); var actionResults = new Dictionary <string, CollectionRuleActionResult>(StringComparer.Ordinal); var dependencyAnalyzer = ActionOptionsDependencyAnalyzer.Create(context); try { // Start and optionally wait for each action to complete foreach (CollectionRuleActionOptions actionOption in context.Options.Actions) { KeyValueLogScope actionScope = new(); actionScope.AddCollectionRuleAction(actionOption.Type, actionIndex); using IDisposable actionScopeRegistration = _logger.BeginScope(actionScope); _logger.CollectionRuleActionStarted(context.Name, actionOption.Type); try { IList <CollectionRuleActionOptions> actionDependencies = dependencyAnalyzer.GetActionDependencies(actionIndex); foreach (CollectionRuleActionOptions actionDependency in actionDependencies) { for (int i = 0; i < deferredCompletions.Count; i++) { ActionCompletionEntry deferredCompletion = deferredCompletions[i]; if (string.Equals(deferredCompletion.Options.Name, actionDependency.Name, StringComparison.Ordinal)) { deferredCompletions.RemoveAt(i); i--; await WaitForCompletion(context, wrappedStartCallback, actionResults, deferredCompletion, cancellationToken); break; } } } ICollectionRuleActionFactoryProxy factory; if (!_actionOperations.TryCreateFactory(actionOption.Type, out factory)) { throw new InvalidOperationException(Strings.ErrorMessage_CouldNotMapToAction); } object newSettings = dependencyAnalyzer.SubstituteOptionValues(actionResults, actionIndex, actionOption.Settings); ICollectionRuleAction action = factory.Create(context.EndpointInfo, newSettings); try { await action.StartAsync(cancellationToken); // Check if the action completion should be awaited synchronously (in respect to // starting the next action). If not, add a deferred entry so that it can be completed // after starting each action in the list. if (actionOption.WaitForCompletion.GetValueOrDefault(CollectionRuleActionOptionsDefaults.WaitForCompletion)) { await WaitForCompletion(context, wrappedStartCallback, actionResults, action, actionOption, cancellationToken); } else { deferredCompletions.Add(new(action, actionOption, actionIndex)); // Set to null to skip disposal action = null; } } finally { await DisposableHelper.DisposeAsync(action); } } catch (Exception ex) when(ShouldHandleException(ex, context.Name, actionOption.Type)) { throw new CollectionRuleActionExecutionException(ex, actionOption.Type, actionIndex); } ++actionIndex; } // Notify that all actions have started wrappedStartCallback?.Invoke(); // Wait for any actions whose completion has been deferred. while (deferredCompletions.Count > 0) { ActionCompletionEntry deferredCompletion = deferredCompletions[0]; deferredCompletions.RemoveAt(0); await WaitForCompletion(context, wrappedStartCallback, actionResults, deferredCompletion, cancellationToken); } return(actionResults); } finally { // Always dispose any deferred action completions so that those actions // are stopped before leaving the action list executor. foreach (ActionCompletionEntry deferredCompletion in deferredCompletions) { await DisposableHelper.DisposeAsync(deferredCompletion.Action); } } }