using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using GitHub.DistributedTask.Pipelines.ContextData; using GitHub.DistributedTask.WebApi; using GitHub.Runner.Worker; using GitHub.Runner.Worker.Container; using GitHub.Runner.Worker.Handlers; using Moq; using Xunit; using GitHub.DistributedTask.ObjectTemplating.Tokens; using Pipelines = GitHub.DistributedTask.Pipelines; namespace GitHub.Runner.Common.Tests.Worker { public sealed class ExecutionContextL0 { [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void AddIssue_CountWarningsErrors() { using (TestHostContext hc = CreateTestContext()) { // Arrange: Create a job request message. TaskOrchestrationPlanReference plan = new(); TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, Id = "github", Version = "sha1" }); jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData(); // Arrange: Setup the paging logger. var pagingLogger = new Mock(); var jobServerQueue = new Mock(); jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.IsAny())); hc.EnqueueInstance(pagingLogger.Object); hc.SetSingleton(jobServerQueue.Object); var ec = new Runner.Worker.ExecutionContext(); ec.Initialize(hc); // Act. ec.InitializeJob(jobRequest, CancellationToken.None); // Flood the ExecutionContext with errors and warnings (past its max capacity of 10). ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default); ec.Complete(); // Assert. jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.Is(t => t.ErrorCount > 0)), Times.AtLeast(10)); jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.Is(t => t.WarningCount > 0)), Times.AtLeast(10)); jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.Is(t => t.Issues.Where(i => i.Type == IssueType.Error).Count() == 10)), Times.AtLeastOnce); jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.Is(t => t.Issues.Where(i => i.Type == IssueType.Warning).Count() == 10)), Times.AtLeastOnce); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void ApplyContinueOnError_CheckResultAndOutcome() { using (TestHostContext hc = CreateTestContext()) { // Arrange: Create a job request message. TaskOrchestrationPlanReference plan = new(); TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, Id = "github", Version = "sha1" }); jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData(); jobRequest.Variables["ACTIONS_STEP_DEBUG"] = "true"; // Arrange: Setup the paging logger. var pagingLogger = new Mock(); var jobServerQueue = new Mock(); jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.IsAny())); jobServerQueue.Setup(x => x.QueueWebConsoleLine(It.IsAny(), It.IsAny(), It.IsAny())).Callback((Guid id, string msg, long? lineNumber) => { hc.GetTrace().Info(msg); }); hc.EnqueueInstance(pagingLogger.Object); hc.SetSingleton(jobServerQueue.Object); var ec = new Runner.Worker.ExecutionContext(); ec.Initialize(hc); // Act. ec.InitializeJob(jobRequest, CancellationToken.None); foreach (var tc in new List<(TemplateToken token, TaskResult result, TaskResult? expectedResult, TaskResult? expectedOutcome)> { (token: new BooleanToken(null, null, null, true), result: TaskResult.Failed, expectedResult: TaskResult.Succeeded, expectedOutcome: TaskResult.Failed), (token: new BooleanToken(null, null, null, true), result: TaskResult.Succeeded, expectedResult: TaskResult.Succeeded, expectedOutcome: null), (token: new BooleanToken(null, null, null, true), result: TaskResult.Canceled, expectedResult: TaskResult.Canceled, expectedOutcome: null), (token: new BooleanToken(null, null, null, false), result: TaskResult.Failed, expectedResult: TaskResult.Failed, expectedOutcome: null), (token: new BooleanToken(null, null, null, false), result: TaskResult.Succeeded, expectedResult: TaskResult.Succeeded, expectedOutcome: null), (token: new BooleanToken(null, null, null, false), result: TaskResult.Canceled, expectedResult: TaskResult.Canceled, expectedOutcome: null), }) { ec.Result = tc.result; ec.Outcome = null; ec.ApplyContinueOnError(tc.token); Assert.Equal(ec.Result, tc.expectedResult); Assert.Equal(ec.Outcome, tc.expectedOutcome); } } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void AddIssue_TrimMessageSize() { using (TestHostContext hc = CreateTestContext()) { // Arrange: Create a job request message. TaskOrchestrationPlanReference plan = new(); TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, Id = "github", Version = "sha1" }); jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData(); // Arrange: Setup the paging logger. var pagingLogger = new Mock(); var jobServerQueue = new Mock(); jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.IsAny())); hc.EnqueueInstance(pagingLogger.Object); hc.SetSingleton(jobServerQueue.Object); var ec = new Runner.Worker.ExecutionContext(); ec.Initialize(hc); // Act. ec.InitializeJob(jobRequest, CancellationToken.None); var bigMessage = ""; for (var i = 0; i < 5000; i++) { bigMessage += "a"; } ec.AddIssue(new Issue() { Type = IssueType.Error, Message = bigMessage }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = bigMessage }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = bigMessage }, ExecutionContextLogOptions.Default); ec.Complete(); // Assert. jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.Is(t => t.Issues.Where(i => i.Type == IssueType.Error).Single().Message.Length <= 4096)), Times.AtLeastOnce); jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.Is(t => t.Issues.Where(i => i.Type == IssueType.Warning).Single().Message.Length <= 4096)), Times.AtLeastOnce); jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.Is(t => t.Issues.Where(i => i.Type == IssueType.Notice).Single().Message.Length <= 4096)), Times.AtLeastOnce); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void AddIssue_OverrideLogMessage() { using (TestHostContext hc = CreateTestContext()) { // Arrange: Create a job request message. TaskOrchestrationPlanReference plan = new(); TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, Id = "github", Version = "sha1" }); jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData(); // Arrange: Setup the paging logger. var pagingLogger = new Mock(); var jobServerQueue = new Mock(); hc.EnqueueInstance(pagingLogger.Object); hc.SetSingleton(jobServerQueue.Object); var ec = new Runner.Worker.ExecutionContext(); ec.Initialize(hc); // Act. ec.InitializeJob(jobRequest, CancellationToken.None); var issueMessage = "Message embedded in issue."; var overrideMessage = "Message override."; var options = new ExecutionContextLogOptions(true, overrideMessage); ec.AddIssue(new Issue() { Type = IssueType.Error, Message = issueMessage }, options); ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = issueMessage }, options); ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = issueMessage }, options); // Finally, add a variation that DOESN'T override the message. ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = issueMessage }, ExecutionContextLogOptions.Default); ec.Complete(); // Assert. jobServerQueue.Verify(x => x.QueueWebConsoleLine(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(4)); jobServerQueue.Verify(x => x.QueueWebConsoleLine(It.IsAny(), It.Is(text => text.EndsWith(overrideMessage)), It.IsAny()), Times.Exactly(3)); jobServerQueue.Verify(x => x.QueueWebConsoleLine(It.IsAny(), It.Is(text => text.EndsWith(issueMessage)), It.IsAny()), Times.Exactly(1)); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void AddIssue_AddStepAndLineNumberInformation() { using (TestHostContext hc = CreateTestContext()) { TaskOrchestrationPlanReference plan = new(); TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, Id = "github", Version = "sha1" }); jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData(); // Arrange: Setup the paging logger. var pagingLogger = new Mock(); var pagingLogger2 = new Mock(); var jobServerQueue = new Mock(); jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.IsAny())); hc.EnqueueInstance(pagingLogger.Object); hc.EnqueueInstance(pagingLogger2.Object); hc.SetSingleton(jobServerQueue.Object); var ec = new Runner.Worker.ExecutionContext(); ec.Initialize(hc); ec.InitializeJob(jobRequest, CancellationToken.None); ec.Start(); var embeddedStep = ec.CreateChild(Guid.NewGuid(), "action_1_pre", "action_1_pre", null, null, ActionRunStage.Main, isEmbedded: true); embeddedStep.Start(); embeddedStep.AddIssue(new Issue() { Type = IssueType.Error, Message = "error annotation that should have step and line number information" }, ExecutionContextLogOptions.Default); embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning annotation that should have step and line number information" }, ExecutionContextLogOptions.Default); embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice annotation that should have step and line number information" }, ExecutionContextLogOptions.Default); jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.IsAny()), Times.AtLeastOnce); // Verify that Error/Warning/Notice issues added to embedded steps don't get sent up to the server. jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.Is(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Error).Count() == 1)), Times.Never); jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.Is(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Warning).Count() == 1)), Times.Never); jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.Is(t => t.Issues.Where(i => i.Data.ContainsKey("stepNumber") && i.Data.ContainsKey("logFileLineNumber") && i.Type == IssueType.Notice).Count() == 1)), Times.Never); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void Debug_Multilines() { using (TestHostContext hc = CreateTestContext()) { // Arrange: Create a job request message. TaskOrchestrationPlanReference plan = new(); TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, Id = "github", Version = "sha1" }); jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData(); jobRequest.Variables["ACTIONS_STEP_DEBUG"] = "true"; // Arrange: Setup the paging logger. var pagingLogger = new Mock(); var jobServerQueue = new Mock(); jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.IsAny())); jobServerQueue.Setup(x => x.QueueWebConsoleLine(It.IsAny(), It.IsAny(), It.IsAny())).Callback((Guid id, string msg, long? lineNumber) => { hc.GetTrace().Info(msg); }); hc.EnqueueInstance(pagingLogger.Object); hc.SetSingleton(jobServerQueue.Object); var ec = new Runner.Worker.ExecutionContext(); ec.Initialize(hc); // Act. ec.InitializeJob(jobRequest, CancellationToken.None); ec.Debug(null); ec.Debug(""); ec.Debug("\n"); ec.Debug("\r\n"); ec.Debug("test"); ec.Debug("te\nst"); ec.Debug("te\r\nst"); ec.Complete(); jobServerQueue.Verify(x => x.QueueWebConsoleLine(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(10)); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void RegisterPostJobAction_ShareState() { using (TestHostContext hc = CreateTestContext()) { // Arrange: Create a job request message. TaskOrchestrationPlanReference plan = new(); TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, Id = "github", Version = "sha1" }); jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData(); jobRequest.Variables["ACTIONS_STEP_DEBUG"] = "true"; // Arrange: Setup the paging logger. var pagingLogger1 = new Mock(); var pagingLogger2 = new Mock(); var pagingLogger3 = new Mock(); var pagingLogger4 = new Mock(); var pagingLogger5 = new Mock(); var jobServerQueue = new Mock(); jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.IsAny())); jobServerQueue.Setup(x => x.QueueWebConsoleLine(It.IsAny(), It.IsAny(), It.IsAny())).Callback((Guid id, string msg, long? lineNumber) => { hc.GetTrace().Info(msg); }); var actionRunner1 = new ActionRunner(); actionRunner1.Initialize(hc); var actionRunner2 = new ActionRunner(); actionRunner2.Initialize(hc); hc.EnqueueInstance(pagingLogger1.Object); hc.EnqueueInstance(pagingLogger2.Object); hc.EnqueueInstance(pagingLogger3.Object); hc.EnqueueInstance(pagingLogger4.Object); hc.EnqueueInstance(pagingLogger5.Object); hc.EnqueueInstance(actionRunner1 as IActionRunner); hc.EnqueueInstance(actionRunner2 as IActionRunner); hc.SetSingleton(jobServerQueue.Object); var jobContext = new Runner.Worker.ExecutionContext(); jobContext.Initialize(hc); // Act. jobContext.InitializeJob(jobRequest, CancellationToken.None); var action1 = jobContext.CreateChild(Guid.NewGuid(), "action_1", "action_1", null, null, 0); action1.IntraActionState["state"] = "1"; var action2 = jobContext.CreateChild(Guid.NewGuid(), "action_2", "action_2", null, null, 0); action2.IntraActionState["state"] = "2"; var postRunner1 = hc.CreateService(); postRunner1.Action = new Pipelines.ActionStep() { Id = Guid.NewGuid(), Name = "post1", DisplayName = "Test 1", Reference = new Pipelines.RepositoryPathReference() { Name = "actions/action" } }; postRunner1.Stage = ActionRunStage.Post; postRunner1.Condition = "always()"; postRunner1.DisplayName = "post1"; var postRunner2 = hc.CreateService(); postRunner2.Action = new Pipelines.ActionStep() { Id = Guid.NewGuid(), Name = "post2", DisplayName = "Test 2", Reference = new Pipelines.RepositoryPathReference() { Name = "actions/action" } }; postRunner2.Stage = ActionRunStage.Post; postRunner2.Condition = "always()"; postRunner2.DisplayName = "post2"; action1.RegisterPostJobStep(postRunner1); action2.RegisterPostJobStep(postRunner2); Assert.NotNull(jobContext.JobSteps); Assert.NotNull(jobContext.PostJobSteps); Assert.Null(action1.JobSteps); Assert.Null(action2.JobSteps); Assert.Null(action1.PostJobSteps); Assert.Null(action2.PostJobSteps); var post1 = jobContext.PostJobSteps.Pop(); var post2 = jobContext.PostJobSteps.Pop(); Assert.Equal("post2", (post1 as IActionRunner).Action.Name); Assert.Equal("post1", (post2 as IActionRunner).Action.Name); Assert.Equal(ActionRunStage.Post, (post1 as IActionRunner).Stage); Assert.Equal(ActionRunStage.Post, (post2 as IActionRunner).Stage); Assert.Equal("always()", (post1 as IActionRunner).Condition); Assert.Equal("always()", (post2 as IActionRunner).Condition); Assert.Equal("2", (post1 as IActionRunner).ExecutionContext.IntraActionState["state"]); Assert.Equal("1", (post2 as IActionRunner).ExecutionContext.IntraActionState["state"]); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void RegisterPostJobAction_NotRegisterPostTwice() { using (TestHostContext hc = CreateTestContext()) { // Arrange: Create a job request message. TaskOrchestrationPlanReference plan = new(); TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, Id = "github", Version = "sha1" }); jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData(); jobRequest.Variables["ACTIONS_STEP_DEBUG"] = "true"; // Arrange: Setup the paging logger. var pagingLogger1 = new Mock(); var pagingLogger2 = new Mock(); var pagingLogger3 = new Mock(); var pagingLogger4 = new Mock(); var pagingLogger5 = new Mock(); var jobServerQueue = new Mock(); jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.IsAny())); jobServerQueue.Setup(x => x.QueueWebConsoleLine(It.IsAny(), It.IsAny(), It.IsAny())).Callback((Guid id, string msg, long? lineNumber) => { hc.GetTrace().Info(msg); }); var actionRunner1 = new ActionRunner(); actionRunner1.Initialize(hc); var actionRunner2 = new ActionRunner(); actionRunner2.Initialize(hc); hc.EnqueueInstance(pagingLogger1.Object); hc.EnqueueInstance(pagingLogger2.Object); hc.EnqueueInstance(pagingLogger3.Object); hc.EnqueueInstance(pagingLogger4.Object); hc.EnqueueInstance(pagingLogger5.Object); hc.EnqueueInstance(actionRunner1 as IActionRunner); hc.EnqueueInstance(actionRunner2 as IActionRunner); hc.SetSingleton(jobServerQueue.Object); var jobContext = new Runner.Worker.ExecutionContext(); jobContext.Initialize(hc); // Act. jobContext.InitializeJob(jobRequest, CancellationToken.None); var action1 = jobContext.CreateChild(Guid.NewGuid(), "action_1_pre", "action_1_pre", null, null, 0); var action2 = jobContext.CreateChild(Guid.NewGuid(), "action_1_main", "action_1_main", null, null, 0); var actionId = Guid.NewGuid(); var postRunner1 = hc.CreateService(); postRunner1.Action = new Pipelines.ActionStep() { Id = actionId, Name = "post1", DisplayName = "Test 1", Reference = new Pipelines.RepositoryPathReference() { Name = "actions/action" } }; postRunner1.Stage = ActionRunStage.Post; postRunner1.Condition = "always()"; postRunner1.DisplayName = "post1"; var postRunner2 = hc.CreateService(); postRunner2.Action = new Pipelines.ActionStep() { Id = actionId, Name = "post2", DisplayName = "Test 2", Reference = new Pipelines.RepositoryPathReference() { Name = "actions/action" } }; postRunner2.Stage = ActionRunStage.Post; postRunner2.Condition = "always()"; postRunner2.DisplayName = "post2"; action1.RegisterPostJobStep(postRunner1); action2.RegisterPostJobStep(postRunner2); Assert.NotNull(jobContext.JobSteps); Assert.NotNull(jobContext.PostJobSteps); Assert.Equal(1, jobContext.PostJobSteps.Count); var post1 = jobContext.PostJobSteps.Pop(); Assert.Equal("post1", (post1 as IActionRunner).Action.Name); Assert.Equal(ActionRunStage.Post, (post1 as IActionRunner).Stage); Assert.Equal("always()", (post1 as IActionRunner).Condition); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void ActionResult_Lowercase() { using (TestHostContext hc = CreateTestContext()) { TaskOrchestrationPlanReference plan = new(); TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, Id = "github", Version = "sha1" }); jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData(); jobRequest.Variables["ACTIONS_STEP_DEBUG"] = "true"; // Arrange: Setup the paging logger. var pagingLogger1 = new Mock(); var jobServerQueue = new Mock(); hc.EnqueueInstance(pagingLogger1.Object); hc.SetSingleton(jobServerQueue.Object); var jobContext = new Runner.Worker.ExecutionContext(); jobContext.Initialize(hc); // Act. jobContext.InitializeJob(jobRequest, CancellationToken.None); jobContext.Global.StepsContext.SetConclusion(null, "step1", ActionResult.Success); var conclusion1 = (jobContext.Global.StepsContext.GetScope(null)["step1"] as DictionaryContextData)["conclusion"].ToString(); Assert.Equal(conclusion1, conclusion1.ToLowerInvariant()); jobContext.Global.StepsContext.SetOutcome(null, "step2", ActionResult.Cancelled); var outcome1 = (jobContext.Global.StepsContext.GetScope(null)["step2"] as DictionaryContextData)["outcome"].ToString(); Assert.Equal(outcome1, outcome1.ToLowerInvariant()); jobContext.Global.StepsContext.SetConclusion(null, "step3", ActionResult.Failure); var conclusion2 = (jobContext.Global.StepsContext.GetScope(null)["step3"] as DictionaryContextData)["conclusion"].ToString(); Assert.Equal(conclusion2, conclusion2.ToLowerInvariant()); jobContext.Global.StepsContext.SetOutcome(null, "step4", ActionResult.Skipped); var outcome2 = (jobContext.Global.StepsContext.GetScope(null)["step4"] as DictionaryContextData)["outcome"].ToString(); Assert.Equal(outcome2, outcome2.ToLowerInvariant()); jobContext.JobContext.Status = ActionResult.Success; Assert.Equal(jobContext.JobContext["status"].ToString(), jobContext.JobContext["status"].ToString().ToLowerInvariant()); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void PublishStepTelemetry_RegularStep_NoOpt() { using (TestHostContext hc = CreateTestContext()) { // Arrange: Create a job request message. TaskOrchestrationPlanReference plan = new(); TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, Id = "github", Version = "sha1" }); jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData(); // Arrange: Setup the paging logger. var pagingLogger = new Mock(); var jobServerQueue = new Mock(); jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.IsAny())); hc.EnqueueInstance(pagingLogger.Object); hc.SetSingleton(jobServerQueue.Object); var ec = new Runner.Worker.ExecutionContext(); ec.Initialize(hc); // Act. ec.InitializeJob(jobRequest, CancellationToken.None); ec.Start(); ec.Complete(); // Assert. Assert.Equal(0, ec.Global.StepsTelemetry.Count); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void PublishStepTelemetry_RegularStep() { using (TestHostContext hc = CreateTestContext()) { // Arrange: Create a job request message. TaskOrchestrationPlanReference plan = new(); TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, Id = "github", Version = "sha1" }); jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData(); // Arrange: Setup the paging logger. var pagingLogger = new Mock(); var jobServerQueue = new Mock(); jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.IsAny())); hc.EnqueueInstance(pagingLogger.Object); hc.SetSingleton(jobServerQueue.Object); var ec = new Runner.Worker.ExecutionContext(); ec.Initialize(hc); // Act. ec.InitializeJob(jobRequest, CancellationToken.None); ec.Start(); ec.StepTelemetry.Type = "node20"; ec.StepTelemetry.Action = "actions/checkout"; ec.StepTelemetry.Ref = "v2"; ec.StepTelemetry.IsEmbedded = false; ec.StepTelemetry.StepId = Guid.NewGuid(); ec.StepTelemetry.Stage = "main"; ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default); ec.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" }, ExecutionContextLogOptions.Default); ec.Complete(); // Assert. Assert.Equal(1, ec.Global.StepsTelemetry.Count); Assert.Equal("node20", ec.Global.StepsTelemetry.Single().Type); Assert.Equal("actions/checkout", ec.Global.StepsTelemetry.Single().Action); Assert.Equal("v2", ec.Global.StepsTelemetry.Single().Ref); Assert.Equal(TaskResult.Succeeded, ec.Global.StepsTelemetry.Single().Result); Assert.NotNull(ec.Global.StepsTelemetry.Single().ExecutionTimeInSeconds); Assert.Equal(3, ec.Global.StepsTelemetry.Single().ErrorMessages.Count); Assert.DoesNotContain(ec.Global.StepsTelemetry.Single().ErrorMessages, x => x.Contains("notice")); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void PublishStepTelemetry_EmbeddedStep() { using (TestHostContext hc = CreateTestContext()) { // Arrange: Create a job request message. TaskOrchestrationPlanReference plan = new(); TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, Id = "github", Version = "sha1" }); jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData(); // Arrange: Setup the paging logger. var pagingLogger = new Mock(); var pagingLogger2 = new Mock(); var jobServerQueue = new Mock(); jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.IsAny())); hc.EnqueueInstance(pagingLogger.Object); hc.EnqueueInstance(pagingLogger2.Object); hc.SetSingleton(jobServerQueue.Object); var ec = new Runner.Worker.ExecutionContext(); ec.Initialize(hc); // Act. ec.InitializeJob(jobRequest, CancellationToken.None); ec.Start(); var embeddedStep = ec.CreateChild(Guid.NewGuid(), "action_1_pre", "action_1_pre", null, null, ActionRunStage.Main, isEmbedded: true); embeddedStep.Start(); embeddedStep.StepTelemetry.Type = "node20"; embeddedStep.StepTelemetry.Action = "actions/checkout"; embeddedStep.StepTelemetry.Ref = "v2"; embeddedStep.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default); embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default); embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" }, ExecutionContextLogOptions.Default); embeddedStep.PublishStepTelemetry(); // Assert. Assert.Equal(1, ec.Global.StepsTelemetry.Count); Assert.Equal("node20", ec.Global.StepsTelemetry.Single().Type); Assert.Equal("actions/checkout", ec.Global.StepsTelemetry.Single().Action); Assert.Equal("v2", ec.Global.StepsTelemetry.Single().Ref); Assert.Equal(ActionRunStage.Main.ToString(), ec.Global.StepsTelemetry.Single().Stage); Assert.True(ec.Global.StepsTelemetry.Single().IsEmbedded); Assert.Null(ec.Global.StepsTelemetry.Single().Result); Assert.Null(ec.Global.StepsTelemetry.Single().ExecutionTimeInSeconds); Assert.Equal(0, ec.Global.StepsTelemetry.Single().ErrorMessages.Count); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void PublishStepResult_EmbeddedStep() { using (TestHostContext hc = CreateTestContext()) { // Job request TaskOrchestrationPlanReference plan = new(); TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; var variables = new Dictionary() { ["RunService.FixEmbeddedIssues"] = new VariableValue("true"), }; var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, variables, new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, Id = "github", Version = "sha1" }); jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData(); // Mocks var pagingLogger = new Mock(); var pagingLogger2 = new Mock(); var jobServerQueue = new Mock(); jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.IsAny())); hc.EnqueueInstance(pagingLogger.Object); hc.EnqueueInstance(pagingLogger2.Object); hc.SetSingleton(jobServerQueue.Object); // Job context var jobContext = new Runner.Worker.ExecutionContext(); jobContext.Initialize(hc); jobContext.InitializeJob(jobRequest, CancellationToken.None); jobContext.Start(); // Step 1 context var step1 = jobContext.CreateChild(Guid.NewGuid(), "my_step", "my_step", null, null, ActionRunStage.Main); step1.Start(); // Embedded step 1a context var embeddedStep1a = step1.CreateEmbeddedChild(null, null, Guid.NewGuid(), ActionRunStage.Main); embeddedStep1a.Start(); embeddedStep1a.StepTelemetry.Type = "node20"; embeddedStep1a.StepTelemetry.Action = "actions/checkout"; embeddedStep1a.StepTelemetry.Ref = "v2"; embeddedStep1a.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default); embeddedStep1a.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default); embeddedStep1a.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" }, ExecutionContextLogOptions.Default); embeddedStep1a.Complete(); // Embedded step 1b context var embeddedStep1b = step1.CreateEmbeddedChild(null, null, Guid.NewGuid(), ActionRunStage.Main); embeddedStep1b.Start(); embeddedStep1b.StepTelemetry.Type = "node20"; embeddedStep1b.StepTelemetry.Action = "actions/checkout"; embeddedStep1b.StepTelemetry.Ref = "v2"; embeddedStep1b.AddIssue(new Issue() { Type = IssueType.Error, Message = "error 2" }, ExecutionContextLogOptions.Default); embeddedStep1b.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning 2" }, ExecutionContextLogOptions.Default); embeddedStep1b.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice 2" }, ExecutionContextLogOptions.Default); embeddedStep1b.Complete(); step1.Complete(); // Assert Assert.Equal(3, jobContext.Global.StepsResult.Count); Assert.Equal(0, jobContext.Global.StepsResult[0].Annotations.Count); Assert.Equal(0, jobContext.Global.StepsResult[1].Annotations.Count); Assert.Equal(6, jobContext.Global.StepsResult[2].Annotations.Count); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void PublishStepResult_EmbeddedStep_Legacy() { using (TestHostContext hc = CreateTestContext()) { // Arrange: Create a job request message. TaskOrchestrationPlanReference plan = new(); TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, Id = "github", Version = "sha1" }); jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData(); // Arrange: Setup the paging logger. var pagingLogger = new Mock(); var pagingLogger2 = new Mock(); var jobServerQueue = new Mock(); jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny(), It.IsAny())); hc.EnqueueInstance(pagingLogger.Object); hc.EnqueueInstance(pagingLogger2.Object); hc.SetSingleton(jobServerQueue.Object); var ec = new Runner.Worker.ExecutionContext(); ec.Initialize(hc); // Act. ec.InitializeJob(jobRequest, CancellationToken.None); ec.Start(); var embeddedStep = ec.CreateEmbeddedChild(null, null, Guid.NewGuid(), ActionRunStage.Main); embeddedStep.Start(); embeddedStep.StepTelemetry.Type = "node20"; embeddedStep.StepTelemetry.Action = "actions/checkout"; embeddedStep.StepTelemetry.Ref = "v2"; embeddedStep.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" }, ExecutionContextLogOptions.Default); embeddedStep.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" }, ExecutionContextLogOptions.Default); embeddedStep.AddIssue(new Issue() { Type = IssueType.Notice, Message = "notice" }, ExecutionContextLogOptions.Default); ec.Complete(); // Assert. Assert.Equal(0, ec.Global.StepsResult.Count); } } private TestHostContext CreateTestContext([CallerMemberName] String testName = "") { var hc = new TestHostContext(this, testName); // Arrange: Setup the configation store. var configurationStore = new Mock(); configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings()); hc.SetSingleton(configurationStore.Object); // Arrange: Create the execution context. hc.SetSingleton(new Mock().Object); return hc; } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void GetExpressionValues_ContainerStepHost() { using (TestHostContext hc = CreateTestContext()) { const string source = "/home/username/Projects/work/runner/_layout"; var containerInfo = new ContainerInfo(); containerInfo.ContainerId = "test"; containerInfo.AddPathTranslateMapping($"{source}/_work", "/__w"); containerInfo.AddPathTranslateMapping($"{source}/_temp", "/__t"); containerInfo.AddPathTranslateMapping($"{source}/externals", "/__e"); containerInfo.AddPathTranslateMapping($"{source}/_work/_temp/_github_home", "/github/home"); containerInfo.AddPathTranslateMapping($"{source}/_work/_temp/_github_workflow", "/github/workflow"); foreach (var v in new List() { $"{source}/_work", $"{source}/externals", $"{source}/_work/_temp", $"{source}/_work/_actions", $"{source}/_work/_tool", }) { containerInfo.MountVolumes.Add(new MountVolume(v, containerInfo.TranslateToContainerPath(v))); }; var stepHost = new ContainerStepHost(); stepHost.Container = containerInfo; var ec = new Runner.Worker.ExecutionContext(); ec.Initialize(hc); var inputGithubContext = new GitHubContext(); var inputeRunnerContext = new RunnerContext(); // string context data inputGithubContext["action_path"] = new StringContextData("/home/username/Projects/work/runner/_layout/_work/_actions/owner/composite/main"); inputGithubContext["action"] = new StringContextData("__owner_composite"); inputGithubContext["api_url"] = new StringContextData("https://api.github.com/custom/path"); inputGithubContext["env"] = new StringContextData("/home/username/Projects/work/runner/_layout/_work/_temp/_runner_file_commands/set_env_265698aa-7f38-40f5-9316-5c01a3153672"); inputGithubContext["path"] = new StringContextData("/home/username/Projects/work/runner/_layout/_work/_temp/_runner_file_commands/add_path_265698aa-7f38-40f5-9316-5c01a3153672"); inputGithubContext["event_path"] = new StringContextData("/home/username/Projects/work/runner/_layout/_work/_temp/_github_workflow/event.json"); inputGithubContext["repository"] = new StringContextData("owner/repo-name"); inputGithubContext["run_id"] = new StringContextData("2033211332"); inputGithubContext["workflow"] = new StringContextData("Name of Workflow"); inputGithubContext["workspace"] = new StringContextData("/home/username/Projects/work/runner/_layout/_work/step-order/step-order"); inputeRunnerContext["temp"] = new StringContextData("/home/username/Projects/work/runner/_layout/_work/_temp"); inputeRunnerContext["tool_cache"] = new StringContextData("/home/username/Projects/work/runner/_layout/_work/_tool"); // dictionary context data var githubEvent = new DictionaryContextData(); githubEvent["inputs"] = null; githubEvent["ref"] = new StringContextData("refs/heads/main"); githubEvent["repository"] = new DictionaryContextData(); githubEvent["sender"] = new DictionaryContextData(); githubEvent["workflow"] = new StringContextData(".github/workflows/composite_step_host_translate.yaml"); inputGithubContext["event"] = githubEvent; ec.ExpressionValues["github"] = inputGithubContext; ec.ExpressionValues["runner"] = inputeRunnerContext; var ecExpect = new Runner.Worker.ExecutionContext(); ecExpect.Initialize(hc); var expectedGithubEvent = new DictionaryContextData(); expectedGithubEvent["inputs"] = null; expectedGithubEvent["ref"] = new StringContextData("refs/heads/main"); expectedGithubEvent["repository"] = new DictionaryContextData(); expectedGithubEvent["sender"] = new DictionaryContextData(); expectedGithubEvent["workflow"] = new StringContextData(".github/workflows/composite_step_host_translate.yaml"); var expectedGithubContext = new GitHubContext(); var expectedRunnerContext = new RunnerContext(); expectedGithubContext["action_path"] = new StringContextData("/__w/_actions/owner/composite/main"); expectedGithubContext["action"] = new StringContextData("__owner_composite"); expectedGithubContext["api_url"] = new StringContextData("https://api.github.com/custom/path"); expectedGithubContext["env"] = new StringContextData("/__w/_temp/_runner_file_commands/set_env_265698aa-7f38-40f5-9316-5c01a3153672"); expectedGithubContext["path"] = new StringContextData("/__w/_temp/_runner_file_commands/add_path_265698aa-7f38-40f5-9316-5c01a3153672"); expectedGithubContext["event_path"] = new StringContextData("/github/workflow/event.json"); expectedGithubContext["repository"] = new StringContextData("owner/repo-name"); expectedGithubContext["run_id"] = new StringContextData("2033211332"); expectedGithubContext["workflow"] = new StringContextData("Name of Workflow"); expectedGithubContext["workspace"] = new StringContextData("/__w/step-order/step-order"); expectedGithubContext["event"] = expectedGithubEvent; expectedRunnerContext["temp"] = new StringContextData("/__w/_temp"); expectedRunnerContext["tool_cache"] = new StringContextData("/__w/_tool"); ecExpect.ExpressionValues["github"] = expectedGithubContext; ecExpect.ExpressionValues["runner"] = expectedRunnerContext; var translatedExpressionValues = ec.GetExpressionValues(stepHost); foreach (var contextName in new string[] { "github", "runner" }) { var dict = translatedExpressionValues[contextName].AssertDictionary($"expected context github to be a dictionary"); var expectedExpressionValues = ecExpect.ExpressionValues[contextName].AssertDictionary("expect dict"); foreach (var key in dict.Keys.ToList()) { if (dict[key] is StringContextData) { var expect = dict[key].AssertString("expect string"); var outcome = expectedExpressionValues[key].AssertString("expect string"); Assert.Equal(expect.Value, outcome.Value); } else if (dict[key] is DictionaryContextData || dict[key] is CaseSensitiveDictionaryContextData) { var expectDict = dict[key].AssertDictionary("expect dict"); var actualDict = expectedExpressionValues[key].AssertDictionary("expect dict"); Assert.True(ExpressionValuesAssertEqual(expectDict, actualDict)); } } } } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void ActionVariables_AddedToVarsContext() { using (TestHostContext hc = CreateTestContext()) { TaskOrchestrationPlanReference plan = new(); TimelineReference timeline = new(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, Id = "github", Version = "sha1" }); jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData(); var inputVarsContext = new DictionaryContextData(); inputVarsContext["VARIABLE_1"] = new StringContextData("value1"); inputVarsContext["VARIABLE_2"] = new StringContextData("value2"); jobRequest.ContextData["vars"] = inputVarsContext; // Arrange: Setup the paging logger. var pagingLogger1 = new Mock(); var jobServerQueue = new Mock(); hc.EnqueueInstance(pagingLogger1.Object); hc.SetSingleton(jobServerQueue.Object); var jobContext = new Runner.Worker.ExecutionContext(); jobContext.Initialize(hc); jobContext.InitializeJob(jobRequest, CancellationToken.None); var expected = new DictionaryContextData(); expected["VARIABLE_1"] = new StringContextData("value1"); expected["VARIABLE_2"] = new StringContextData("value1"); Assert.True(ExpressionValuesAssertEqual(expected, jobContext.ExpressionValues["vars"] as DictionaryContextData)); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void ActionVariables_DebugUsingVars() { using (TestHostContext hc = CreateTestContext()) { TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference(); TimelineReference timeline = new TimelineReference(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, Id = "github", Version = "sha1" }); jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData(); var inputVarsContext = new DictionaryContextData(); inputVarsContext[Constants.Variables.Actions.StepDebug] = new StringContextData("true"); inputVarsContext[Constants.Variables.Actions.RunnerDebug] = new StringContextData("true"); jobRequest.ContextData["vars"] = inputVarsContext; // Arrange: Setup the paging logger. var pagingLogger1 = new Mock(); var jobServerQueue = new Mock(); hc.EnqueueInstance(pagingLogger1.Object); hc.SetSingleton(jobServerQueue.Object); var jobContext = new Runner.Worker.ExecutionContext(); jobContext.Initialize(hc); jobContext.InitializeJob(jobRequest, CancellationToken.None); Assert.Equal("true", jobContext.Global.Variables.Get(Constants.Variables.Actions.StepDebug)); Assert.Equal("true", jobContext.Global.Variables.Get(Constants.Variables.Actions.RunnerDebug)); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void ActionVariables_SecretsPrecedenceForDebugUsingVars() { using (TestHostContext hc = CreateTestContext()) { TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference(); TimelineReference timeline = new TimelineReference(); Guid jobId = Guid.NewGuid(); string jobName = "some job name"; var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, jobId, jobName, jobName, null, null, null, new Dictionary(), new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); jobRequest.Resources.Repositories.Add(new Pipelines.RepositoryResource() { Alias = Pipelines.PipelineConstants.SelfAlias, Id = "github", Version = "sha1" }); jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData(); var inputVarsContext = new DictionaryContextData(); inputVarsContext[Constants.Variables.Actions.StepDebug] = new StringContextData("true"); inputVarsContext[Constants.Variables.Actions.RunnerDebug] = new StringContextData("true"); jobRequest.ContextData["vars"] = inputVarsContext; jobRequest.Variables[Constants.Variables.Actions.StepDebug] = "false"; jobRequest.Variables[Constants.Variables.Actions.RunnerDebug] = "false"; // Arrange: Setup the paging logger. var pagingLogger1 = new Mock(); var jobServerQueue = new Mock(); hc.EnqueueInstance(pagingLogger1.Object); hc.SetSingleton(jobServerQueue.Object); var jobContext = new Runner.Worker.ExecutionContext(); jobContext.Initialize(hc); jobContext.InitializeJob(jobRequest, CancellationToken.None); Assert.Equal("false", jobContext.Global.Variables.Get(Constants.Variables.Actions.StepDebug)); Assert.Equal("false", jobContext.Global.Variables.Get(Constants.Variables.Actions.RunnerDebug)); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void InitializeJob_HydratesJobContextWithCheckRunId() { using (TestHostContext hc = CreateTestContext()) { // Arrange: Create a job request message and make sure the feature flag is enabled var variables = new Dictionary() { [Constants.Runner.Features.AddCheckRunIdToJobContext] = new VariableValue("true"), }; var jobRequest = new Pipelines.AgentJobRequestMessage(new TaskOrchestrationPlanReference(), new TimelineReference(), Guid.NewGuid(), "some job name", "some job name", null, null, null, variables, new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); var pagingLogger = new Moq.Mock(); var jobServerQueue = new Moq.Mock(); hc.EnqueueInstance(pagingLogger.Object); hc.SetSingleton(jobServerQueue.Object); var ec = new Runner.Worker.ExecutionContext(); ec.Initialize(hc); // Arrange: Add check_run_id to the job context var jobContext = new Pipelines.ContextData.DictionaryContextData(); jobContext["check_run_id"] = new NumberContextData(123456); jobRequest.ContextData["job"] = jobContext; jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData(); // Act ec.InitializeJob(jobRequest, CancellationToken.None); // Assert Assert.NotNull(ec.JobContext); Assert.Equal(123456, ec.JobContext.CheckRunId); } } // TODO: this test can be deleted when `AddCheckRunIdToJobContext` is fully rolled out [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void InitializeJob_HydratesJobContextWithCheckRunId_FeatureFlagDisabled() { using (TestHostContext hc = CreateTestContext()) { // Arrange: Create a job request message and make sure the feature flag is disabled var variables = new Dictionary() { [Constants.Runner.Features.AddCheckRunIdToJobContext] = new VariableValue("false"), }; var jobRequest = new Pipelines.AgentJobRequestMessage(new TaskOrchestrationPlanReference(), new TimelineReference(), Guid.NewGuid(), "some job name", "some job name", null, null, null, variables, new List(), new Pipelines.JobResources(), new Pipelines.ContextData.DictionaryContextData(), new Pipelines.WorkspaceOptions(), new List(), null, null, null, null, null); var pagingLogger = new Moq.Mock(); var jobServerQueue = new Moq.Mock(); hc.EnqueueInstance(pagingLogger.Object); hc.SetSingleton(jobServerQueue.Object); var ec = new Runner.Worker.ExecutionContext(); ec.Initialize(hc); // Arrange: Add check_run_id to the job context var jobContext = new Pipelines.ContextData.DictionaryContextData(); jobContext["check_run_id"] = new NumberContextData(123456); jobRequest.ContextData["job"] = jobContext; jobRequest.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData(); // Act ec.InitializeJob(jobRequest, CancellationToken.None); // Assert Assert.NotNull(ec.JobContext); Assert.Null(ec.JobContext.CheckRunId); // with the feature flag disabled we should not have added a CheckRunId to the JobContext } } private bool ExpressionValuesAssertEqual(DictionaryContextData expect, DictionaryContextData actual) { foreach (var key in expect.Keys.ToList()) { if (expect[key] is StringContextData) { var expectValue = expect[key].AssertString("expect string"); var actualValue = actual[key].AssertString("expect string"); if (expectValue.Equals(actualValue)) { return false; } } else if (expect[key] is DictionaryContextData || expect[key] is CaseSensitiveDictionaryContextData) { var expectDict = expect[key].AssertDictionary("expect dict"); var actualDict = actual[key].AssertDictionary("expect dict"); if (!ExpressionValuesAssertEqual(expectDict, actualDict)) { return false; } } } return true; } } }