mirror of
https://github.com/actions/runner.git
synced 2025-12-13 00:36:29 +00:00
[1742] Ensure multiple composite annoations are correctly written. (#2311)
* [1742] Ensure multiple composite annoations are correctly written. This implementation uses a collector pattern to allow embedded ExecutionContexts to stash Issue objects for later processing by a non-embedded ancestor ExecutionContext. Also: - Provide explicit constructor implementations for ExecutionContext - Leverage explicit constructors to solidify immutability of several ExecutionContext class members. - Fixed erroneous call to ExecutionContext.Complete in CompositeActionHandler.cs - Use a consistent timestamp for FinishTime in ExecutionContext::Complete * Ensure collected issues are processed only by a non-embedded ExecutionContext. This was already implicit. Now, just making it explicit. * Provide a clear mechanism that allows callers to opt-in/opt-out of ExecutionContext::AddIssue's logging behavior. * Addressed deserialization inconsistencies in TimelineRecord.cs * Added TimelineRecord unit tests. * Refined unit tests related to TimelineRecord::Variables case-insensitivity * Add a unit test that verifies ExecutionContextLogOptions::LogMessageOverride has the desired effect. * Responded to PR feedback. * Don't allow embedded ExecutionContexts to add Issues to a TimelineRecord
This commit is contained in:
committed by
GitHub
parent
1bc14f0607
commit
f8a28c3c4e
@@ -756,17 +756,17 @@ namespace GitHub.Runner.Common
|
|||||||
timelineRecord.State = rec.State ?? timelineRecord.State;
|
timelineRecord.State = rec.State ?? timelineRecord.State;
|
||||||
timelineRecord.WorkerName = rec.WorkerName ?? timelineRecord.WorkerName;
|
timelineRecord.WorkerName = rec.WorkerName ?? timelineRecord.WorkerName;
|
||||||
|
|
||||||
if (rec.ErrorCount != null && rec.ErrorCount > 0)
|
if (rec.ErrorCount > 0)
|
||||||
{
|
{
|
||||||
timelineRecord.ErrorCount = rec.ErrorCount;
|
timelineRecord.ErrorCount = rec.ErrorCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rec.WarningCount != null && rec.WarningCount > 0)
|
if (rec.WarningCount > 0)
|
||||||
{
|
{
|
||||||
timelineRecord.WarningCount = rec.WarningCount;
|
timelineRecord.WarningCount = rec.WarningCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rec.NoticeCount != null && rec.NoticeCount > 0)
|
if (rec.NoticeCount > 0)
|
||||||
{
|
{
|
||||||
timelineRecord.NoticeCount = rec.NoticeCount;
|
timelineRecord.NoticeCount = rec.NoticeCount;
|
||||||
}
|
}
|
||||||
@@ -797,7 +797,7 @@ namespace GitHub.Runner.Common
|
|||||||
foreach (var record in mergedRecords)
|
foreach (var record in mergedRecords)
|
||||||
{
|
{
|
||||||
Trace.Verbose($" Record: t={record.RecordType}, n={record.Name}, s={record.State}, st={record.StartTime}, {record.PercentComplete}%, ft={record.FinishTime}, r={record.Result}: {record.CurrentOperation}");
|
Trace.Verbose($" Record: t={record.RecordType}, n={record.Name}, s={record.State}, st={record.StartTime}, {record.PercentComplete}%, ft={record.FinishTime}, r={record.Result}: {record.CurrentOperation}");
|
||||||
if (record.Issues != null && record.Issues.Count > 0)
|
if (record.Issues != null)
|
||||||
{
|
{
|
||||||
foreach (var issue in record.Issues)
|
foreach (var issue in record.Issues)
|
||||||
{
|
{
|
||||||
@@ -807,7 +807,7 @@ namespace GitHub.Runner.Common
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (record.Variables != null && record.Variables.Count > 0)
|
if (record.Variables != null)
|
||||||
{
|
{
|
||||||
foreach (var variable in record.Variables)
|
foreach (var variable in record.Variables)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Worker.Container;
|
using GitHub.Runner.Worker.Container;
|
||||||
using System;
|
using System;
|
||||||
@@ -276,7 +276,7 @@ namespace GitHub.Runner.Worker
|
|||||||
Message = $"Can't update {blocked} environment variable using ::set-env:: command."
|
Message = $"Can't update {blocked} environment variable using ::set-env:: command."
|
||||||
};
|
};
|
||||||
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = $"{Constants.Runner.UnsupportedCommand}_{envName}";
|
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = $"{Constants.Runner.UnsupportedCommand}_{envName}";
|
||||||
context.AddIssue(issue);
|
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -315,7 +315,7 @@ namespace GitHub.Runner.Worker
|
|||||||
Message = String.Format(Constants.Runner.UnsupportedCommandMessage, this.Command)
|
Message = String.Format(Constants.Runner.UnsupportedCommandMessage, this.Command)
|
||||||
};
|
};
|
||||||
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.UnsupportedCommand;
|
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.UnsupportedCommand;
|
||||||
context.AddIssue(issue);
|
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!command.Properties.TryGetValue(SetOutputCommandProperties.Name, out string outputName) || string.IsNullOrEmpty(outputName))
|
if (!command.Properties.TryGetValue(SetOutputCommandProperties.Name, out string outputName) || string.IsNullOrEmpty(outputName))
|
||||||
@@ -350,7 +350,7 @@ namespace GitHub.Runner.Worker
|
|||||||
Message = String.Format(Constants.Runner.UnsupportedCommandMessage, this.Command)
|
Message = String.Format(Constants.Runner.UnsupportedCommandMessage, this.Command)
|
||||||
};
|
};
|
||||||
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.UnsupportedCommand;
|
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.UnsupportedCommand;
|
||||||
context.AddIssue(issue);
|
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!command.Properties.TryGetValue(SaveStateCommandProperties.Name, out string stateName) || string.IsNullOrEmpty(stateName))
|
if (!command.Properties.TryGetValue(SaveStateCommandProperties.Name, out string stateName) || string.IsNullOrEmpty(stateName))
|
||||||
@@ -666,7 +666,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.AddIssue(issue);
|
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ValidateLinesAndColumns(ActionCommand command, IExecutionContext context)
|
public static void ValidateLinesAndColumns(ActionCommand command, IExecutionContext context)
|
||||||
|
|||||||
@@ -24,10 +24,16 @@ using Pipelines = GitHub.DistributedTask.Pipelines;
|
|||||||
|
|
||||||
namespace GitHub.Runner.Worker
|
namespace GitHub.Runner.Worker
|
||||||
{
|
{
|
||||||
public class ExecutionContextType
|
public static class ExecutionContextType
|
||||||
{
|
{
|
||||||
public static string Job = "Job";
|
public const string Job = "Job";
|
||||||
public static string Task = "Task";
|
public const string Task = "Task";
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ExecutionContextLogOptions(bool WriteToLog, string LogMessageOverride)
|
||||||
|
{
|
||||||
|
public static readonly ExecutionContextLogOptions None = new(false, null);
|
||||||
|
public static readonly ExecutionContextLogOptions Default = new(true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
[ServiceLocator(Default = typeof(ExecutionContext))]
|
[ServiceLocator(Default = typeof(ExecutionContext))]
|
||||||
@@ -93,7 +99,7 @@ namespace GitHub.Runner.Worker
|
|||||||
void SetGitHubContext(string name, string value);
|
void SetGitHubContext(string name, string value);
|
||||||
void SetOutput(string name, string value, out string reference);
|
void SetOutput(string name, string value, out string reference);
|
||||||
void SetTimeout(TimeSpan? timeout);
|
void SetTimeout(TimeSpan? timeout);
|
||||||
void AddIssue(Issue issue, string message = null);
|
void AddIssue(Issue issue, ExecutionContextLogOptions logOptions);
|
||||||
void Progress(int percentage, string currentOperation = null);
|
void Progress(int percentage, string currentOperation = null);
|
||||||
void UpdateDetailTimelineRecord(TimelineRecord record);
|
void UpdateDetailTimelineRecord(TimelineRecord record);
|
||||||
|
|
||||||
@@ -119,7 +125,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public sealed class ExecutionContext : RunnerService, IExecutionContext
|
public sealed class ExecutionContext : RunnerService, IExecutionContext
|
||||||
{
|
{
|
||||||
private const int _maxIssueCount = 10;
|
private const int _maxCountPerIssueType = 10;
|
||||||
private const int _throttlingDelayReportThreshold = 10 * 1000; // Don't report throttling with less than 10 seconds delay
|
private const int _throttlingDelayReportThreshold = 10 * 1000; // Don't report throttling with less than 10 seconds delay
|
||||||
private const int _maxIssueMessageLength = 4096; // Don't send issue with huge message since we can't forward them from actions to check annotation.
|
private const int _maxIssueMessageLength = 4096; // Don't send issue with huge message since we can't forward them from actions to check annotation.
|
||||||
private const int _maxIssueCountInTelemetry = 3; // Only send the first 3 issues to telemetry
|
private const int _maxIssueCountInTelemetry = 3; // Only send the first 3 issues to telemetry
|
||||||
@@ -127,8 +133,10 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
private readonly TimelineRecord _record = new();
|
private readonly TimelineRecord _record = new();
|
||||||
private readonly Dictionary<Guid, TimelineRecord> _detailRecords = new();
|
private readonly Dictionary<Guid, TimelineRecord> _detailRecords = new();
|
||||||
|
private readonly List<Issue> _embeddedIssueCollector;
|
||||||
private readonly object _loggerLock = new();
|
private readonly object _loggerLock = new();
|
||||||
private readonly object _matchersLock = new();
|
private readonly object _matchersLock = new();
|
||||||
|
private readonly ExecutionContext _parentExecutionContext;
|
||||||
|
|
||||||
private event OnMatcherChanged _onMatcherChanged;
|
private event OnMatcherChanged _onMatcherChanged;
|
||||||
|
|
||||||
@@ -136,7 +144,6 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
private IPagingLogger _logger;
|
private IPagingLogger _logger;
|
||||||
private IJobServerQueue _jobServerQueue;
|
private IJobServerQueue _jobServerQueue;
|
||||||
private ExecutionContext _parentExecutionContext;
|
|
||||||
|
|
||||||
private Guid _mainTimelineId;
|
private Guid _mainTimelineId;
|
||||||
private Guid _detailTimelineId;
|
private Guid _detailTimelineId;
|
||||||
@@ -150,6 +157,29 @@ namespace GitHub.Runner.Worker
|
|||||||
private long _totalThrottlingDelayInMilliseconds = 0;
|
private long _totalThrottlingDelayInMilliseconds = 0;
|
||||||
private bool _stepTelemetryPublished = false;
|
private bool _stepTelemetryPublished = false;
|
||||||
|
|
||||||
|
public ExecutionContext()
|
||||||
|
: this(parent: null, embedded: false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExecutionContext(ExecutionContext parent, bool embedded)
|
||||||
|
{
|
||||||
|
if (embedded)
|
||||||
|
{
|
||||||
|
ArgUtil.NotNull(parent, nameof(parent));
|
||||||
|
}
|
||||||
|
|
||||||
|
_parentExecutionContext = parent;
|
||||||
|
this.IsEmbedded = embedded;
|
||||||
|
this.StepTelemetry = new ActionsStepTelemetry
|
||||||
|
{
|
||||||
|
IsEmbedded = embedded
|
||||||
|
};
|
||||||
|
|
||||||
|
//Embedded Execution Contexts pseudo-inherit their parent's embeddedIssueCollector.
|
||||||
|
_embeddedIssueCollector = embedded ? parent._embeddedIssueCollector : new();
|
||||||
|
}
|
||||||
|
|
||||||
public Guid Id => _record.Id;
|
public Guid Id => _record.Id;
|
||||||
public Guid EmbeddedId { get; private set; }
|
public Guid EmbeddedId { get; private set; }
|
||||||
public string ScopeName { get; private set; }
|
public string ScopeName { get; private set; }
|
||||||
@@ -162,7 +192,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 ActionsStepTelemetry StepTelemetry { get; } = new ActionsStepTelemetry();
|
public ActionsStepTelemetry StepTelemetry { get; private init; }
|
||||||
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>();
|
||||||
|
|
||||||
@@ -187,7 +217,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
// An embedded execution context shares the same record ID, record name, and logger
|
// An embedded execution context shares the same record ID, record name, and logger
|
||||||
// as its enclosing execution context.
|
// as its enclosing execution context.
|
||||||
public bool IsEmbedded { get; private set; }
|
public bool IsEmbedded { get; private init; }
|
||||||
|
|
||||||
public TaskResult? Result
|
public TaskResult? Result
|
||||||
{
|
{
|
||||||
@@ -322,7 +352,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
Trace.Entering();
|
Trace.Entering();
|
||||||
|
|
||||||
var child = new ExecutionContext();
|
var child = new ExecutionContext(this, isEmbedded);
|
||||||
child.Initialize(HostContext);
|
child.Initialize(HostContext);
|
||||||
child.Global = Global;
|
child.Global = Global;
|
||||||
child.ScopeName = scopeName;
|
child.ScopeName = scopeName;
|
||||||
@@ -347,7 +377,6 @@ namespace GitHub.Runner.Worker
|
|||||||
child.ExpressionFunctions.Add(item);
|
child.ExpressionFunctions.Add(item);
|
||||||
}
|
}
|
||||||
child._cancellationTokenSource = cancellationTokenSource ?? new CancellationTokenSource();
|
child._cancellationTokenSource = cancellationTokenSource ?? new CancellationTokenSource();
|
||||||
child._parentExecutionContext = this;
|
|
||||||
child.EchoOnActionCommand = EchoOnActionCommand;
|
child.EchoOnActionCommand = EchoOnActionCommand;
|
||||||
|
|
||||||
if (recordOrder != null)
|
if (recordOrder != null)
|
||||||
@@ -368,11 +397,9 @@ namespace GitHub.Runner.Worker
|
|||||||
child._logger.Setup(_mainTimelineId, recordId);
|
child._logger.Setup(_mainTimelineId, recordId);
|
||||||
}
|
}
|
||||||
|
|
||||||
child.IsEmbedded = isEmbedded;
|
|
||||||
child.StepTelemetry.StepId = recordId;
|
child.StepTelemetry.StepId = recordId;
|
||||||
child.StepTelemetry.Stage = stage.ToString();
|
child.StepTelemetry.Stage = stage.ToString();
|
||||||
child.StepTelemetry.IsEmbedded = isEmbedded;
|
child.StepTelemetry.StepContextName = child.GetFullyQualifiedContextName();
|
||||||
child.StepTelemetry.StepContextName = child.GetFullyQualifiedContextName(); ;
|
|
||||||
|
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
@@ -414,13 +441,24 @@ namespace GitHub.Runner.Worker
|
|||||||
this.Warning($"The job has experienced {TimeSpan.FromMilliseconds(_totalThrottlingDelayInMilliseconds).TotalSeconds} seconds total delay caused by server throttling.");
|
this.Warning($"The job has experienced {TimeSpan.FromMilliseconds(_totalThrottlingDelayInMilliseconds).TotalSeconds} seconds total delay caused by server throttling.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DateTime now = DateTime.UtcNow;
|
||||||
_record.CurrentOperation = currentOperation ?? _record.CurrentOperation;
|
_record.CurrentOperation = currentOperation ?? _record.CurrentOperation;
|
||||||
_record.ResultCode = resultCode ?? _record.ResultCode;
|
_record.ResultCode = resultCode ?? _record.ResultCode;
|
||||||
_record.FinishTime = DateTime.UtcNow;
|
_record.FinishTime = now;
|
||||||
_record.PercentComplete = 100;
|
_record.PercentComplete = 100;
|
||||||
_record.Result = _record.Result ?? TaskResult.Succeeded;
|
_record.Result = _record.Result ?? TaskResult.Succeeded;
|
||||||
_record.State = TimelineRecordState.Completed;
|
_record.State = TimelineRecordState.Completed;
|
||||||
|
|
||||||
|
// Before our main timeline's final QueueTimelineRecordUpdate,
|
||||||
|
// inject any issues collected by embedded ExecutionContexts.
|
||||||
|
if (!this.IsEmbedded)
|
||||||
|
{
|
||||||
|
foreach (var issue in _embeddedIssueCollector)
|
||||||
|
{
|
||||||
|
AddIssue(issue, ExecutionContextLogOptions.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_jobServerQueue.QueueTimelineRecordUpdate(_mainTimelineId, _record);
|
_jobServerQueue.QueueTimelineRecordUpdate(_mainTimelineId, _record);
|
||||||
|
|
||||||
// complete all detail timeline records.
|
// complete all detail timeline records.
|
||||||
@@ -428,7 +466,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
foreach (var record in _detailRecords)
|
foreach (var record in _detailRecords)
|
||||||
{
|
{
|
||||||
record.Value.FinishTime = record.Value.FinishTime ?? DateTime.UtcNow;
|
record.Value.FinishTime = record.Value.FinishTime ?? now;
|
||||||
record.Value.PercentComplete = record.Value.PercentComplete ?? 100;
|
record.Value.PercentComplete = record.Value.PercentComplete ?? 100;
|
||||||
record.Value.Result = record.Value.Result ?? TaskResult.Succeeded;
|
record.Value.Result = record.Value.Result ?? TaskResult.Succeeded;
|
||||||
record.Value.State = TimelineRecordState.Completed;
|
record.Value.State = TimelineRecordState.Completed;
|
||||||
@@ -572,14 +610,10 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This is not thread safe, the caller need to take lock before calling issue()
|
// This is not thread safe, the caller need to take lock before calling issue()
|
||||||
public void AddIssue(Issue issue, string logMessage = null)
|
public void AddIssue(Issue issue, ExecutionContextLogOptions logOptions)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(issue, nameof(issue));
|
ArgUtil.NotNull(issue, nameof(issue));
|
||||||
|
ArgUtil.NotNull(logOptions, nameof(logOptions));
|
||||||
if (string.IsNullOrEmpty(logMessage))
|
|
||||||
{
|
|
||||||
logMessage = issue.Message;
|
|
||||||
}
|
|
||||||
|
|
||||||
issue.Message = HostContext.SecretMasker.MaskSecrets(issue.Message);
|
issue.Message = HostContext.SecretMasker.MaskSecrets(issue.Message);
|
||||||
if (issue.Message.Length > _maxIssueMessageLength)
|
if (issue.Message.Length > _maxIssueMessageLength)
|
||||||
@@ -594,53 +628,64 @@ namespace GitHub.Runner.Worker
|
|||||||
issue.Data["stepNumber"] = _record.Order.ToString();
|
issue.Data["stepNumber"] = _record.Order.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (issue.Type == IssueType.Error)
|
string wellKnownTag = null;
|
||||||
|
Int32 previousCountForIssueType = 0;
|
||||||
|
Action incrementIssueTypeCount = NoOp;
|
||||||
|
switch (issue.Type)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(logMessage))
|
case IssueType.Error:
|
||||||
{
|
wellKnownTag = WellKnownTags.Error;
|
||||||
long logLineNumber = Write(WellKnownTags.Error, logMessage);
|
previousCountForIssueType = _record.ErrorCount;
|
||||||
issue.Data["logFileLineNumber"] = logLineNumber.ToString();
|
incrementIssueTypeCount = () => { _record.ErrorCount++; };
|
||||||
}
|
break;
|
||||||
|
case IssueType.Warning:
|
||||||
|
wellKnownTag = WellKnownTags.Warning;
|
||||||
|
previousCountForIssueType = _record.WarningCount;
|
||||||
|
incrementIssueTypeCount = () => { _record.WarningCount++; };
|
||||||
|
break;
|
||||||
|
case IssueType.Notice:
|
||||||
|
wellKnownTag = WellKnownTags.Notice;
|
||||||
|
previousCountForIssueType = _record.NoticeCount;
|
||||||
|
incrementIssueTypeCount = () => { _record.NoticeCount++; };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (_record.ErrorCount < _maxIssueCount)
|
if (!string.IsNullOrEmpty(wellKnownTag))
|
||||||
|
{
|
||||||
|
if (!this.IsEmbedded && previousCountForIssueType < _maxCountPerIssueType)
|
||||||
{
|
{
|
||||||
|
incrementIssueTypeCount();
|
||||||
_record.Issues.Add(issue);
|
_record.Issues.Add(issue);
|
||||||
}
|
}
|
||||||
|
|
||||||
_record.ErrorCount++;
|
if (logOptions.WriteToLog)
|
||||||
|
{
|
||||||
|
string logMessage = issue.Message;
|
||||||
|
if (!string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
||||||
|
{
|
||||||
|
logMessage = logOptions.LogMessageOverride;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(logMessage))
|
||||||
|
{
|
||||||
|
// Note that ::Write() has its own secret-masking logic.
|
||||||
|
long logLineNumber = Write(wellKnownTag, logMessage);
|
||||||
|
issue.Data["logFileLineNumber"] = logLineNumber.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (issue.Type == IssueType.Warning)
|
|
||||||
|
// Embedded ExecutionContexts (a.k.a. Composite actions) should never upload a timeline record to the server.
|
||||||
|
// Instead, we store processed issues on a shared (psuedo-inherited) list (belonging to the closest
|
||||||
|
// non-embedded ancestor ExecutionContext) so that they can be processed when that ancestor completes.
|
||||||
|
if (this.IsEmbedded)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(logMessage))
|
_embeddedIssueCollector.Add(issue);
|
||||||
{
|
|
||||||
long logLineNumber = Write(WellKnownTags.Warning, logMessage);
|
|
||||||
issue.Data["logFileLineNumber"] = logLineNumber.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_record.WarningCount < _maxIssueCount)
|
|
||||||
{
|
|
||||||
_record.Issues.Add(issue);
|
|
||||||
}
|
|
||||||
|
|
||||||
_record.WarningCount++;
|
|
||||||
}
|
}
|
||||||
else if (issue.Type == IssueType.Notice)
|
else
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(logMessage))
|
_jobServerQueue.QueueTimelineRecordUpdate(_mainTimelineId, _record);
|
||||||
{
|
|
||||||
long logLineNumber = Write(WellKnownTags.Notice, logMessage);
|
|
||||||
issue.Data["logFileLineNumber"] = logLineNumber.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_record.NoticeCount < _maxIssueCount)
|
|
||||||
{
|
|
||||||
_record.Issues.Add(issue);
|
|
||||||
}
|
|
||||||
|
|
||||||
_record.NoticeCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_jobServerQueue.QueueTimelineRecordUpdate(_mainTimelineId, _record);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateDetailTimelineRecord(TimelineRecord record)
|
public void UpdateDetailTimelineRecord(TimelineRecord record)
|
||||||
@@ -1014,8 +1059,7 @@ namespace GitHub.Runner.Worker
|
|||||||
StepTelemetry.FinishTime = _record.FinishTime;
|
StepTelemetry.FinishTime = _record.FinishTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsEmbedded &&
|
if (!IsEmbedded)
|
||||||
_record.Issues.Count > 0)
|
|
||||||
{
|
{
|
||||||
foreach (var issue in _record.Issues)
|
foreach (var issue in _record.Issues)
|
||||||
{
|
{
|
||||||
@@ -1181,6 +1225,11 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
UpdateGlobalStepsContext();
|
UpdateGlobalStepsContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void NoOp()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Error/Warning/etc methods are created as extension methods to simplify unit testing.
|
// The Error/Warning/etc methods are created as extension methods to simplify unit testing.
|
||||||
@@ -1205,19 +1254,22 @@ namespace GitHub.Runner.Worker
|
|||||||
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
||||||
public static void Error(this IExecutionContext context, string message)
|
public static void Error(this IExecutionContext context, string message)
|
||||||
{
|
{
|
||||||
context.AddIssue(new Issue() { Type = IssueType.Error, Message = message });
|
var issue = new Issue() { Type = IssueType.Error, Message = message };
|
||||||
|
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
||||||
public static void InfrastructureError(this IExecutionContext context, string message)
|
public static void InfrastructureError(this IExecutionContext context, string message)
|
||||||
{
|
{
|
||||||
context.AddIssue(new Issue() { Type = IssueType.Error, Message = message, IsInfrastructureIssue = true });
|
var issue = new Issue() { Type = IssueType.Error, Message = message, IsInfrastructureIssue = true };
|
||||||
|
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
||||||
public static void Warning(this IExecutionContext context, string message)
|
public static void Warning(this IExecutionContext context, string message)
|
||||||
{
|
{
|
||||||
context.AddIssue(new Issue() { Type = IssueType.Warning, Message = message });
|
var issue = new Issue() { Type = IssueType.Warning, Message = message };
|
||||||
|
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
// Do not add a format string overload. See comment on ExecutionContext.Write().
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
// Evaluation error
|
// Evaluation error
|
||||||
Trace.Info("Caught exception from expression for embedded step.env");
|
Trace.Info("Caught exception from expression for embedded step.env");
|
||||||
step.ExecutionContext.Error(ex);
|
step.ExecutionContext.Error(ex);
|
||||||
step.ExecutionContext.Complete(TaskResult.Failed);
|
SetStepConclusion(step, TaskResult.Failed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register Callback
|
// Register Callback
|
||||||
|
|||||||
@@ -143,7 +143,8 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
if (issue != null)
|
if (issue != null)
|
||||||
{
|
{
|
||||||
// Log issue
|
// Log issue
|
||||||
_executionContext.AddIssue(issue, stripped);
|
var logOptions = new ExecutionContextLogOptions(true, stripped);
|
||||||
|
_executionContext.AddIssue(issue, logOptions);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -683,7 +683,7 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
var issue = new Issue() { Type = IssueType.Warning, Message = $"You are running out of disk space. The runner will stop working when the machine runs out of disk space. Free space left: {freeSpaceInMB} MB" };
|
var issue = new Issue() { Type = IssueType.Warning, Message = $"You are running out of disk space. The runner will stop working when the machine runs out of disk space. Free space left: {freeSpaceInMB} MB" };
|
||||||
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.LowDiskSpace;
|
issue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.LowDiskSpace;
|
||||||
context.AddIssue(issue);
|
context.AddIssue(issue, ExecutionContextLogOptions.Default);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,8 @@ namespace GitHub.Runner.Worker
|
|||||||
default:
|
default:
|
||||||
throw new ArgumentException(HostContext.RunnerShutdownReason.ToString(), nameof(HostContext.RunnerShutdownReason));
|
throw new ArgumentException(HostContext.RunnerShutdownReason.ToString(), nameof(HostContext.RunnerShutdownReason));
|
||||||
}
|
}
|
||||||
jobContext.AddIssue(new Issue() { Type = IssueType.Error, Message = errorMessage });
|
var issue = new Issue() { Type = IssueType.Error, Message = errorMessage };
|
||||||
|
jobContext.AddIssue(issue, ExecutionContextLogOptions.Default);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Validate directory permissions.
|
// Validate directory permissions.
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using GitHub.Services.Common;
|
|
||||||
|
|
||||||
namespace GitHub.DistributedTask.WebApi
|
namespace GitHub.DistributedTask.WebApi
|
||||||
{
|
{
|
||||||
@@ -10,69 +9,78 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
public sealed class TimelineRecord
|
public sealed class TimelineRecord
|
||||||
{
|
{
|
||||||
public TimelineRecord()
|
public TimelineRecord()
|
||||||
|
: this(null)
|
||||||
{
|
{
|
||||||
this.Attempt = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TimelineRecord(TimelineRecord recordToBeCloned)
|
private TimelineRecord(TimelineRecord recordToBeCloned)
|
||||||
{
|
{
|
||||||
this.Attempt = recordToBeCloned.Attempt;
|
this.EnsureInitialized();
|
||||||
this.ChangeId = recordToBeCloned.ChangeId;
|
|
||||||
this.CurrentOperation = recordToBeCloned.CurrentOperation;
|
|
||||||
this.FinishTime = recordToBeCloned.FinishTime;
|
|
||||||
this.Id = recordToBeCloned.Id;
|
|
||||||
this.Identifier = recordToBeCloned.Identifier;
|
|
||||||
this.LastModified = recordToBeCloned.LastModified;
|
|
||||||
this.Location = recordToBeCloned.Location;
|
|
||||||
this.Name = recordToBeCloned.Name;
|
|
||||||
this.Order = recordToBeCloned.Order;
|
|
||||||
this.ParentId = recordToBeCloned.ParentId;
|
|
||||||
this.PercentComplete = recordToBeCloned.PercentComplete;
|
|
||||||
this.RecordType = recordToBeCloned.RecordType;
|
|
||||||
this.Result = recordToBeCloned.Result;
|
|
||||||
this.ResultCode = recordToBeCloned.ResultCode;
|
|
||||||
this.StartTime = recordToBeCloned.StartTime;
|
|
||||||
this.State = recordToBeCloned.State;
|
|
||||||
this.TimelineId = recordToBeCloned.TimelineId;
|
|
||||||
this.WorkerName = recordToBeCloned.WorkerName;
|
|
||||||
this.RefName = recordToBeCloned.RefName;
|
|
||||||
this.ErrorCount = recordToBeCloned.ErrorCount;
|
|
||||||
this.WarningCount = recordToBeCloned.WarningCount;
|
|
||||||
this.NoticeCount = recordToBeCloned.NoticeCount;
|
|
||||||
this.AgentPlatform = recordToBeCloned.AgentPlatform;
|
|
||||||
|
|
||||||
if (recordToBeCloned.Log != null)
|
if (recordToBeCloned != null)
|
||||||
{
|
{
|
||||||
this.Log = new TaskLogReference
|
this.Attempt = recordToBeCloned.Attempt;
|
||||||
|
this.ChangeId = recordToBeCloned.ChangeId;
|
||||||
|
this.CurrentOperation = recordToBeCloned.CurrentOperation;
|
||||||
|
this.FinishTime = recordToBeCloned.FinishTime;
|
||||||
|
this.Id = recordToBeCloned.Id;
|
||||||
|
this.Identifier = recordToBeCloned.Identifier;
|
||||||
|
this.LastModified = recordToBeCloned.LastModified;
|
||||||
|
this.Location = recordToBeCloned.Location;
|
||||||
|
this.Name = recordToBeCloned.Name;
|
||||||
|
this.Order = recordToBeCloned.Order;
|
||||||
|
this.ParentId = recordToBeCloned.ParentId;
|
||||||
|
this.PercentComplete = recordToBeCloned.PercentComplete;
|
||||||
|
this.RecordType = recordToBeCloned.RecordType;
|
||||||
|
this.Result = recordToBeCloned.Result;
|
||||||
|
this.ResultCode = recordToBeCloned.ResultCode;
|
||||||
|
this.StartTime = recordToBeCloned.StartTime;
|
||||||
|
this.State = recordToBeCloned.State;
|
||||||
|
this.TimelineId = recordToBeCloned.TimelineId;
|
||||||
|
this.WorkerName = recordToBeCloned.WorkerName;
|
||||||
|
this.RefName = recordToBeCloned.RefName;
|
||||||
|
this.ErrorCount = recordToBeCloned.ErrorCount;
|
||||||
|
this.WarningCount = recordToBeCloned.WarningCount;
|
||||||
|
this.NoticeCount = recordToBeCloned.NoticeCount;
|
||||||
|
this.AgentPlatform = recordToBeCloned.AgentPlatform;
|
||||||
|
|
||||||
|
if (recordToBeCloned.Log != null)
|
||||||
{
|
{
|
||||||
Id = recordToBeCloned.Log.Id,
|
this.Log = new TaskLogReference
|
||||||
Location = recordToBeCloned.Log.Location,
|
{
|
||||||
};
|
Id = recordToBeCloned.Log.Id,
|
||||||
}
|
Location = recordToBeCloned.Log.Location,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (recordToBeCloned.Details != null)
|
if (recordToBeCloned.Details != null)
|
||||||
{
|
|
||||||
this.Details = new TimelineReference
|
|
||||||
{
|
{
|
||||||
ChangeId = recordToBeCloned.Details.ChangeId,
|
this.Details = new TimelineReference
|
||||||
Id = recordToBeCloned.Details.Id,
|
{
|
||||||
Location = recordToBeCloned.Details.Location,
|
ChangeId = recordToBeCloned.Details.ChangeId,
|
||||||
};
|
Id = recordToBeCloned.Details.Id,
|
||||||
}
|
Location = recordToBeCloned.Details.Location,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (recordToBeCloned.m_issues?.Count> 0)
|
if (recordToBeCloned.m_issues?.Count > 0)
|
||||||
{
|
{
|
||||||
this.Issues.AddRange(recordToBeCloned.Issues.Select(i => i.Clone()));
|
this.Issues.AddRange(recordToBeCloned.Issues.Select(i => i.Clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recordToBeCloned.m_previousAttempts?.Count > 0)
|
if (recordToBeCloned.m_previousAttempts?.Count > 0)
|
||||||
{
|
{
|
||||||
this.PreviousAttempts.AddRange(recordToBeCloned.PreviousAttempts);
|
this.m_previousAttempts.AddRange(recordToBeCloned.m_previousAttempts);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recordToBeCloned.m_variables?.Count > 0)
|
if (recordToBeCloned.m_variables?.Count > 0)
|
||||||
{
|
{
|
||||||
this.m_variables = recordToBeCloned.Variables.ToDictionary(k => k.Key, v => v.Value.Clone());
|
// Don't pave over the case-insensitive Dictionary we initialized above.
|
||||||
|
foreach (var kvp in recordToBeCloned.m_variables)
|
||||||
|
{
|
||||||
|
m_variables[kvp.Key] = kvp.Value.Clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,14 +106,14 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Name = "Type", Order = 3)]
|
[DataMember(Name = "Type", Order = 3)]
|
||||||
public String RecordType
|
public string RecordType
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Order = 4)]
|
[DataMember(Order = 4)]
|
||||||
public String Name
|
public string Name
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
@@ -126,7 +134,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Order = 7)]
|
[DataMember(Order = 7)]
|
||||||
public String CurrentOperation
|
public string CurrentOperation
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
@@ -154,7 +162,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Order = 11)]
|
[DataMember(Order = 11)]
|
||||||
public String ResultCode
|
public string ResultCode
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
@@ -175,7 +183,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Order = 14)]
|
[DataMember(Order = 14)]
|
||||||
public String WorkerName
|
public string WorkerName
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
@@ -189,7 +197,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Order = 16, EmitDefaultValue = false)]
|
[DataMember(Order = 16, EmitDefaultValue = false)]
|
||||||
public String RefName
|
public string RefName
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
@@ -209,35 +217,46 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Order = 40)]
|
public Int32 ErrorCount
|
||||||
public Int32? ErrorCount
|
|
||||||
{
|
{
|
||||||
get;
|
get
|
||||||
set;
|
{
|
||||||
|
return m_errorCount.GetValueOrDefault(0);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
m_errorCount = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Order = 50)]
|
public Int32 WarningCount
|
||||||
public Int32? WarningCount
|
|
||||||
{
|
{
|
||||||
get;
|
get
|
||||||
set;
|
{
|
||||||
|
return m_warningCount.GetValueOrDefault(0);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
m_warningCount = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Order = 55)]
|
public Int32 NoticeCount
|
||||||
public Int32? NoticeCount
|
|
||||||
{
|
{
|
||||||
get;
|
get
|
||||||
set;
|
{
|
||||||
|
return m_noticeCount.GetValueOrDefault(0);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
m_noticeCount = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Issue> Issues
|
public List<Issue> Issues
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (m_issues == null)
|
|
||||||
{
|
|
||||||
m_issues = new List<Issue>();
|
|
||||||
}
|
|
||||||
return m_issues;
|
return m_issues;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,7 +276,7 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Order = 131)]
|
[DataMember(Order = 131)]
|
||||||
public String Identifier
|
public string Identifier
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
@@ -274,22 +293,14 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (m_previousAttempts == null)
|
|
||||||
{
|
|
||||||
m_previousAttempts = new List<TimelineAttempt>();
|
|
||||||
}
|
|
||||||
return m_previousAttempts;
|
return m_previousAttempts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDictionary<String, VariableValue> Variables
|
public IDictionary<string, VariableValue> Variables
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (m_variables == null)
|
|
||||||
{
|
|
||||||
m_variables = new Dictionary<String, VariableValue>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
return m_variables;
|
return m_variables;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,13 +310,53 @@ namespace GitHub.DistributedTask.WebApi
|
|||||||
return new TimelineRecord(this);
|
return new TimelineRecord(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataMember(Name = "Issues", EmitDefaultValue = false, Order = 60)]
|
[OnDeserialized]
|
||||||
|
private void OnDeserialized(StreamingContext context)
|
||||||
|
{
|
||||||
|
this.EnsureInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DataContractSerializer bypasses all constructor logic and inline initialization!
|
||||||
|
/// This method takes the place of a workhorse constructor for baseline initialization.
|
||||||
|
/// The expectation is for this logic to be accessible to constructors and also to the OnDeserialized helper.
|
||||||
|
/// </summary>
|
||||||
|
private void EnsureInitialized()
|
||||||
|
{
|
||||||
|
// Note that ?? is a short-circuiting operator. (??= would be preferable, but it's not supported in the .NET Framework version currently used by actions/runner.)
|
||||||
|
|
||||||
|
// De-nullify the following historically-nullable ints.
|
||||||
|
// (After several weeks in production, it may be possible to eliminate these nullable backing fields.)
|
||||||
|
m_errorCount = m_errorCount ?? 0;
|
||||||
|
m_warningCount = m_warningCount ?? 0;
|
||||||
|
m_noticeCount = m_noticeCount ?? 0;
|
||||||
|
|
||||||
|
m_issues = m_issues ?? new List<Issue>();
|
||||||
|
m_previousAttempts = m_previousAttempts ?? new List<TimelineAttempt>();
|
||||||
|
this.Attempt = Math.Max(this.Attempt, 1);
|
||||||
|
|
||||||
|
// Ensure whatever content may have been deserialized for m_variables is backed by a case-insensitive Dictionary.
|
||||||
|
var empty = Enumerable.Empty<KeyValuePair<string, VariableValue>>();
|
||||||
|
m_variables = new Dictionary<string, VariableValue>(m_variables ?? empty, StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataMember(Name = nameof(ErrorCount), Order = 40)]
|
||||||
|
private Int32? m_errorCount;
|
||||||
|
|
||||||
|
[DataMember(Name = nameof(WarningCount), Order = 50)]
|
||||||
|
private Int32? m_warningCount;
|
||||||
|
|
||||||
|
[DataMember(Name = nameof(NoticeCount), Order = 55)]
|
||||||
|
private Int32? m_noticeCount;
|
||||||
|
|
||||||
|
[DataMember(Name = nameof(Issues), EmitDefaultValue = false, Order = 60)]
|
||||||
private List<Issue> m_issues;
|
private List<Issue> m_issues;
|
||||||
|
|
||||||
[DataMember(Name = "Variables", EmitDefaultValue = false, Order = 80)]
|
[DataMember(Name = nameof(Variables), EmitDefaultValue = false, Order = 80)]
|
||||||
private Dictionary<String, VariableValue> m_variables;
|
private Dictionary<string, VariableValue> m_variables;
|
||||||
|
|
||||||
[DataMember(Name = "PreviousAttempts", EmitDefaultValue = false, Order = 120)]
|
[DataMember(Name = nameof(PreviousAttempts), EmitDefaultValue = false, Order = 120)]
|
||||||
private List<TimelineAttempt> m_previousAttempts;
|
private List<TimelineAttempt> m_previousAttempts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
270
src/Test/L0/DistributedTask/WebApi/TimelineRecordL0.cs
Normal file
270
src/Test/L0/DistributedTask/WebApi/TimelineRecordL0.cs
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.Serialization.Json;
|
||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using Xunit;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Common.Tests.DistributedTask
|
||||||
|
{
|
||||||
|
public sealed class TimelineRecordL0
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "DistributedTask")]
|
||||||
|
public void VerifyTimelineRecord_Defaults()
|
||||||
|
{
|
||||||
|
var tr = new TimelineRecord();
|
||||||
|
|
||||||
|
Assert.Equal(0, tr.ErrorCount);
|
||||||
|
Assert.Equal(0, tr.WarningCount);
|
||||||
|
Assert.Equal(0, tr.NoticeCount);
|
||||||
|
Assert.Equal(1, tr.Attempt);
|
||||||
|
Assert.NotNull(tr.Issues);
|
||||||
|
Assert.NotNull(tr.PreviousAttempts);
|
||||||
|
Assert.NotNull(tr.Variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "DistributedTask")]
|
||||||
|
public void VerifyTimelineRecord_Clone()
|
||||||
|
{
|
||||||
|
var original = new TimelineRecord();
|
||||||
|
original.ErrorCount = 100;
|
||||||
|
original.WarningCount = 200;
|
||||||
|
original.NoticeCount = 300;
|
||||||
|
original.Attempt = 3;
|
||||||
|
|
||||||
|
// The Variables dictionary should be a case-insensitive dictionary.
|
||||||
|
original.Variables["xxx"] = new VariableValue("first", false);
|
||||||
|
original.Variables["XXX"] = new VariableValue("second", false);
|
||||||
|
|
||||||
|
Assert.Equal(1, original.Variables.Count);
|
||||||
|
Assert.Equal("second", original.Variables.Values.First().Value);
|
||||||
|
Assert.Equal("second", original.Variables["xXx"].Value);
|
||||||
|
|
||||||
|
var clone = original.Clone();
|
||||||
|
|
||||||
|
Assert.NotSame(original, clone);
|
||||||
|
Assert.NotSame(original.Variables, clone.Variables);
|
||||||
|
Assert.Equal(100, clone.ErrorCount);
|
||||||
|
Assert.Equal(200, clone.WarningCount);
|
||||||
|
Assert.Equal(300, clone.NoticeCount);
|
||||||
|
Assert.Equal(3, clone.Attempt);
|
||||||
|
|
||||||
|
// Now, mutate the original post-clone.
|
||||||
|
original.ErrorCount++;
|
||||||
|
original.WarningCount += 10;
|
||||||
|
original.NoticeCount *= 3;
|
||||||
|
original.Attempt--;
|
||||||
|
original.Variables["a"] = new VariableValue("1", false);
|
||||||
|
|
||||||
|
// Verify that the clone was unaffected by the changes to the original.
|
||||||
|
Assert.Equal(100, clone.ErrorCount);
|
||||||
|
Assert.Equal(200, clone.WarningCount);
|
||||||
|
Assert.Equal(300, clone.NoticeCount);
|
||||||
|
Assert.Equal(3, clone.Attempt);
|
||||||
|
Assert.Equal(1, clone.Variables.Count);
|
||||||
|
Assert.Equal("second", clone.Variables.Values.First().Value);
|
||||||
|
|
||||||
|
// Verify that the clone's Variables dictionary is also case-sensitive.
|
||||||
|
clone.Variables["yyy"] = new VariableValue("third", false);
|
||||||
|
clone.Variables["YYY"] = new VariableValue("fourth", false);
|
||||||
|
|
||||||
|
Assert.Equal(2, clone.Variables.Count);
|
||||||
|
Assert.Equal("second", clone.Variables["xXx"].Value);
|
||||||
|
Assert.Equal("fourth", clone.Variables["yYy"].Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "DistributedTask")]
|
||||||
|
public void VerifyTimelineRecord_DeserializationEdgeCase_NonNullCollections()
|
||||||
|
{
|
||||||
|
var jsonSamples = LoadJsonSamples(JsonSamplesFilePath);
|
||||||
|
|
||||||
|
// Verify that missing JSON fields don't result in null values for collection properties.
|
||||||
|
var tr = Deserialize(jsonSamples["minimal"]);
|
||||||
|
Assert.NotNull(tr);
|
||||||
|
Assert.Equal("minimal", tr!.Name);
|
||||||
|
Assert.NotNull(tr.Issues);
|
||||||
|
Assert.NotNull(tr.PreviousAttempts);
|
||||||
|
Assert.NotNull(tr.Variables);
|
||||||
|
|
||||||
|
// Verify that explicitly-null JSON fields don't result in null values for collection properties.
|
||||||
|
// (Our deserialization logic should fix these up and instantiate an empty collection.)
|
||||||
|
tr = Deserialize(jsonSamples["explicit-null-collections"]);
|
||||||
|
Assert.NotNull(tr);
|
||||||
|
Assert.Equal("explicit-null-collections", tr!.Name);
|
||||||
|
Assert.NotNull(tr.Issues);
|
||||||
|
Assert.NotNull(tr.PreviousAttempts);
|
||||||
|
Assert.NotNull(tr.Variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "DistributedTask")]
|
||||||
|
public void VerifyTimelineRecord_DeserializationEdgeCase_AttemptCannotBeLessThan1()
|
||||||
|
{
|
||||||
|
var jsonSamples = LoadJsonSamples(JsonSamplesFilePath);
|
||||||
|
|
||||||
|
// Verify that 1 is the effective floor for TimelineRecord::Attempt.
|
||||||
|
var tr = Deserialize(jsonSamples["minimal"]);
|
||||||
|
Assert.NotNull(tr);
|
||||||
|
Assert.Equal("minimal", tr!.Name);
|
||||||
|
Assert.Equal(1, tr.Attempt);
|
||||||
|
|
||||||
|
tr = Deserialize(jsonSamples["invalid-attempt-value"]);
|
||||||
|
Assert.NotNull(tr);
|
||||||
|
Assert.Equal("invalid-attempt-value", tr!.Name);
|
||||||
|
Assert.Equal(1, tr.Attempt);
|
||||||
|
|
||||||
|
tr = Deserialize(jsonSamples["zero-attempt-value"]);
|
||||||
|
Assert.NotNull(tr);
|
||||||
|
Assert.Equal("zero-attempt-value", tr!.Name);
|
||||||
|
Assert.Equal(1, tr.Attempt);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "DistributedTask")]
|
||||||
|
public void VerifyTimelineRecord_DeserializationEdgeCase_HandleLegacyNullsGracefully()
|
||||||
|
{
|
||||||
|
var jsonSamples = LoadJsonSamples(JsonSamplesFilePath);
|
||||||
|
|
||||||
|
// Verify that nulls for ErrorCount, WarningCount, and NoticeCount are interpreted as 0.
|
||||||
|
var tr = Deserialize(jsonSamples["legacy-nulls"]);
|
||||||
|
Assert.NotNull(tr);
|
||||||
|
Assert.Equal("legacy-nulls", tr!.Name);
|
||||||
|
Assert.Equal(0, tr.ErrorCount);
|
||||||
|
Assert.Equal(0, tr.WarningCount);
|
||||||
|
Assert.Equal(0, tr.NoticeCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "DistributedTask")]
|
||||||
|
public void VerifyTimelineRecord_DeserializationEdgeCase_HandleMissingCountsGracefully()
|
||||||
|
{
|
||||||
|
var jsonSamples = LoadJsonSamples(JsonSamplesFilePath);
|
||||||
|
|
||||||
|
// Verify that nulls for ErrorCount, WarningCount, and NoticeCount are interpreted as 0.
|
||||||
|
var tr = Deserialize(jsonSamples["missing-counts"]);
|
||||||
|
Assert.NotNull(tr);
|
||||||
|
Assert.Equal("missing-counts", tr!.Name);
|
||||||
|
Assert.Equal(0, tr.ErrorCount);
|
||||||
|
Assert.Equal(0, tr.WarningCount);
|
||||||
|
Assert.Equal(0, tr.NoticeCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "DistributedTask")]
|
||||||
|
public void VerifyTimelineRecord_DeserializationEdgeCase_NonZeroCounts()
|
||||||
|
{
|
||||||
|
var jsonSamples = LoadJsonSamples(JsonSamplesFilePath);
|
||||||
|
|
||||||
|
// Verify that nulls for ErrorCount, WarningCount, and NoticeCount are interpreted as 0.
|
||||||
|
var tr = Deserialize(jsonSamples["non-zero-counts"]);
|
||||||
|
Assert.NotNull(tr);
|
||||||
|
Assert.Equal("non-zero-counts", tr!.Name);
|
||||||
|
Assert.Equal(10, tr.ErrorCount);
|
||||||
|
Assert.Equal(20, tr.WarningCount);
|
||||||
|
Assert.Equal(30, tr.NoticeCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "DistributedTask")]
|
||||||
|
public void VerifyTimelineRecord_Deserialization_LeanTimelineRecord()
|
||||||
|
{
|
||||||
|
var jsonSamples = LoadJsonSamples(JsonSamplesFilePath);
|
||||||
|
|
||||||
|
// Verify that a lean TimelineRecord can be deserialized.
|
||||||
|
var tr = Deserialize(jsonSamples["lean"]);
|
||||||
|
Assert.NotNull(tr);
|
||||||
|
Assert.Equal("lean", tr!.Name);
|
||||||
|
Assert.Equal(4, tr.Attempt);
|
||||||
|
Assert.Equal(1, tr.Issues.Count);
|
||||||
|
Assert.Equal(3, tr.Variables.Count);
|
||||||
|
Assert.Equal(3, tr.PreviousAttempts.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "DistributedTask")]
|
||||||
|
public void VerifyTimelineRecord_Deserialization_VariablesDictionaryIsCaseInsensitive()
|
||||||
|
{
|
||||||
|
var jsonSamples = LoadJsonSamples(JsonSamplesFilePath);
|
||||||
|
|
||||||
|
var tr = Deserialize(jsonSamples["lean"]);
|
||||||
|
Assert.NotNull(tr);
|
||||||
|
Assert.Equal("lean", tr!.Name);
|
||||||
|
Assert.Equal(3, tr.Variables.Count);
|
||||||
|
|
||||||
|
// Verify that the Variables Dictionary is case-insensitive.
|
||||||
|
tr.Variables["X"] = new VariableValue("overwritten", false);
|
||||||
|
Assert.Equal(3, tr.Variables.Count);
|
||||||
|
|
||||||
|
tr.Variables["new"] = new VariableValue("new.1", false);
|
||||||
|
Assert.Equal(4, tr.Variables.Count);
|
||||||
|
|
||||||
|
tr.Variables["NEW"] = new VariableValue("new.2", false);
|
||||||
|
Assert.Equal(4, tr.Variables.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "DistributedTask")]
|
||||||
|
public void VerifyTimelineRecord_DeserializationEdgeCase_DuplicateVariableKeysThrowsException()
|
||||||
|
{
|
||||||
|
var jsonSamples = LoadJsonSamples(JsonSamplesFilePath);
|
||||||
|
|
||||||
|
// We could be more forgiving in this case if we discover that it's not uncommon in Production for serialized TimelineRecords to:
|
||||||
|
// 1) get incorrectly instantiated with a case-sensitive Variables dictionary (in older versions, this was possible via TimelineRecord::Clone)
|
||||||
|
// 2) end up with case variations of the same key
|
||||||
|
// 3) make another serialization/deserialization round trip.
|
||||||
|
//
|
||||||
|
// If we wanted to grant clemency to such incorrectly-serialized TimelineRecords,
|
||||||
|
// the fix to TimelineRecord::EnsureInitialized would look something like the following:
|
||||||
|
//
|
||||||
|
// var seedVariables = m_variables ?? Enumerable.Empty<KeyValuePair<string, VariableValue>>();
|
||||||
|
// m_variables = new Dictionary<string, VariableValue>(seedVariables.Count(), StringComparer.OrdinalIgnoreCase);
|
||||||
|
// foreach (var kvp in seedVariables)
|
||||||
|
// {
|
||||||
|
// m_variables[kvp.Key] = kvp.Value;
|
||||||
|
// }
|
||||||
|
Assert.Throws<ArgumentException>(() => Deserialize(jsonSamples["duplicate-variable-keys"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static Dictionary<string, string> LoadJsonSamples(string path)
|
||||||
|
{
|
||||||
|
// Embedding independent JSON samples within YML works well because JSON generally doesn't need to be escaped or otherwise mangled.
|
||||||
|
var yamlDeserializer = new YamlDotNet.Serialization.Deserializer();
|
||||||
|
using var stream = new StreamReader(path);
|
||||||
|
return yamlDeserializer.Deserialize<Dictionary<string, string>>(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TimelineRecord? Deserialize(string rawJson)
|
||||||
|
{
|
||||||
|
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(rawJson ?? string.Empty));
|
||||||
|
return m_jsonSerializer.ReadObject(stream) as TimelineRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string JsonSamplesFilePath
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(TestUtil.GetTestDataPath(), "timelinerecord_json_samples.yml");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly DataContractJsonSerializer m_jsonSerializer = new(typeof(TimelineRecord));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
@@ -32,10 +32,10 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
hc.GetTrace().Info($"{tag} {line}");
|
hc.GetTrace().Info($"{tag} {line}");
|
||||||
return 1;
|
return 1;
|
||||||
});
|
});
|
||||||
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>()))
|
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||||
.Callback((Issue issue, string message) =>
|
.Callback((Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||||
{
|
{
|
||||||
hc.GetTrace().Info($"{issue.Type} {issue.Message} {message ?? string.Empty}");
|
hc.GetTrace().Info($"{issue.Type} {issue.Message} {logOptions.LogMessageOverride ?? string.Empty}");
|
||||||
});
|
});
|
||||||
|
|
||||||
_commandManager.EnablePluginInternalCommand();
|
_commandManager.EnablePluginInternalCommand();
|
||||||
@@ -59,10 +59,10 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
hc.GetTrace().Info($"{tag} {line}");
|
hc.GetTrace().Info($"{tag} {line}");
|
||||||
return 1;
|
return 1;
|
||||||
});
|
});
|
||||||
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>()))
|
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||||
.Callback((Issue issue, string message) =>
|
.Callback((Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||||
{
|
{
|
||||||
hc.GetTrace().Info($"{issue.Type} {issue.Message} {message ?? string.Empty}");
|
hc.GetTrace().Info($"{issue.Type} {issue.Message} {logOptions.LogMessageOverride ?? string.Empty}");
|
||||||
});
|
});
|
||||||
|
|
||||||
_commandManager.EnablePluginInternalCommand();
|
_commandManager.EnablePluginInternalCommand();
|
||||||
@@ -92,10 +92,10 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
return 1;
|
return 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>()))
|
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||||
.Callback((Issue issue, string message) =>
|
.Callback((Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||||
{
|
{
|
||||||
hc.GetTrace().Info($"{issue.Type} {issue.Message} {message ?? string.Empty}");
|
hc.GetTrace().Info($"{issue.Type} {issue.Message} {logOptions.LogMessageOverride ?? string.Empty}");
|
||||||
});
|
});
|
||||||
|
|
||||||
_ec.Object.Global.EnvironmentVariables = new Dictionary<string, string>();
|
_ec.Object.Global.EnvironmentVariables = new Dictionary<string, string>();
|
||||||
|
|||||||
@@ -2148,7 +2148,7 @@ runs:
|
|||||||
_ec.Object.Global.FileTable = new List<String>();
|
_ec.Object.Global.FileTable = new List<String>();
|
||||||
_ec.Object.Global.Plan = new TaskOrchestrationPlanReference();
|
_ec.Object.Global.Plan = new TaskOrchestrationPlanReference();
|
||||||
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); });
|
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); });
|
||||||
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); });
|
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<ExecutionContextLogOptions>())).Callback((Issue issue, ExecutionContextLogOptions logOptions) => { _hc.GetTrace().Info($"[{issue.Type}]{logOptions.LogMessageOverride ?? issue.Message}"); });
|
||||||
_ec.Setup(x => x.GetGitHubContext("workspace")).Returns(Path.Combine(_workFolder, "actions", "actions"));
|
_ec.Setup(x => x.GetGitHubContext("workspace")).Returns(Path.Combine(_workFolder, "actions", "actions"));
|
||||||
|
|
||||||
_dockerManager = new Mock<IDockerCommandManager>();
|
_dockerManager = new Mock<IDockerCommandManager>();
|
||||||
|
|||||||
@@ -670,7 +670,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
{
|
{
|
||||||
Teardown();
|
Teardown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
@@ -715,7 +715,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
//Assert
|
//Assert
|
||||||
var err = Assert.Throws<ArgumentException>(() => actionManifest.Load(_ec.Object, action_path));
|
var err = Assert.Throws<ArgumentException>(() => actionManifest.Load(_ec.Object, action_path));
|
||||||
Assert.Contains($"Fail to load {action_path}", err.Message);
|
Assert.Contains($"Fail to load {action_path}", err.Message);
|
||||||
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12' or 'node16'.")), It.IsAny<string>()), Times.Once);
|
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Missing 'using' value. 'using' requires 'composite', 'docker', 'node12' or 'node16'.")), It.IsAny<ExecutionContextLogOptions>()), Times.Once);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -860,7 +860,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_ec.Setup(x => x.ExpressionValues).Returns(new DictionaryContextData());
|
_ec.Setup(x => x.ExpressionValues).Returns(new DictionaryContextData());
|
||||||
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
|
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
|
||||||
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"{tag}{message}"); });
|
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"{tag}{message}"); });
|
||||||
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); });
|
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<ExecutionContextLogOptions>())).Callback((Issue issue, ExecutionContextLogOptions logOptions) => { _hc.GetTrace().Info($"[{issue.Type}]{logOptions.LogMessageOverride ?? issue.Message}"); });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Teardown()
|
private void Teardown()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using GitHub.DistributedTask.Expressions2;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
using GitHub.DistributedTask.Pipelines;
|
using GitHub.DistributedTask.Pipelines;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
@@ -366,7 +366,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
Assert.Equal("invalid1", finialInputs["invalid1"]);
|
Assert.Equal("invalid1", finialInputs["invalid1"]);
|
||||||
Assert.Equal("invalid2", finialInputs["invalid2"]);
|
Assert.Equal("invalid2", finialInputs["invalid2"]);
|
||||||
|
|
||||||
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Unexpected input(s) 'invalid1', 'invalid2'")), It.IsAny<string>()), Times.Once);
|
_ec.Verify(x => x.AddIssue(It.Is<Issue>(s => s.Message.Contains("Unexpected input(s) 'invalid1', 'invalid2'")), It.IsAny<ExecutionContextLogOptions>()), Times.Once);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -485,7 +485,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token);
|
_ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token);
|
||||||
_ec.Object.Global.Variables = new Variables(_hc, new Dictionary<string, VariableValue>());
|
_ec.Object.Global.Variables = new Variables(_hc, new Dictionary<string, VariableValue>());
|
||||||
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); });
|
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); });
|
||||||
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); });
|
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<ExecutionContextLogOptions>())).Callback((Issue issue, ExecutionContextLogOptions logOptions) => { _hc.GetTrace().Info($"[{issue.Type}]{logOptions.LogMessageOverride ?? issue.Message}"); });
|
||||||
|
|
||||||
_hc.SetSingleton<IActionManager>(_actionManager.Object);
|
_hc.SetSingleton<IActionManager>(_actionManager.Object);
|
||||||
_hc.SetSingleton<IHandlerFactory>(_handlerFactory.Object);
|
_hc.SetSingleton<IHandlerFactory>(_handlerFactory.Object);
|
||||||
|
|||||||
@@ -247,12 +247,16 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
WriteDebug = true,
|
WriteDebug = true,
|
||||||
Variables = _variables,
|
Variables = _variables,
|
||||||
});
|
});
|
||||||
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<string>()))
|
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||||
.Callback((DTWebApi.Issue issue, string logMessage) =>
|
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||||
{
|
{
|
||||||
_issues.Add(new Tuple<DTWebApi.Issue, string>(issue, logMessage));
|
var resolvedMessage = issue.Message;
|
||||||
var message = !string.IsNullOrEmpty(logMessage) ? logMessage : issue.Message;
|
if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
||||||
_trace.Info($"Issue '{issue.Type}': {message}");
|
{
|
||||||
|
resolvedMessage = logOptions.LogMessageOverride;
|
||||||
|
}
|
||||||
|
_issues.Add(new(issue, resolvedMessage));
|
||||||
|
_trace.Info($"Issue '{issue.Type}': {resolvedMessage}");
|
||||||
});
|
});
|
||||||
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Callback((string tag, string message) =>
|
.Callback((string tag, string message) =>
|
||||||
@@ -268,4 +272,4 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
return hostContext;
|
return hostContext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
@@ -52,42 +52,43 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
// Act.
|
// Act.
|
||||||
ec.InitializeJob(jobRequest, CancellationToken.None);
|
ec.InitializeJob(jobRequest, CancellationToken.None);
|
||||||
|
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
// Flood the ExecutionContext with errors and warnings (past its max capacity of 10).
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
||||||
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
||||||
|
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
||||||
|
|
||||||
ec.Complete();
|
ec.Complete();
|
||||||
|
|
||||||
// Assert.
|
// Assert.
|
||||||
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.ErrorCount == 15)), Times.AtLeastOnce);
|
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.ErrorCount > 0)), Times.AtLeast(10));
|
||||||
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.WarningCount == 14)), Times.AtLeastOnce);
|
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.WarningCount > 0)), Times.AtLeast(10));
|
||||||
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Type == IssueType.Error).Count() == 10)), Times.AtLeastOnce);
|
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Type == IssueType.Error).Count() == 10)), Times.AtLeastOnce);
|
||||||
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Type == IssueType.Warning).Count() == 10)), Times.AtLeastOnce);
|
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Type == IssueType.Warning).Count() == 10)), Times.AtLeastOnce);
|
||||||
}
|
}
|
||||||
@@ -190,9 +191,9 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
bigMessage += "a";
|
bigMessage += "a";
|
||||||
}
|
}
|
||||||
|
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = bigMessage });
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = bigMessage }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = bigMessage });
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = bigMessage }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = bigMessage });
|
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = bigMessage }, ExecutionContextLogOptions.Default);
|
||||||
|
|
||||||
ec.Complete();
|
ec.Complete();
|
||||||
|
|
||||||
@@ -203,6 +204,61 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void AddIssue_OverrideLogMessage()
|
||||||
|
{
|
||||||
|
using (TestHostContext hc = CreateTestContext())
|
||||||
|
{
|
||||||
|
// Arrange: Create a job request message.
|
||||||
|
TaskOrchestrationPlanReference plan = new();
|
||||||
|
TimelineReference timeline = new();
|
||||||
|
Guid jobId = Guid.NewGuid();
|
||||||
|
string jobName = "some job name";
|
||||||
|
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary<string, VariableValue>(), new List<MaskHint>(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List<Pipelines.ActionStep>(), null, null, null, null);
|
||||||
|
jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource()
|
||||||
|
{
|
||||||
|
Alias = Pipelines.PipelineConstants.SelfAlias,
|
||||||
|
Id = "github",
|
||||||
|
Version = "sha1"
|
||||||
|
});
|
||||||
|
jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
|
||||||
|
|
||||||
|
// Arrange: Setup the paging logger.
|
||||||
|
var pagingLogger = new Mock<IPagingLogger>();
|
||||||
|
var jobServerQueue = new Mock<IJobServerQueue>();
|
||||||
|
|
||||||
|
hc.EnqueueInstance(pagingLogger.Object);
|
||||||
|
hc.SetSingleton(jobServerQueue.Object);
|
||||||
|
|
||||||
|
var ec = new Runner.Worker.ExecutionContext();
|
||||||
|
ec.Initialize(hc);
|
||||||
|
|
||||||
|
// Act.
|
||||||
|
ec.InitializeJob(jobRequest, CancellationToken.None);
|
||||||
|
|
||||||
|
var issueMessage = "Message embedded in issue.";
|
||||||
|
var overrideMessage = "Message override.";
|
||||||
|
var options = new ExecutionContextLogOptions(true, overrideMessage);
|
||||||
|
|
||||||
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = issueMessage }, options);
|
||||||
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = issueMessage }, options);
|
||||||
|
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = issueMessage }, options);
|
||||||
|
|
||||||
|
// Finally, add a variation that DOESN'T override the message.
|
||||||
|
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = issueMessage }, ExecutionContextLogOptions.Default);
|
||||||
|
|
||||||
|
ec.Complete();
|
||||||
|
|
||||||
|
// Assert.
|
||||||
|
jobServerQueue.Verify(x => x.QueueWebConsoleLine(It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<long?>()), Times.Exactly(4));
|
||||||
|
jobServerQueue.Verify(x => x.QueueWebConsoleLine(It.IsAny<Guid>(), It.Is<string>(text => text.EndsWith(overrideMessage)), It.IsAny<long?>()), Times.Exactly(3));
|
||||||
|
jobServerQueue.Verify(x => x.QueueWebConsoleLine(It.IsAny<Guid>(), It.Is<string>(text => text.EndsWith(issueMessage)), It.IsAny<long?>()), Times.Exactly(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
@@ -242,13 +298,15 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
var embeddedStep = ec.CreateChild(Guid.NewGuid(), "action_1_pre", "action_1_pre", null, null, ActionRunStage.Main, isEmbedded: true);
|
var embeddedStep = ec.CreateChild(Guid.NewGuid(), "action_1_pre", "action_1_pre", null, null, ActionRunStage.Main, isEmbedded: true);
|
||||||
embeddedStep.Start();
|
embeddedStep.Start();
|
||||||
|
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Error, Message = "error annotation that should have step and line number information" });
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Error, Message = "error annotation that should have step and line number information" }, ExecutionContextLogOptions.Default);
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning annotation that should have step and line number information" });
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning annotation that should have step and line number information" }, ExecutionContextLogOptions.Default);
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice annotation that should have step and line number information" });
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice annotation that should have step and line number information" }, ExecutionContextLogOptions.Default);
|
||||||
|
|
||||||
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Error).Count() == 1)), Times.AtLeastOnce);
|
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.IsAny<TimelineRecord>()), Times.AtLeastOnce);
|
||||||
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Warning).Count() == 1)), Times.AtLeastOnce);
|
// Verify that Error/Warning/Notice issues added to embedded steps don't get sent up to the server.
|
||||||
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Notice).Count() == 1)), Times.AtLeastOnce);
|
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Error).Count() == 1)), Times.Never);
|
||||||
|
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Warning).Count() == 1)), Times.Never);
|
||||||
|
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Notice).Count() == 1)), Times.Never);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -626,12 +684,12 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
ec.StepTelemetry.StepId = Guid.NewGuid();
|
ec.StepTelemetry.StepId = Guid.NewGuid();
|
||||||
ec.StepTelemetry.Stage = "main";
|
ec.StepTelemetry.Stage = "main";
|
||||||
|
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" });
|
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
||||||
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" });
|
ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" }, ExecutionContextLogOptions.Default);
|
||||||
|
|
||||||
ec.Complete();
|
ec.Complete();
|
||||||
|
|
||||||
@@ -692,9 +750,9 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
embeddedStep.StepTelemetry.Action = "actions/checkout";
|
embeddedStep.StepTelemetry.Action = "actions/checkout";
|
||||||
embeddedStep.StepTelemetry.Ref = "v2";
|
embeddedStep.StepTelemetry.Ref = "v2";
|
||||||
|
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" });
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" }, ExecutionContextLogOptions.Default);
|
||||||
|
|
||||||
embeddedStep.PublishStepTelemetry();
|
embeddedStep.PublishStepTelemetry();
|
||||||
|
|
||||||
@@ -756,9 +814,9 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
embeddedStep.StepTelemetry.Action = "actions/checkout";
|
embeddedStep.StepTelemetry.Action = "actions/checkout";
|
||||||
embeddedStep.StepTelemetry.Ref = "v2";
|
embeddedStep.StepTelemetry.Ref = "v2";
|
||||||
|
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default);
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default);
|
||||||
embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" });
|
embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" }, ExecutionContextLogOptions.Default);
|
||||||
|
|
||||||
ec.Complete();
|
ec.Complete();
|
||||||
|
|
||||||
@@ -927,7 +985,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
inputVarsContext["VARIABLE_2"] = new StringContextData("value2");
|
inputVarsContext["VARIABLE_2"] = new StringContextData("value2");
|
||||||
jobRequest.ContextData["vars"] = inputVarsContext;
|
jobRequest.ContextData["vars"] = inputVarsContext;
|
||||||
|
|
||||||
// Arrange: Setup the paging logger.
|
// Arrange: Setup the paging logger.
|
||||||
var pagingLogger1 = new Mock<IPagingLogger>();
|
var pagingLogger1 = new Mock<IPagingLogger>();
|
||||||
var jobServerQueue = new Mock<IJobServerQueue>();
|
var jobServerQueue = new Mock<IJobServerQueue>();
|
||||||
hc.EnqueueInstance(pagingLogger1.Object);
|
hc.EnqueueInstance(pagingLogger1.Object);
|
||||||
@@ -941,7 +999,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
var expected = new DictionaryContextData();
|
var expected = new DictionaryContextData();
|
||||||
expected["VARIABLE_1"] = new StringContextData("value1");
|
expected["VARIABLE_1"] = new StringContextData("value1");
|
||||||
expected["VARIABLE_2"] = new StringContextData("value1");
|
expected["VARIABLE_2"] = new StringContextData("value1");
|
||||||
|
|
||||||
Assert.True(ExpressionValuesAssertEqual(expected, jobContext.ExpressionValues["vars"] as DictionaryContextData));
|
Assert.True(ExpressionValuesAssertEqual(expected, jobContext.ExpressionValues["vars"] as DictionaryContextData));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -972,7 +1030,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
inputVarsContext[Constants.Variables.Actions.RunnerDebug] = new StringContextData("true");
|
inputVarsContext[Constants.Variables.Actions.RunnerDebug] = new StringContextData("true");
|
||||||
jobRequest.ContextData["vars"] = inputVarsContext;
|
jobRequest.ContextData["vars"] = inputVarsContext;
|
||||||
|
|
||||||
// Arrange: Setup the paging logger.
|
// Arrange: Setup the paging logger.
|
||||||
var pagingLogger1 = new Mock<IPagingLogger>();
|
var pagingLogger1 = new Mock<IPagingLogger>();
|
||||||
var jobServerQueue = new Mock<IJobServerQueue>();
|
var jobServerQueue = new Mock<IJobServerQueue>();
|
||||||
hc.EnqueueInstance(pagingLogger1.Object);
|
hc.EnqueueInstance(pagingLogger1.Object);
|
||||||
@@ -983,7 +1041,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
jobContext.InitializeJob(jobRequest, CancellationToken.None);
|
jobContext.InitializeJob(jobRequest, CancellationToken.None);
|
||||||
|
|
||||||
|
|
||||||
Assert.Equal("true", jobContext.Global.Variables.Get(Constants.Variables.Actions.StepDebug));
|
Assert.Equal("true", jobContext.Global.Variables.Get(Constants.Variables.Actions.StepDebug));
|
||||||
Assert.Equal("true", jobContext.Global.Variables.Get(Constants.Variables.Actions.RunnerDebug));
|
Assert.Equal("true", jobContext.Global.Variables.Get(Constants.Variables.Actions.RunnerDebug));
|
||||||
}
|
}
|
||||||
@@ -1018,7 +1076,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
jobRequest.Variables[Constants.Variables.Actions.StepDebug] = "false";
|
jobRequest.Variables[Constants.Variables.Actions.StepDebug] = "false";
|
||||||
jobRequest.Variables[Constants.Variables.Actions.RunnerDebug] = "false";
|
jobRequest.Variables[Constants.Variables.Actions.RunnerDebug] = "false";
|
||||||
|
|
||||||
// Arrange: Setup the paging logger.
|
// Arrange: Setup the paging logger.
|
||||||
var pagingLogger1 = new Mock<IPagingLogger>();
|
var pagingLogger1 = new Mock<IPagingLogger>();
|
||||||
var jobServerQueue = new Mock<IJobServerQueue>();
|
var jobServerQueue = new Mock<IJobServerQueue>();
|
||||||
hc.EnqueueInstance(pagingLogger1.Object);
|
hc.EnqueueInstance(pagingLogger1.Object);
|
||||||
@@ -1029,7 +1087,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
|
|
||||||
jobContext.InitializeJob(jobRequest, CancellationToken.None);
|
jobContext.InitializeJob(jobRequest, CancellationToken.None);
|
||||||
|
|
||||||
|
|
||||||
Assert.Equal("false", jobContext.Global.Variables.Get(Constants.Variables.Actions.StepDebug));
|
Assert.Equal("false", jobContext.Global.Variables.Get(Constants.Variables.Actions.StepDebug));
|
||||||
Assert.Equal("false", jobContext.Global.Variables.Get(Constants.Variables.Actions.RunnerDebug));
|
Assert.Equal("false", jobContext.Global.Variables.Get(Constants.Variables.Actions.RunnerDebug));
|
||||||
}
|
}
|
||||||
@@ -1061,4 +1119,4 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -984,10 +984,15 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
{
|
{
|
||||||
_onMatcherChanged = handler;
|
_onMatcherChanged = handler;
|
||||||
});
|
});
|
||||||
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<string>()))
|
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||||
.Callback((DTWebApi.Issue issue, string logMessage) =>
|
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||||
{
|
{
|
||||||
_issues.Add(new Tuple<DTWebApi.Issue, string>(issue, logMessage));
|
var resolvedMessage = issue.Message;
|
||||||
|
if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
||||||
|
{
|
||||||
|
resolvedMessage = logOptions.LogMessageOverride;
|
||||||
|
}
|
||||||
|
_issues.Add(new(issue, resolvedMessage));
|
||||||
});
|
});
|
||||||
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Callback((string tag, string message) =>
|
.Callback((string tag, string message) =>
|
||||||
|
|||||||
@@ -413,12 +413,16 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
|
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
|
||||||
WriteDebug = true,
|
WriteDebug = true,
|
||||||
});
|
});
|
||||||
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<string>()))
|
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||||
.Callback((DTWebApi.Issue issue, string logMessage) =>
|
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||||
{
|
{
|
||||||
_issues.Add(new Tuple<DTWebApi.Issue, string>(issue, logMessage));
|
var resolvedMessage = issue.Message;
|
||||||
var message = !string.IsNullOrEmpty(logMessage) ? logMessage : issue.Message;
|
if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
||||||
_trace.Info($"Issue '{issue.Type}': {message}");
|
{
|
||||||
|
resolvedMessage = logOptions.LogMessageOverride;
|
||||||
|
}
|
||||||
|
_issues.Add(new(issue, resolvedMessage));
|
||||||
|
_trace.Info($"Issue '{issue.Type}': {resolvedMessage}");
|
||||||
});
|
});
|
||||||
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Callback((string tag, string message) =>
|
.Callback((string tag, string message) =>
|
||||||
|
|||||||
@@ -411,12 +411,16 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
|
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
|
||||||
WriteDebug = true,
|
WriteDebug = true,
|
||||||
});
|
});
|
||||||
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<string>()))
|
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||||
.Callback((DTWebApi.Issue issue, string logMessage) =>
|
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||||
{
|
{
|
||||||
_issues.Add(new Tuple<DTWebApi.Issue, string>(issue, logMessage));
|
var resolvedMessage = issue.Message;
|
||||||
var message = !string.IsNullOrEmpty(logMessage) ? logMessage : issue.Message;
|
if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
||||||
_trace.Info($"Issue '{issue.Type}': {message}");
|
{
|
||||||
|
resolvedMessage = logOptions.LogMessageOverride;
|
||||||
|
}
|
||||||
|
_issues.Add(new(issue, resolvedMessage));
|
||||||
|
_trace.Info($"Issue '{issue.Type}': {resolvedMessage}");
|
||||||
});
|
});
|
||||||
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Callback((string tag, string message) =>
|
.Callback((string tag, string message) =>
|
||||||
|
|||||||
@@ -413,12 +413,16 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
|
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
|
||||||
WriteDebug = true,
|
WriteDebug = true,
|
||||||
});
|
});
|
||||||
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<string>()))
|
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<ExecutionContextLogOptions>()))
|
||||||
.Callback((DTWebApi.Issue issue, string logMessage) =>
|
.Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) =>
|
||||||
{
|
{
|
||||||
_issues.Add(new Tuple<DTWebApi.Issue, string>(issue, logMessage));
|
var resolvedMessage = issue.Message;
|
||||||
var message = !string.IsNullOrEmpty(logMessage) ? logMessage : issue.Message;
|
if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride))
|
||||||
_trace.Info($"Issue '{issue.Type}': {message}");
|
{
|
||||||
|
resolvedMessage = logOptions.LogMessageOverride;
|
||||||
|
}
|
||||||
|
_issues.Add(new(issue, resolvedMessage));
|
||||||
|
_trace.Info($"Issue '{issue.Type}': {resolvedMessage}");
|
||||||
});
|
});
|
||||||
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Callback((string tag, string message) =>
|
.Callback((string tag, string message) =>
|
||||||
@@ -430,8 +434,8 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
_executionContext.Setup(x => x.SetOutput(It.IsAny<string>(), It.IsAny<string>(), out reference))
|
_executionContext.Setup(x => x.SetOutput(It.IsAny<string>(), It.IsAny<string>(), out reference))
|
||||||
.Callback((string name, string value, out string reference) =>
|
.Callback((string name, string value, out string reference) =>
|
||||||
{
|
{
|
||||||
reference = value;
|
reference = value;
|
||||||
_outputs[name] = value;
|
_outputs[name] = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
// SetOutputFileCommand
|
// SetOutputFileCommand
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -337,7 +337,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
// Act.
|
// Act.
|
||||||
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
await _stepsRunner.RunAsync(jobContext: _ec.Object);
|
||||||
|
|
||||||
// Assert.
|
// Assert.
|
||||||
Assert.Equal(2, variableSet.Step.Length);
|
Assert.Equal(2, variableSet.Step.Length);
|
||||||
variableSet.Step[0].Verify(x => x.RunAsync());
|
variableSet.Step[0].Verify(x => x.RunAsync());
|
||||||
variableSet.Step[1].Verify(x => x.RunAsync(), variableSet.Expected ? Times.Once() : Times.Never());
|
variableSet.Step[1].Verify(x => x.RunAsync(), variableSet.Expected ? Times.Once() : Times.Never());
|
||||||
@@ -590,7 +590,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
step.Setup(x => x.Condition).Returns(condition);
|
step.Setup(x => x.Condition).Returns(condition);
|
||||||
step.Setup(x => x.ContinueOnError).Returns(new BooleanToken(null, null, null, continueOnError));
|
step.Setup(x => x.ContinueOnError).Returns(new BooleanToken(null, null, null, continueOnError));
|
||||||
step.Setup(x => x.Action)
|
step.Setup(x => x.Action)
|
||||||
.Returns(new DistributedTask.Pipelines.ActionStep()
|
.Returns(new GitHub.DistributedTask.Pipelines.ActionStep()
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
Id = Guid.NewGuid(),
|
Id = Guid.NewGuid(),
|
||||||
|
|||||||
70
src/Test/TestData/timelinerecord_json_samples.yml
Normal file
70
src/Test/TestData/timelinerecord_json_samples.yml
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
minimal: |
|
||||||
|
{ "Name": "minimal" }
|
||||||
|
invalid-attempt-value: |
|
||||||
|
{
|
||||||
|
"Name": "invalid-attempt-value",
|
||||||
|
"Attempt": -99
|
||||||
|
}
|
||||||
|
zero-attempt-value: |
|
||||||
|
{
|
||||||
|
"Name": "zero-attempt-value",
|
||||||
|
"Attempt": 0
|
||||||
|
}
|
||||||
|
legacy-nulls: |
|
||||||
|
{
|
||||||
|
"Name": "legacy-nulls",
|
||||||
|
"ErrorCount": null,
|
||||||
|
"WarningCount": null,
|
||||||
|
"NoticeCount": null
|
||||||
|
}
|
||||||
|
missing-counts: |
|
||||||
|
{
|
||||||
|
"Name": "missing-counts"
|
||||||
|
}
|
||||||
|
non-zero-counts: |
|
||||||
|
{
|
||||||
|
"Name": "non-zero-counts",
|
||||||
|
"ErrorCount": 10,
|
||||||
|
"WarningCount": 20,
|
||||||
|
"NoticeCount": 30
|
||||||
|
}
|
||||||
|
explicit-null-collections: |
|
||||||
|
{
|
||||||
|
"Name": "explicit-null-collections",
|
||||||
|
"Issues": null,
|
||||||
|
"PreviousAttempts": null,
|
||||||
|
"Variables": null
|
||||||
|
}
|
||||||
|
lean: |
|
||||||
|
{
|
||||||
|
"Id": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"Name": "lean",
|
||||||
|
"LastModified": "\/Date(1679073003252+0000)\/",
|
||||||
|
"Issues": [
|
||||||
|
{
|
||||||
|
"Type": 0,
|
||||||
|
"Category": null,
|
||||||
|
"Message": null,
|
||||||
|
"IsInfrastructureIssue": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Variables": [
|
||||||
|
{ "Key": "x", "Value": { "Value": "1" } },
|
||||||
|
{ "Key": "y", "Value": { "Value": "2" } },
|
||||||
|
{ "Key": "z", "Value": { "Value": "3" } }
|
||||||
|
],
|
||||||
|
"Attempt": 4,
|
||||||
|
"PreviousAttempts": [
|
||||||
|
{ "Attempt": 1 },
|
||||||
|
{ "Attempt": 2 },
|
||||||
|
{ "Attempt": 3 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
duplicate-variable-keys: |
|
||||||
|
{
|
||||||
|
"Name": "duplicate-variable-keys",
|
||||||
|
"Variables": [
|
||||||
|
{ "Key": "aaa", "Value": { "Value": "a.1" } },
|
||||||
|
{ "Key": "AAA", "Value": { "Value": "a.2" } }
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user