diff --git a/src/Test/L0/Listener/RunnerL0.cs b/src/Test/L0/Listener/RunnerL0.cs index 5750bc2ed..1c438f3cb 100644 --- a/src/Test/L0/Listener/RunnerL0.cs +++ b/src/Test/L0/Listener/RunnerL0.cs @@ -4,7 +4,6 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using GitHub.DistributedTask.WebApi; -using GitHub.Runner.Common.Util; using GitHub.Runner.Listener; using GitHub.Runner.Listener.Configuration; using GitHub.Services.Common; @@ -15,7 +14,7 @@ using Pipelines = GitHub.DistributedTask.Pipelines; namespace GitHub.Runner.Common.Tests.Listener { - public sealed class RunnerL0 + public sealed class RunnerL0 : IDisposable { private Mock _configurationManager; private Mock _jobNotification; @@ -31,8 +30,17 @@ namespace GitHub.Runner.Common.Tests.Listener private Mock _actionsRunServer; private Mock _runServer; + // ACTIONS_RUNNER_RETURN_JOB_RESULT_FOR_HOSTED is set on GitHub-hosted runners and + // silently forces runOnce=true, which breaks tests that expect non-ephemeral behaviour + // or assert a specific return code. Clear it for the entire test class and restore on + // Dispose so individual tests remain environment-independent. + private readonly string _savedJobResultEnvVar; + public RunnerL0() { + _savedJobResultEnvVar = Environment.GetEnvironmentVariable("ACTIONS_RUNNER_RETURN_JOB_RESULT_FOR_HOSTED"); + Environment.SetEnvironmentVariable("ACTIONS_RUNNER_RETURN_JOB_RESULT_FOR_HOSTED", null); + _configurationManager = new Mock(); _jobNotification = new Mock(); _messageListener = new Mock(); @@ -48,6 +56,11 @@ namespace GitHub.Runner.Common.Tests.Listener _runServer = new Mock(); } + public void Dispose() + { + Environment.SetEnvironmentVariable("ACTIONS_RUNNER_RETURN_JOB_RESULT_FOR_HOSTED", _savedJobResultEnvVar); + } + private Pipelines.AgentJobRequestMessage CreateJobRequestMessage(string jobName) { TaskOrchestrationPlanReference plan = new(); @@ -68,115 +81,103 @@ namespace GitHub.Runner.Common.Tests.Listener //process 2 new job messages, and one cancel message public async Task TestRunAsync() { - // Clear the hosted-runner env var so that this non-ephemeral test runs with runOnce=false - // regardless of the environment (e.g. ACTIONS_RUNNER_RETURN_JOB_RESULT_FOR_HOSTED is set on - // GitHub-hosted runners which would otherwise force runOnce=true). - var savedEnvVar = Environment.GetEnvironmentVariable("ACTIONS_RUNNER_RETURN_JOB_RESULT_FOR_HOSTED"); - Environment.SetEnvironmentVariable("ACTIONS_RUNNER_RETURN_JOB_RESULT_FOR_HOSTED", null); - try + using (var hc = new TestHostContext(this)) { - using (var hc = new TestHostContext(this)) + //Arrange + var runner = new Runner.Listener.Runner(); + hc.SetSingleton(_configurationManager.Object); + hc.SetSingleton(_jobNotification.Object); + hc.SetSingleton(_messageListener.Object); + hc.SetSingleton(_promptManager.Object); + hc.SetSingleton(_runnerServer.Object); + hc.SetSingleton(_configStore.Object); + hc.EnqueueInstance(_acquireJobThrottler.Object); + runner.Initialize(hc); + var settings = new RunnerSettings { - //Arrange - var runner = new Runner.Listener.Runner(); - hc.SetSingleton(_configurationManager.Object); - hc.SetSingleton(_jobNotification.Object); - hc.SetSingleton(_messageListener.Object); - hc.SetSingleton(_promptManager.Object); - hc.SetSingleton(_runnerServer.Object); - hc.SetSingleton(_configStore.Object); - hc.EnqueueInstance(_acquireJobThrottler.Object); - runner.Initialize(hc); - var settings = new RunnerSettings - { - PoolId = 43242 - }; + PoolId = 43242 + }; - var message = new TaskAgentMessage() - { - Body = JsonUtility.ToString(CreateJobRequestMessage("job1")), - MessageId = 4234, - MessageType = JobRequestMessageTypes.PipelineAgentJobRequest - }; + var message = new TaskAgentMessage() + { + Body = JsonUtility.ToString(CreateJobRequestMessage("job1")), + MessageId = 4234, + MessageType = JobRequestMessageTypes.PipelineAgentJobRequest + }; - var messages = new Queue(); - messages.Enqueue(message); - var signalWorkerComplete = new SemaphoreSlim(0, 1); - _configurationManager.Setup(x => x.LoadSettings()) - .Returns(settings); - _configurationManager.Setup(x => x.IsConfigured()) - .Returns(true); - _messageListener.Setup(x => x.CreateSessionAsync(It.IsAny())) - .Returns(Task.FromResult(CreateSessionResult.Success)); - _messageListener.Setup(x => x.GetNextMessageAsync(It.IsAny())) - .Returns(async () => + var messages = new Queue(); + messages.Enqueue(message); + var signalWorkerComplete = new SemaphoreSlim(0, 1); + _configurationManager.Setup(x => x.LoadSettings()) + .Returns(settings); + _configurationManager.Setup(x => x.IsConfigured()) + .Returns(true); + _messageListener.Setup(x => x.CreateSessionAsync(It.IsAny())) + .Returns(Task.FromResult(CreateSessionResult.Success)); + _messageListener.Setup(x => x.GetNextMessageAsync(It.IsAny())) + .Returns(async () => + { + if (0 == messages.Count) { - if (0 == messages.Count) - { - signalWorkerComplete.Release(); - await Task.Delay(2000, hc.RunnerShutdownToken); - } - - return messages.Dequeue(); - }); - _messageListener.Setup(x => x.DeleteSessionAsync()) - .Returns(Task.CompletedTask); - _messageListener.Setup(x => x.DeleteMessageAsync(It.IsAny())) - .Returns(Task.CompletedTask); - _jobDispatcher.Setup(x => x.Run(It.IsAny(), It.IsAny())) - .Callback(() => - { + signalWorkerComplete.Release(); + await Task.Delay(2000, hc.RunnerShutdownToken); + } + return messages.Dequeue(); }); - _jobNotification.Setup(x => x.StartClient(It.IsAny())) - .Callback(() => - { + _messageListener.Setup(x => x.DeleteSessionAsync()) + .Returns(Task.CompletedTask); + _messageListener.Setup(x => x.DeleteMessageAsync(It.IsAny())) + .Returns(Task.CompletedTask); + _jobDispatcher.Setup(x => x.Run(It.IsAny(), It.IsAny())) + .Callback(() => + { - }); + }); + _jobNotification.Setup(x => x.StartClient(It.IsAny())) + .Callback(() => + { - hc.EnqueueInstance(_jobDispatcher.Object); + }); - _configStore.Setup(x => x.IsServiceConfigured()).Returns(false); + hc.EnqueueInstance(_jobDispatcher.Object); + + _configStore.Setup(x => x.IsServiceConfigured()).Returns(false); + //Act + var command = new CommandSettings(hc, new string[] { "run" }); + Task runnerTask = runner.ExecuteCommand(command); + + //Assert + //wait for the runner to run one job + if (!await signalWorkerComplete.WaitAsync(2000)) + { + Assert.Fail($"{nameof(_messageListener.Object.GetNextMessageAsync)} was not invoked."); + } + else + { //Act - var command = new CommandSettings(hc, new string[] { "run" }); - Task runnerTask = runner.ExecuteCommand(command); + hc.ShutdownRunner(ShutdownReason.UserCancelled); //stop Runner //Assert - //wait for the runner to run one job - if (!await signalWorkerComplete.WaitAsync(2000)) - { - Assert.Fail($"{nameof(_messageListener.Object.GetNextMessageAsync)} was not invoked."); - } - else - { - //Act - hc.ShutdownRunner(ShutdownReason.UserCancelled); //stop Runner + Task[] taskToWait2 = { runnerTask, Task.Delay(2000) }; + //wait for the runner to exit + await Task.WhenAny(taskToWait2); - //Assert - Task[] taskToWait2 = { runnerTask, Task.Delay(2000) }; - //wait for the runner to exit - await Task.WhenAny(taskToWait2); + Assert.True(runnerTask.IsCompleted, $"{nameof(runner.ExecuteCommand)} timed out."); + Assert.True(!runnerTask.IsFaulted, runnerTask.Exception?.ToString()); + Assert.True(runnerTask.IsCanceled); - Assert.True(runnerTask.IsCompleted, $"{nameof(runner.ExecuteCommand)} timed out."); - Assert.True(!runnerTask.IsFaulted, runnerTask.Exception?.ToString()); - Assert.True(runnerTask.IsCanceled); + _jobDispatcher.Verify(x => x.Run(It.IsAny(), It.IsAny()), Times.Once(), + $"{nameof(_jobDispatcher.Object.Run)} was not invoked."); + _messageListener.Verify(x => x.GetNextMessageAsync(It.IsAny()), Times.AtLeastOnce()); + _messageListener.Verify(x => x.CreateSessionAsync(It.IsAny()), Times.Once()); + _messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once()); + _messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny()), Times.AtLeastOnce()); - _jobDispatcher.Verify(x => x.Run(It.IsAny(), It.IsAny()), Times.Once(), - $"{nameof(_jobDispatcher.Object.Run)} was not invoked."); - _messageListener.Verify(x => x.GetNextMessageAsync(It.IsAny()), Times.AtLeastOnce()); - _messageListener.Verify(x => x.CreateSessionAsync(It.IsAny()), Times.Once()); - _messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once()); - _messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny()), Times.AtLeastOnce()); - - // verify that we didn't try to delete local settings file (since we're not ephemeral) - _configurationManager.Verify(x => x.DeleteLocalRunnerConfig(), Times.Never()); - } + // verify that we didn't try to delete local settings file (since we're not ephemeral) + _configurationManager.Verify(x => x.DeleteLocalRunnerConfig(), Times.Never()); } } - finally - { - Environment.SetEnvironmentVariable("ACTIONS_RUNNER_RETURN_JOB_RESULT_FOR_HOSTED", savedEnvVar); - } } public static TheoryData RunAsServiceTestData = new TheoryData() @@ -337,12 +338,7 @@ namespace GitHub.Runner.Common.Tests.Listener Assert.False(runnerTask.IsFaulted, runnerTask.Exception?.ToString()); if (runnerTask.IsCompleted) { - int returnCode = await runnerTask; - // Accept Success (0) or TaskResult.Succeeded offset (100) - the latter occurs when - // ACTIONS_RUNNER_RETURN_JOB_RESULT_FOR_HOSTED is set in the hosted runner environment. - Assert.True( - returnCode == Constants.Runner.ReturnCode.Success || returnCode == TaskResultUtil.TranslateToReturnCode(TaskResult.Succeeded), - $"Expected return code {Constants.Runner.ReturnCode.Success} or {TaskResultUtil.TranslateToReturnCode(TaskResult.Succeeded)}, but got {returnCode}"); + Assert.Equal(Constants.Runner.ReturnCode.Success, await runnerTask); } _jobDispatcher.Verify(x => x.Run(It.IsAny(), true), Times.Once(), @@ -446,10 +442,7 @@ namespace GitHub.Runner.Common.Tests.Listener Assert.True(!runnerTask.IsFaulted, runnerTask.Exception?.ToString()); if (runnerTask.IsCompleted) { - int returnCode = await runnerTask; - Assert.True( - returnCode == Constants.Runner.ReturnCode.Success || returnCode == TaskResultUtil.TranslateToReturnCode(TaskResult.Succeeded), - $"Expected return code {Constants.Runner.ReturnCode.Success} or {TaskResultUtil.TranslateToReturnCode(TaskResult.Succeeded)}, but got {returnCode}"); + Assert.Equal(Constants.Runner.ReturnCode.Success, await runnerTask); } _jobDispatcher.Verify(x => x.Run(It.IsAny(), true), Times.Once(), @@ -770,10 +763,7 @@ namespace GitHub.Runner.Common.Tests.Listener Assert.True(!runnerTask.IsFaulted, runnerTask.Exception?.ToString()); if (runnerTask.IsCompleted) { - int returnCode = await runnerTask; - Assert.True( - returnCode == Constants.Runner.ReturnCode.Success || returnCode == TaskResultUtil.TranslateToReturnCode(TaskResult.Succeeded), - $"Expected return code {Constants.Runner.ReturnCode.Success} or {TaskResultUtil.TranslateToReturnCode(TaskResult.Succeeded)}, but got {returnCode}"); + Assert.Equal(Constants.Runner.ReturnCode.Success, await runnerTask); } _jobDispatcher.Verify(x => x.Run(It.IsAny(), true), Times.Once()); @@ -874,10 +864,7 @@ namespace GitHub.Runner.Common.Tests.Listener Assert.True(!runnerTask.IsFaulted, runnerTask.Exception?.ToString()); if (runnerTask.IsCompleted) { - int returnCode = await runnerTask; - Assert.True( - returnCode == Constants.Runner.ReturnCode.Success || returnCode == TaskResultUtil.TranslateToReturnCode(TaskResult.Succeeded), - $"Expected return code {Constants.Runner.ReturnCode.Success} or {TaskResultUtil.TranslateToReturnCode(TaskResult.Succeeded)}, but got {returnCode}"); + Assert.Equal(Constants.Runner.ReturnCode.Success, await runnerTask); } _jobDispatcher.Verify(x => x.Run(It.IsAny(), true), Times.Once()); @@ -997,10 +984,7 @@ namespace GitHub.Runner.Common.Tests.Listener Assert.True(!runnerTask.IsFaulted, runnerTask.Exception?.ToString()); if (runnerTask.IsCompleted) { - int returnCode = await runnerTask; - Assert.True( - returnCode == Constants.Runner.ReturnCode.Success || returnCode == TaskResultUtil.TranslateToReturnCode(TaskResult.Succeeded), - $"Expected return code {Constants.Runner.ReturnCode.Success} or {TaskResultUtil.TranslateToReturnCode(TaskResult.Succeeded)}, but got {returnCode}"); + Assert.Equal(Constants.Runner.ReturnCode.Success, await runnerTask); } _jobDispatcher.Verify(x => x.Run(It.IsAny(), true), Times.Once());