diff --git a/src/Runner.Worker/ActionManifestManager.cs b/src/Runner.Worker/ActionManifestManager.cs index 980b87e17..9b94faaf8 100644 --- a/src/Runner.Worker/ActionManifestManager.cs +++ b/src/Runner.Worker/ActionManifestManager.cs @@ -22,11 +22,11 @@ namespace GitHub.Runner.Worker { ActionDefinitionData Load(IExecutionContext executionContext, string manifestFile); - List EvaluateContainerArguments(IExecutionContext executionContext, SequenceToken token, IDictionary contextData); + List EvaluateContainerArguments(IExecutionContext executionContext, SequenceToken token, IDictionary extraExpressionValues); - Dictionary EvaluateContainerEnvironment(IExecutionContext executionContext, MappingToken token, IDictionary contextData); + Dictionary EvaluateContainerEnvironment(IExecutionContext executionContext, MappingToken token, IDictionary extraExpressionValues); - string EvaluateDefaultInput(IExecutionContext executionContext, string inputName, TemplateToken token, IDictionary contextData); + string EvaluateDefaultInput(IExecutionContext executionContext, string inputName, TemplateToken token); } public sealed class ActionManifestManager : RunnerService, IActionManifestManager @@ -54,7 +54,7 @@ namespace GitHub.Runner.Worker public ActionDefinitionData Load(IExecutionContext executionContext, string manifestFile) { - var context = CreateContext(executionContext, null); + var context = CreateContext(executionContext); ActionDefinitionData actionDefinition = new ActionDefinitionData(); try { @@ -133,13 +133,13 @@ namespace GitHub.Runner.Worker public List EvaluateContainerArguments( IExecutionContext executionContext, SequenceToken token, - IDictionary contextData) + IDictionary extraExpressionValues) { var result = new List(); if (token != null) { - var context = CreateContext(executionContext, contextData); + var context = CreateContext(executionContext, extraExpressionValues); try { var evaluateResult = TemplateEvaluator.Evaluate(context, "container-runs-args", token, 0, null, omitHeader: true); @@ -172,13 +172,13 @@ namespace GitHub.Runner.Worker public Dictionary EvaluateContainerEnvironment( IExecutionContext executionContext, MappingToken token, - IDictionary contextData) + IDictionary extraExpressionValues) { var result = new Dictionary(StringComparer.OrdinalIgnoreCase); if (token != null) { - var context = CreateContext(executionContext, contextData); + var context = CreateContext(executionContext, extraExpressionValues); try { var evaluateResult = TemplateEvaluator.Evaluate(context, "container-runs-env", token, 0, null, omitHeader: true); @@ -216,13 +216,12 @@ namespace GitHub.Runner.Worker public string EvaluateDefaultInput( IExecutionContext executionContext, string inputName, - TemplateToken token, - IDictionary contextData) + TemplateToken token) { string result = ""; if (token != null) { - var context = CreateContext(executionContext, contextData); + var context = CreateContext(executionContext); try { var evaluateResult = TemplateEvaluator.Evaluate(context, "input-default-context", token, 0, null, omitHeader: true); @@ -247,7 +246,7 @@ namespace GitHub.Runner.Worker private TemplateContext CreateContext( IExecutionContext executionContext, - IDictionary contextData) + IDictionary extraExpressionValues = null) { var result = new TemplateContext { @@ -261,14 +260,27 @@ namespace GitHub.Runner.Worker TraceWriter = executionContext.ToTemplateTraceWriter(), }; - if (contextData?.Count > 0) + // Expression values from execution context + foreach (var pair in executionContext.ExpressionValues) { - foreach (var pair in contextData) + result.ExpressionValues[pair.Key] = pair.Value; + } + + // Extra expression values + if (extraExpressionValues?.Count > 0) + { + foreach (var pair in extraExpressionValues) { result.ExpressionValues[pair.Key] = pair.Value; } } + // Expression functions from execution context + foreach (var item in executionContext.ExpressionFunctions) + { + result.ExpressionFunctions.Add(item); + } + // Add the file table if (_fileTable?.Count > 0) { diff --git a/src/Runner.Worker/ActionRunner.cs b/src/Runner.Worker/ActionRunner.cs index 3b7e83e95..7272303bc 100644 --- a/src/Runner.Worker/ActionRunner.cs +++ b/src/Runner.Worker/ActionRunner.cs @@ -26,7 +26,7 @@ namespace GitHub.Runner.Worker public interface IActionRunner : IStep, IRunnerService { ActionRunStage Stage { get; set; } - Boolean TryEvaluateDisplayName(DictionaryContextData contextData, IExecutionContext context); + bool TryEvaluateDisplayName(DictionaryContextData contextData, IExecutionContext context); Pipelines.ActionStep Action { get; set; } } @@ -142,7 +142,7 @@ namespace GitHub.Runner.Worker // Load the inputs. ExecutionContext.Debug("Loading inputs"); var templateEvaluator = ExecutionContext.ToPipelineTemplateEvaluator(); - var inputs = templateEvaluator.EvaluateStepInputs(Action.Inputs, ExecutionContext.ExpressionValues); + var inputs = templateEvaluator.EvaluateStepInputs(Action.Inputs, ExecutionContext.ExpressionValues, ExecutionContext.ExpressionFunctions); foreach (KeyValuePair input in inputs) { @@ -162,13 +162,7 @@ namespace GitHub.Runner.Worker string key = input.Key.AssertString("action input name").Value; if (!inputs.ContainsKey(key)) { - var evaluateContext = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var data in ExecutionContext.ExpressionValues) - { - evaluateContext[data.Key] = data.Value; - } - - inputs[key] = manifestManager.EvaluateDefaultInput(ExecutionContext, key, input.Value, evaluateContext); + inputs[key] = manifestManager.EvaluateDefaultInput(ExecutionContext, key, input.Value); } } } @@ -293,10 +287,14 @@ namespace GitHub.Runner.Worker return displayName; } // Try evaluating fully - var templateEvaluator = context.ToPipelineTemplateEvaluator(); try { - didFullyEvaluate = templateEvaluator.TryEvaluateStepDisplayName(tokenToParse, contextData, out displayName); + if (tokenToParse.CheckHasRequiredContext(contextData, context.ExpressionFunctions)) + { + var templateEvaluator = context.ToPipelineTemplateEvaluator(); + displayName = templateEvaluator.EvaluateStepDisplayName(tokenToParse, contextData, context.ExpressionFunctions); + didFullyEvaluate = true; + } } catch (TemplateValidationException e) { diff --git a/src/Runner.Worker/ExecutionContext.cs b/src/Runner.Worker/ExecutionContext.cs index 6464e0807..25cbeefb1 100644 --- a/src/Runner.Worker/ExecutionContext.cs +++ b/src/Runner.Worker/ExecutionContext.cs @@ -1,14 +1,15 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Globalization; using System.IO; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web; -using GitHub.Runner.Worker.Container; -using GitHub.Services.WebApi; +using GitHub.DistributedTask.Expressions2; using GitHub.DistributedTask.Pipelines; using GitHub.DistributedTask.Pipelines.ContextData; using GitHub.DistributedTask.Pipelines.ObjectTemplating; @@ -16,12 +17,11 @@ using GitHub.DistributedTask.WebApi; using GitHub.Runner.Common.Util; using GitHub.Runner.Common; using GitHub.Runner.Sdk; +using GitHub.Runner.Worker.Container; +using GitHub.Services.WebApi; using Newtonsoft.Json; -using System.Text; -using System.Collections; using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating; using Pipelines = GitHub.DistributedTask.Pipelines; -using GitHub.DistributedTask.Expressions2; namespace GitHub.Runner.Worker { @@ -55,6 +55,7 @@ namespace GitHub.Runner.Worker IList FileTable { get; } StepsContext StepsContext { get; } DictionaryContextData ExpressionValues { get; } + IList ExpressionFunctions { get; } List PrependPath { get; } ContainerInfo Container { get; set; } List ServiceContainers { get; } @@ -148,6 +149,7 @@ namespace GitHub.Runner.Worker public IList FileTable { get; private set; } public StepsContext StepsContext { get; private set; } public DictionaryContextData ExpressionValues { get; } = new DictionaryContextData(); + public IList ExpressionFunctions { get; } = new List(); public bool WriteDebug { get; private set; } public List PrependPath { get; private set; } public ContainerInfo Container { get; set; } @@ -280,6 +282,10 @@ namespace GitHub.Runner.Worker { child.ExpressionValues[pair.Key] = pair.Value; } + foreach (var item in ExpressionFunctions) + { + child.ExpressionFunctions.Add(item); + } child._cancellationTokenSource = new CancellationTokenSource(); child.WriteDebug = WriteDebug; child._parentExecutionContext = this; @@ -593,12 +599,6 @@ namespace GitHub.Runner.Worker // File table FileTable = new List(message.FileTable ?? new string[0]); - // Expression functions - if (Variables.GetBoolean("System.HashFilesV2") == true) - { - ExpressionConstants.UpdateFunction("hashFiles", 1, byte.MaxValue); - } - // Expression values if (message.ContextData?.Count > 0) { @@ -915,11 +915,19 @@ namespace GitHub.Runner.Worker } } - public static PipelineTemplateEvaluator ToPipelineTemplateEvaluator(this IExecutionContext context) + public static IEnumerable> ToExpressionState(this IExecutionContext context) { - var templateTrace = context.ToTemplateTraceWriter(); - var schema = new PipelineTemplateSchemaFactory().CreateSchema(); - return new PipelineTemplateEvaluator(templateTrace, schema, context.FileTable); + return new[] { new KeyValuePair(nameof(IExecutionContext), context) }; + } + + public static PipelineTemplateEvaluator ToPipelineTemplateEvaluator(this IExecutionContext context, ObjectTemplating.ITraceWriter traceWriter = null) + { + if (traceWriter == null) + { + traceWriter = context.ToTemplateTraceWriter(); + } + var schema = PipelineTemplateSchemaFactory.GetSchema(); + return new PipelineTemplateEvaluator(traceWriter, schema, context.FileTable); } public static ObjectTemplating.ITraceWriter ToTemplateTraceWriter(this IExecutionContext context) @@ -934,6 +942,7 @@ namespace GitHub.Runner.Worker internal TemplateTraceWriter(IExecutionContext executionContext) { + ArgUtil.NotNull(executionContext, nameof(executionContext)); _executionContext = executionContext; } diff --git a/src/Runner.Worker/ExpressionManager.cs b/src/Runner.Worker/ExpressionManager.cs deleted file mode 100644 index b5218a806..000000000 --- a/src/Runner.Worker/ExpressionManager.cs +++ /dev/null @@ -1,162 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using GitHub.DistributedTask.Expressions2; -using GitHub.DistributedTask.Expressions2.Sdk; -using GitHub.DistributedTask.WebApi; -using GitHub.Runner.Common; -using GitHub.Runner.Common.Util; -using GitHub.Runner.Sdk; -using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating; -using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants; - -namespace GitHub.Runner.Worker -{ - [ServiceLocator(Default = typeof(ExpressionManager))] - public interface IExpressionManager : IRunnerService - { - ConditionResult Evaluate(IExecutionContext context, string condition, bool hostTracingOnly = false); - } - - public sealed class ExpressionManager : RunnerService, IExpressionManager - { - public ConditionResult Evaluate(IExecutionContext executionContext, string condition, bool hostTracingOnly = false) - { - ArgUtil.NotNull(executionContext, nameof(executionContext)); - - ConditionResult result = new ConditionResult(); - var expressionTrace = new TraceWriter(Trace, hostTracingOnly ? null : executionContext); - var tree = Parse(executionContext, expressionTrace, condition); - var expressionResult = tree.Evaluate(expressionTrace, HostContext.SecretMasker, state: executionContext, options: null); - result.Value = expressionResult.IsTruthy; - result.Trace = expressionTrace.Trace; - - return result; - } - - private static IExpressionNode Parse(IExecutionContext executionContext, TraceWriter expressionTrace, string condition) - { - ArgUtil.NotNull(executionContext, nameof(executionContext)); - - if (string.IsNullOrWhiteSpace(condition)) - { - condition = $"{PipelineTemplateConstants.Success}()"; - } - - var parser = new ExpressionParser(); - var namedValues = executionContext.ExpressionValues.Keys.Select(x => new NamedValueInfo(x)).ToArray(); - var functions = new IFunctionInfo[] - { - new FunctionInfo(name: Constants.Expressions.Always, minParameters: 0, maxParameters: 0), - new FunctionInfo(name: Constants.Expressions.Cancelled, minParameters: 0, maxParameters: 0), - new FunctionInfo(name: Constants.Expressions.Failure, minParameters: 0, maxParameters: 0), - new FunctionInfo(name: Constants.Expressions.Success, minParameters: 0, maxParameters: 0), - }; - return parser.CreateTree(condition, expressionTrace, namedValues, functions) ?? new SuccessNode(); - } - - private sealed class TraceWriter : DistributedTask.Expressions2.ITraceWriter - { - private readonly IExecutionContext _executionContext; - private readonly Tracing _trace; - private readonly StringBuilder _traceBuilder = new StringBuilder(); - - public string Trace => _traceBuilder.ToString(); - - public TraceWriter(Tracing trace, IExecutionContext executionContext) - { - ArgUtil.NotNull(trace, nameof(trace)); - _trace = trace; - _executionContext = executionContext; - } - - public void Info(string message) - { - _trace.Info(message); - _executionContext?.Debug(message); - _traceBuilder.AppendLine(message); - } - - public void Verbose(string message) - { - _trace.Verbose(message); - _executionContext?.Debug(message); - } - } - - private sealed class AlwaysNode : Function - { - protected override Object EvaluateCore(EvaluationContext context, out ResultMemory resultMemory) - { - resultMemory = null; - return true; - } - } - - private sealed class CancelledNode : Function - { - protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory) - { - resultMemory = null; - var executionContext = evaluationContext.State as IExecutionContext; - ArgUtil.NotNull(executionContext, nameof(executionContext)); - ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success; - return jobStatus == ActionResult.Cancelled; - } - } - - private sealed class FailureNode : Function - { - protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory) - { - resultMemory = null; - var executionContext = evaluationContext.State as IExecutionContext; - ArgUtil.NotNull(executionContext, nameof(executionContext)); - ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success; - return jobStatus == ActionResult.Failure; - } - } - - private sealed class SuccessNode : Function - { - protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory) - { - resultMemory = null; - var executionContext = evaluationContext.State as IExecutionContext; - ArgUtil.NotNull(executionContext, nameof(executionContext)); - ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success; - return jobStatus == ActionResult.Success; - } - } - - private sealed class ContextValueNode : NamedValue - { - protected override Object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory) - { - resultMemory = null; - var jobContext = evaluationContext.State as IExecutionContext; - ArgUtil.NotNull(jobContext, nameof(jobContext)); - return jobContext.ExpressionValues[Name]; - } - } - } - - public class ConditionResult - { - public ConditionResult(bool value = false, string trace = null) - { - this.Value = value; - this.Trace = trace; - } - - public bool Value { get; set; } - public string Trace { get; set; } - - public static implicit operator ConditionResult(bool value) - { - return new ConditionResult(value); - } - } -} diff --git a/src/Runner.Worker/Expressions/AlwaysFunction.cs b/src/Runner.Worker/Expressions/AlwaysFunction.cs new file mode 100644 index 000000000..1101e1917 --- /dev/null +++ b/src/Runner.Worker/Expressions/AlwaysFunction.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using GitHub.DistributedTask.Expressions2; +using GitHub.DistributedTask.Expressions2.Sdk; +using GitHub.DistributedTask.WebApi; +using GitHub.Runner.Common; +using GitHub.Runner.Common.Util; +using GitHub.Runner.Sdk; +using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating; +using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants; + +namespace GitHub.Runner.Worker.Expressions +{ + public sealed class AlwaysFunction : Function + { + protected override Object EvaluateCore(EvaluationContext context, out ResultMemory resultMemory) + { + resultMemory = null; + return true; + } + } +} diff --git a/src/Runner.Worker/Expressions/CancelledFunction.cs b/src/Runner.Worker/Expressions/CancelledFunction.cs new file mode 100644 index 000000000..ae676e8d6 --- /dev/null +++ b/src/Runner.Worker/Expressions/CancelledFunction.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using GitHub.DistributedTask.Expressions2; +using GitHub.DistributedTask.Expressions2.Sdk; +using GitHub.DistributedTask.ObjectTemplating; +using GitHub.DistributedTask.WebApi; +using GitHub.Runner.Common; +using GitHub.Runner.Common.Util; +using GitHub.Runner.Sdk; +using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating; +using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants; + +namespace GitHub.Runner.Worker.Expressions +{ + public sealed class CancelledFunction : Function + { + protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory) + { + resultMemory = null; + var templateContext = evaluationContext.State as TemplateContext; + ArgUtil.NotNull(templateContext, nameof(templateContext)); + var executionContext = templateContext.State[nameof(IExecutionContext)] as IExecutionContext; + ArgUtil.NotNull(executionContext, nameof(executionContext)); + ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success; + return jobStatus == ActionResult.Cancelled; + } + } +} diff --git a/src/Runner.Worker/Expressions/FailureFunction.cs b/src/Runner.Worker/Expressions/FailureFunction.cs new file mode 100644 index 000000000..4c8aa569e --- /dev/null +++ b/src/Runner.Worker/Expressions/FailureFunction.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using GitHub.DistributedTask.Expressions2; +using GitHub.DistributedTask.Expressions2.Sdk; +using GitHub.DistributedTask.ObjectTemplating; +using GitHub.DistributedTask.WebApi; +using GitHub.Runner.Common; +using GitHub.Runner.Common.Util; +using GitHub.Runner.Sdk; +using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating; +using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants; + +namespace GitHub.Runner.Worker.Expressions +{ + public sealed class FailureFunction : Function + { + protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory) + { + resultMemory = null; + var templateContext = evaluationContext.State as TemplateContext; + ArgUtil.NotNull(templateContext, nameof(templateContext)); + var executionContext = templateContext.State[nameof(IExecutionContext)] as IExecutionContext; + ArgUtil.NotNull(executionContext, nameof(executionContext)); + ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success; + return jobStatus == ActionResult.Failure; + } + } +} diff --git a/src/Runner.Worker/ExpressionFunctions/HashFiles.cs b/src/Runner.Worker/Expressions/HashFilesFunction.cs similarity index 87% rename from src/Runner.Worker/ExpressionFunctions/HashFiles.cs rename to src/Runner.Worker/Expressions/HashFilesFunction.cs index 915533ba2..ecbe00ce2 100644 --- a/src/Runner.Worker/ExpressionFunctions/HashFiles.cs +++ b/src/Runner.Worker/Expressions/HashFilesFunction.cs @@ -8,28 +8,9 @@ using System.Reflection; using System.Threading; using System.Collections.Generic; -namespace GitHub.Runner.Worker.Handlers +namespace GitHub.Runner.Worker.Expressions { - public class FunctionTrace : ITraceWriter - { - private GitHub.DistributedTask.Expressions2.ITraceWriter _trace; - - public FunctionTrace(GitHub.DistributedTask.Expressions2.ITraceWriter trace) - { - _trace = trace; - } - public void Info(string message) - { - _trace.Info(message); - } - - public void Verbose(string message) - { - _trace.Info(message); - } - } - - public sealed class HashFiles : Function + public sealed class HashFilesFunction : Function { protected sealed override Object EvaluateCore( EvaluationContext context, @@ -82,7 +63,7 @@ namespace GitHub.Runner.Worker.Handlers string node = Path.Combine(runnerRoot, "externals", "node12", "bin", $"node{IOUtil.ExeExtension}"); string hashFilesScript = Path.Combine(binDir, "hashFiles"); var hashResult = string.Empty; - var p = new ProcessInvoker(new FunctionTrace(context.Trace)); + var p = new ProcessInvoker(new HashFilesTrace(context.Trace)); p.ErrorDataReceived += ((_, data) => { if (!string.IsNullOrEmpty(data.Data) && data.Data.StartsWith("__OUTPUT__") && data.Data.EndsWith("__OUTPUT__")) @@ -122,5 +103,24 @@ namespace GitHub.Runner.Worker.Handlers return hashResult; } + + private sealed class HashFilesTrace : ITraceWriter + { + private GitHub.DistributedTask.Expressions2.ITraceWriter _trace; + + public HashFilesTrace(GitHub.DistributedTask.Expressions2.ITraceWriter trace) + { + _trace = trace; + } + public void Info(string message) + { + _trace.Info(message); + } + + public void Verbose(string message) + { + _trace.Info(message); + } + } } } \ No newline at end of file diff --git a/src/Runner.Worker/Expressions/SuccessFunction.cs b/src/Runner.Worker/Expressions/SuccessFunction.cs new file mode 100644 index 000000000..3d161abb5 --- /dev/null +++ b/src/Runner.Worker/Expressions/SuccessFunction.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using GitHub.DistributedTask.Expressions2; +using GitHub.DistributedTask.Expressions2.Sdk; +using GitHub.DistributedTask.ObjectTemplating; +using GitHub.DistributedTask.WebApi; +using GitHub.Runner.Common; +using GitHub.Runner.Common.Util; +using GitHub.Runner.Sdk; +using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating; +using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants; + +namespace GitHub.Runner.Worker.Expressions +{ + public sealed class SuccessFunction : Function + { + protected sealed override object EvaluateCore(EvaluationContext evaluationContext, out ResultMemory resultMemory) + { + resultMemory = null; + var templateContext = evaluationContext.State as TemplateContext; + ArgUtil.NotNull(templateContext, nameof(templateContext)); + var executionContext = templateContext.State[nameof(IExecutionContext)] as IExecutionContext; + ArgUtil.NotNull(executionContext, nameof(executionContext)); + ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success; + return jobStatus == ActionResult.Success; + } + } +} diff --git a/src/Runner.Worker/Handlers/ContainerActionHandler.cs b/src/Runner.Worker/Handlers/ContainerActionHandler.cs index a623da968..8c4f22602 100644 --- a/src/Runner.Worker/Handlers/ContainerActionHandler.cs +++ b/src/Runner.Worker/Handlers/ContainerActionHandler.cs @@ -97,14 +97,14 @@ namespace GitHub.Runner.Worker.Handlers } } - var evaluateContext = new Dictionary(StringComparer.OrdinalIgnoreCase); - evaluateContext["inputs"] = inputsContext; + var extraExpressionValues = new Dictionary(StringComparer.OrdinalIgnoreCase); + extraExpressionValues["inputs"] = inputsContext; var manifestManager = HostContext.GetService(); if (Data.Arguments != null) { container.ContainerEntryPointArgs = ""; - var evaluatedArgs = manifestManager.EvaluateContainerArguments(ExecutionContext, Data.Arguments, evaluateContext); + var evaluatedArgs = manifestManager.EvaluateContainerArguments(ExecutionContext, Data.Arguments, extraExpressionValues); foreach (var arg in evaluatedArgs) { if (!string.IsNullOrEmpty(arg)) @@ -124,7 +124,7 @@ namespace GitHub.Runner.Worker.Handlers if (Data.Environment != null) { - var evaluatedEnv = manifestManager.EvaluateContainerEnvironment(ExecutionContext, Data.Environment, evaluateContext); + var evaluatedEnv = manifestManager.EvaluateContainerEnvironment(ExecutionContext, Data.Environment, extraExpressionValues); foreach (var env in evaluatedEnv) { if (!this.Environment.ContainsKey(env.Key)) diff --git a/src/Runner.Worker/JobExtension.cs b/src/Runner.Worker/JobExtension.cs index 343b5d87e..fe3a1ed24 100644 --- a/src/Runner.Worker/JobExtension.cs +++ b/src/Runner.Worker/JobExtension.cs @@ -132,7 +132,7 @@ namespace GitHub.Runner.Worker var templateEvaluator = context.ToPipelineTemplateEvaluator(); foreach (var token in message.EnvironmentVariables) { - var environmentVariables = templateEvaluator.EvaluateStepEnvironment(token, jobContext.ExpressionValues, VarUtil.EnvironmentVariableKeyComparer); + var environmentVariables = templateEvaluator.EvaluateStepEnvironment(token, jobContext.ExpressionValues, jobContext.ExpressionFunctions, VarUtil.EnvironmentVariableKeyComparer); foreach (var pair in environmentVariables) { context.EnvironmentVariables[pair.Key] = pair.Value ?? string.Empty; @@ -142,7 +142,7 @@ namespace GitHub.Runner.Worker // Evaluate the job container context.Debug("Evaluating job container"); - var container = templateEvaluator.EvaluateJobContainer(message.JobContainer, jobContext.ExpressionValues); + var container = templateEvaluator.EvaluateJobContainer(message.JobContainer, jobContext.ExpressionValues, jobContext.ExpressionFunctions); if (container != null) { jobContext.Container = new Container.ContainerInfo(HostContext, container); @@ -150,7 +150,7 @@ namespace GitHub.Runner.Worker // Evaluate the job service containers context.Debug("Evaluating job service containers"); - var serviceContainers = templateEvaluator.EvaluateJobServiceContainers(message.JobServiceContainers, jobContext.ExpressionValues); + var serviceContainers = templateEvaluator.EvaluateJobServiceContainers(message.JobServiceContainers, jobContext.ExpressionValues, jobContext.ExpressionFunctions); if (serviceContainers?.Count > 0) { foreach (var pair in serviceContainers) @@ -170,7 +170,7 @@ namespace GitHub.Runner.Worker { context.JobDefaults["run"] = new Dictionary(StringComparer.OrdinalIgnoreCase); var defaultsRun = defaults.First(x => string.Equals(x.Key.AssertString("defaults key").Value, "run", StringComparison.OrdinalIgnoreCase)); - var jobDefaults = templateEvaluator.EvaluateJobDefaultsRun(defaultsRun.Value, jobContext.ExpressionValues); + var jobDefaults = templateEvaluator.EvaluateJobDefaultsRun(defaultsRun.Value, jobContext.ExpressionValues, jobContext.ExpressionFunctions); foreach (var pair in jobDefaults) { if (!string.IsNullOrEmpty(pair.Value)) @@ -337,7 +337,7 @@ namespace GitHub.Runner.Worker context.ExpressionValues["steps"] = context.StepsContext.GetScope(context.ScopeName); var templateEvaluator = context.ToPipelineTemplateEvaluator(); - var outputs = templateEvaluator.EvaluateJobOutput(message.JobOutputs, context.ExpressionValues); + var outputs = templateEvaluator.EvaluateJobOutput(message.JobOutputs, context.ExpressionValues, context.ExpressionFunctions); foreach (var output in outputs) { if (string.IsNullOrEmpty(output.Value)) diff --git a/src/Runner.Worker/StepsRunner.cs b/src/Runner.Worker/StepsRunner.cs index ec1d7069c..3e758c6d5 100644 --- a/src/Runner.Worker/StepsRunner.cs +++ b/src/Runner.Worker/StepsRunner.cs @@ -1,8 +1,6 @@ -using GitHub.DistributedTask.WebApi; -using Pipelines = GitHub.DistributedTask.Pipelines; -using GitHub.Runner.Common.Util; using System; using System.Collections.Generic; +using System.Text; using System.Threading; using System.Threading.Tasks; using GitHub.DistributedTask.Expressions2; @@ -10,8 +8,13 @@ using GitHub.DistributedTask.ObjectTemplating.Tokens; using GitHub.DistributedTask.Pipelines; using GitHub.DistributedTask.Pipelines.ContextData; using GitHub.DistributedTask.Pipelines.ObjectTemplating; +using GitHub.DistributedTask.WebApi; using GitHub.Runner.Common; +using GitHub.Runner.Common.Util; using GitHub.Runner.Sdk; +using GitHub.Runner.Worker.Expressions; +using ObjectTemplating = GitHub.DistributedTask.ObjectTemplating; +using Pipelines = GitHub.DistributedTask.Pipelines; namespace GitHub.Runner.Worker { @@ -63,11 +66,7 @@ namespace GitHub.Runner.Worker } var step = jobContext.JobSteps.Dequeue(); - IStep nextStep = null; - if (jobContext.JobSteps.Count > 0) - { - nextStep = jobContext.JobSteps.Peek(); - } + var nextStep = jobContext.JobSteps.Count > 0 ? jobContext.JobSteps.Peek() : null; Trace.Info($"Processing step: DisplayName='{step.DisplayName}'"); ArgUtil.NotNull(step.ExecutionContext, nameof(step.ExecutionContext)); @@ -76,6 +75,13 @@ namespace GitHub.Runner.Worker // Start step.ExecutionContext.Start(); + // Expression functions + step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo(PipelineTemplateConstants.Always, 0, 0)); + step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo(PipelineTemplateConstants.Cancelled, 0, 0)); + step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo(PipelineTemplateConstants.Failure, 0, 0)); + step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo(PipelineTemplateConstants.Success, 0, 0)); + step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo(PipelineTemplateConstants.HashFiles, 1, byte.MaxValue)); + // Initialize scope if (InitializeScope(step, scopeInputs)) { @@ -99,14 +105,13 @@ namespace GitHub.Runner.Worker // Evaluate and merge action's env block to env context var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(); - var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, VarUtil.EnvironmentVariableKeyComparer); + var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, VarUtil.EnvironmentVariableKeyComparer); foreach (var env in actionEnvironment) { envContext[env.Key] = new StringContextData(env.Value ?? string.Empty); } } - var expressionManager = HostContext.GetService(); try { // Register job cancellation call back only if job cancellation token not been fire before each step run @@ -120,28 +125,29 @@ namespace GitHub.Runner.Worker jobContext.JobContext.Status = jobContext.Result?.ToActionResult(); step.ExecutionContext.Debug($"Re-evaluate condition on job cancellation for step: '{step.DisplayName}'."); - ConditionResult conditionReTestResult; + var conditionReTestTraceWriter = new ConditionTraceWriter(Trace, null); // host tracing only + var conditionReTestResult = false; if (HostContext.RunnerShutdownToken.IsCancellationRequested) { step.ExecutionContext.Debug($"Skip Re-evaluate condition on runner shutdown."); - conditionReTestResult = false; } else { try { - conditionReTestResult = expressionManager.Evaluate(step.ExecutionContext, step.Condition, hostTracingOnly: true); + var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionReTestTraceWriter); + var condition = new BasicExpressionToken(null, null, null, step.Condition); + conditionReTestResult = templateEvaluator.EvaluateStepIf(condition, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, step.ExecutionContext.ToExpressionState()); } catch (Exception ex) { // Cancel the step since we get exception while re-evaluate step condition. Trace.Info("Caught exception from expression when re-test condition on job cancellation."); step.ExecutionContext.Error(ex); - conditionReTestResult = false; } } - if (!conditionReTestResult.Value) + if (!conditionReTestResult) { // Cancel the step. Trace.Info("Cancel current running step."); @@ -161,34 +167,35 @@ namespace GitHub.Runner.Worker // Evaluate condition. step.ExecutionContext.Debug($"Evaluating condition for step: '{step.DisplayName}'"); - Exception conditionEvaluateError = null; - ConditionResult conditionResult; + var conditionTraceWriter = new ConditionTraceWriter(Trace, step.ExecutionContext); + var conditionResult = false; + var conditionEvaluateError = default(Exception); if (HostContext.RunnerShutdownToken.IsCancellationRequested) { step.ExecutionContext.Debug($"Skip evaluate condition on runner shutdown."); - conditionResult = false; } else { try { - conditionResult = expressionManager.Evaluate(step.ExecutionContext, step.Condition); + var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionTraceWriter); + var condition = new BasicExpressionToken(null, null, null, step.Condition); + conditionResult = templateEvaluator.EvaluateStepIf(condition, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, step.ExecutionContext.ToExpressionState()); } catch (Exception ex) { Trace.Info("Caught exception from expression."); Trace.Error(ex); - conditionResult = false; conditionEvaluateError = ex; } } // no evaluate error but condition is false - if (!conditionResult.Value && conditionEvaluateError == null) + if (!conditionResult && conditionEvaluateError == null) { // Condition == false Trace.Info("Skipping step due to condition evaluation."); - CompleteStep(step, nextStep, TaskResult.Skipped, resultCode: conditionResult.Trace); + CompleteStep(step, nextStep, TaskResult.Skipped, resultCode: conditionTraceWriter.Trace); } else if (conditionEvaluateError != null) { @@ -248,7 +255,7 @@ namespace GitHub.Runner.Worker var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(); try { - timeoutMinutes = templateEvaluator.EvaluateStepTimeout(step.Timeout, step.ExecutionContext.ExpressionValues); + timeoutMinutes = templateEvaluator.EvaluateStepTimeout(step.Timeout, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions); } catch (Exception ex) { @@ -339,7 +346,7 @@ namespace GitHub.Runner.Worker var continueOnError = false; try { - continueOnError = templateEvaluator.EvaluateStepContinueOnError(step.ContinueOnError, step.ExecutionContext.ExpressionValues); + continueOnError = templateEvaluator.EvaluateStepContinueOnError(step.ContinueOnError, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions); } catch (Exception ex) { @@ -392,7 +399,7 @@ namespace GitHub.Runner.Worker var inputs = default(DictionaryContextData); try { - inputs = templateEvaluator.EvaluateStepScopeInputs(scope.Inputs, executionContext.ExpressionValues); + inputs = templateEvaluator.EvaluateStepScopeInputs(scope.Inputs, executionContext.ExpressionValues, executionContext.ExpressionFunctions); } catch (Exception ex) { @@ -448,7 +455,7 @@ namespace GitHub.Runner.Worker var outputs = default(DictionaryContextData); try { - outputs = templateEvaluator.EvaluateStepScopeOutputs(scope.Outputs, executionContext.ExpressionValues); + outputs = templateEvaluator.EvaluateStepScopeOutputs(scope.Outputs, executionContext.ExpressionValues, executionContext.ExpressionFunctions); } catch (Exception ex) { @@ -476,5 +483,43 @@ namespace GitHub.Runner.Worker executionContext.Complete(result, resultCode: resultCode); } + + private sealed class ConditionTraceWriter : ObjectTemplating::ITraceWriter + { + private readonly IExecutionContext _executionContext; + private readonly Tracing _trace; + private readonly StringBuilder _traceBuilder = new StringBuilder(); + + public string Trace => _traceBuilder.ToString(); + + public ConditionTraceWriter(Tracing trace, IExecutionContext executionContext) + { + ArgUtil.NotNull(trace, nameof(trace)); + _trace = trace; + _executionContext = executionContext; + } + + public void Error(string format, params Object[] args) + { + var message = StringUtil.Format(format, args); + _trace.Error(message); + _executionContext?.Debug(message); + } + + public void Info(string format, params Object[] args) + { + var message = StringUtil.Format(format, args); + _trace.Info(message); + _executionContext?.Debug(message); + _traceBuilder.AppendLine(message); + } + + public void Verbose(string format, params Object[] args) + { + var message = StringUtil.Format(format, args); + _trace.Verbose(message); + _executionContext?.Debug(message); + } + } } } diff --git a/src/Runner.Worker/action_yaml.json b/src/Runner.Worker/action_yaml.json index c9eb2d38d..10e691694 100644 --- a/src/Runner.Worker/action_yaml.json +++ b/src/Runner.Worker/action_yaml.json @@ -91,7 +91,8 @@ "strategy", "matrix", "job", - "runner" + "runner", + "hashFiles(1,255)" ], "string": {} }, diff --git a/src/Sdk/DTExpressions2/Expressions2/ExpressionConstants.cs b/src/Sdk/DTExpressions2/Expressions2/ExpressionConstants.cs index 7974c85bc..99e19debf 100644 --- a/src/Sdk/DTExpressions2/Expressions2/ExpressionConstants.cs +++ b/src/Sdk/DTExpressions2/Expressions2/ExpressionConstants.cs @@ -5,7 +5,7 @@ using GitHub.DistributedTask.Expressions2.Sdk.Functions; namespace GitHub.DistributedTask.Expressions2 { - public static class ExpressionConstants + internal static class ExpressionConstants { static ExpressionConstants() { @@ -16,7 +16,6 @@ namespace GitHub.DistributedTask.Expressions2 AddFunction("startsWith", 2, 2); AddFunction("toJson", 1, 1); AddFunction("fromJson", 1, 1); - AddFunction("hashFiles", 1, 1); } private static void AddFunction(String name, Int32 minParameters, Int32 maxParameters) @@ -25,12 +24,6 @@ namespace GitHub.DistributedTask.Expressions2 WellKnownFunctions.Add(name, new FunctionInfo(name, minParameters, maxParameters)); } - public static void UpdateFunction(String name, Int32 minParameters, Int32 maxParameters) - where T : Function, new() - { - WellKnownFunctions[name] = new FunctionInfo(name, minParameters, maxParameters); - } - internal static readonly String False = "false"; internal static readonly String Infinity = "Infinity"; internal static readonly Int32 MaxDepth = 50; diff --git a/src/Sdk/DTExpressions2/Expressions2/Sdk/Functions/HashFiles.cs b/src/Sdk/DTExpressions2/Expressions2/Sdk/Functions/HashFiles.cs deleted file mode 100644 index 82862e000..000000000 --- a/src/Sdk/DTExpressions2/Expressions2/Sdk/Functions/HashFiles.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Minimatch; -using System.IO; -using System.Security.Cryptography; -using GitHub.DistributedTask.Expressions2.Sdk; -using GitHub.DistributedTask.Pipelines.ContextData; -using GitHub.DistributedTask.Pipelines.ObjectTemplating; -namespace GitHub.DistributedTask.Expressions2.Sdk.Functions -{ - internal sealed class HashFiles : Function - { - protected sealed override Object EvaluateCore( - EvaluationContext context, - out ResultMemory resultMemory) - { - resultMemory = null; - - // hashFiles() only works on the runner and only works with files under GITHUB_WORKSPACE - // Since GITHUB_WORKSPACE is set by runner, I am using that as the fact of this code runs on server or runner. - if (context.State is ObjectTemplating.TemplateContext templateContext && - templateContext.ExpressionValues.TryGetValue(PipelineTemplateConstants.GitHub, out var githubContextData) && - githubContextData is DictionaryContextData githubContext && - githubContext.TryGetValue(PipelineTemplateConstants.Workspace, out var workspace) == true && - workspace is StringContextData workspaceData) - { - string searchRoot = workspaceData.Value; - string pattern = Parameters[0].Evaluate(context).ConvertToString(); - - // Convert slashes on Windows - if (s_isWindows) - { - pattern = pattern.Replace('\\', '/'); - } - - // Root the pattern - if (!Path.IsPathRooted(pattern)) - { - var patternRoot = s_isWindows ? searchRoot.Replace('\\', '/').TrimEnd('/') : searchRoot.TrimEnd('/'); - pattern = string.Concat(patternRoot, "/", pattern); - } - - // Get all files - context.Trace.Info($"Search root directory: '{searchRoot}'"); - context.Trace.Info($"Search pattern: '{pattern}'"); - var files = Directory.GetFiles(searchRoot, "*", SearchOption.AllDirectories) - .Select(x => s_isWindows ? x.Replace('\\', '/') : x) - .OrderBy(x => x, StringComparer.Ordinal) - .ToList(); - if (files.Count == 0) - { - throw new ArgumentException($"hashFiles('{ExpressionUtility.StringEscape(pattern)}') failed. Directory '{searchRoot}' is empty"); - } - else - { - context.Trace.Info($"Found {files.Count} files"); - } - - // Match - var matcher = new Minimatcher(pattern, s_minimatchOptions); - files = matcher.Filter(files) - .Select(x => s_isWindows ? x.Replace('/', '\\') : x) - .ToList(); - if (files.Count == 0) - { - throw new ArgumentException($"hashFiles('{ExpressionUtility.StringEscape(pattern)}') failed. Search pattern '{pattern}' doesn't match any file under '{searchRoot}'"); - } - else - { - context.Trace.Info($"{files.Count} matches to hash"); - } - - // Hash each file - List filesSha256 = new List(); - foreach (var file in files) - { - context.Trace.Info($"Hash {file}"); - using (SHA256 sha256hash = SHA256.Create()) - { - using (var fileStream = File.OpenRead(file)) - { - filesSha256.AddRange(sha256hash.ComputeHash(fileStream)); - } - } - } - - // Hash the hashes - using (SHA256 sha256hash = SHA256.Create()) - { - var hashBytes = sha256hash.ComputeHash(filesSha256.ToArray()); - StringBuilder hashString = new StringBuilder(); - for (int i = 0; i < hashBytes.Length; i++) - { - hashString.Append(hashBytes[i].ToString("x2")); - } - var result = hashString.ToString(); - context.Trace.Info($"Final hash result: '{result}'"); - return result; - } - } - else - { - throw new InvalidOperationException("'hashfiles' expression function is only supported under runner context."); - } - } - - private static readonly bool s_isWindows = Environment.OSVersion.Platform != PlatformID.Unix && Environment.OSVersion.Platform != PlatformID.MacOSX; - - // Only support basic globbing (* ? and []) and globstar (**) - private static readonly Options s_minimatchOptions = new Options - { - Dot = true, - NoBrace = true, - NoCase = s_isWindows, - NoComment = true, - NoExt = true, - NoNegate = true, - }; - } -} \ No newline at end of file diff --git a/src/Sdk/DTObjectTemplating/ObjectTemplating/Schema/Definition.cs b/src/Sdk/DTObjectTemplating/ObjectTemplating/Schema/Definition.cs index 259724c2d..e74656fee 100644 --- a/src/Sdk/DTObjectTemplating/ObjectTemplating/Schema/Definition.cs +++ b/src/Sdk/DTObjectTemplating/ObjectTemplating/Schema/Definition.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using GitHub.DistributedTask.ObjectTemplating.Tokens; @@ -22,10 +23,27 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema { var context = definition[i].Value.AssertSequence($"{TemplateConstants.Context}"); definition.RemoveAt(i); - Context = context - .Select(x => x.AssertString($"{TemplateConstants.Context} item").Value) - .Distinct() - .ToArray(); + var readerContext = new HashSet(StringComparer.OrdinalIgnoreCase); + var evaluatorContext = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (TemplateToken item in context) + { + var itemStr = item.AssertString($"{TemplateConstants.Context} item").Value; + readerContext.Add(itemStr); + + // Remove min/max parameter info + var paramIndex = itemStr.IndexOf('('); + if (paramIndex > 0) + { + evaluatorContext.Add(String.Concat(itemStr.Substring(0, paramIndex + 1), ")")); + } + else + { + evaluatorContext.Add(itemStr); + } + } + + ReaderContext = readerContext.ToArray(); + EvaluatorContext = evaluatorContext.ToArray(); } else if (String.Equals(definitionKey.Value, TemplateConstants.Description, StringComparison.Ordinal)) { @@ -40,7 +58,17 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema internal abstract DefinitionType DefinitionType { get; } - internal String[] Context { get; private set; } = new String[0]; + /// + /// Used by the template reader to determine allowed expression values and functions. + /// Also used by the template reader to validate function min/max parameters. + /// + internal String[] ReaderContext { get; private set; } = new String[0]; + + /// + /// Used by the template evaluator to determine allowed expression values and functions. + /// The min/max parameter info is omitted. + /// + internal String[] EvaluatorContext { get; private set; } = new String[0]; internal abstract void Validate( TemplateSchema schema, diff --git a/src/Sdk/DTObjectTemplating/ObjectTemplating/Schema/MappingDefinition.cs b/src/Sdk/DTObjectTemplating/ObjectTemplating/Schema/MappingDefinition.cs index 3da980185..2d63c4008 100644 --- a/src/Sdk/DTObjectTemplating/ObjectTemplating/Schema/MappingDefinition.cs +++ b/src/Sdk/DTObjectTemplating/ObjectTemplating/Schema/MappingDefinition.cs @@ -108,7 +108,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema { var inherited = schema.GetDefinition(Inherits); - if (inherited.Context.Length > 0) + if (inherited.ReaderContext.Length > 0) { throw new NotSupportedException($"Property '{TemplateConstants.Context}' is not supported on inhertied definitions"); } diff --git a/src/Sdk/DTObjectTemplating/ObjectTemplating/Schema/OneOfDefinition.cs b/src/Sdk/DTObjectTemplating/ObjectTemplating/Schema/OneOfDefinition.cs index 200933ebf..671f13fb5 100644 --- a/src/Sdk/DTObjectTemplating/ObjectTemplating/Schema/OneOfDefinition.cs +++ b/src/Sdk/DTObjectTemplating/ObjectTemplating/Schema/OneOfDefinition.cs @@ -62,7 +62,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Schema { var nestedDefinition = schema.GetDefinition(nestedType); - if (nestedDefinition.Context.Length > 0) + if (nestedDefinition.ReaderContext.Length > 0) { throw new ArgumentException($"'{name}' is a one-of definition and references another definition that defines context. This is currently not supported."); } diff --git a/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateEvaluator.cs b/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateEvaluator.cs index 63f516319..915fc3cbc 100644 --- a/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateEvaluator.cs +++ b/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateEvaluator.cs @@ -47,7 +47,16 @@ namespace GitHub.DistributedTask.ObjectTemplating var evaluator = new TemplateEvaluator(context, template, removeBytes); try { - var availableContext = new HashSet(context.ExpressionValues.Keys.Concat(context.ExpressionFunctions.Select(x => $"{x.Name}({x.MinParameters},{x.MaxParameters})"))); + var availableContext = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var key in context.ExpressionValues.Keys) + { + availableContext.Add(key); + } + foreach (var function in context.ExpressionFunctions) + { + availableContext.Add($"{function.Name}()"); + } + var definitionInfo = new DefinitionInfo(context.Schema, type, availableContext); result = evaluator.Evaluate(definitionInfo); @@ -393,14 +402,13 @@ namespace GitHub.DistributedTask.ObjectTemplating Definition = m_schema.GetDefinition(name); // Determine whether to expand - if (Definition.Context.Length > 0) + m_allowedContext = Definition.EvaluatorContext; + if (Definition.EvaluatorContext.Length > 0) { - m_allowedContext = Definition.Context; Expand = m_availableContext.IsSupersetOf(m_allowedContext); } else { - m_allowedContext = new String[0]; Expand = false; } } @@ -416,9 +424,9 @@ namespace GitHub.DistributedTask.ObjectTemplating Definition = m_schema.GetDefinition(name); // Determine whether to expand - if (Definition.Context.Length > 0) + if (Definition.EvaluatorContext.Length > 0) { - m_allowedContext = new HashSet(parent.m_allowedContext.Concat(Definition.Context)).ToArray(); + m_allowedContext = new HashSet(parent.m_allowedContext.Concat(Definition.EvaluatorContext), StringComparer.OrdinalIgnoreCase).ToArray(); Expand = m_availableContext.IsSupersetOf(m_allowedContext); } else diff --git a/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateException.cs b/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateException.cs index cc9d57c69..835b75ebd 100644 --- a/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateException.cs +++ b/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateException.cs @@ -49,6 +49,14 @@ namespace GitHub.DistributedTask.ObjectTemplating m_errors = new List(errors ?? Enumerable.Empty()); } + public TemplateValidationException( + String message, + IEnumerable errors) + : this(message) + { + m_errors = new List(errors ?? Enumerable.Empty()); + } + public TemplateValidationException(String message) : base(message) { diff --git a/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateReader.cs b/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateReader.cs index eab601bc0..886bea4c3 100644 --- a/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateReader.cs +++ b/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateReader.cs @@ -780,15 +780,8 @@ namespace GitHub.DistributedTask.ObjectTemplating // Lookup the definition Definition = m_schema.GetDefinition(name); - // Determine whether to expand - if (Definition.Context.Length > 0) - { - AllowedContext = Definition.Context; - } - else - { - AllowedContext = new String[0]; - } + // Record allowed context + AllowedContext = Definition.ReaderContext; } public DefinitionInfo( @@ -800,10 +793,10 @@ namespace GitHub.DistributedTask.ObjectTemplating // Lookup the definition Definition = m_schema.GetDefinition(name); - // Determine whether to expand - if (Definition.Context.Length > 0) + // Record allowed context + if (Definition.ReaderContext.Length > 0) { - AllowedContext = new HashSet(parent.AllowedContext.Concat(Definition.Context)).ToArray(); + AllowedContext = new HashSet(parent.AllowedContext.Concat(Definition.ReaderContext), StringComparer.OrdinalIgnoreCase).ToArray(); } else { diff --git a/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateValidationErrors.cs b/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateValidationErrors.cs index 4b1e738d0..4ada3c8e6 100644 --- a/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateValidationErrors.cs +++ b/src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateValidationErrors.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Runtime.Serialization; namespace GitHub.DistributedTask.ObjectTemplating @@ -41,7 +42,7 @@ namespace GitHub.DistributedTask.ObjectTemplating { for (int i = 0; i < 50; i++) { - String message = !String.IsNullOrEmpty(messagePrefix) ? $"{messagePrefix} {ex.Message}" : ex.Message; + String message = !String.IsNullOrEmpty(messagePrefix) ? $"{messagePrefix} {ex.Message}" : ex.ToString(); Add(new TemplateValidationError(message)); if (ex.InnerException == null) { @@ -88,6 +89,23 @@ namespace GitHub.DistributedTask.ObjectTemplating } } + /// + /// Throws if any errors. + /// The error message prefix + /// + public void Check(String prefix) + { + if (String.IsNullOrEmpty(prefix)) + { + this.Check(); + } + else if (m_errors.Count > 0) + { + var message = $"{prefix.Trim()} {String.Join(",", m_errors.Select(e => e.Message))}"; + throw new TemplateValidationException(message, m_errors); + } + } + public void Clear() { m_errors.Clear(); diff --git a/src/Sdk/DTObjectTemplating/ObjectTemplating/Tokens/ExpressionToken.cs b/src/Sdk/DTObjectTemplating/ObjectTemplating/Tokens/ExpressionToken.cs index 0709e236c..11f5f1bbf 100644 --- a/src/Sdk/DTObjectTemplating/ObjectTemplating/Tokens/ExpressionToken.cs +++ b/src/Sdk/DTObjectTemplating/ObjectTemplating/Tokens/ExpressionToken.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using System.ComponentModel; -using System.Linq; +using System.Globalization; using System.Runtime.Serialization; +using System.Text.RegularExpressions; using GitHub.DistributedTask.Expressions2; using GitHub.DistributedTask.Expressions2.Sdk; using GitHub.Services.WebApi.Internal; @@ -35,11 +37,29 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens String[] allowedContext, out Exception ex) { - // Create dummy allowed contexts - INamedValueInfo[] namedValues = null; + // Create dummy named values and functions + var namedValues = new List(); + var functions = new List(); if (allowedContext?.Length > 0) { - namedValues = allowedContext.Select(x => new NamedValueInfo(x)).ToArray(); + foreach (var contextItem in allowedContext) + { + var match = s_function.Match(contextItem); + if (match.Success) + { + var functionName = match.Groups[1].Value; + var minParameters = Int32.Parse(match.Groups[2].Value, NumberStyles.None, CultureInfo.InvariantCulture); + var maxParametersRaw = match.Groups[3].Value; + var maxParameters = String.Equals(maxParametersRaw, TemplateConstants.MaxConstant, StringComparison.Ordinal) + ? Int32.MaxValue + : Int32.Parse(maxParametersRaw, NumberStyles.None, CultureInfo.InvariantCulture); + functions.Add(new FunctionInfo(functionName, minParameters, maxParameters)); + } + else + { + namedValues.Add(new NamedValueInfo(contextItem)); + } + } } // Parse @@ -47,7 +67,7 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens ExpressionNode root = null; try { - root = new ExpressionParser().CreateTree(expression, null, namedValues, null) as ExpressionNode; + root = new ExpressionParser().CreateTree(expression, null, namedValues, functions) as ExpressionNode; result = true; ex = null; @@ -60,5 +80,18 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens return result; } + + private sealed class DummyFunction : Function + { + protected override Object EvaluateCore( + EvaluationContext context, + out ResultMemory resultMemory) + { + resultMemory = null; + return null; + } + } + + private static readonly Regex s_function = new Regex(@"^([a-zA-Z0-9_]+)\(([0-9]+),([0-9]+|MAX)\)$", RegexOptions.Compiled); } } diff --git a/src/Sdk/DTObjectTemplating/ObjectTemplating/Tokens/TemplateTokenExtensions.cs b/src/Sdk/DTObjectTemplating/ObjectTemplating/Tokens/TemplateTokenExtensions.cs index 7b368404e..c8c0eabb7 100644 --- a/src/Sdk/DTObjectTemplating/ObjectTemplating/Tokens/TemplateTokenExtensions.cs +++ b/src/Sdk/DTObjectTemplating/ObjectTemplating/Tokens/TemplateTokenExtensions.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using GitHub.DistributedTask.Expressions2; +using GitHub.DistributedTask.Expressions2.Sdk; namespace GitHub.DistributedTask.ObjectTemplating.Tokens { @@ -106,6 +109,43 @@ namespace GitHub.DistributedTask.ObjectTemplating.Tokens throw new ArgumentException($"Error while reading '{objectDescription}'. Unexpected value '{literal.ToString()}'"); } + /// + /// Traverses the token and checks whether all required expression values + /// and functions are provided. + /// + public static bool CheckHasRequiredContext( + this TemplateToken token, + IReadOnlyObject expressionValues, + IList expressionFunctions) + { + var expressionTokens = token.Traverse() + .OfType() + .ToArray(); + var parser = new ExpressionParser(); + foreach (var expressionToken in expressionTokens) + { + var tree = parser.ValidateSyntax(expressionToken.Expression, null); + foreach (var node in tree.Traverse()) + { + if (node is NamedValue namedValue) + { + if (expressionValues?.Keys.Any(x => string.Equals(x, namedValue.Name, StringComparison.OrdinalIgnoreCase)) != true) + { + return false; + } + } + else if (node is Function function && + !ExpressionConstants.WellKnownFunctions.ContainsKey(function.Name) && + expressionFunctions?.Any(x => string.Equals(x.Name, function.Name, StringComparison.OrdinalIgnoreCase)) != true) + { + return false; + } + } + } + + return true; + } + /// /// Returns all tokens (depth first) /// diff --git a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConstants.cs b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConstants.cs index 86db41170..464cd9aad 100644 --- a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConstants.cs +++ b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConstants.cs @@ -24,6 +24,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating public const String FetchDepth = "fetch-depth"; public const String GeneratedId = "generated-id"; public const String GitHub = "github"; + public const String HashFiles = "hashFiles"; public const String Id = "id"; public const String If = "if"; public const String Image = "image"; @@ -31,6 +32,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating public const String Inputs = "inputs"; public const String Job = "job"; public const String JobDefaultsRun = "job-defaults-run"; + public const String JobIfResult = "job-if-result"; public const String JobOutputs = "job-outputs"; public const String Jobs = "jobs"; public const String Labels = "labels"; @@ -60,6 +62,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating public const String Shell = "shell"; public const String Skipped = "skipped"; public const String StepEnv = "step-env"; + public const String StepIfResult = "step-if-result"; public const String Steps = "steps"; public const String StepsScopeInputs = "steps-scope-inputs"; public const String StepsScopeOutputs = "steps-scope-outputs"; diff --git a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConverter.cs b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConverter.cs index 951d0869f..43be43d33 100644 --- a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConverter.cs +++ b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConverter.cs @@ -16,6 +16,20 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating { internal static class PipelineTemplateConverter { + internal static Boolean ConvertToIfResult( + TemplateContext context, + TemplateToken ifResult) + { + var expression = ifResult.Traverse().FirstOrDefault(x => x is ExpressionToken); + if (expression != null) + { + throw new ArgumentException($"Unexpected type '{expression.GetType().Name}' encountered while reading 'if'."); + } + + var evaluationResult = EvaluationResult.CreateIntermediateResult(null, ifResult); + return evaluationResult.IsTruthy; + } + internal static Boolean? ConvertToStepContinueOnError( TemplateContext context, TemplateToken token, diff --git a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateEvaluator.cs b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateEvaluator.cs index d60fcc529..a36f5b7e3 100644 --- a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateEvaluator.cs +++ b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateEvaluator.cs @@ -4,7 +4,7 @@ using System.ComponentModel; using System.Linq; using System.Threading; using GitHub.DistributedTask.Expressions2; -using GitHub.DistributedTask.Expressions2.Sdk; +using GitHub.DistributedTask.Expressions2.Sdk.Functions; using GitHub.DistributedTask.ObjectTemplating; using GitHub.DistributedTask.ObjectTemplating.Schema; using GitHub.DistributedTask.ObjectTemplating.Tokens; @@ -14,6 +14,9 @@ using ITraceWriter = GitHub.DistributedTask.ObjectTemplating.ITraceWriter; namespace GitHub.DistributedTask.Pipelines.ObjectTemplating { + /// + /// Evaluates parts of the workflow DOM. For example, a job strategy or step inputs. + /// [EditorBrowsable(EditorBrowsableState.Never)] public class PipelineTemplateEvaluator { @@ -50,13 +53,14 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating public DictionaryContextData EvaluateStepScopeInputs( TemplateToken token, - DictionaryContextData contextData) + DictionaryContextData contextData, + IList expressionFunctions) { var result = default(DictionaryContextData); if (token != null && token.Type != TokenType.Null) { - var context = CreateContext(contextData); + var context = CreateContext(contextData, expressionFunctions); try { token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepsScopeInputs, token, 0, null, omitHeader: true); @@ -76,13 +80,14 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating public DictionaryContextData EvaluateStepScopeOutputs( TemplateToken token, - DictionaryContextData contextData) + DictionaryContextData contextData, + IList expressionFunctions) { var result = default(DictionaryContextData); if (token != null && token.Type != TokenType.Null) { - var context = CreateContext(contextData); + var context = CreateContext(contextData, expressionFunctions); try { token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepsScopeOutputs, token, 0, null, omitHeader: true); @@ -102,13 +107,14 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating public Boolean EvaluateStepContinueOnError( TemplateToken token, - DictionaryContextData contextData) + DictionaryContextData contextData, + IList expressionFunctions) { var result = default(Boolean?); if (token != null && token.Type != TokenType.Null) { - var context = CreateContext(contextData); + var context = CreateContext(contextData, expressionFunctions); try { token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.BooleanStepsContext, token, 0, null, omitHeader: true); @@ -126,16 +132,44 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating return result ?? false; } + public String EvaluateStepDisplayName( + TemplateToken token, + DictionaryContextData contextData, + IList expressionFunctions) + { + var result = default(String); + + if (token != null && token.Type != TokenType.Null) + { + var context = CreateContext(contextData, expressionFunctions); + try + { + token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StringStepsContext, token, 0, null, omitHeader: true); + context.Errors.Check(); + result = PipelineTemplateConverter.ConvertToStepDisplayName(context, token); + } + catch (Exception ex) when (!(ex is TemplateValidationException)) + { + context.Errors.Add(ex); + } + + context.Errors.Check(); + } + + return result; + } + public Dictionary EvaluateStepEnvironment( TemplateToken token, DictionaryContextData contextData, + IList expressionFunctions, StringComparer keyComparer) { var result = default(Dictionary); if (token != null && token.Type != TokenType.Null) { - var context = CreateContext(contextData); + var context = CreateContext(contextData, expressionFunctions); try { token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepEnv, token, 0, null, omitHeader: true); @@ -153,15 +187,44 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating return result ?? new Dictionary(keyComparer); } + public Boolean EvaluateStepIf( + TemplateToken token, + DictionaryContextData contextData, + IList expressionFunctions, + IEnumerable> expressionState) + { + var result = default(Boolean?); + + if (token != null && token.Type != TokenType.Null) + { + var context = CreateContext(contextData, expressionFunctions, expressionState); + try + { + token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepIfResult, token, 0, null, omitHeader: true); + context.Errors.Check(); + result = PipelineTemplateConverter.ConvertToIfResult(context, token); + } + catch (Exception ex) when (!(ex is TemplateValidationException)) + { + context.Errors.Add(ex); + } + + context.Errors.Check(); + } + + return result ?? throw new InvalidOperationException("Step if cannot be null"); + } + public Dictionary EvaluateStepInputs( TemplateToken token, - DictionaryContextData contextData) + DictionaryContextData contextData, + IList expressionFunctions) { var result = default(Dictionary); if (token != null && token.Type != TokenType.Null) { - var context = CreateContext(contextData); + var context = CreateContext(contextData, expressionFunctions); try { token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StepWith, token, 0, null, omitHeader: true); @@ -181,13 +244,14 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating public Int32 EvaluateStepTimeout( TemplateToken token, - DictionaryContextData contextData) + DictionaryContextData contextData, + IList expressionFunctions) { var result = default(Int32?); if (token != null && token.Type != TokenType.Null) { - var context = CreateContext(contextData); + var context = CreateContext(contextData, expressionFunctions); try { token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.NumberStepsContext, token, 0, null, omitHeader: true); @@ -207,13 +271,14 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating public JobContainer EvaluateJobContainer( TemplateToken token, - DictionaryContextData contextData) + DictionaryContextData contextData, + IList expressionFunctions) { var result = default(JobContainer); if (token != null && token.Type != TokenType.Null) { - var context = CreateContext(contextData); + var context = CreateContext(contextData, expressionFunctions); try { token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.Container, token, 0, null, omitHeader: true); @@ -233,13 +298,14 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating public Dictionary EvaluateJobOutput( TemplateToken token, - DictionaryContextData contextData) + DictionaryContextData contextData, + IList expressionFunctions) { var result = default(Dictionary); if (token != null && token.Type != TokenType.Null) { - var context = CreateContext(contextData); + var context = CreateContext(contextData, expressionFunctions); try { token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.JobOutputs, token, 0, null, omitHeader: true); @@ -269,13 +335,14 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating public Dictionary EvaluateJobDefaultsRun( TemplateToken token, - DictionaryContextData contextData) + DictionaryContextData contextData, + IList expressionFunctions) { var result = default(Dictionary); if (token != null && token.Type != TokenType.Null) { - var context = CreateContext(contextData); + var context = CreateContext(contextData, expressionFunctions); try { token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.JobDefaultsRun, token, 0, null, omitHeader: true); @@ -305,13 +372,14 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating public IList> EvaluateJobServiceContainers( TemplateToken token, - DictionaryContextData contextData) + DictionaryContextData contextData, + IList expressionFunctions) { var result = default(List>); if (token != null && token.Type != TokenType.Null) { - var context = CreateContext(contextData); + var context = CreateContext(contextData, expressionFunctions); try { token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.Services, token, 0, null, omitHeader: true); @@ -329,62 +397,10 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating return result; } - public Boolean TryEvaluateStepDisplayName( - TemplateToken token, + private TemplateContext CreateContext( DictionaryContextData contextData, - out String stepName) - { - stepName = default(String); - var context = CreateContext(contextData); - - if (token != null && token.Type != TokenType.Null) - { - // We should only evaluate basic expressions if we are sure we have context on all the Named Values and functions - // Otherwise return and use a default name - if (token is BasicExpressionToken expressionToken) - { - ExpressionNode root = null; - try - { - root = new ExpressionParser().ValidateSyntax(expressionToken.Expression, null) as ExpressionNode; - } - catch (Exception exception) - { - context.Errors.Add(exception); - context.Errors.Check(); - } - foreach (var node in root.Traverse()) - { - if (node is NamedValue namedValue && !contextData.ContainsKey(namedValue.Name)) - { - return false; - } - else if (node is Function function && - !context.ExpressionFunctions.Any(item => String.Equals(item.Name, function.Name)) && - !ExpressionConstants.WellKnownFunctions.ContainsKey(function.Name)) - { - return false; - } - } - } - - try - { - token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.StringStepsContext, token, 0, null, omitHeader: true); - context.Errors.Check(); - stepName = PipelineTemplateConverter.ConvertToStepDisplayName(context, token); - } - catch (Exception ex) when (!(ex is TemplateValidationException)) - { - context.Errors.Add(ex); - } - - context.Errors.Check(); - } - return true; - } - - private TemplateContext CreateContext(DictionaryContextData contextData) + IList expressionFunctions, + IEnumerable> expressionState = null) { var result = new TemplateContext { @@ -407,7 +423,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating } } - // Add named context + // Add named values if (contextData != null) { foreach (var pair in contextData) @@ -416,14 +432,46 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating } } - // Compat for new agent against old server - foreach (var name in s_contextNames) + // Add functions + var functionNames = new HashSet(StringComparer.OrdinalIgnoreCase); + if (expressionFunctions?.Count > 0) + { + foreach (var function in expressionFunctions) + { + result.ExpressionFunctions.Add(function); + functionNames.Add(function.Name); + } + } + + // Add missing expression values and expression functions. + // This solves the following problems: + // - Compat for new agent against old server (new contexts not sent down in job message) + // - Evaluating early when all referenced contexts are available, even though all allowed + // contexts may not yet be available. For example, evaluating step display name can often + // be performed early. + foreach (var name in s_expressionValueNames) { if (!result.ExpressionValues.ContainsKey(name)) { result.ExpressionValues[name] = null; } } + foreach (var name in s_expressionFunctionNames) + { + if (!functionNames.Contains(name)) + { + result.ExpressionFunctions.Add(new FunctionInfo(name, 0, Int32.MaxValue)); + } + } + + // Add state + if (expressionState != null) + { + foreach (var pair in expressionState) + { + result.State[pair.Key] = pair.Value; + } + } return result; } @@ -431,9 +479,10 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating private readonly ITraceWriter m_trace; private readonly TemplateSchema m_schema; private readonly IList m_fileTable; - private readonly String[] s_contextNames = new[] + private readonly String[] s_expressionValueNames = new[] { PipelineTemplateConstants.GitHub, + PipelineTemplateConstants.Needs, PipelineTemplateConstants.Strategy, PipelineTemplateConstants.Matrix, PipelineTemplateConstants.Needs, @@ -444,5 +493,13 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating PipelineTemplateConstants.Runner, PipelineTemplateConstants.Env, }; + private readonly String[] s_expressionFunctionNames = new[] + { + PipelineTemplateConstants.Always, + PipelineTemplateConstants.Cancelled, + PipelineTemplateConstants.Failure, + PipelineTemplateConstants.HashFiles, + PipelineTemplateConstants.Success, + }; } } diff --git a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateSchemaFactory.cs b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateSchemaFactory.cs index 55db1ea13..47048322f 100644 --- a/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateSchemaFactory.cs +++ b/src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateSchemaFactory.cs @@ -2,25 +2,35 @@ using System.ComponentModel; using System.IO; using System.Reflection; +using System.Threading; +using System.Threading.Tasks; using GitHub.DistributedTask.ObjectTemplating.Schema; namespace GitHub.DistributedTask.Pipelines.ObjectTemplating { [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class PipelineTemplateSchemaFactory + public static class PipelineTemplateSchemaFactory { - public TemplateSchema CreateSchema() + public static TemplateSchema GetSchema() { - var assembly = Assembly.GetExecutingAssembly(); - var json = default(String); - using (var stream = assembly.GetManifestResourceStream("GitHub.DistributedTask.Pipelines.ObjectTemplating.workflow-v1.0.json")) - using (var streamReader = new StreamReader(stream)) + if (s_schema == null) { - json = streamReader.ReadToEnd(); + var assembly = Assembly.GetExecutingAssembly(); + var json = default(String); + using (var stream = assembly.GetManifestResourceStream("GitHub.DistributedTask.Pipelines.ObjectTemplating.workflow-v1.0.json")) + using (var streamReader = new StreamReader(stream)) + { + json = streamReader.ReadToEnd(); + } + + var objectReader = new JsonObjectReader(null, json); + var schema = TemplateSchema.Load(objectReader); + Interlocked.CompareExchange(ref s_schema, schema, null); } - var objectReader = new JsonObjectReader(null, json); - return TemplateSchema.Load(objectReader); + return s_schema; } + + private static TemplateSchema s_schema; } } diff --git a/src/Sdk/DTPipelines/workflow-v1.0.json b/src/Sdk/DTPipelines/workflow-v1.0.json index 5c8c4c43e..f1e364c5c 100644 --- a/src/Sdk/DTPipelines/workflow-v1.0.json +++ b/src/Sdk/DTPipelines/workflow-v1.0.json @@ -38,8 +38,8 @@ "steps-scope-input-value": { "context": [ "github", - "strategy", "needs", + "strategy", "matrix", "secrets", "steps", @@ -66,9 +66,9 @@ "steps-scope-output-value": { "context": [ "github", + "needs", "strategy", "matrix", - "needs", "secrets", "steps", "inputs", @@ -91,9 +91,9 @@ "description": "Default input values for a steps template", "context": [ "github", + "needs", "strategy", - "matrix", - "needs" + "matrix" ], "one-of": [ "string", @@ -114,9 +114,9 @@ "description": "Output values for a steps template", "context": [ "github", + "needs", "strategy", "matrix", - "needs", "secrets", "steps", "job", @@ -204,6 +204,25 @@ "string": {} }, + "job-if-result": { + "context": [ + "github", + "needs", + "always(0,0)", + "failure(0,MAX)", + "cancelled(0,0)", + "success(0,MAX)" + ], + "one-of": [ + "null", + "boolean", + "number", + "string", + "sequence", + "mapping" + ] + }, + "strategy": { "context": [ "github", @@ -272,9 +291,9 @@ "runs-on": { "context": [ "github", + "needs", "strategy", - "matrix", - "needs" + "matrix" ], "one-of": [ "non-empty-string", @@ -297,10 +316,10 @@ "job-env": { "context": [ "github", - "secrets", + "needs", "strategy", "matrix", - "needs" + "secrets" ], "mapping": { "loose-key-type": "non-empty-string", @@ -444,9 +463,9 @@ "step-if": { "context": [ "github", + "needs", "strategy", "matrix", - "needs", "steps", "job", "runner", @@ -454,7 +473,8 @@ "always(0,0)", "failure(0,0)", "cancelled(0,0)", - "success(0,0)" + "success(0,0)", + "hashFiles(1,255)" ], "string": {} }, @@ -462,9 +482,9 @@ "step-if-in-template": { "context": [ "github", + "needs", "strategy", "matrix", - "needs", "steps", "inputs", "job", @@ -473,11 +493,63 @@ "always(0,0)", "failure(0,0)", "cancelled(0,0)", - "success(0,0)" + "success(0,0)", + "hashFiles(1,255)" ], "string": {} }, + "step-if-result": { + "context": [ + "github", + "strategy", + "matrix", + "steps", + "job", + "runner", + "env", + "always(0,0)", + "failure(0,0)", + "cancelled(0,0)", + "success(0,0)", + "hashFiles(1,255)" + ], + "one-of": [ + "null", + "boolean", + "number", + "string", + "sequence", + "mapping" + ] + }, + + "step-if-result-in-template": { + "context": [ + "github", + "strategy", + "matrix", + "steps", + "inputs", + "job", + "runner", + "env", + "always(0,0)", + "failure(0,0)", + "cancelled(0,0)", + "success(0,0)", + "hashFiles(1,255)" + ], + "one-of": [ + "null", + "boolean", + "number", + "string", + "sequence", + "mapping" + ] + }, + "steps-template-reference": { "mapping": { "properties": { @@ -501,9 +573,9 @@ "steps-template-reference-inputs": { "context": [ "github", + "needs", "strategy", "matrix", - "needs", "secrets", "steps", "job", @@ -519,9 +591,9 @@ "steps-template-reference-inputs-in-template": { "context": [ "github", + "needs", "strategy", "matrix", - "needs", "secrets", "steps", "inputs", @@ -538,14 +610,15 @@ "step-env": { "context": [ "github", + "needs", "strategy", "matrix", - "needs", "secrets", "steps", "job", "runner", - "env" + "env", + "hashFiles(1,255)" ], "mapping": { "loose-key-type": "non-empty-string", @@ -556,15 +629,16 @@ "step-env-in-template": { "context": [ "github", + "needs", "strategy", "matrix", - "needs", "secrets", "steps", "inputs", "job", "runner", - "env" + "env", + "hashFiles(1,255)" ], "mapping": { "loose-key-type": "non-empty-string", @@ -575,14 +649,35 @@ "step-with": { "context": [ "github", + "needs", "strategy", "matrix", - "needs", "secrets", "steps", "job", "runner", - "env" + "env", + "hashFiles(1,255)" + ], + "mapping": { + "loose-key-type": "non-empty-string", + "loose-value-type": "string" + } + }, + + "step-with-in-template": { + "context": [ + "github", + "needs", + "strategy", + "matrix", + "secrets", + "steps", + "inputs", + "job", + "runner", + "env", + "hashFiles(1,255)" ], "mapping": { "loose-key-type": "non-empty-string", @@ -593,9 +688,9 @@ "container": { "context": [ "github", + "needs", "strategy", - "matrix", - "needs" + "matrix" ], "one-of": [ "string", @@ -618,9 +713,9 @@ "services": { "context": [ "github", + "needs", "strategy", - "matrix", - "needs" + "matrix" ], "mapping": { "loose-key-type": "non-empty-string", @@ -631,9 +726,9 @@ "services-container": { "context": [ "github", + "needs", "strategy", - "matrix", - "needs" + "matrix" ], "one-of": [ "non-empty-string", @@ -648,25 +743,6 @@ } }, - "step-with-in-template": { - "context": [ - "github", - "strategy", - "matrix", - "needs", - "secrets", - "steps", - "inputs", - "job", - "runner", - "env" - ], - "mapping": { - "loose-key-type": "non-empty-string", - "loose-value-type": "string" - } - }, - "non-empty-string": { "string": { "require-non-empty": true @@ -682,9 +758,9 @@ "boolean-strategy-context": { "context": [ "github", + "needs", "strategy", - "matrix", - "needs" + "matrix" ], "boolean": {} }, @@ -692,9 +768,9 @@ "number-strategy-context": { "context": [ "github", + "needs", "strategy", - "matrix", - "needs" + "matrix" ], "number": {} }, @@ -702,9 +778,9 @@ "string-strategy-context": { "context": [ "github", + "needs", "strategy", - "matrix", - "needs" + "matrix" ], "string": {} }, @@ -712,14 +788,15 @@ "boolean-steps-context": { "context": [ "github", + "needs", "strategy", "matrix", - "needs", "secrets", "steps", "job", "runner", - "env" + "env", + "hashFiles(1,255)" ], "boolean": {} }, @@ -727,15 +804,16 @@ "boolean-steps-context-in-template": { "context": [ "github", + "needs", "strategy", "matrix", - "needs", "secrets", "steps", "inputs", "job", "runner", - "env" + "env", + "hashFiles(1,255)" ], "boolean": {} }, @@ -743,14 +821,15 @@ "number-steps-context": { "context": [ "github", + "needs", "strategy", "matrix", - "needs", "secrets", "steps", "job", "runner", - "env" + "env", + "hashFiles(1,255)" ], "number": {} }, @@ -758,15 +837,16 @@ "number-steps-context-in-template": { "context": [ "github", + "needs", "strategy", "matrix", - "needs", "secrets", "steps", "inputs", "job", "runner", - "env" + "env", + "hashFiles(1,255)" ], "number": {} }, @@ -774,9 +854,9 @@ "string-runner-context": { "context": [ "github", + "needs", "strategy", "matrix", - "needs", "secrets", "steps", "job", @@ -789,14 +869,15 @@ "string-steps-context": { "context": [ "github", + "needs", "strategy", "matrix", - "needs", "secrets", "steps", "job", "runner", - "env" + "env", + "hashFiles(1,255)" ], "string": {} }, @@ -804,15 +885,16 @@ "string-steps-context-in-template": { "context": [ "github", + "needs", "strategy", "matrix", - "needs", "secrets", "steps", "inputs", "job", "runner", - "env" + "env", + "hashFiles(1,255)" ], "string": {} } diff --git a/src/Test/L0/Worker/ActionManagerL0.cs b/src/Test/L0/Worker/ActionManagerL0.cs index 51e09bff1..b5a2ed5db 100644 --- a/src/Test/L0/Worker/ActionManagerL0.cs +++ b/src/Test/L0/Worker/ActionManagerL0.cs @@ -1,4 +1,6 @@ -using GitHub.DistributedTask.ObjectTemplating.Tokens; +using GitHub.DistributedTask.Expressions2; +using GitHub.DistributedTask.ObjectTemplating.Tokens; +using GitHub.DistributedTask.Pipelines.ContextData; using GitHub.DistributedTask.WebApi; using GitHub.Runner.Common.Util; using GitHub.Runner.Worker; @@ -1600,6 +1602,8 @@ runs: _ec = new Mock(); _ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token); _ec.Setup(x => x.Variables).Returns(new Variables(_hc, new Dictionary())); + _ec.Setup(x => x.ExpressionValues).Returns(new DictionaryContextData()); + _ec.Setup(x => x.ExpressionFunctions).Returns(new List()); _ec.Setup(x => x.Write(It.IsAny(), It.IsAny())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); }); _ec.Setup(x => x.AddIssue(It.IsAny(), It.IsAny())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); }); _ec.Setup(x => x.GetGitHubContext("workspace")).Returns(Path.Combine(_workFolder, "actions", "actions")); diff --git a/src/Test/L0/Worker/ActionManifestManagerL0.cs b/src/Test/L0/Worker/ActionManifestManagerL0.cs index 757610706..ca789b7f3 100644 --- a/src/Test/L0/Worker/ActionManifestManagerL0.cs +++ b/src/Test/L0/Worker/ActionManifestManagerL0.cs @@ -1,7 +1,9 @@ +using GitHub.DistributedTask.Expressions2; using GitHub.DistributedTask.ObjectTemplating.Tokens; using GitHub.DistributedTask.Pipelines.ContextData; using GitHub.DistributedTask.WebApi; using GitHub.Runner.Worker; +using GitHub.Runner.Worker.Expressions; using Moq; using System; using System.Collections.Generic; @@ -533,26 +535,26 @@ namespace GitHub.Runner.Common.Tests.Worker var actionManifest = new ActionManifestManager(); actionManifest.Initialize(_hc); - var githubContext = new DictionaryContextData(); - githubContext.Add("ref", new StringContextData("refs/heads/master")); - - var evaluateContext = new Dictionary(StringComparer.OrdinalIgnoreCase); - evaluateContext["github"] = githubContext; - evaluateContext["strategy"] = new DictionaryContextData(); - evaluateContext["matrix"] = new DictionaryContextData(); - evaluateContext["steps"] = new DictionaryContextData(); - evaluateContext["job"] = new DictionaryContextData(); - evaluateContext["runner"] = new DictionaryContextData(); - evaluateContext["env"] = new DictionaryContextData(); + _ec.Object.ExpressionValues["github"] = new DictionaryContextData + { + { "ref", new StringContextData("refs/heads/master") }, + }; + _ec.Object.ExpressionValues["strategy"] = new DictionaryContextData(); + _ec.Object.ExpressionValues["matrix"] = new DictionaryContextData(); + _ec.Object.ExpressionValues["steps"] = new DictionaryContextData(); + _ec.Object.ExpressionValues["job"] = new DictionaryContextData(); + _ec.Object.ExpressionValues["runner"] = new DictionaryContextData(); + _ec.Object.ExpressionValues["env"] = new DictionaryContextData(); + _ec.Object.ExpressionFunctions.Add(new FunctionInfo("hashFiles", 1, 255)); //Act - var result = actionManifest.EvaluateDefaultInput(_ec.Object, "testInput", new StringToken(null, null, null, "defaultValue"), evaluateContext); + var result = actionManifest.EvaluateDefaultInput(_ec.Object, "testInput", new StringToken(null, null, null, "defaultValue")); //Assert Assert.Equal("defaultValue", result); //Act - result = actionManifest.EvaluateDefaultInput(_ec.Object, "testInput", new BasicExpressionToken(null, null, null, "github.ref"), evaluateContext); + result = actionManifest.EvaluateDefaultInput(_ec.Object, "testInput", new BasicExpressionToken(null, null, null, "github.ref")); //Assert Assert.Equal("refs/heads/master", result); @@ -575,6 +577,8 @@ namespace GitHub.Runner.Common.Tests.Worker _ec.Setup(x => x.WriteDebug).Returns(true); _ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token); _ec.Setup(x => x.Variables).Returns(new Variables(_hc, new Dictionary())); + _ec.Setup(x => x.ExpressionValues).Returns(new DictionaryContextData()); + _ec.Setup(x => x.ExpressionFunctions).Returns(new List()); _ec.Setup(x => x.Write(It.IsAny(), It.IsAny())).Callback((string tag, string message) => { _hc.GetTrace().Info($"{tag}{message}"); }); _ec.Setup(x => x.AddIssue(It.IsAny(), It.IsAny())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); }); } diff --git a/src/Test/L0/Worker/ActionRunnerL0.cs b/src/Test/L0/Worker/ActionRunnerL0.cs index b0d0c0ff4..24ff73f4f 100644 --- a/src/Test/L0/Worker/ActionRunnerL0.cs +++ b/src/Test/L0/Worker/ActionRunnerL0.cs @@ -1,4 +1,5 @@ -using GitHub.DistributedTask.ObjectTemplating.Tokens; +using GitHub.DistributedTask.Expressions2; +using GitHub.DistributedTask.ObjectTemplating.Tokens; using GitHub.DistributedTask.Pipelines; using GitHub.DistributedTask.Pipelines.ContextData; using GitHub.DistributedTask.WebApi; @@ -322,6 +323,7 @@ namespace GitHub.Runner.Common.Tests.Worker _ec = new Mock(); _ec.Setup(x => x.ExpressionValues).Returns(_context); + _ec.Setup(x => x.ExpressionFunctions).Returns(new List()); _ec.Setup(x => x.IntraActionState).Returns(new Dictionary()); _ec.Setup(x => x.EnvironmentVariables).Returns(new Dictionary()); _ec.Setup(x => x.SetGitHubContext(It.IsAny(), It.IsAny())); diff --git a/src/Test/L0/Worker/ExpressionManagerL0.cs b/src/Test/L0/Worker/Expressions/ConditionFunctionsL0.cs similarity index 63% rename from src/Test/L0/Worker/ExpressionManagerL0.cs rename to src/Test/L0/Worker/Expressions/ConditionFunctionsL0.cs index 9bdcdeeee..4ffcdc9dc 100644 --- a/src/Test/L0/Worker/ExpressionManagerL0.cs +++ b/src/Test/L0/Worker/Expressions/ConditionFunctionsL0.cs @@ -1,20 +1,20 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; +using GitHub.DistributedTask.Expressions2; +using GitHub.DistributedTask.ObjectTemplating; +using GitHub.DistributedTask.Pipelines.ObjectTemplating; using GitHub.DistributedTask.WebApi; using GitHub.Runner.Worker; +using GitHub.Runner.Worker.Expressions; using Moq; using Xunit; -using GitHub.DistributedTask.Expressions2; -using GitHub.DistributedTask.Pipelines.ContextData; -namespace GitHub.Runner.Common.Tests.Worker +namespace GitHub.Runner.Common.Tests.Worker.Expressions { - public sealed class ExpressionManagerL0 + public sealed class ConditionFunctionsL0 { - private Mock _ec; - private ExpressionManager _expressionManager; - private DictionaryContextData _expressions; + private TemplateContext _templateContext; private JobContext _jobContext; [Fact] @@ -38,7 +38,7 @@ namespace GitHub.Runner.Common.Tests.Worker _jobContext.Status = variableSet.JobStatus; // Act. - bool actual = _expressionManager.Evaluate(_ec.Object, "always()").Value; + bool actual = Evaluate("always()"); // Assert. Assert.Equal(variableSet.Expected, actual); @@ -68,7 +68,7 @@ namespace GitHub.Runner.Common.Tests.Worker _jobContext.Status = variableSet.JobStatus; // Act. - bool actual = _expressionManager.Evaluate(_ec.Object, "cancelled()").Value; + bool actual = Evaluate("cancelled()"); // Assert. Assert.Equal(variableSet.Expected, actual); @@ -97,7 +97,7 @@ namespace GitHub.Runner.Common.Tests.Worker _jobContext.Status = variableSet.JobStatus; // Act. - bool actual = _expressionManager.Evaluate(_ec.Object, "failure()").Value; + bool actual = Evaluate("failure()"); // Assert. Assert.Equal(variableSet.Expected, actual); @@ -126,37 +126,7 @@ namespace GitHub.Runner.Common.Tests.Worker _jobContext.Status = variableSet.JobStatus; // Act. - bool actual = _expressionManager.Evaluate(_ec.Object, "success()").Value; - - // Assert. - Assert.Equal(variableSet.Expected, actual); - } - } - } - - [Fact] - [Trait("Level", "L0")] - [Trait("Category", "Worker")] - public void ContextNamedValue() - { - using (TestHostContext hc = CreateTestContext()) - { - // Arrange. - var variableSets = new[] - { - new { Condition = "github.ref == 'refs/heads/master'", VariableName = "ref", VariableValue = "refs/heads/master", Expected = true }, - new { Condition = "github['ref'] == 'refs/heads/master'", VariableName = "ref", VariableValue = "refs/heads/master", Expected = true }, - new { Condition = "github.nosuch || '' == ''", VariableName = "ref", VariableValue = "refs/heads/master", Expected = true }, - new { Condition = "github['ref'] == 'refs/heads/release'", VariableName = "ref", VariableValue = "refs/heads/master", Expected = false }, - new { Condition = "github.ref == 'refs/heads/release'", VariableName = "ref", VariableValue = "refs/heads/master", Expected = false }, - }; - foreach (var variableSet in variableSets) - { - InitializeExecutionContext(hc); - _ec.Object.ExpressionValues["github"] = new GitHubContext() { { variableSet.VariableName, new StringContextData(variableSet.VariableValue) } }; - - // Act. - bool actual = _expressionManager.Evaluate(_ec.Object, variableSet.Condition).Value; + bool actual = Evaluate("success()"); // Assert. Assert.Equal(variableSet.Expected, actual); @@ -166,21 +136,34 @@ namespace GitHub.Runner.Common.Tests.Worker private TestHostContext CreateTestContext([CallerMemberName] String testName = "") { - var hc = new TestHostContext(this, testName); - _expressionManager = new ExpressionManager(); - _expressionManager.Initialize(hc); - return hc; + return new TestHostContext(this, testName); } private void InitializeExecutionContext(TestHostContext hc) { - _expressions = new DictionaryContextData(); _jobContext = new JobContext(); - _ec = new Mock(); - _ec.SetupAllProperties(); - _ec.Setup(x => x.ExpressionValues).Returns(_expressions); - _ec.Setup(x => x.JobContext).Returns(_jobContext); + var executionContext = new Mock(); + executionContext.SetupAllProperties(); + executionContext.Setup(x => x.JobContext).Returns(_jobContext); + + _templateContext = new TemplateContext(); + _templateContext.State[nameof(IExecutionContext)] = executionContext.Object; + } + + private bool Evaluate(string expression) + { + var parser = new ExpressionParser(); + var functions = new IFunctionInfo[] + { + new FunctionInfo(PipelineTemplateConstants.Always, 0, 0), + new FunctionInfo(PipelineTemplateConstants.Cancelled, 0, 0), + new FunctionInfo(PipelineTemplateConstants.Failure, 0, 0), + new FunctionInfo(PipelineTemplateConstants.Success, 0, 0), + }; + var tree = parser.CreateTree(expression, null, null, functions); + var result = tree.Evaluate(null, null, _templateContext, null); + return result.IsTruthy; } } } diff --git a/src/Test/L0/Worker/JobExtensionL0.cs b/src/Test/L0/Worker/JobExtensionL0.cs index bedd24c36..a8c4573e6 100644 --- a/src/Test/L0/Worker/JobExtensionL0.cs +++ b/src/Test/L0/Worker/JobExtensionL0.cs @@ -22,7 +22,6 @@ namespace GitHub.Runner.Common.Tests.Worker private Mock _jobServerQueue; private Mock _config; private Mock _logger; - private Mock _express; private Mock _containerProvider; private Mock _diagnosticLogManager; @@ -35,7 +34,6 @@ namespace GitHub.Runner.Common.Tests.Worker _jobServerQueue = new Mock(); _config = new Mock(); _logger = new Mock(); - _express = new Mock(); _containerProvider = new Mock(); _diagnosticLogManager = new Mock(); _directoryManager = new Mock(); @@ -108,7 +106,6 @@ namespace GitHub.Runner.Common.Tests.Worker hc.SetSingleton(_actionManager.Object); hc.SetSingleton(_config.Object); hc.SetSingleton(_jobServerQueue.Object); - hc.SetSingleton(_express.Object); hc.SetSingleton(_containerProvider.Object); hc.SetSingleton(_directoryManager.Object); hc.SetSingleton(_diagnosticLogManager.Object); diff --git a/src/Test/L0/Worker/JobRunnerL0.cs b/src/Test/L0/Worker/JobRunnerL0.cs index de09a4c96..198d378b9 100644 --- a/src/Test/L0/Worker/JobRunnerL0.cs +++ b/src/Test/L0/Worker/JobRunnerL0.cs @@ -53,9 +53,6 @@ namespace GitHub.Runner.Common.Tests.Worker } _tokenSource = new CancellationTokenSource(); - var expressionManager = new ExpressionManager(); - expressionManager.Initialize(hc); - hc.SetSingleton(expressionManager); _jobRunner = new JobRunner(); _jobRunner.Initialize(hc); diff --git a/src/Test/L0/Worker/StepsRunnerL0.cs b/src/Test/L0/Worker/StepsRunnerL0.cs index 23534813f..c2996fab5 100644 --- a/src/Test/L0/Worker/StepsRunnerL0.cs +++ b/src/Test/L0/Worker/StepsRunnerL0.cs @@ -1,17 +1,17 @@ -using GitHub.DistributedTask.WebApi; -using GitHub.Runner.Worker; -using Moq; -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; +using Moq; using Xunit; using GitHub.DistributedTask.Expressions2; using GitHub.DistributedTask.Pipelines.ContextData; using GitHub.DistributedTask.ObjectTemplating.Tokens; +using GitHub.DistributedTask.WebApi; using GitHub.Runner.Common.Util; +using GitHub.Runner.Worker; namespace GitHub.Runner.Common.Tests.Worker { @@ -27,9 +27,6 @@ namespace GitHub.Runner.Common.Tests.Worker private TestHostContext CreateTestContext([CallerMemberName] String testName = "") { var hc = new TestHostContext(this, testName); - var expressionManager = new ExpressionManager(); - expressionManager.Initialize(hc); - hc.SetSingleton(expressionManager); Dictionary variablesToCopy = new Dictionary(); _variables = new Variables( hostContext: hc, @@ -49,6 +46,7 @@ namespace GitHub.Runner.Common.Tests.Worker _contexts["runner"] = new DictionaryContextData(); _contexts["job"] = _jobContext; _ec.Setup(x => x.ExpressionValues).Returns(_contexts); + _ec.Setup(x => x.ExpressionFunctions).Returns(new List()); _ec.Setup(x => x.JobContext).Returns(_jobContext); _stepContext = new StepsContext(); @@ -383,16 +381,11 @@ namespace GitHub.Runner.Common.Tests.Worker { using (TestHostContext hc = CreateTestContext()) { - var expressionManager = new Mock(); - expressionManager.Object.Initialize(hc); - hc.SetSingleton(expressionManager.Object); - expressionManager.Setup(x => x.Evaluate(It.IsAny(), It.IsAny(), It.IsAny())).Throws(new Exception()); - // Arrange. var variableSets = new[] { - new[] { CreateStep(hc, TaskResult.Succeeded, "success()") }, - new[] { CreateStep(hc, TaskResult.Succeeded, "success()") }, + new[] { CreateStep(hc, TaskResult.Succeeded, "fromJson('not json')") }, + new[] { CreateStep(hc, TaskResult.Succeeded, "fromJson('not json')") }, }; foreach (var variableSet in variableSets) { @@ -610,6 +603,7 @@ namespace GitHub.Runner.Common.Tests.Worker stepContext.Setup(x => x.Variables).Returns(_variables); stepContext.Setup(x => x.EnvironmentVariables).Returns(_env); stepContext.Setup(x => x.ExpressionValues).Returns(_contexts); + stepContext.Setup(x => x.ExpressionFunctions).Returns(new List()); stepContext.Setup(x => x.JobContext).Returns(_jobContext); stepContext.Setup(x => x.StepsContext).Returns(_stepContext); stepContext.Setup(x => x.ContextName).Returns(step.Object.Action.ContextName);