Move JobTelemetry and StepsTelemetry into GlobalContext. (#1680)

* Move JobTelemetry and StepsTelemetry into GlobalContext.

* .

* .
This commit is contained in:
Tingluo Huang
2022-02-11 16:18:41 -05:00
committed by GitHub
parent d64190927f
commit cb19da9638
10 changed files with 146 additions and 116 deletions

View File

@@ -178,7 +178,7 @@ namespace GitHub.Runner.Worker
Message = $"Invoked ::stopCommand:: with token: [{stopToken}]", Message = $"Invoked ::stopCommand:: with token: [{stopToken}]",
Type = JobTelemetryType.ActionCommand Type = JobTelemetryType.ActionCommand
}; };
context.JobTelemetry.Add(telemetry); context.Global.JobTelemetry.Add(telemetry);
} }
if (isTokenInvalid && !allowUnsecureStopCommandTokens) if (isTokenInvalid && !allowUnsecureStopCommandTokens)

View File

@@ -15,8 +15,8 @@ using GitHub.DistributedTask.Pipelines;
using GitHub.DistributedTask.Pipelines.ContextData; using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.Pipelines.ObjectTemplating; using GitHub.DistributedTask.Pipelines.ObjectTemplating;
using GitHub.DistributedTask.WebApi; using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using GitHub.Runner.Worker.Container; using GitHub.Runner.Worker.Container;
using GitHub.Services.WebApi; using GitHub.Services.WebApi;
@@ -52,8 +52,7 @@ namespace GitHub.Runner.Worker
Dictionary<string, string> IntraActionState { get; } Dictionary<string, string> IntraActionState { get; }
Dictionary<string, VariableValue> JobOutputs { get; } Dictionary<string, VariableValue> JobOutputs { get; }
ActionsEnvironmentReference ActionsEnvironment { get; } ActionsEnvironmentReference ActionsEnvironment { get; }
List<ActionsStepTelemetry> ActionsStepsTelemetry { get; } ActionsStepTelemetry StepTelemetry { get; }
List<JobTelemetry> JobTelemetry { get; }
DictionaryContextData ExpressionValues { get; } DictionaryContextData ExpressionValues { get; }
IList<IFunctionInfo> ExpressionFunctions { get; } IList<IFunctionInfo> ExpressionFunctions { get; }
JobContext JobContext { get; } JobContext JobContext { get; }
@@ -109,6 +108,7 @@ namespace GitHub.Runner.Worker
// others // others
void ForceTaskComplete(); void ForceTaskComplete();
void RegisterPostJobStep(IStep step); void RegisterPostJobStep(IStep step);
void PublishStepTelemetry();
} }
public sealed class ExecutionContext : RunnerService, IExecutionContext public sealed class ExecutionContext : RunnerService, IExecutionContext
@@ -139,6 +139,7 @@ namespace GitHub.Runner.Worker
// only job level ExecutionContext will track throttling delay. // only job level ExecutionContext will track throttling delay.
private long _totalThrottlingDelayInMilliseconds = 0; private long _totalThrottlingDelayInMilliseconds = 0;
private bool _stepTelemetryPublished = false;
public Guid Id => _record.Id; public Guid Id => _record.Id;
public Guid EmbeddedId { get; private set; } public Guid EmbeddedId { get; private set; }
@@ -152,8 +153,7 @@ namespace GitHub.Runner.Worker
public Dictionary<string, VariableValue> JobOutputs { get; private set; } public Dictionary<string, VariableValue> JobOutputs { get; private set; }
public ActionsEnvironmentReference ActionsEnvironment { get; private set; } public ActionsEnvironmentReference ActionsEnvironment { get; private set; }
public List<ActionsStepTelemetry> ActionsStepsTelemetry { get; private set; } public ActionsStepTelemetry StepTelemetry { get; } = new ActionsStepTelemetry();
public List<JobTelemetry> JobTelemetry { get; private set; }
public DictionaryContextData ExpressionValues { get; } = new DictionaryContextData(); public DictionaryContextData ExpressionValues { get; } = new DictionaryContextData();
public IList<IFunctionInfo> ExpressionFunctions { get; } = new List<IFunctionInfo>(); public IList<IFunctionInfo> ExpressionFunctions { get; } = new List<IFunctionInfo>();
@@ -294,7 +294,20 @@ namespace GitHub.Runner.Worker
Root.PostJobSteps.Push(step); Root.PostJobSteps.Push(step);
} }
public IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, ActionRunStage stage, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid), string siblingScopeName = null) public IExecutionContext CreateChild(
Guid recordId,
string displayName,
string refName,
string scopeName,
string contextName,
ActionRunStage stage,
Dictionary<string, string> intraActionState = null,
int? recordOrder = null,
IPagingLogger logger = null,
bool isEmbedded = false,
CancellationTokenSource cancellationTokenSource = null,
Guid embeddedId = default(Guid),
string siblingScopeName = null)
{ {
Trace.Entering(); Trace.Entering();
@@ -306,7 +319,6 @@ namespace GitHub.Runner.Worker
child.Stage = stage; child.Stage = stage;
child.EmbeddedId = embeddedId; child.EmbeddedId = embeddedId;
child.SiblingScopeName = siblingScopeName; child.SiblingScopeName = siblingScopeName;
child.JobTelemetry = JobTelemetry;
if (intraActionState == null) if (intraActionState == null)
{ {
child.IntraActionState = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); child.IntraActionState = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@@ -354,7 +366,13 @@ namespace GitHub.Runner.Worker
/// An embedded execution context shares the same record ID, record name, logger, /// An embedded execution context shares the same record ID, record name, logger,
/// and a linked cancellation token. /// and a linked cancellation token.
/// </summary> /// </summary>
public IExecutionContext CreateEmbeddedChild(string scopeName, string contextName, Guid embeddedId, ActionRunStage stage, Dictionary<string, string> intraActionState = null, string siblingScopeName = null) public IExecutionContext CreateEmbeddedChild(
string scopeName,
string contextName,
Guid embeddedId,
ActionRunStage stage,
Dictionary<string, string> intraActionState = null,
string siblingScopeName = null)
{ {
return Root.CreateChild(_record.Id, _record.Name, _record.Id.ToString("N"), scopeName, contextName, stage, logger: _logger, isEmbedded: true, cancellationTokenSource: CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token), intraActionState: intraActionState, embeddedId: embeddedId, siblingScopeName: siblingScopeName); return Root.CreateChild(_record.Id, _record.Name, _record.Id.ToString("N"), scopeName, contextName, stage, logger: _logger, isEmbedded: true, cancellationTokenSource: CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token), intraActionState: intraActionState, embeddedId: embeddedId, siblingScopeName: siblingScopeName);
} }
@@ -404,6 +422,8 @@ namespace GitHub.Runner.Worker
} }
} }
PublishStepTelemetry();
if (Root != this) if (Root != this)
{ {
// only dispose TokenSource for step level ExecutionContext // only dispose TokenSource for step level ExecutionContext
@@ -654,16 +674,18 @@ namespace GitHub.Runner.Worker
// Job defaults shared across all actions // Job defaults shared across all actions
Global.JobDefaults = new Dictionary<string, IDictionary<string, string>>(StringComparer.OrdinalIgnoreCase); Global.JobDefaults = new Dictionary<string, IDictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
// Job Telemetry
Global.JobTelemetry = new List<JobTelemetry>();
// ActionsStepTelemetry for entire job
Global.StepsTelemetry = new List<ActionsStepTelemetry>();
// Job Outputs // Job Outputs
JobOutputs = new Dictionary<string, VariableValue>(StringComparer.OrdinalIgnoreCase); JobOutputs = new Dictionary<string, VariableValue>(StringComparer.OrdinalIgnoreCase);
// Actions environment // Actions environment
ActionsEnvironment = message.ActionsEnvironment; ActionsEnvironment = message.ActionsEnvironment;
// ActionsStepTelemetry
ActionsStepsTelemetry = new List<ActionsStepTelemetry>();
JobTelemetry = new List<JobTelemetry>();
// Service container info // Service container info
Global.ServiceContainers = new List<ContainerInfo>(); Global.ServiceContainers = new List<ContainerInfo>();
@@ -895,6 +917,24 @@ namespace GitHub.Runner.Worker
return Root._matchers ?? Array.Empty<IssueMatcherConfig>(); return Root._matchers ?? Array.Empty<IssueMatcherConfig>();
} }
public void PublishStepTelemetry()
{
if (!_stepTelemetryPublished)
{
// Add to the global steps telemetry only if we have something to log.
if (!string.IsNullOrEmpty(StepTelemetry?.Type))
{
Trace.Info($"Publish step telemetry for current step {StringUtil.ConvertToJson(StepTelemetry)}.");
Global.StepsTelemetry.Add(StepTelemetry);
_stepTelemetryPublished = true;
}
}
else
{
Trace.Info($"Step telemetry has already been published.");
}
}
private void InitializeTimelineRecord(Guid timelineId, Guid timelineRecordId, Guid? parentTimelineRecordId, string recordType, string displayName, string refName, int? order) private void InitializeTimelineRecord(Guid timelineId, Guid timelineRecordId, Guid? parentTimelineRecordId, string recordType, string displayName, string refName, int? order)
{ {
_mainTimelineId = timelineId; _mainTimelineId = timelineId;

View File

@@ -14,6 +14,8 @@ namespace GitHub.Runner.Worker
public PlanFeatures Features { get; set; } public PlanFeatures Features { get; set; }
public IList<String> FileTable { get; set; } public IList<String> FileTable { get; set; }
public IDictionary<String, IDictionary<String, String>> JobDefaults { get; set; } public IDictionary<String, IDictionary<String, String>> JobDefaults { get; set; }
public List<ActionsStepTelemetry> StepsTelemetry { get; set; }
public List<JobTelemetry> JobTelemetry { get; set; }
public TaskOrchestrationPlanReference Plan { get; set; } public TaskOrchestrationPlanReference Plan { get; set; }
public List<string> PrependPath { get; set; } public List<string> PrependPath { get; set; }
public List<ContainerInfo> ServiceContainers { get; set; } public List<ContainerInfo> ServiceContainers { get; set; }

View File

@@ -83,18 +83,15 @@ namespace GitHub.Runner.Worker.Handlers
hasUsesStep = true; hasUsesStep = true;
} }
} }
var pathReference = Action as Pipelines.RepositoryPathReference;
var telemetry = new ActionsStepTelemetry { ExecutionContext.StepTelemetry.Ref = GetActionRef();
Ref = GetActionRef(), ExecutionContext.StepTelemetry.HasPreStep = Data.HasPre;
HasPreStep = Data.HasPre, ExecutionContext.StepTelemetry.HasPostStep = Data.HasPost;
HasPostStep = Data.HasPost, ExecutionContext.StepTelemetry.IsEmbedded = ExecutionContext.IsEmbedded;
IsEmbedded = ExecutionContext.IsEmbedded, ExecutionContext.StepTelemetry.Type = "composite";
Type = "composite", ExecutionContext.StepTelemetry.HasRunsStep = hasRunsStep;
HasRunsStep = hasRunsStep, ExecutionContext.StepTelemetry.HasUsesStep = hasUsesStep;
HasUsesStep = hasUsesStep, ExecutionContext.StepTelemetry.StepCount = steps.Count;
StepCount = steps.Count
};
ExecutionContext.Root.ActionsStepsTelemetry.Add(telemetry);
} }
try try
@@ -458,6 +455,7 @@ namespace GitHub.Runner.Worker.Handlers
Trace.Info($"Step result: {step.ExecutionContext.Result}"); Trace.Info($"Step result: {step.ExecutionContext.Result}");
step.ExecutionContext.Debug($"Finished: {step.DisplayName}"); step.ExecutionContext.Debug($"Finished: {step.DisplayName}");
step.ExecutionContext.PublishStepTelemetry();
} }
private void SetStepConclusion(IStep step, TaskResult result) private void SetStepConclusion(IStep step, TaskResult result)

View File

@@ -1,14 +1,14 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System; using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.Runner.Worker.Container; using GitHub.DistributedTask.WebApi;
using Pipelines = GitHub.DistributedTask.Pipelines;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using GitHub.DistributedTask.WebApi; using GitHub.Runner.Worker.Container;
using GitHub.DistributedTask.Pipelines.ContextData; using Pipelines = GitHub.DistributedTask.Pipelines;
using System.Linq;
namespace GitHub.Runner.Worker.Handlers namespace GitHub.Runner.Worker.Handlers
{ {
@@ -73,14 +73,11 @@ namespace GitHub.Runner.Worker.Handlers
// Add Telemetry to JobContext to send with JobCompleteMessage // Add Telemetry to JobContext to send with JobCompleteMessage
if (stage == ActionRunStage.Main) if (stage == ActionRunStage.Main)
{ {
var telemetry = new ActionsStepTelemetry { ExecutionContext.StepTelemetry.Ref = GetActionRef();
Ref = GetActionRef(), ExecutionContext.StepTelemetry.HasPreStep = Data.HasPre;
HasPreStep = Data.HasPre, ExecutionContext.StepTelemetry.HasPostStep = Data.HasPost;
HasPostStep = Data.HasPost, ExecutionContext.StepTelemetry.IsEmbedded = ExecutionContext.IsEmbedded;
IsEmbedded = ExecutionContext.IsEmbedded, ExecutionContext.StepTelemetry.Type = type;
Type = type
};
ExecutionContext.Root.ActionsStepsTelemetry.Add(telemetry);
} }
// run container // run container

View File

@@ -1,13 +1,13 @@
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common.Util;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using System.IO; using System.IO;
using Pipelines = GitHub.DistributedTask.Pipelines; using System.Linq;
using System.Threading.Tasks;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using Pipelines = GitHub.DistributedTask.Pipelines;
namespace GitHub.Runner.Worker.Handlers namespace GitHub.Runner.Worker.Handlers
{ {
@@ -44,39 +44,7 @@ namespace GitHub.Runner.Worker.Handlers
public string ActionDirectory { get; set; } public string ActionDirectory { get; set; }
public List<JobExtensionRunner> LocalActionContainerSetupSteps { get; set; } public List<JobExtensionRunner> LocalActionContainerSetupSteps { get; set; }
public virtual string GetActionRef()
{
if (Action.Type == Pipelines.ActionSourceType.ContainerRegistry)
{
var registryAction = Action as Pipelines.ContainerRegistryReference;
return registryAction.Image;
}
else if (Action.Type == Pipelines.ActionSourceType.Repository)
{
var repoAction = Action as Pipelines.RepositoryPathReference;
if (string.Equals(repoAction.RepositoryType, Pipelines.PipelineConstants.SelfAlias, StringComparison.OrdinalIgnoreCase))
{
return repoAction.Path;
}
else
{
if (string.IsNullOrEmpty(repoAction.Path))
{
return $"{repoAction.Name}@{repoAction.Ref}";
}
else
{
return $"{repoAction.Name}/{repoAction.Path}@{repoAction.Ref}";
}
}
}
else
{
// this should never happen
Trace.Error($"Can't generate ref for {Action.Type.ToString()}");
}
return "";
}
public virtual void PrintActionDetails(ActionRunStage stage) public virtual void PrintActionDetails(ActionRunStage stage)
{ {
@@ -150,6 +118,40 @@ namespace GitHub.Runner.Worker.Handlers
ActionCommandManager = hostContext.CreateService<IActionCommandManager>(); ActionCommandManager = hostContext.CreateService<IActionCommandManager>();
} }
protected string GetActionRef()
{
if (Action.Type == Pipelines.ActionSourceType.ContainerRegistry)
{
var registryAction = Action as Pipelines.ContainerRegistryReference;
return registryAction.Image;
}
else if (Action.Type == Pipelines.ActionSourceType.Repository)
{
var repoAction = Action as Pipelines.RepositoryPathReference;
if (string.Equals(repoAction.RepositoryType, Pipelines.PipelineConstants.SelfAlias, StringComparison.OrdinalIgnoreCase))
{
return repoAction.Path;
}
else
{
if (string.IsNullOrEmpty(repoAction.Path))
{
return $"{repoAction.Name}@{repoAction.Ref}";
}
else
{
return $"{repoAction.Name}/{repoAction.Path}@{repoAction.Ref}";
}
}
}
else
{
// this should never happen
Trace.Error($"Can't generate ref for {Action.Type.ToString()}");
}
return "";
}
protected void AddInputsToEnvironment() protected void AddInputsToEnvironment()
{ {
// Validate args. // Validate args.

View File

@@ -1,12 +1,12 @@
using System.IO; using System;
using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using GitHub.DistributedTask.WebApi;
using Pipelines = GitHub.DistributedTask.Pipelines; using Pipelines = GitHub.DistributedTask.Pipelines;
using System;
using System.Linq;
namespace GitHub.Runner.Worker.Handlers namespace GitHub.Runner.Worker.Handlers
{ {
@@ -77,15 +77,11 @@ namespace GitHub.Runner.Worker.Handlers
// Add Telemetry to JobContext to send with JobCompleteMessage // Add Telemetry to JobContext to send with JobCompleteMessage
if (stage == ActionRunStage.Main) if (stage == ActionRunStage.Main)
{ {
var telemetry = new ActionsStepTelemetry ExecutionContext.StepTelemetry.Ref = GetActionRef();
{ ExecutionContext.StepTelemetry.HasPreStep = Data.HasPre;
Ref = GetActionRef(), ExecutionContext.StepTelemetry.HasPostStep = Data.HasPost;
HasPreStep = Data.HasPre, ExecutionContext.StepTelemetry.IsEmbedded = ExecutionContext.IsEmbedded;
HasPostStep = Data.HasPost, ExecutionContext.StepTelemetry.Type = Data.NodeVersion;
IsEmbedded = ExecutionContext.IsEmbedded,
Type = Data.NodeVersion
};
ExecutionContext.Root.ActionsStepsTelemetry.Add(telemetry);
} }
ArgUtil.NotNullOrEmpty(target, nameof(target)); ArgUtil.NotNullOrEmpty(target, nameof(target));

View File

@@ -1,12 +1,12 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Linq;
using GitHub.DistributedTask.Pipelines.ContextData; using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common; using GitHub.Runner.Common;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using GitHub.DistributedTask.WebApi;
using Pipelines = GitHub.DistributedTask.Pipelines; using Pipelines = GitHub.DistributedTask.Pipelines;
namespace GitHub.Runner.Worker.Handlers namespace GitHub.Runner.Worker.Handlers
@@ -147,12 +147,8 @@ namespace GitHub.Runner.Worker.Handlers
// Add Telemetry to JobContext to send with JobCompleteMessage // Add Telemetry to JobContext to send with JobCompleteMessage
if (stage == ActionRunStage.Main) if (stage == ActionRunStage.Main)
{ {
var telemetry = new ActionsStepTelemetry ExecutionContext.StepTelemetry.IsEmbedded = ExecutionContext.IsEmbedded;
{ ExecutionContext.StepTelemetry.Type = "run";
IsEmbedded = ExecutionContext.IsEmbedded,
Type = "run",
};
ExecutionContext.Root.ActionsStepsTelemetry.Add(telemetry);
} }
var tempDirectory = HostContext.GetDirectory(WellKnownDirectory.Temp); var tempDirectory = HostContext.GetDirectory(WellKnownDirectory.Temp);

View File

@@ -279,14 +279,13 @@ namespace GitHub.Runner.Worker
} }
// Load any upgrade telemetry // Load any upgrade telemetry
LoadFromTelemetryFile(jobContext.JobTelemetry); LoadFromTelemetryFile(jobContext.Global.JobTelemetry);
// Make sure we don't submit secrets as telemetry // Make sure we don't submit secrets as telemetry
MaskTelemetrySecrets(jobContext.JobTelemetry); MaskTelemetrySecrets(jobContext.Global.JobTelemetry);
Trace.Info("Raising job completed event.");
var jobCompletedEvent = new JobCompletedEvent(message.RequestId, message.JobId, result, jobContext.JobOutputs, jobContext.ActionsEnvironment, jobContext.ActionsStepsTelemetry, jobContext.JobTelemetry);
var jobCompletedEvent = new JobCompletedEvent(message.RequestId, message.JobId, result, jobContext.JobOutputs, jobContext.ActionsEnvironment, jobContext.Global.StepsTelemetry, jobContext.Global.JobTelemetry);
Trace.Info($"Raising job completed event: {StringUtil.ConvertToJson(jobCompletedEvent)}");
var completeJobRetryLimit = 5; var completeJobRetryLimit = 5;
var exceptions = new List<Exception>(); var exceptions = new List<Exception>();

View File

@@ -121,6 +121,7 @@ namespace GitHub.Runner.Common.Tests.Worker
using (TestHostContext hc = CreateTestContext()) using (TestHostContext hc = CreateTestContext())
{ {
_ec.Object.Global.EnvironmentVariables = new Dictionary<string, string>(); _ec.Object.Global.EnvironmentVariables = new Dictionary<string, string>();
_ec.Object.Global.JobTelemetry = new List<JobTelemetry>();
var expressionValues = new DictionaryContextData var expressionValues = new DictionaryContextData
{ {
["env"] = ["env"] =
@@ -131,7 +132,6 @@ namespace GitHub.Runner.Common.Tests.Worker
#endif #endif
}; };
_ec.Setup(x => x.ExpressionValues).Returns(expressionValues); _ec.Setup(x => x.ExpressionValues).Returns(expressionValues);
_ec.Setup(x => x.JobTelemetry).Returns(new List<JobTelemetry>());
Assert.True(_commandManager.TryProcessCommand(_ec.Object, $"::stop-commands::{invalidToken}", null)); Assert.True(_commandManager.TryProcessCommand(_ec.Object, $"::stop-commands::{invalidToken}", null));
} }
@@ -148,8 +148,8 @@ namespace GitHub.Runner.Common.Tests.Worker
using (TestHostContext hc = CreateTestContext()) using (TestHostContext hc = CreateTestContext())
{ {
_ec.Object.Global.EnvironmentVariables = new Dictionary<string, string>(); _ec.Object.Global.EnvironmentVariables = new Dictionary<string, string>();
_ec.Object.Global.JobTelemetry = new List<JobTelemetry>();
_ec.Setup(x => x.ExpressionValues).Returns(GetExpressionValues()); _ec.Setup(x => x.ExpressionValues).Returns(GetExpressionValues());
_ec.Setup(x => x.JobTelemetry).Returns(new List<JobTelemetry>());
Assert.Throws<Exception>(() => _commandManager.TryProcessCommand(_ec.Object, $"::stop-commands::{invalidToken}", null)); Assert.Throws<Exception>(() => _commandManager.TryProcessCommand(_ec.Object, $"::stop-commands::{invalidToken}", null));
} }
} }