using GitHub.DistributedTask.Expressions2; using GitHub.DistributedTask.ObjectTemplating.Tokens; using GitHub.DistributedTask.Pipelines; using GitHub.DistributedTask.Pipelines.ContextData; using GitHub.DistributedTask.WebApi; using GitHub.Runner.Common.Util; using GitHub.Runner.Worker; using GitHub.Runner.Worker.Container; using GitHub.Runner.Worker.Handlers; using Moq; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Xunit; using Pipelines = GitHub.DistributedTask.Pipelines; namespace GitHub.Runner.Common.Tests.Worker { public sealed class ActionRunnerL0 { private CancellationTokenSource _ecTokenSource; private Mock _handlerFactory; private Mock _actionManager; private Mock _defaultStepHost; private Mock _ec; private TestHostContext _hc; private ActionRunner _actionRunner; private IActionManifestManager _actionManifestManager; private DictionaryContextData _context = new DictionaryContextData(); [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public async void MergeDefaultInputs() { //Arrange Setup(); var actionId = Guid.NewGuid(); var actionInputs = new MappingToken(null, null, null); actionInputs.Add(new StringToken(null, null, null, "input1"), new StringToken(null, null, null, "test1")); actionInputs.Add(new StringToken(null, null, null, "input2"), new StringToken(null, null, null, "test2")); var action = new Pipelines.ActionStep() { Name = "action", Id = actionId, Reference = new Pipelines.ContainerRegistryReference() { Image = "ubuntu:16.04" }, Inputs = actionInputs }; _actionRunner.Action = action; Dictionary finialInputs = new Dictionary(); _handlerFactory.Setup(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny())) .Callback((IExecutionContext executionContext, Pipelines.ActionStepDefinitionReference actionReference, IStepHost stepHost, ActionExecutionData data, Dictionary inputs, Dictionary environment, Variables runtimeVariables, string taskDirectory) => { finialInputs = inputs; }) .Returns(new Mock().Object); //Act await _actionRunner.RunAsync(); foreach (var input in finialInputs) { _hc.GetTrace().Info($"Input: {input.Key}={input.Value}"); } //Assert Assert.Equal("test1", finialInputs["input1"]); Assert.Equal("test2", finialInputs["input2"]); Assert.Equal("github", finialInputs["input3"]); } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public async void WriteEventPayload() { //Arrange Setup(); var actionId = Guid.NewGuid(); var actionInputs = new MappingToken(null, null, null); actionInputs.Add(new StringToken(null, null, null, "input1"), new StringToken(null, null, null, "test1")); actionInputs.Add(new StringToken(null, null, null, "input2"), new StringToken(null, null, null, "test2")); var action = new Pipelines.ActionStep() { Name = "action", Id = actionId, Reference = new Pipelines.ContainerRegistryReference() { Image = "ubuntu:16.04" }, Inputs = actionInputs }; _actionRunner.Action = action; Dictionary finialInputs = new Dictionary(); _handlerFactory.Setup(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny())) .Callback((IExecutionContext executionContext, Pipelines.ActionStepDefinitionReference actionReference, IStepHost stepHost, ActionExecutionData data, Dictionary inputs, Dictionary environment, Variables runtimeVariables, string taskDirectory) => { finialInputs = inputs; }) .Returns(new Mock().Object); //Act await _actionRunner.RunAsync(); //Assert _ec.Verify(x => x.SetGitHubContext("event_path", Path.Combine(_hc.GetDirectory(WellKnownDirectory.Temp), "_github_workflow", "event.json")), Times.Once); } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void EvaluateLegacyDisplayName() { // Arrange Setup(); var actionInputs = new MappingToken(null, null, null); actionInputs.Add(new StringToken(null, null, null, "script"), new StringToken(null, null, null, "echo hello world")); var actionId = Guid.NewGuid(); var actionDisplayName = "Run echo hello world"; var action = new Pipelines.ActionStep() { Name = "action", Id = actionId, DisplayName = actionDisplayName, Inputs = actionInputs, }; _actionRunner.Action = action; var matrixData = new DictionaryContextData { ["node"] = new NumberContextData(8) }; _context.Add("matrix", matrixData); // Act // Should not do anything if we don't have a displayNameToken to expand var didUpdateDisplayName = _actionRunner.TryEvaluateDisplayName(_context, _actionRunner.ExecutionContext); // Assert Assert.False(didUpdateDisplayName); Assert.Equal(actionDisplayName, _actionRunner.DisplayName); } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void EvaluateExpansionOfDisplayNameToken() { // Arrange Setup(); var actionId = Guid.NewGuid(); var action = new Pipelines.ActionStep() { Name = "action", Id = actionId, DisplayNameToken = new BasicExpressionToken(null, null, null, "matrix.node"), }; _actionRunner.Action = action; var expectedString = "8"; var matrixData = new DictionaryContextData { ["node"] = new StringContextData(expectedString) }; _context.Add("matrix", matrixData); // Act // Should expand the displaynameToken and set the display name to that var didUpdateDisplayName = _actionRunner.TryEvaluateDisplayName(_context, _actionRunner.ExecutionContext); // Assert Assert.True(didUpdateDisplayName); Assert.Equal(expectedString, _actionRunner.DisplayName); } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void EvaluateExpansionOfScriptDisplayName() { // Arrange Setup(); var actionInputs = new MappingToken(null, null, null); actionInputs.Add(new StringToken(null, null, null, "script"), new BasicExpressionToken(null, null, null, "matrix.node")); var actionId = Guid.NewGuid(); var action = new Pipelines.ActionStep() { Name = "action", Id = actionId, Inputs = actionInputs, Reference = new Pipelines.ScriptReference() }; _actionRunner.Action = action; var matrixData = new DictionaryContextData { ["node"] = new StringContextData("8") }; _context.Add("matrix", matrixData); // Act // Should expand the displaynameToken and set the display name to that var didUpdateDisplayName = _actionRunner.TryEvaluateDisplayName(_context, _actionRunner.ExecutionContext); // Assert Assert.True(didUpdateDisplayName); Assert.Equal("Run 8", _actionRunner.DisplayName); } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void EvaluateExpansionOfContainerDisplayName() { // Arrange Setup(); var actionId = Guid.NewGuid(); var action = new Pipelines.ActionStep() { Name = "action", Id = actionId, Reference = new Pipelines.ContainerRegistryReference() { Image = "TestImageName:latest" } }; _actionRunner.Action = action; // Act // Should expand the displaynameToken and set the display name to that var didUpdateDisplayName = _actionRunner.TryEvaluateDisplayName(_context, _actionRunner.ExecutionContext); // Assert Assert.True(didUpdateDisplayName); Assert.Equal("Run TestImageName:latest", _actionRunner.DisplayName); } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void EvaluateDisplayNameWithoutContext() { // Arrange Setup(); var actionId = Guid.NewGuid(); var action = new Pipelines.ActionStep() { Name = "action", Id = actionId, DisplayNameToken = new BasicExpressionToken(null, null, null, "matrix.node"), }; _actionRunner.Action = action; // Act // Should not do anything if we don't have context on the display name var didUpdateDisplayName = _actionRunner.TryEvaluateDisplayName(_context, _actionRunner.ExecutionContext); // Assert Assert.False(didUpdateDisplayName); // Should use the pretty display name until we can eval Assert.Equal("${{ matrix.node }}", _actionRunner.DisplayName); } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public async void WarnInvalidInputs() { //Arrange Setup(); var actionId = Guid.NewGuid(); var actionInputs = new MappingToken(null, null, null); actionInputs.Add(new StringToken(null, null, null, "input1"), new StringToken(null, null, null, "test1")); actionInputs.Add(new StringToken(null, null, null, "input2"), new StringToken(null, null, null, "test2")); actionInputs.Add(new StringToken(null, null, null, "invalid1"), new StringToken(null, null, null, "invalid1")); actionInputs.Add(new StringToken(null, null, null, "invalid2"), new StringToken(null, null, null, "invalid2")); var action = new Pipelines.ActionStep() { Name = "action", Id = actionId, Reference = new Pipelines.RepositoryPathReference() { Name = "actions/runner", Ref = "v1" }, Inputs = actionInputs }; _actionRunner.Action = action; Dictionary finialInputs = new Dictionary(); _handlerFactory.Setup(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny())) .Callback((IExecutionContext executionContext, Pipelines.ActionStepDefinitionReference actionReference, IStepHost stepHost, ActionExecutionData data, Dictionary inputs, Dictionary environment, Variables runtimeVariables, string taskDirectory) => { finialInputs = inputs; }) .Returns(new Mock().Object); //Act await _actionRunner.RunAsync(); foreach (var input in finialInputs) { _hc.GetTrace().Info($"Input: {input.Key}={input.Value}"); } //Assert Assert.Equal("test1", finialInputs["input1"]); Assert.Equal("test2", finialInputs["input2"]); Assert.Equal("github", finialInputs["input3"]); Assert.Equal("invalid1", finialInputs["invalid1"]); Assert.Equal("invalid2", finialInputs["invalid2"]); _ec.Verify(x => x.AddIssue(It.Is(s => s.Message.Contains("Unexpected input 'invalid1'")), It.IsAny()), Times.Once); _ec.Verify(x => x.AddIssue(It.Is(s => s.Message.Contains("Unexpected input 'invalid2'")), It.IsAny()), Times.Once); } private void Setup([CallerMemberName] string name = "") { _ecTokenSource?.Dispose(); _ecTokenSource = new CancellationTokenSource(); // Test host context. _hc = new TestHostContext(this, name); var actionInputs = new MappingToken(null, null, null); actionInputs.Add(new StringToken(null, null, null, "input1"), new StringToken(null, null, null, "input1")); actionInputs.Add(new StringToken(null, null, null, "input2"), new StringToken(null, null, null, "")); actionInputs.Add(new StringToken(null, null, null, "input3"), new StringToken(null, null, null, "github")); var actionDefinition = new Definition() { Directory = _hc.GetDirectory(WellKnownDirectory.Work), Data = new ActionDefinitionData() { Name = name, Description = name, Inputs = actionInputs, Execution = new ScriptActionExecutionData() } }; // Mocks. _actionManager = new Mock(); _actionManager.Setup(x => x.LoadAction(It.IsAny(), It.IsAny())).Returns(actionDefinition); _handlerFactory = new Mock(); _defaultStepHost = new Mock(); _actionManifestManager = new ActionManifestManager(); _actionManifestManager.Initialize(_hc); var githubContext = new GitHubContext(); githubContext.Add("event", JToken.Parse("{\"foo\":\"bar\"}").ToPipelineContextData()); _context.Add("github", githubContext); #if OS_WINDOWS _context["env"] = new DictionaryContextData(); #else _context["env"] = new CaseSensitiveDictionaryContextData(); #endif _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())); _ec.Setup(x => x.GetGitHubContext(It.IsAny())).Returns("{\"foo\":\"bar\"}"); _ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token); _ec.Setup(x => x.Variables).Returns(new Variables(_hc, new Dictionary())); _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}"); }); _hc.SetSingleton(_actionManager.Object); _hc.SetSingleton(_handlerFactory.Object); _hc.SetSingleton(_actionManifestManager); _hc.EnqueueInstance(_defaultStepHost.Object); // Instance to test. _actionRunner = new ActionRunner(); _actionRunner.Initialize(_hc); _actionRunner.ExecutionContext = _ec.Object; } } }