Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90da9fbb8c | ||
|
|
3ffedabea3 | ||
|
|
3a80a78cae | ||
|
|
6822f4aba2 | ||
|
|
ad43c639cf | ||
|
|
5d4fb30d5b | ||
|
|
1df72a54ca | ||
|
|
02013cf967 | ||
|
|
7d5c17a190 |
@@ -4,7 +4,7 @@
|
|||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
||||||
"ghcr.io/devcontainers/features/dotnet": {
|
"ghcr.io/devcontainers/features/dotnet": {
|
||||||
"version": "8.0.416"
|
"version": "8.0.417"
|
||||||
},
|
},
|
||||||
"ghcr.io/devcontainers/features/node:1": {
|
"ghcr.io/devcontainers/features/node:1": {
|
||||||
"version": "20"
|
"version": "20"
|
||||||
|
|||||||
+2
-2
@@ -5,8 +5,8 @@ ARG TARGETOS
|
|||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ARG RUNNER_VERSION
|
ARG RUNNER_VERSION
|
||||||
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.7.0
|
ARG RUNNER_CONTAINER_HOOKS_VERSION=0.7.0
|
||||||
ARG DOCKER_VERSION=29.0.2
|
ARG DOCKER_VERSION=29.2.0
|
||||||
ARG BUILDX_VERSION=0.30.1
|
ARG BUILDX_VERSION=0.31.1
|
||||||
|
|
||||||
RUN apt update -y && apt install curl unzip -y
|
RUN apt update -y && apt install curl unzip -y
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
2.331.0
|
<Update to ./src/runnerversion when creating release>
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ NODE_URL=https://nodejs.org/dist
|
|||||||
NODE_ALPINE_URL=https://github.com/actions/alpine_nodejs/releases/download
|
NODE_ALPINE_URL=https://github.com/actions/alpine_nodejs/releases/download
|
||||||
# When you update Node versions you must also create a new release of alpine_nodejs at that updated version.
|
# When you update Node versions you must also create a new release of alpine_nodejs at that updated version.
|
||||||
# Follow the instructions here: https://github.com/actions/alpine_nodejs?tab=readme-ov-file#getting-started
|
# Follow the instructions here: https://github.com/actions/alpine_nodejs?tab=readme-ov-file#getting-started
|
||||||
NODE20_VERSION="20.19.6"
|
NODE20_VERSION="20.20.0"
|
||||||
NODE24_VERSION="24.12.0"
|
NODE24_VERSION="24.13.0"
|
||||||
|
|
||||||
get_abs_path() {
|
get_abs_path() {
|
||||||
# exploits the fact that pwd will print abs path when no args
|
# exploits the fact that pwd will print abs path when no args
|
||||||
|
|||||||
@@ -172,7 +172,9 @@ namespace GitHub.Runner.Common
|
|||||||
public static readonly string SnapshotPreflightHostedRunnerCheck = "actions_snapshot_preflight_hosted_runner_check";
|
public static readonly string SnapshotPreflightHostedRunnerCheck = "actions_snapshot_preflight_hosted_runner_check";
|
||||||
public static readonly string SnapshotPreflightImageGenPoolCheck = "actions_snapshot_preflight_image_gen_pool_check";
|
public static readonly string SnapshotPreflightImageGenPoolCheck = "actions_snapshot_preflight_image_gen_pool_check";
|
||||||
public static readonly string CompareWorkflowParser = "actions_runner_compare_workflow_parser";
|
public static readonly string CompareWorkflowParser = "actions_runner_compare_workflow_parser";
|
||||||
|
public static readonly string CutoverWorkflowParser = "actions_runner_cutover_workflow_parser";
|
||||||
public static readonly string SetOrchestrationIdEnvForActions = "actions_set_orchestration_id_env_for_actions";
|
public static readonly string SetOrchestrationIdEnvForActions = "actions_set_orchestration_id_env_for_actions";
|
||||||
|
public static readonly string SendJobLevelAnnotations = "actions_send_job_level_annotations";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Node version migration related constants
|
// Node version migration related constants
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public ActionDefinitionData Load(IExecutionContext executionContext, string manifestFile)
|
public ActionDefinitionData Load(IExecutionContext executionContext, string manifestFile)
|
||||||
{
|
{
|
||||||
return EvaluateAndCompare(
|
return EvaluateWrapper(
|
||||||
executionContext,
|
executionContext,
|
||||||
"Load",
|
"Load",
|
||||||
() => _legacyManager.Load(executionContext, manifestFile),
|
() => _legacyManager.Load(executionContext, manifestFile),
|
||||||
@@ -53,7 +53,7 @@ namespace GitHub.Runner.Worker
|
|||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
IDictionary<string, PipelineContextData> extraExpressionValues)
|
IDictionary<string, PipelineContextData> extraExpressionValues)
|
||||||
{
|
{
|
||||||
return EvaluateAndCompare(
|
return EvaluateWrapper(
|
||||||
executionContext,
|
executionContext,
|
||||||
"EvaluateCompositeOutputs",
|
"EvaluateCompositeOutputs",
|
||||||
() => _legacyManager.EvaluateCompositeOutputs(executionContext, token, extraExpressionValues),
|
() => _legacyManager.EvaluateCompositeOutputs(executionContext, token, extraExpressionValues),
|
||||||
@@ -66,7 +66,7 @@ namespace GitHub.Runner.Worker
|
|||||||
SequenceToken token,
|
SequenceToken token,
|
||||||
IDictionary<string, PipelineContextData> extraExpressionValues)
|
IDictionary<string, PipelineContextData> extraExpressionValues)
|
||||||
{
|
{
|
||||||
return EvaluateAndCompare(
|
return EvaluateWrapper(
|
||||||
executionContext,
|
executionContext,
|
||||||
"EvaluateContainerArguments",
|
"EvaluateContainerArguments",
|
||||||
() => _legacyManager.EvaluateContainerArguments(executionContext, token, extraExpressionValues),
|
() => _legacyManager.EvaluateContainerArguments(executionContext, token, extraExpressionValues),
|
||||||
@@ -79,12 +79,13 @@ namespace GitHub.Runner.Worker
|
|||||||
MappingToken token,
|
MappingToken token,
|
||||||
IDictionary<string, PipelineContextData> extraExpressionValues)
|
IDictionary<string, PipelineContextData> extraExpressionValues)
|
||||||
{
|
{
|
||||||
return EvaluateAndCompare(
|
return EvaluateWrapper(
|
||||||
executionContext,
|
executionContext,
|
||||||
"EvaluateContainerEnvironment",
|
"EvaluateContainerEnvironment",
|
||||||
() => _legacyManager.EvaluateContainerEnvironment(executionContext, token, extraExpressionValues),
|
() => _legacyManager.EvaluateContainerEnvironment(executionContext, token, extraExpressionValues),
|
||||||
() => _newManager.EvaluateContainerEnvironment(executionContext, ConvertToNewToken(token) as GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens.MappingToken, ConvertToNewExpressionValues(extraExpressionValues)),
|
() => _newManager.EvaluateContainerEnvironment(executionContext, ConvertToNewToken(token) as GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens.MappingToken, ConvertToNewExpressionValues(extraExpressionValues)),
|
||||||
(legacyResult, newResult) => {
|
(legacyResult, newResult) =>
|
||||||
|
{
|
||||||
var trace = HostContext.GetTrace(nameof(ActionManifestManagerWrapper));
|
var trace = HostContext.GetTrace(nameof(ActionManifestManagerWrapper));
|
||||||
return CompareDictionaries(trace, legacyResult, newResult, "ContainerEnvironment");
|
return CompareDictionaries(trace, legacyResult, newResult, "ContainerEnvironment");
|
||||||
});
|
});
|
||||||
@@ -95,7 +96,7 @@ namespace GitHub.Runner.Worker
|
|||||||
string inputName,
|
string inputName,
|
||||||
TemplateToken token)
|
TemplateToken token)
|
||||||
{
|
{
|
||||||
return EvaluateAndCompare(
|
return EvaluateWrapper(
|
||||||
executionContext,
|
executionContext,
|
||||||
"EvaluateDefaultInput",
|
"EvaluateDefaultInput",
|
||||||
() => _legacyManager.EvaluateDefaultInput(executionContext, inputName, token),
|
() => _legacyManager.EvaluateDefaultInput(executionContext, inputName, token),
|
||||||
@@ -216,13 +217,27 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Comparison helper methods
|
// Comparison helper methods
|
||||||
private TLegacy EvaluateAndCompare<TLegacy, TNew>(
|
private TLegacy EvaluateWrapper<TLegacy, TNew>(
|
||||||
IExecutionContext context,
|
IExecutionContext context,
|
||||||
string methodName,
|
string methodName,
|
||||||
Func<TLegacy> legacyEvaluator,
|
Func<TLegacy> legacyEvaluator,
|
||||||
Func<TNew> newEvaluator,
|
Func<TNew> newEvaluator,
|
||||||
Func<TLegacy, TNew, bool> resultComparer)
|
Func<TLegacy, TNew, bool> resultComparer)
|
||||||
{
|
{
|
||||||
|
// Cutover: use only the new evaluator, convert result to legacy type
|
||||||
|
if ((context.Global.Variables.GetBoolean(Constants.Runner.Features.CutoverWorkflowParser) ?? false)
|
||||||
|
|| StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("ACTIONS_RUNNER_CUTOVER_WORKFLOW_PARSER")))
|
||||||
|
{
|
||||||
|
var newResult = newEvaluator();
|
||||||
|
if (typeof(TLegacy) == typeof(TNew))
|
||||||
|
{
|
||||||
|
return (TLegacy)(object)newResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = StringUtil.ConvertToJson(newResult, Newtonsoft.Json.Formatting.None);
|
||||||
|
return StringUtil.ConvertFromJson<TLegacy>(json);
|
||||||
|
}
|
||||||
|
|
||||||
// Legacy only?
|
// Legacy only?
|
||||||
if (!((context.Global.Variables.GetBoolean(Constants.Runner.Features.CompareWorkflowParser) ?? false)
|
if (!((context.Global.Variables.GetBoolean(Constants.Runner.Features.CompareWorkflowParser) ?? false)
|
||||||
|| StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("ACTIONS_RUNNER_COMPARE_WORKFLOW_PARSER"))))
|
|| StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("ACTIONS_RUNNER_COMPARE_WORKFLOW_PARSER"))))
|
||||||
|
|||||||
@@ -379,7 +379,14 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
prefix = PipelineTemplateConstants.RunDisplayPrefix;
|
prefix = PipelineTemplateConstants.RunDisplayPrefix;
|
||||||
var repositoryReference = action.Reference as RepositoryPathReference;
|
var repositoryReference = action.Reference as RepositoryPathReference;
|
||||||
var pathString = string.IsNullOrEmpty(repositoryReference.Path) ? string.Empty : $"/{repositoryReference.Path}";
|
var pathString = string.Empty;
|
||||||
|
if (!string.IsNullOrEmpty(repositoryReference.Path))
|
||||||
|
{
|
||||||
|
// For local actions (Name is empty), don't prepend "/" to avoid "/./"
|
||||||
|
pathString = string.IsNullOrEmpty(repositoryReference.Name)
|
||||||
|
? repositoryReference.Path
|
||||||
|
: $"/{repositoryReference.Path}";
|
||||||
|
}
|
||||||
var repoString = string.IsNullOrEmpty(repositoryReference.Ref) ? $"{repositoryReference.Name}{pathString}" :
|
var repoString = string.IsNullOrEmpty(repositoryReference.Ref) ? $"{repositoryReference.Name}{pathString}" :
|
||||||
$"{repositoryReference.Name}{pathString}@{repositoryReference.Ref}";
|
$"{repositoryReference.Name}{pathString}@{repositoryReference.Ref}";
|
||||||
tokenToParse = new StringToken(null, null, null, repoString);
|
tokenToParse = new StringToken(null, null, null, repoString);
|
||||||
|
|||||||
@@ -499,7 +499,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
PublishStepTelemetry();
|
PublishStepTelemetry();
|
||||||
|
|
||||||
if (_record.RecordType == "Task")
|
if (_record.RecordType == ExecutionContextType.Task)
|
||||||
{
|
{
|
||||||
var stepResult = new StepResult
|
var stepResult = new StepResult
|
||||||
{
|
{
|
||||||
@@ -532,6 +532,25 @@ namespace GitHub.Runner.Worker
|
|||||||
Global.StepsResult.Add(stepResult);
|
Global.StepsResult.Add(stepResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Global.Variables.GetBoolean(Constants.Runner.Features.SendJobLevelAnnotations) ?? false)
|
||||||
|
{
|
||||||
|
if (_record.RecordType == ExecutionContextType.Job)
|
||||||
|
{
|
||||||
|
_record.Issues?.ForEach(issue =>
|
||||||
|
{
|
||||||
|
var annotation = issue.ToAnnotation();
|
||||||
|
if (annotation != null)
|
||||||
|
{
|
||||||
|
Global.JobAnnotations.Add(annotation.Value);
|
||||||
|
if (annotation.Value.IsInfrastructureIssue && string.IsNullOrEmpty(Global.InfrastructureFailureCategory))
|
||||||
|
{
|
||||||
|
Global.InfrastructureFailureCategory = issue.Category;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (Root != this)
|
if (Root != this)
|
||||||
{
|
{
|
||||||
// only dispose TokenSource for step level ExecutionContext
|
// only dispose TokenSource for step level ExecutionContext
|
||||||
@@ -1397,7 +1416,8 @@ namespace GitHub.Runner.Worker
|
|||||||
public static IPipelineTemplateEvaluator ToPipelineTemplateEvaluator(this IExecutionContext context, ObjectTemplating.ITraceWriter traceWriter = null)
|
public static IPipelineTemplateEvaluator ToPipelineTemplateEvaluator(this IExecutionContext context, ObjectTemplating.ITraceWriter traceWriter = null)
|
||||||
{
|
{
|
||||||
// Create wrapper?
|
// Create wrapper?
|
||||||
if ((context.Global.Variables.GetBoolean(Constants.Runner.Features.CompareWorkflowParser) ?? false) || StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("ACTIONS_RUNNER_COMPARE_WORKFLOW_PARSER")))
|
if ((context.Global.Variables.GetBoolean(Constants.Runner.Features.CompareWorkflowParser) ?? false) || StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("ACTIONS_RUNNER_COMPARE_WORKFLOW_PARSER"))
|
||||||
|
|| (context.Global.Variables.GetBoolean(Constants.Runner.Features.CutoverWorkflowParser) ?? false) || StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("ACTIONS_RUNNER_CUTOVER_WORKFLOW_PARSER")))
|
||||||
{
|
{
|
||||||
return (context as ExecutionContext).ToPipelineTemplateEvaluatorInternal(traceWriter);
|
return (context as ExecutionContext).ToPipelineTemplateEvaluatorInternal(traceWriter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Test")]
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using GitHub.Actions.WorkflowParser;
|
using GitHub.Actions.WorkflowParser;
|
||||||
using GitHub.DistributedTask.Expressions2;
|
using GitHub.DistributedTask.Expressions2;
|
||||||
@@ -19,6 +19,7 @@ namespace GitHub.Runner.Worker
|
|||||||
private WorkflowTemplateEvaluator _newEvaluator;
|
private WorkflowTemplateEvaluator _newEvaluator;
|
||||||
private IExecutionContext _context;
|
private IExecutionContext _context;
|
||||||
private Tracing _trace;
|
private Tracing _trace;
|
||||||
|
private bool _cutover;
|
||||||
|
|
||||||
public PipelineTemplateEvaluatorWrapper(
|
public PipelineTemplateEvaluatorWrapper(
|
||||||
IHostContext hostContext,
|
IHostContext hostContext,
|
||||||
@@ -29,6 +30,8 @@ namespace GitHub.Runner.Worker
|
|||||||
ArgUtil.NotNull(context, nameof(context));
|
ArgUtil.NotNull(context, nameof(context));
|
||||||
_context = context;
|
_context = context;
|
||||||
_trace = hostContext.GetTrace(nameof(PipelineTemplateEvaluatorWrapper));
|
_trace = hostContext.GetTrace(nameof(PipelineTemplateEvaluatorWrapper));
|
||||||
|
_cutover = (context.Global.Variables.GetBoolean(Constants.Runner.Features.CutoverWorkflowParser) ?? false)
|
||||||
|
|| StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("ACTIONS_RUNNER_CUTOVER_WORKFLOW_PARSER"));
|
||||||
|
|
||||||
if (traceWriter == null)
|
if (traceWriter == null)
|
||||||
{
|
{
|
||||||
@@ -55,7 +58,7 @@ namespace GitHub.Runner.Worker
|
|||||||
DictionaryContextData contextData,
|
DictionaryContextData contextData,
|
||||||
IList<IFunctionInfo> expressionFunctions)
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
return EvaluateAndCompare(
|
return EvaluateWrapper(
|
||||||
"EvaluateStepContinueOnError",
|
"EvaluateStepContinueOnError",
|
||||||
() => _legacyEvaluator.EvaluateStepContinueOnError(token, contextData, expressionFunctions),
|
() => _legacyEvaluator.EvaluateStepContinueOnError(token, contextData, expressionFunctions),
|
||||||
() => _newEvaluator.EvaluateStepContinueOnError(ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions)),
|
() => _newEvaluator.EvaluateStepContinueOnError(ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions)),
|
||||||
@@ -67,7 +70,7 @@ namespace GitHub.Runner.Worker
|
|||||||
DictionaryContextData contextData,
|
DictionaryContextData contextData,
|
||||||
IList<IFunctionInfo> expressionFunctions)
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
return EvaluateAndCompare(
|
return EvaluateWrapper(
|
||||||
"EvaluateStepDisplayName",
|
"EvaluateStepDisplayName",
|
||||||
() => _legacyEvaluator.EvaluateStepDisplayName(token, contextData, expressionFunctions),
|
() => _legacyEvaluator.EvaluateStepDisplayName(token, contextData, expressionFunctions),
|
||||||
() => _newEvaluator.EvaluateStepName(ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions)),
|
() => _newEvaluator.EvaluateStepName(ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions)),
|
||||||
@@ -80,7 +83,7 @@ namespace GitHub.Runner.Worker
|
|||||||
IList<IFunctionInfo> expressionFunctions,
|
IList<IFunctionInfo> expressionFunctions,
|
||||||
StringComparer keyComparer)
|
StringComparer keyComparer)
|
||||||
{
|
{
|
||||||
return EvaluateAndCompare(
|
return EvaluateWrapper(
|
||||||
"EvaluateStepEnvironment",
|
"EvaluateStepEnvironment",
|
||||||
() => _legacyEvaluator.EvaluateStepEnvironment(token, contextData, expressionFunctions, keyComparer),
|
() => _legacyEvaluator.EvaluateStepEnvironment(token, contextData, expressionFunctions, keyComparer),
|
||||||
() => _newEvaluator.EvaluateStepEnvironment(ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions), keyComparer),
|
() => _newEvaluator.EvaluateStepEnvironment(ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions), keyComparer),
|
||||||
@@ -93,7 +96,7 @@ namespace GitHub.Runner.Worker
|
|||||||
IList<IFunctionInfo> expressionFunctions,
|
IList<IFunctionInfo> expressionFunctions,
|
||||||
IEnumerable<KeyValuePair<string, object>> expressionState)
|
IEnumerable<KeyValuePair<string, object>> expressionState)
|
||||||
{
|
{
|
||||||
return EvaluateAndCompare(
|
return EvaluateWrapper(
|
||||||
"EvaluateStepIf",
|
"EvaluateStepIf",
|
||||||
() => _legacyEvaluator.EvaluateStepIf(token, contextData, expressionFunctions, expressionState),
|
() => _legacyEvaluator.EvaluateStepIf(token, contextData, expressionFunctions, expressionState),
|
||||||
() => _newEvaluator.EvaluateStepIf(ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions), expressionState),
|
() => _newEvaluator.EvaluateStepIf(ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions), expressionState),
|
||||||
@@ -105,7 +108,7 @@ namespace GitHub.Runner.Worker
|
|||||||
DictionaryContextData contextData,
|
DictionaryContextData contextData,
|
||||||
IList<IFunctionInfo> expressionFunctions)
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
return EvaluateAndCompare(
|
return EvaluateWrapper(
|
||||||
"EvaluateStepInputs",
|
"EvaluateStepInputs",
|
||||||
() => _legacyEvaluator.EvaluateStepInputs(token, contextData, expressionFunctions),
|
() => _legacyEvaluator.EvaluateStepInputs(token, contextData, expressionFunctions),
|
||||||
() => _newEvaluator.EvaluateStepInputs(ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions)),
|
() => _newEvaluator.EvaluateStepInputs(ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions)),
|
||||||
@@ -117,7 +120,7 @@ namespace GitHub.Runner.Worker
|
|||||||
DictionaryContextData contextData,
|
DictionaryContextData contextData,
|
||||||
IList<IFunctionInfo> expressionFunctions)
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
return EvaluateAndCompare(
|
return EvaluateWrapper(
|
||||||
"EvaluateStepTimeout",
|
"EvaluateStepTimeout",
|
||||||
() => _legacyEvaluator.EvaluateStepTimeout(token, contextData, expressionFunctions),
|
() => _legacyEvaluator.EvaluateStepTimeout(token, contextData, expressionFunctions),
|
||||||
() => _newEvaluator.EvaluateStepTimeout(ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions)),
|
() => _newEvaluator.EvaluateStepTimeout(ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions)),
|
||||||
@@ -129,7 +132,7 @@ namespace GitHub.Runner.Worker
|
|||||||
DictionaryContextData contextData,
|
DictionaryContextData contextData,
|
||||||
IList<IFunctionInfo> expressionFunctions)
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
return EvaluateAndCompare(
|
return EvaluateWrapper(
|
||||||
"EvaluateJobContainer",
|
"EvaluateJobContainer",
|
||||||
() => _legacyEvaluator.EvaluateJobContainer(token, contextData, expressionFunctions),
|
() => _legacyEvaluator.EvaluateJobContainer(token, contextData, expressionFunctions),
|
||||||
() => _newEvaluator.EvaluateJobContainer(ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions)),
|
() => _newEvaluator.EvaluateJobContainer(ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions)),
|
||||||
@@ -141,7 +144,7 @@ namespace GitHub.Runner.Worker
|
|||||||
DictionaryContextData contextData,
|
DictionaryContextData contextData,
|
||||||
IList<IFunctionInfo> expressionFunctions)
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
return EvaluateAndCompare(
|
return EvaluateWrapper(
|
||||||
"EvaluateJobOutput",
|
"EvaluateJobOutput",
|
||||||
() => _legacyEvaluator.EvaluateJobOutput(token, contextData, expressionFunctions),
|
() => _legacyEvaluator.EvaluateJobOutput(token, contextData, expressionFunctions),
|
||||||
() => _newEvaluator.EvaluateJobOutputs(ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions)),
|
() => _newEvaluator.EvaluateJobOutputs(ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions)),
|
||||||
@@ -153,7 +156,7 @@ namespace GitHub.Runner.Worker
|
|||||||
DictionaryContextData contextData,
|
DictionaryContextData contextData,
|
||||||
IList<IFunctionInfo> expressionFunctions)
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
return EvaluateAndCompare(
|
return EvaluateWrapper(
|
||||||
"EvaluateEnvironmentUrl",
|
"EvaluateEnvironmentUrl",
|
||||||
() => _legacyEvaluator.EvaluateEnvironmentUrl(token, contextData, expressionFunctions),
|
() => _legacyEvaluator.EvaluateEnvironmentUrl(token, contextData, expressionFunctions),
|
||||||
() => _newEvaluator.EvaluateJobEnvironmentUrl(ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions)),
|
() => _newEvaluator.EvaluateJobEnvironmentUrl(ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions)),
|
||||||
@@ -165,7 +168,7 @@ namespace GitHub.Runner.Worker
|
|||||||
DictionaryContextData contextData,
|
DictionaryContextData contextData,
|
||||||
IList<IFunctionInfo> expressionFunctions)
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
return EvaluateAndCompare(
|
return EvaluateWrapper(
|
||||||
"EvaluateJobDefaultsRun",
|
"EvaluateJobDefaultsRun",
|
||||||
() => _legacyEvaluator.EvaluateJobDefaultsRun(token, contextData, expressionFunctions),
|
() => _legacyEvaluator.EvaluateJobDefaultsRun(token, contextData, expressionFunctions),
|
||||||
() => _newEvaluator.EvaluateJobDefaultsRun(ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions)),
|
() => _newEvaluator.EvaluateJobDefaultsRun(ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions)),
|
||||||
@@ -177,7 +180,7 @@ namespace GitHub.Runner.Worker
|
|||||||
DictionaryContextData contextData,
|
DictionaryContextData contextData,
|
||||||
IList<IFunctionInfo> expressionFunctions)
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
return EvaluateAndCompare(
|
return EvaluateWrapper(
|
||||||
"EvaluateJobServiceContainers",
|
"EvaluateJobServiceContainers",
|
||||||
() => _legacyEvaluator.EvaluateJobServiceContainers(token, contextData, expressionFunctions),
|
() => _legacyEvaluator.EvaluateJobServiceContainers(token, contextData, expressionFunctions),
|
||||||
() => _newEvaluator.EvaluateJobServiceContainers(ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions)),
|
() => _newEvaluator.EvaluateJobServiceContainers(ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions)),
|
||||||
@@ -189,7 +192,7 @@ namespace GitHub.Runner.Worker
|
|||||||
DictionaryContextData contextData,
|
DictionaryContextData contextData,
|
||||||
IList<IFunctionInfo> expressionFunctions)
|
IList<IFunctionInfo> expressionFunctions)
|
||||||
{
|
{
|
||||||
return EvaluateAndCompare(
|
return EvaluateWrapper(
|
||||||
"EvaluateJobSnapshotRequest",
|
"EvaluateJobSnapshotRequest",
|
||||||
() => _legacyEvaluator.EvaluateJobSnapshotRequest(token, contextData, expressionFunctions),
|
() => _legacyEvaluator.EvaluateJobSnapshotRequest(token, contextData, expressionFunctions),
|
||||||
() => _newEvaluator.EvaluateSnapshot(string.Empty, ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions)),
|
() => _newEvaluator.EvaluateSnapshot(string.Empty, ConvertToken(token), ConvertData(contextData), ConvertFunctions(expressionFunctions)),
|
||||||
@@ -216,12 +219,25 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TLegacy EvaluateAndCompare<TLegacy, TNew>(
|
private TLegacy EvaluateWrapper<TLegacy, TNew>(
|
||||||
string methodName,
|
string methodName,
|
||||||
Func<TLegacy> legacyEvaluator,
|
Func<TLegacy> legacyEvaluator,
|
||||||
Func<TNew> newEvaluator,
|
Func<TNew> newEvaluator,
|
||||||
Func<TLegacy, TNew, bool> resultComparer)
|
Func<TLegacy, TNew, bool> resultComparer)
|
||||||
{
|
{
|
||||||
|
// Cutover: use only the new evaluator, convert result to legacy type
|
||||||
|
if (_cutover)
|
||||||
|
{
|
||||||
|
var newResult = newEvaluator();
|
||||||
|
if (typeof(TLegacy) == typeof(TNew))
|
||||||
|
{
|
||||||
|
return (TLegacy)(object)newResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = StringUtil.ConvertToJson(newResult, Newtonsoft.Json.Formatting.None);
|
||||||
|
return StringUtil.ConvertFromJson<TLegacy>(json);
|
||||||
|
}
|
||||||
|
|
||||||
// Legacy evaluator
|
// Legacy evaluator
|
||||||
var legacyException = default(Exception);
|
var legacyException = default(Exception);
|
||||||
var legacyResult = default(TLegacy);
|
var legacyResult = default(TLegacy);
|
||||||
|
|||||||
@@ -421,7 +421,7 @@
|
|||||||
"mapping": {
|
"mapping": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"image": "string",
|
"image": "string",
|
||||||
"options": "non-empty-string",
|
"options": "string",
|
||||||
"env": "container-env",
|
"env": "container-env",
|
||||||
"ports": "sequence-of-non-empty-string",
|
"ports": "sequence-of-non-empty-string",
|
||||||
"volumes": "sequence-of-non-empty-string",
|
"volumes": "sequence-of-non-empty-string",
|
||||||
|
|||||||
+2
-2
@@ -23,14 +23,14 @@
|
|||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
|
||||||
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
|
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
|
||||||
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="8.0.0" />
|
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="10.0.2" />
|
||||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
|
||||||
<PackageReference Include="Minimatch" Version="2.0.0" />
|
<PackageReference Include="Minimatch" Version="2.0.0" />
|
||||||
<PackageReference Include="YamlDotNet.Signed" Version="5.3.0" />
|
<PackageReference Include="YamlDotNet.Signed" Version="5.3.0" />
|
||||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||||
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
|
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
|
||||||
<PackageReference Include="System.Formats.Asn1" Version="8.0.1" />
|
<PackageReference Include="System.Formats.Asn1" Version="10.0.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -2593,7 +2593,7 @@
|
|||||||
"description": "Use `jobs.<job_id>.container.image` to define the Docker image to use as the container to run the action. The value can be the Docker Hub image or a registry name."
|
"description": "Use `jobs.<job_id>.container.image` to define the Docker image to use as the container to run the action. The value can be the Docker Hub image or a registry name."
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"type": "non-empty-string",
|
"type": "string",
|
||||||
"description": "Use `jobs.<job_id>.container.options` to configure additional Docker container resource options."
|
"description": "Use `jobs.<job_id>.container.options` to configure additional Docker container resource options."
|
||||||
},
|
},
|
||||||
"env": "container-env",
|
"env": "container-env",
|
||||||
|
|||||||
@@ -0,0 +1,456 @@
|
|||||||
|
using GitHub.Actions.WorkflowParser;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
|
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
using GitHub.Runner.Worker;
|
||||||
|
using LegacyContextData = GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
|
using LegacyExpressions = GitHub.DistributedTask.Expressions2;
|
||||||
|
using Moq;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Common.Tests.Worker
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tests for parser comparison wrapper classes.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ActionManifestParserComparisonL0
|
||||||
|
{
|
||||||
|
private CancellationTokenSource _ecTokenSource;
|
||||||
|
private Mock<IExecutionContext> _ec;
|
||||||
|
private TestHostContext _hc;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void ConvertToLegacySteps_ProducesCorrectSteps_WithExplicitPropertyMapping()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange - Test that ActionManifestManagerWrapper properly converts new steps to legacy format
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
// Enable comparison feature
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
// Register required services
|
||||||
|
var legacyManager = new ActionManifestManagerLegacy();
|
||||||
|
legacyManager.Initialize(_hc);
|
||||||
|
_hc.SetSingleton<IActionManifestManagerLegacy>(legacyManager);
|
||||||
|
|
||||||
|
var newManager = new ActionManifestManager();
|
||||||
|
newManager.Initialize(_hc);
|
||||||
|
_hc.SetSingleton<IActionManifestManager>(newManager);
|
||||||
|
|
||||||
|
var wrapper = new ActionManifestManagerWrapper();
|
||||||
|
wrapper.Initialize(_hc);
|
||||||
|
|
||||||
|
var manifestPath = Path.Combine(TestUtil.GetTestDataPath(), "conditional_composite_action.yml");
|
||||||
|
|
||||||
|
// Act - Load through the wrapper (which internally converts)
|
||||||
|
var result = wrapper.Load(_ec.Object, manifestPath);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal(ActionExecutionType.Composite, result.Execution.ExecutionType);
|
||||||
|
|
||||||
|
var compositeExecution = result.Execution as CompositeActionExecutionData;
|
||||||
|
Assert.NotNull(compositeExecution);
|
||||||
|
Assert.NotNull(compositeExecution.Steps);
|
||||||
|
Assert.Equal(6, compositeExecution.Steps.Count);
|
||||||
|
|
||||||
|
// Verify steps are NOT null (this was the bug - JSON round-trip produced nulls)
|
||||||
|
foreach (var step in compositeExecution.Steps)
|
||||||
|
{
|
||||||
|
Assert.NotNull(step);
|
||||||
|
Assert.NotNull(step.Reference);
|
||||||
|
Assert.IsType<GitHub.DistributedTask.Pipelines.ScriptReference>(step.Reference);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify step with condition
|
||||||
|
var successStep = compositeExecution.Steps[2];
|
||||||
|
Assert.Equal("success-conditional", successStep.ContextName);
|
||||||
|
Assert.Equal("success()", successStep.Condition);
|
||||||
|
|
||||||
|
// Verify step with complex condition
|
||||||
|
var lastStep = compositeExecution.Steps[5];
|
||||||
|
Assert.Contains("inputs.exit-code == 1", lastStep.Condition);
|
||||||
|
Assert.Contains("failure()", lastStep.Condition);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateJobContainer_EmptyImage_BothParsersReturnNull()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange - Test that both parsers return null for empty container image at runtime
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
var fileTable = new List<string>();
|
||||||
|
|
||||||
|
// Create legacy evaluator
|
||||||
|
var legacyTraceWriter = new GitHub.DistributedTask.ObjectTemplating.EmptyTraceWriter();
|
||||||
|
var schema = PipelineTemplateSchemaFactory.GetSchema();
|
||||||
|
var legacyEvaluator = new PipelineTemplateEvaluator(legacyTraceWriter, schema, fileTable);
|
||||||
|
|
||||||
|
// Create new evaluator
|
||||||
|
var newTraceWriter = new GitHub.Actions.WorkflowParser.ObjectTemplating.EmptyTraceWriter();
|
||||||
|
var newEvaluator = new WorkflowTemplateEvaluator(newTraceWriter, fileTable, features: null);
|
||||||
|
|
||||||
|
// Create a token representing an empty container image (simulates expression evaluated to empty string)
|
||||||
|
var emptyImageToken = new StringToken(null, null, null, "");
|
||||||
|
|
||||||
|
var contextData = new DictionaryContextData();
|
||||||
|
var expressionFunctions = new List<LegacyExpressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
// Act - Call both evaluators
|
||||||
|
var legacyResult = legacyEvaluator.EvaluateJobContainer(emptyImageToken, contextData, expressionFunctions);
|
||||||
|
|
||||||
|
// Convert token for new evaluator
|
||||||
|
var newToken = new GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens.StringToken(null, null, null, "");
|
||||||
|
var newContextData = new GitHub.Actions.Expressions.Data.DictionaryExpressionData();
|
||||||
|
var newExpressionFunctions = new List<GitHub.Actions.Expressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
var newResult = newEvaluator.EvaluateJobContainer(newToken, newContextData, newExpressionFunctions);
|
||||||
|
|
||||||
|
// Assert - Both should return null for empty image (no container)
|
||||||
|
Assert.Null(legacyResult);
|
||||||
|
Assert.Null(newResult);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void FromJsonEmptyString_BothParsersFail_WithDifferentMessages()
|
||||||
|
{
|
||||||
|
// This test verifies that both parsers fail with different error messages when parsing fromJSON('')
|
||||||
|
// The comparison layer should treat these as semantically equivalent (both are JSON parse errors)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
var fileTable = new List<string>();
|
||||||
|
|
||||||
|
// Create legacy evaluator
|
||||||
|
var legacyTraceWriter = new GitHub.DistributedTask.ObjectTemplating.EmptyTraceWriter();
|
||||||
|
var schema = PipelineTemplateSchemaFactory.GetSchema();
|
||||||
|
var legacyEvaluator = new PipelineTemplateEvaluator(legacyTraceWriter, schema, fileTable);
|
||||||
|
|
||||||
|
// Create new evaluator
|
||||||
|
var newTraceWriter = new GitHub.Actions.WorkflowParser.ObjectTemplating.EmptyTraceWriter();
|
||||||
|
var newEvaluator = new WorkflowTemplateEvaluator(newTraceWriter, fileTable, features: null);
|
||||||
|
|
||||||
|
// Create expression token for fromJSON('')
|
||||||
|
var legacyToken = new BasicExpressionToken(null, null, null, "fromJson('')");
|
||||||
|
var newToken = new GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens.BasicExpressionToken(null, null, null, "fromJson('')");
|
||||||
|
|
||||||
|
var contextData = new DictionaryContextData();
|
||||||
|
var newContextData = new GitHub.Actions.Expressions.Data.DictionaryExpressionData();
|
||||||
|
var expressionFunctions = new List<LegacyExpressions.IFunctionInfo>();
|
||||||
|
var newExpressionFunctions = new List<GitHub.Actions.Expressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
// Act - Both should throw
|
||||||
|
Exception legacyException = null;
|
||||||
|
Exception newException = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
legacyEvaluator.EvaluateStepDisplayName(legacyToken, contextData, expressionFunctions);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
legacyException = ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
newEvaluator.EvaluateStepName(newToken, newContextData, newExpressionFunctions);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
newException = ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert - Both threw exceptions
|
||||||
|
Assert.NotNull(legacyException);
|
||||||
|
Assert.NotNull(newException);
|
||||||
|
|
||||||
|
// Verify the error messages are different (which is why we need semantic comparison)
|
||||||
|
Assert.NotEqual(legacyException.Message, newException.Message);
|
||||||
|
|
||||||
|
// Verify both are JSON parse errors (contain JSON-related error indicators)
|
||||||
|
var legacyFullMsg = GetFullExceptionMessage(legacyException);
|
||||||
|
var newFullMsg = GetFullExceptionMessage(newException);
|
||||||
|
|
||||||
|
// At least one should contain indicators of JSON parsing failure
|
||||||
|
var legacyIsJsonError = legacyFullMsg.Contains("JToken") ||
|
||||||
|
legacyFullMsg.Contains("JsonReader") ||
|
||||||
|
legacyFullMsg.Contains("fromJson");
|
||||||
|
var newIsJsonError = newFullMsg.Contains("JToken") ||
|
||||||
|
newFullMsg.Contains("JsonReader") ||
|
||||||
|
newFullMsg.Contains("fromJson");
|
||||||
|
|
||||||
|
Assert.True(legacyIsJsonError, $"Legacy exception should be JSON error: {legacyFullMsg}");
|
||||||
|
Assert.True(newIsJsonError, $"New exception should be JSON error: {newFullMsg}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateWrapper_SkipsMismatchRecording_WhenCancellationOccursDuringEvaluation()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange - Test that mismatches are not recorded when cancellation state changes during evaluation
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
// Enable comparison feature
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
// Create the wrapper
|
||||||
|
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||||
|
|
||||||
|
// Create a simple token for evaluation
|
||||||
|
var token = new StringToken(null, null, null, "test-value");
|
||||||
|
var contextData = new DictionaryContextData();
|
||||||
|
var expressionFunctions = new List<LegacyExpressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
// First evaluation without cancellation - should work normally
|
||||||
|
var result1 = wrapper.EvaluateStepDisplayName(token, contextData, expressionFunctions);
|
||||||
|
Assert.Equal("test-value", result1);
|
||||||
|
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||||
|
|
||||||
|
// Now simulate a scenario where cancellation occurs during evaluation
|
||||||
|
// Cancel the token before next evaluation
|
||||||
|
_ecTokenSource.Cancel();
|
||||||
|
|
||||||
|
// Evaluate again - even if there were a mismatch, it should be skipped due to cancellation
|
||||||
|
var result2 = wrapper.EvaluateStepDisplayName(token, contextData, expressionFunctions);
|
||||||
|
Assert.Equal("test-value", result2);
|
||||||
|
|
||||||
|
// Verify no mismatch was recorded (cancellation race detection should have prevented it)
|
||||||
|
// Note: In this test, both parsers return the same result, so there's no actual mismatch.
|
||||||
|
// The cancellation race detection is a safeguard for when results differ due to timing.
|
||||||
|
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateWrapper_DoesNotRecordMismatch_WhenResultsMatch()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange - Test that no mismatch is recorded when both parsers return matching results
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
// Enable comparison feature
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
|
||||||
|
// Create the wrapper
|
||||||
|
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||||
|
|
||||||
|
// Create a simple token for evaluation
|
||||||
|
var token = new StringToken(null, null, null, "test-value");
|
||||||
|
var contextData = new DictionaryContextData();
|
||||||
|
var expressionFunctions = new List<LegacyExpressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
// Evaluation without cancellation - should work normally and not record mismatch for matching results
|
||||||
|
var result = wrapper.EvaluateStepDisplayName(token, contextData, expressionFunctions);
|
||||||
|
Assert.Equal("test-value", result);
|
||||||
|
|
||||||
|
// Since both parsers return the same result, no mismatch should be recorded
|
||||||
|
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void CutoverFlag_UsesNewEvaluator_ForPipelineTemplateEvaluator()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange - Test that cutover flag causes the wrapper to use only the new evaluator
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
// Enable cutover feature (not comparison)
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CutoverWorkflowParser, "true");
|
||||||
|
|
||||||
|
// Create the wrapper
|
||||||
|
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||||
|
|
||||||
|
// Create a simple token for evaluation
|
||||||
|
var token = new StringToken(null, null, null, "test-value");
|
||||||
|
var contextData = new DictionaryContextData();
|
||||||
|
var expressionFunctions = new List<LegacyExpressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
// Act - Evaluate in cutover mode
|
||||||
|
var result = wrapper.EvaluateStepDisplayName(token, contextData, expressionFunctions);
|
||||||
|
|
||||||
|
// Assert - Should get the correct result from the new evaluator
|
||||||
|
Assert.Equal("test-value", result);
|
||||||
|
|
||||||
|
// No mismatch should be recorded (comparison is skipped entirely in cutover mode)
|
||||||
|
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void CutoverFlag_UsesNewManager_ForActionManifestLoad()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange - Test that cutover flag causes the manifest wrapper to use only the new manager
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
// Enable cutover feature (not comparison)
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CutoverWorkflowParser, "true");
|
||||||
|
|
||||||
|
// Register required services
|
||||||
|
var legacyManager = new ActionManifestManagerLegacy();
|
||||||
|
legacyManager.Initialize(_hc);
|
||||||
|
_hc.SetSingleton<IActionManifestManagerLegacy>(legacyManager);
|
||||||
|
|
||||||
|
var newManager = new ActionManifestManager();
|
||||||
|
newManager.Initialize(_hc);
|
||||||
|
_hc.SetSingleton<IActionManifestManager>(newManager);
|
||||||
|
|
||||||
|
var wrapper = new ActionManifestManagerWrapper();
|
||||||
|
wrapper.Initialize(_hc);
|
||||||
|
|
||||||
|
var manifestPath = Path.Combine(TestUtil.GetTestDataPath(), "conditional_composite_action.yml");
|
||||||
|
|
||||||
|
// Act - Load through the wrapper in cutover mode
|
||||||
|
var result = wrapper.Load(_ec.Object, manifestPath);
|
||||||
|
|
||||||
|
// Assert - Should get the correct result from the new manager
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.Equal(ActionExecutionType.Composite, result.Execution.ExecutionType);
|
||||||
|
|
||||||
|
// No mismatch should be recorded (comparison is skipped in cutover mode)
|
||||||
|
Assert.False(_ec.Object.Global.HasActionManifestMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void CutoverFlag_TakesPrecedence_OverCompareFlag()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Arrange - Test that cutover flag takes precedence over compare flag
|
||||||
|
Setup();
|
||||||
|
|
||||||
|
// Enable both flags
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||||
|
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CutoverWorkflowParser, "true");
|
||||||
|
|
||||||
|
// Create the wrapper
|
||||||
|
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||||
|
|
||||||
|
var token = new StringToken(null, null, null, "test-value");
|
||||||
|
var contextData = new DictionaryContextData();
|
||||||
|
var expressionFunctions = new List<LegacyExpressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
// Act - Evaluate (cutover should take precedence, skipping comparison entirely)
|
||||||
|
var result = wrapper.EvaluateStepDisplayName(token, contextData, expressionFunctions);
|
||||||
|
|
||||||
|
// Assert - Should get correct result, no comparison mismatch recorded
|
||||||
|
Assert.Equal("test-value", result);
|
||||||
|
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Teardown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetFullExceptionMessage(Exception ex)
|
||||||
|
{
|
||||||
|
var messages = new List<string>();
|
||||||
|
var current = ex;
|
||||||
|
while (current != null)
|
||||||
|
{
|
||||||
|
messages.Add(current.Message);
|
||||||
|
current = current.InnerException;
|
||||||
|
}
|
||||||
|
return string.Join(" -> ", messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Setup([CallerMemberName] string name = "")
|
||||||
|
{
|
||||||
|
_ecTokenSource?.Dispose();
|
||||||
|
_ecTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
_hc = new TestHostContext(this, name);
|
||||||
|
|
||||||
|
var expressionValues = new LegacyContextData.DictionaryContextData();
|
||||||
|
var expressionFunctions = new List<LegacyExpressions.IFunctionInfo>();
|
||||||
|
|
||||||
|
_ec = new Mock<IExecutionContext>();
|
||||||
|
_ec.Setup(x => x.Global)
|
||||||
|
.Returns(new GlobalContext
|
||||||
|
{
|
||||||
|
FileTable = new List<String>(),
|
||||||
|
Variables = new Variables(_hc, new Dictionary<string, VariableValue>()),
|
||||||
|
WriteDebug = true,
|
||||||
|
});
|
||||||
|
_ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token);
|
||||||
|
_ec.Setup(x => x.ExpressionValues).Returns(expressionValues);
|
||||||
|
_ec.Setup(x => x.ExpressionFunctions).Returns(expressionFunctions);
|
||||||
|
_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<ExecutionContextLogOptions>())).Callback((Issue issue, ExecutionContextLogOptions logOptions) => { _hc.GetTrace().Info($"[{issue.Type}]{logOptions.LogMessageOverride ?? issue.Message}"); });
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Teardown()
|
||||||
|
{
|
||||||
|
_hc?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -316,6 +316,94 @@ namespace GitHub.Runner.Common.Tests.Worker
|
|||||||
Assert.Equal("${{ matrix.node }}", _actionRunner.DisplayName);
|
Assert.Equal("${{ matrix.node }}", _actionRunner.DisplayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateDisplayNameForLocalAction()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Setup();
|
||||||
|
var actionId = Guid.NewGuid();
|
||||||
|
var action = new Pipelines.ActionStep()
|
||||||
|
{
|
||||||
|
Name = "action",
|
||||||
|
Id = actionId,
|
||||||
|
Reference = new Pipelines.RepositoryPathReference()
|
||||||
|
{
|
||||||
|
RepositoryType = Pipelines.PipelineConstants.SelfAlias,
|
||||||
|
Path = "./"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_actionRunner.Action = action;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var validDisplayName = _actionRunner.EvaluateDisplayName(_context, _actionRunner.ExecutionContext, out bool updated);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(validDisplayName);
|
||||||
|
Assert.True(updated);
|
||||||
|
Assert.Equal("Run ./", _actionRunner.DisplayName); // NOT "Run /./"
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateDisplayNameForLocalActionWithPath()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Setup();
|
||||||
|
var actionId = Guid.NewGuid();
|
||||||
|
var action = new Pipelines.ActionStep()
|
||||||
|
{
|
||||||
|
Name = "action",
|
||||||
|
Id = actionId,
|
||||||
|
Reference = new Pipelines.RepositoryPathReference()
|
||||||
|
{
|
||||||
|
RepositoryType = Pipelines.PipelineConstants.SelfAlias,
|
||||||
|
Path = "./.github/actions/my-action"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_actionRunner.Action = action;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var validDisplayName = _actionRunner.EvaluateDisplayName(_context, _actionRunner.ExecutionContext, out bool updated);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(validDisplayName);
|
||||||
|
Assert.True(updated);
|
||||||
|
Assert.Equal("Run ./.github/actions/my-action", _actionRunner.DisplayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Level", "L0")]
|
||||||
|
[Trait("Category", "Worker")]
|
||||||
|
public void EvaluateDisplayNameForRemoteActionWithPath()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
Setup();
|
||||||
|
var actionId = Guid.NewGuid();
|
||||||
|
var action = new Pipelines.ActionStep()
|
||||||
|
{
|
||||||
|
Name = "action",
|
||||||
|
Id = actionId,
|
||||||
|
Reference = new Pipelines.RepositoryPathReference()
|
||||||
|
{
|
||||||
|
Name = "owner/repo",
|
||||||
|
Path = "subdir",
|
||||||
|
Ref = "v1"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_actionRunner.Action = action;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var validDisplayName = _actionRunner.EvaluateDisplayName(_context, _actionRunner.ExecutionContext, out bool updated);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(validDisplayName);
|
||||||
|
Assert.True(updated);
|
||||||
|
Assert.Equal("Run owner/repo/subdir@v1", _actionRunner.DisplayName);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Level", "L0")]
|
[Trait("Level", "L0")]
|
||||||
[Trait("Category", "Worker")]
|
[Trait("Category", "Worker")]
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@ LAYOUT_DIR="$SCRIPT_DIR/../_layout"
|
|||||||
DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x"
|
||||||
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
PACKAGE_DIR="$SCRIPT_DIR/../_package"
|
||||||
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk"
|
||||||
DOTNETSDK_VERSION="8.0.416"
|
DOTNETSDK_VERSION="8.0.417"
|
||||||
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION"
|
||||||
RUNNER_VERSION=$(cat runnerversion)
|
RUNNER_VERSION=$(cat runnerversion)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "8.0.416"
|
"version": "8.0.417"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user