GitHub Actions Runner

This commit is contained in:
Tingluo Huang
2019-10-10 00:52:42 -04:00
commit c8afc84840
1255 changed files with 198670 additions and 0 deletions

View File

@@ -0,0 +1,127 @@
using Moq;
using System.Runtime.CompilerServices;
using Xunit;
namespace GitHub.Runner.Common.Tests
{
public sealed class CommandLineParserL0
{
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void CanConstruct()
{
using (TestHostContext hc = CreateTestContext())
{
Tracing trace = hc.GetTrace();
CommandLineParser clp = new CommandLineParser(hc, secretArgNames: new string[0]);
trace.Info("Constructed");
Assert.NotNull(clp);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void MasksSecretArgs()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
CommandLineParser clp = new CommandLineParser(
hc,
secretArgNames: new[] { "SecretArg1", "SecretArg2" });
// Assert.
clp.Parse(new string[]
{
"cmd",
"--secretarg1",
"secret value 1",
"--publicarg",
"public arg value",
"--secretarg2",
"secret value 2",
});
// Assert.
Assert.Equal(hc.SecretMasker.MaskSecrets("secret value 1"), "***");
Assert.Equal(hc.SecretMasker.MaskSecrets("secret value 2"), "***");
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void ParsesCommands()
{
using (TestHostContext hc = CreateTestContext())
{
Tracing trace = hc.GetTrace();
CommandLineParser clp = new CommandLineParser(hc, secretArgNames: new string[0]);
trace.Info("Constructed.");
clp.Parse(new string[] { "cmd1", "cmd2", "--arg1", "arg1val", "badcmd" });
trace.Info("Parsed");
trace.Info("Commands: {0}", clp.Commands.Count);
Assert.True(clp.Commands.Count == 2);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void ParsesArgs()
{
using (TestHostContext hc = CreateTestContext())
{
Tracing trace = hc.GetTrace();
CommandLineParser clp = new CommandLineParser(hc, secretArgNames: new string[0]);
trace.Info("Constructed.");
clp.Parse(new string[] { "cmd1", "--arg1", "arg1val", "--arg2", "arg2val" });
trace.Info("Parsed");
trace.Info("Args: {0}", clp.Args.Count);
Assert.True(clp.Args.Count == 2);
Assert.True(clp.Args.ContainsKey("arg1"));
Assert.Equal(clp.Args["arg1"], "arg1val");
Assert.True(clp.Args.ContainsKey("arg2"));
Assert.Equal(clp.Args["arg2"], "arg2val");
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void ParsesFlags()
{
using (TestHostContext hc = CreateTestContext())
{
Tracing trace = hc.GetTrace();
CommandLineParser clp = new CommandLineParser(hc, secretArgNames: new string[0]);
trace.Info("Constructed.");
clp.Parse(new string[] { "cmd1", "--flag1", "--arg1", "arg1val", "--flag2" });
trace.Info("Parsed");
trace.Info("Args: {0}", clp.Flags.Count);
Assert.True(clp.Flags.Count == 2);
Assert.True(clp.Flags.Contains("flag1"));
Assert.True(clp.Flags.Contains("flag2"));
}
}
private TestHostContext CreateTestContext([CallerMemberName] string testName = "")
{
TestHostContext hc = new TestHostContext(this, testName);
return hc;
}
}
}

View File

@@ -0,0 +1,29 @@
using System.Collections.Generic;
using GitHub.Runner.Sdk;
using Xunit;
namespace GitHub.Runner.Common.Tests
{
public sealed class ConstantGenerationL0
{
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Agent")]
public void BuildConstantGenerateSucceed()
{
List<string> validPackageNames = new List<string>()
{
"win-x64",
"win-x86",
"linux-x64",
"linux-arm",
"rhel.6-x64",
"osx-x64"
};
Assert.True(BuildConstants.Source.CommitHash.Length == 40, $"CommitHash should be SHA-1 hash {BuildConstants.Source.CommitHash}");
Assert.True(validPackageNames.Contains(BuildConstants.RunnerPackage.PackageName), $"PackageName should be one of the following '{string.Join(", ", validPackageNames)}', current PackageName is '{BuildConstants.RunnerPackage.PackageName}'");
}
}
}

View File

@@ -0,0 +1,38 @@
using System;
using GitHub.Runner.Worker.Container;
using Xunit;
namespace GitHub.Runner.Common.Tests.Worker.Container
{
public sealed class ContainerInfoL0
{
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void MountVolumeConstructorParsesStringInput()
{
// Arrange
MountVolume target = new MountVolume("/dst/dir"); // Maps anonymous Docker volume into target dir
MountVolume source_target = new MountVolume("/src/dir:/dst/dir"); // Maps source to target dir
MountVolume target_ro = new MountVolume("/dst/dir:ro");
MountVolume source_target_ro = new MountVolume("/src/dir:/dst/dir:ro");
// Assert
Assert.Null(target.SourceVolumePath);
Assert.Equal("/dst/dir", target.TargetVolumePath);
Assert.False(target.ReadOnly);
Assert.Equal("/src/dir", source_target.SourceVolumePath);
Assert.Equal("/dst/dir", source_target.TargetVolumePath);
Assert.False(source_target.ReadOnly);
Assert.Null(target_ro.SourceVolumePath);
Assert.Equal("/dst/dir", target_ro.TargetVolumePath);
Assert.True(target_ro.ReadOnly);
Assert.Equal("/src/dir", source_target_ro.SourceVolumePath);
Assert.Equal("/dst/dir", source_target_ro.TargetVolumePath);
Assert.True(source_target_ro.ReadOnly);
}
}
}

View File

@@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using GitHub.Runner.Worker.Container;
using Xunit;
namespace GitHub.Runner.Common.Tests.Worker.Container
{
public sealed class DockerUtilL0
{
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void RegexParsesDockerPort()
{
// Arrange
var dockerPortOutput0 = new List<string>();
var dockerPortOutput1 = new List<string>
{
"80/tcp -> 0.0.0.0:32881"
};
var dockerPortOutput1Empty = new List<string>
{
""
};
var dockerPortOutput2 = new List<string>
{
"80/tcp -> 0.0.0.0:32881",
"6379/tcp -> 0.0.0.0:32882"
};
// Act
var result0 = DockerUtil.ParseDockerPort(dockerPortOutput0);
var result1 = DockerUtil.ParseDockerPort(dockerPortOutput1);
var result1Empty = DockerUtil.ParseDockerPort(dockerPortOutput1Empty);
var result2 = DockerUtil.ParseDockerPort(dockerPortOutput2);
// Assert
Assert.NotNull(result0);
Assert.Equal(result0.Count, 0);
Assert.NotNull(result1);
Assert.Equal(result1.Count, 1);
var result1Port80Mapping = result1.Find(pm =>
string.Equals(pm.ContainerPort, "80") &&
string.Equals(pm.HostPort, "32881") &&
string.Equals(pm.Protocol, "tcp", StringComparison.OrdinalIgnoreCase)
);
Assert.NotNull(result1Port80Mapping);
Assert.NotNull(result1Empty);
Assert.Equal(result1Empty.Count, 0);
Assert.NotNull(result2);
Assert.Equal(result2.Count, 2);
var result2Port80Mapping = result2.Find(pm =>
string.Equals(pm.ContainerPort, "80") &&
string.Equals(pm.HostPort, "32881") &&
string.Equals(pm.Protocol, "tcp", StringComparison.OrdinalIgnoreCase)
);
Assert.NotNull(result2Port80Mapping);
var result2Port6379Mapping = result2.Find(pm =>
string.Equals(pm.ContainerPort, "6379") &&
string.Equals(pm.HostPort, "32882") &&
string.Equals(pm.Protocol, "tcp", StringComparison.OrdinalIgnoreCase)
);
Assert.NotNull(result2Port6379Mapping);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void RegexParsesPathFromDockerConfigEnv()
{
// Arrange
var configOutput0 = new List<string>
{
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"MY_VAR=test"
};
var configOutput1 = new List<string>
{
"PATH=/bad idea:/really,bad,idea:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"MY_VAR=test"
};
var configOutput2 = new List<string>();
var configOutput3 = new List<string>
{
"NOT_A_PATH=/bad idea:/really,bad,idea:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"MY_VAR=test"
};
var configOutput4 = new List<string>
{
"PATH",
"PATH="
};
var configOutput5 = new List<string>
{
"PATH=/foo/bar:/baz",
"Path=/no/where"
};
// Act
var result0 = DockerUtil.ParsePathFromConfigEnv(configOutput0);
var result1 = DockerUtil.ParsePathFromConfigEnv(configOutput1);
var result2 = DockerUtil.ParsePathFromConfigEnv(configOutput2);
var result3 = DockerUtil.ParsePathFromConfigEnv(configOutput3);
var result4 = DockerUtil.ParsePathFromConfigEnv(configOutput4);
var result5 = DockerUtil.ParsePathFromConfigEnv(configOutput5);
// Assert
Assert.NotNull(result0);
Assert.Equal("/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", result0);
Assert.NotNull(result1);
Assert.Equal("/bad idea:/really,bad,idea:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", result1);
Assert.NotNull(result2);
Assert.Equal("", result2);
Assert.NotNull(result3);
Assert.Equal("", result3);
Assert.NotNull(result4);
Assert.Equal("", result4);
Assert.NotNull(result5);
Assert.Equal("/foo/bar:/baz", result5);
}
}
}

View File

@@ -0,0 +1,58 @@
using Xunit;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
namespace GitHub.Runner.Common.Tests
{
public sealed class DotnetsdkDownloadScriptL0
{
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Agent")]
public async Task EnsureDotnetsdkBashDownloadScriptUpToDate()
{
string shDownloadUrl = "https://dot.net/v1/dotnet-install.sh";
using (HttpClient downloadClient = new HttpClient())
{
var response = await downloadClient.GetAsync("https://www.bing.com");
if (!response.IsSuccessStatusCode)
{
return;
}
string shScript = await downloadClient.GetStringAsync(shDownloadUrl);
string existingShScript = File.ReadAllText(Path.Combine(TestUtil.GetSrcPath(), "Misc/dotnet-install.sh"));
bool shScriptMatched = string.Equals(shScript.TrimEnd('\n', '\r', '\0').Replace("\r\n", "\n").Replace("\r", "\n"), existingShScript.TrimEnd('\n', '\r', '\0').Replace("\r\n", "\n").Replace("\r", "\n"));
Assert.True(shScriptMatched, "Fix the test by updating Src/Misc/dotnet-install.sh with content from https://dot.net/v1/dotnet-install.sh");
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Agent")]
public async Task EnsureDotnetsdkPowershellDownloadScriptUpToDate()
{
string ps1DownloadUrl = "https://dot.net/v1/dotnet-install.ps1";
using (HttpClient downloadClient = new HttpClient())
{
var response = await downloadClient.GetAsync("https://www.bing.com");
if (!response.IsSuccessStatusCode)
{
return;
}
string ps1Script = await downloadClient.GetStringAsync(ps1DownloadUrl);
string existingPs1Script = File.ReadAllText(Path.Combine(TestUtil.GetSrcPath(), "Misc/dotnet-install.ps1"));
bool ps1ScriptMatched = string.Equals(ps1Script.TrimEnd('\n', '\r', '\0').Replace("\r\n", "\n").Replace("\r", "\n"), existingPs1Script.TrimEnd('\n', '\r', '\0').Replace("\r\n", "\n").Replace("\r", "\n"));
Assert.True(ps1ScriptMatched, "Fix the test by updating Src/Misc/dotnet-install.ps1 with content from https://dot.net/v1/dotnet-install.ps1");
}
}
}
}

View File

@@ -0,0 +1,62 @@
using GitHub.Runner.Common.Capabilities;
using GitHub.Runner.Worker;
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace GitHub.Runner.Common.Tests
{
public sealed class ExtensionManagerL0
{
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void LoadsTypeFromString()
{
using (TestHostContext tc = new TestHostContext(this))
{
// Arrange.
var manager = new ExtensionManager();
manager.Initialize(tc);
// Act.
List<ICapabilitiesProvider> extensions = manager.GetExtensions<ICapabilitiesProvider>();
// Assert.
Assert.True(
extensions.Any(x => x is RunnerCapabilitiesProvider),
$"Expected {nameof(RunnerCapabilitiesProvider)} extension to be returned as a job extension.");
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void LoadsTypes()
{
using (TestHostContext tc = new TestHostContext(this))
{
// Arrange.
var manager = new ExtensionManager();
manager.Initialize(tc);
// Act/Assert.
AssertContains<GitHub.Runner.Common.Capabilities.ICapabilitiesProvider>(
manager,
concreteType: typeof(GitHub.Runner.Common.Capabilities.RunnerCapabilitiesProvider));
}
}
private static void AssertContains<T>(ExtensionManager manager, Type concreteType) where T : class, IExtension
{
// Act.
List<T> extensions = manager.GetExtensions<T>();
// Assert.
Assert.True(
extensions.Any(x => x.GetType() == concreteType),
$"Expected '{typeof(T).FullName}' extensions to contain concrete type '{concreteType.FullName}'.");
}
}
}

View File

@@ -0,0 +1,84 @@
using GitHub.Runner.Common.Util;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using Xunit;
namespace GitHub.Runner.Common.Tests
{
public sealed class HostContextL0
{
private HostContext _hc;
private CancellationTokenSource _tokenSource;
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void CreateServiceReturnsNewInstance()
{
try
{
// Arrange.
Setup();
// Act.
var reference1 = _hc.CreateService<IRunnerServer>();
var reference2 = _hc.CreateService<IRunnerServer>();
// Assert.
Assert.NotNull(reference1);
Assert.IsType<RunnerServer>(reference1);
Assert.NotNull(reference2);
Assert.IsType<RunnerServer>(reference2);
Assert.False(object.ReferenceEquals(reference1, reference2));
}
finally
{
// Cleanup.
Teardown();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void GetServiceReturnsSingleton()
{
try
{
// Arrange.
Setup();
// Act.
var reference1 = _hc.GetService<IRunnerServer>();
var reference2 = _hc.GetService<IRunnerServer>();
// Assert.
Assert.NotNull(reference1);
Assert.IsType<RunnerServer>(reference1);
Assert.NotNull(reference2);
Assert.True(object.ReferenceEquals(reference1, reference2));
}
finally
{
// Cleanup.
Teardown();
}
}
public void Setup([CallerMemberName] string testName = "")
{
_tokenSource = new CancellationTokenSource();
_hc = new HostContext(
hostType: "L0Test",
logFile: Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"trace_{nameof(HostContextL0)}_{testName}.log"));
}
public void Teardown()
{
_hc?.Dispose();
_tokenSource?.Dispose();
}
}
}

View File

@@ -0,0 +1,574 @@
// using GitHub.DistributedTask.WebApi;
// using GitHub.Runner.Listener;
// using GitHub.Runner.Listener.Configuration;
// using Moq;
// using System;
// using System.Collections.Generic;
// using System.Threading;
// using System.Threading.Tasks;
// using Xunit;
// using GitHub.Services.WebApi;
// using Pipelines = GitHub.DistributedTask.Pipelines;
// using GitHub.Runner.Common.Util;
// namespace GitHub.Runner.Common.Tests.Listener
// {
// public sealed class AgentL0
// {
// private Mock<IConfigurationManager> _configurationManager;
// private Mock<IJobNotification> _jobNotification;
// private Mock<IMessageListener> _messageListener;
// private Mock<IPromptManager> _promptManager;
// private Mock<IJobDispatcher> _jobDispatcher;
// private Mock<IRunnerServer> _agentServer;
// private Mock<ITerminal> _term;
// private Mock<IConfigurationStore> _configStore;
// private Mock<IRunnerWebProxy> _proxy;
// private Mock<IRunnerCertificateManager> _cert;
// private Mock<ISelfUpdater> _updater;
// public AgentL0()
// {
// _configurationManager = new Mock<IConfigurationManager>();
// _jobNotification = new Mock<IJobNotification>();
// _messageListener = new Mock<IMessageListener>();
// _promptManager = new Mock<IPromptManager>();
// _jobDispatcher = new Mock<IJobDispatcher>();
// _agentServer = new Mock<IRunnerServer>();
// _term = new Mock<ITerminal>();
// _configStore = new Mock<IConfigurationStore>();
// _proxy = new Mock<IRunnerWebProxy>();
// _cert = new Mock<IRunnerCertificateManager>();
// _updater = new Mock<ISelfUpdater>();
// }
// private AgentJobRequestMessage CreateJobRequestMessage(string jobName)
// {
// TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
// TimelineReference timeline = null;
// JobEnvironment environment = new JobEnvironment();
// List<TaskInstance> tasks = new List<TaskInstance>();
// Guid JobId = Guid.NewGuid();
// var jobRequest = new AgentJobRequestMessage(plan, timeline, JobId, jobName, jobName, environment, tasks);
// return jobRequest as AgentJobRequestMessage;
// }
// private JobCancelMessage CreateJobCancelMessage()
// {
// var message = new JobCancelMessage(Guid.NewGuid(), TimeSpan.FromSeconds(0));
// return message;
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Agent")]
// //process 2 new job messages, and one cancel message
// public async void TestRunAsync()
// {
// using (var hc = new TestHostContext(this))
// {
// //Arrange
// var agent = new Runner.Listener.Runner();
// hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
// hc.SetSingleton<IJobNotification>(_jobNotification.Object);
// hc.SetSingleton<IMessageListener>(_messageListener.Object);
// hc.SetSingleton<IPromptManager>(_promptManager.Object);
// hc.SetSingleton<IRunnerServer>(_agentServer.Object);
// hc.SetSingleton<IRunnerWebProxy>(_proxy.Object);
// hc.SetSingleton<IRunnerCertificateManager>(_cert.Object);
// hc.SetSingleton<IConfigurationStore>(_configStore.Object);
// agent.Initialize(hc);
// var settings = new RunnerSettings
// {
// PoolId = 43242
// };
// var message = new TaskAgentMessage()
// {
// Body = JsonUtility.ToString(CreateJobRequestMessage("job1")),
// MessageId = 4234,
// MessageType = JobRequestMessageTypes.AgentJobRequest
// };
// var messages = new Queue<TaskAgentMessage>();
// 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<CancellationToken>()))
// .Returns(Task.FromResult<bool>(true));
// _messageListener.Setup(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()))
// .Returns(async () =>
// {
// 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<TaskAgentMessage>()))
// .Returns(Task.CompletedTask);
// _jobDispatcher.Setup(x => x.Run(It.IsAny<Pipelines.AgentJobRequestMessage>(), It.IsAny<bool>()))
// .Callback(() =>
// {
// });
// _jobNotification.Setup(x => x.StartClient(It.IsAny<String>(), It.IsAny<String>(), It.IsAny<CancellationToken>()))
// .Callback(() =>
// {
// });
// _jobNotification.Setup(x => x.StartClient(It.IsAny<String>(), It.IsAny<String>()))
// .Callback(() =>
// {
// });
// hc.EnqueueInstance<IJobDispatcher>(_jobDispatcher.Object);
// _configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
// //Act
// var command = new CommandSettings(hc, new string[] { "run" });
// Task agentTask = agent.ExecuteCommand(command);
// //Assert
// //wait for the agent to run one job
// if (!await signalWorkerComplete.WaitAsync(2000))
// {
// Assert.True(false, $"{nameof(_messageListener.Object.GetNextMessageAsync)} was not invoked.");
// }
// else
// {
// //Act
// hc.ShutdownRunner(ShutdownReason.UserCancelled); //stop Agent
// //Assert
// Task[] taskToWait2 = { agentTask, Task.Delay(2000) };
// //wait for the Agent to exit
// await Task.WhenAny(taskToWait2);
// Assert.True(agentTask.IsCompleted, $"{nameof(agent.ExecuteCommand)} timed out.");
// Assert.True(!agentTask.IsFaulted, agentTask.Exception?.ToString());
// Assert.True(agentTask.IsCanceled);
// _jobDispatcher.Verify(x => x.Run(It.IsAny<Pipelines.AgentJobRequestMessage>(), It.IsAny<bool>()), Times.Once(),
// $"{nameof(_jobDispatcher.Object.Run)} was not invoked.");
// _messageListener.Verify(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()), Times.AtLeastOnce());
// _messageListener.Verify(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()), Times.Once());
// _messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once());
// _messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.AtLeastOnce());
// }
// }
// }
// public static TheoryData<string[], bool, Times> RunAsServiceTestData = new TheoryData<string[], bool, Times>()
// {
// // staring with run command, configured as run as service, should start the agent
// { new [] { "run" }, true, Times.Once() },
// // starting with no argument, configured not to run as service, should start agent interactively
// { new [] { "run" }, false, Times.Once() }
// };
// [Theory]
// [MemberData("RunAsServiceTestData")]
// [Trait("Level", "L0")]
// [Trait("Category", "Agent")]
// public async void TestExecuteCommandForRunAsService(string[] args, bool configureAsService, Times expectedTimes)
// {
// using (var hc = new TestHostContext(this))
// {
// hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
// hc.SetSingleton<IPromptManager>(_promptManager.Object);
// hc.SetSingleton<IMessageListener>(_messageListener.Object);
// hc.SetSingleton<IRunnerWebProxy>(_proxy.Object);
// hc.SetSingleton<IRunnerCertificateManager>(_cert.Object);
// hc.SetSingleton<IConfigurationStore>(_configStore.Object);
// var command = new CommandSettings(hc, args);
// _configurationManager.Setup(x => x.IsConfigured()).Returns(true);
// _configurationManager.Setup(x => x.LoadSettings())
// .Returns(new RunnerSettings { });
// _configStore.Setup(x => x.IsServiceConfigured()).Returns(configureAsService);
// _messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
// .Returns(Task.FromResult(false));
// var agent = new Runner.Listener.Runner();
// agent.Initialize(hc);
// await agent.ExecuteCommand(command);
// _messageListener.Verify(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()), expectedTimes);
// }
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Agent")]
// //process 2 new job messages, and one cancel message
// public async void TestMachineProvisionerCLI()
// {
// using (var hc = new TestHostContext(this))
// {
// hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
// hc.SetSingleton<IPromptManager>(_promptManager.Object);
// hc.SetSingleton<IMessageListener>(_messageListener.Object);
// hc.SetSingleton<IRunnerWebProxy>(_proxy.Object);
// hc.SetSingleton<IRunnerCertificateManager>(_cert.Object);
// hc.SetSingleton<IConfigurationStore>(_configStore.Object);
// var command = new CommandSettings(hc, new[] { "run" });
// _configurationManager.Setup(x => x.IsConfigured()).
// Returns(true);
// _configurationManager.Setup(x => x.LoadSettings())
// .Returns(new RunnerSettings { });
// _configStore.Setup(x => x.IsServiceConfigured())
// .Returns(false);
// _messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
// .Returns(Task.FromResult(false));
// var agent = new Runner.Listener.Runner();
// agent.Initialize(hc);
// await agent.ExecuteCommand(command);
// _messageListener.Verify(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()), Times.Once());
// }
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Agent")]
// //process 2 new job messages, and one cancel message
// public async void TestMachineProvisionerCLICompat()
// {
// using (var hc = new TestHostContext(this))
// {
// hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
// hc.SetSingleton<IPromptManager>(_promptManager.Object);
// hc.SetSingleton<IMessageListener>(_messageListener.Object);
// hc.SetSingleton<IRunnerWebProxy>(_proxy.Object);
// hc.SetSingleton<IRunnerCertificateManager>(_cert.Object);
// hc.SetSingleton<IConfigurationStore>(_configStore.Object);
// var command = new CommandSettings(hc, new string[] { });
// _configurationManager.Setup(x => x.IsConfigured()).
// Returns(true);
// _configurationManager.Setup(x => x.LoadSettings())
// .Returns(new RunnerSettings { });
// _configStore.Setup(x => x.IsServiceConfigured())
// .Returns(false);
// _messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
// .Returns(Task.FromResult(false));
// var agent = new Runner.Listener.Runner();
// agent.Initialize(hc);
// await agent.ExecuteCommand(command);
// _messageListener.Verify(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()), Times.Once());
// }
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Agent")]
// public async void TestRunOnce()
// {
// using (var hc = new TestHostContext(this))
// {
// //Arrange
// var agent = new Runner.Listener.Runner();
// hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
// hc.SetSingleton<IJobNotification>(_jobNotification.Object);
// hc.SetSingleton<IMessageListener>(_messageListener.Object);
// hc.SetSingleton<IPromptManager>(_promptManager.Object);
// hc.SetSingleton<IRunnerServer>(_agentServer.Object);
// hc.SetSingleton<IRunnerWebProxy>(_proxy.Object);
// hc.SetSingleton<IRunnerCertificateManager>(_cert.Object);
// hc.SetSingleton<IConfigurationStore>(_configStore.Object);
// agent.Initialize(hc);
// var settings = new RunnerSettings
// {
// PoolId = 43242
// };
// var message = new TaskAgentMessage()
// {
// Body = JsonUtility.ToString(CreateJobRequestMessage("job1")),
// MessageId = 4234,
// MessageType = JobRequestMessageTypes.AgentJobRequest
// };
// var messages = new Queue<TaskAgentMessage>();
// messages.Enqueue(message);
// _configurationManager.Setup(x => x.LoadSettings())
// .Returns(settings);
// _configurationManager.Setup(x => x.IsConfigured())
// .Returns(true);
// _messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
// .Returns(Task.FromResult<bool>(true));
// _messageListener.Setup(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()))
// .Returns(async () =>
// {
// if (0 == messages.Count)
// {
// await Task.Delay(2000);
// }
// return messages.Dequeue();
// });
// _messageListener.Setup(x => x.DeleteSessionAsync())
// .Returns(Task.CompletedTask);
// _messageListener.Setup(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()))
// .Returns(Task.CompletedTask);
// var runOnceJobCompleted = new TaskCompletionSource<bool>();
// _jobDispatcher.Setup(x => x.RunOnceJobCompleted)
// .Returns(runOnceJobCompleted);
// _jobDispatcher.Setup(x => x.Run(It.IsAny<Pipelines.AgentJobRequestMessage>(), It.IsAny<bool>()))
// .Callback(() =>
// {
// runOnceJobCompleted.TrySetResult(true);
// });
// _jobNotification.Setup(x => x.StartClient(It.IsAny<String>(), It.IsAny<String>(), It.IsAny<CancellationToken>()))
// .Callback(() =>
// {
// });
// _jobNotification.Setup(x => x.StartClient(It.IsAny<String>(), It.IsAny<String>()))
// .Callback(() =>
// {
// });
// hc.EnqueueInstance<IJobDispatcher>(_jobDispatcher.Object);
// _configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
// //Act
// var command = new CommandSettings(hc, new string[] { "run", "--once" });
// Task<int> agentTask = agent.ExecuteCommand(command);
// //Assert
// //wait for the agent to run one job and exit
// await Task.WhenAny(agentTask, Task.Delay(30000));
// Assert.True(agentTask.IsCompleted, $"{nameof(agent.ExecuteCommand)} timed out.");
// Assert.True(!agentTask.IsFaulted, agentTask.Exception?.ToString());
// Assert.True(agentTask.Result == Constants.Runner.ReturnCode.Success);
// _jobDispatcher.Verify(x => x.Run(It.IsAny<Pipelines.AgentJobRequestMessage>(), true), Times.Once(),
// $"{nameof(_jobDispatcher.Object.Run)} was not invoked.");
// _messageListener.Verify(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()), Times.AtLeastOnce());
// _messageListener.Verify(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()), Times.Once());
// _messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once());
// _messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.AtLeastOnce());
// }
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Agent")]
// public async void TestRunOnceOnlyTakeOneJobMessage()
// {
// using (var hc = new TestHostContext(this))
// {
// //Arrange
// var agent = new Runner.Listener.Runner();
// hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
// hc.SetSingleton<IJobNotification>(_jobNotification.Object);
// hc.SetSingleton<IMessageListener>(_messageListener.Object);
// hc.SetSingleton<IPromptManager>(_promptManager.Object);
// hc.SetSingleton<IRunnerServer>(_agentServer.Object);
// hc.SetSingleton<IRunnerWebProxy>(_proxy.Object);
// hc.SetSingleton<IRunnerCertificateManager>(_cert.Object);
// hc.SetSingleton<IConfigurationStore>(_configStore.Object);
// agent.Initialize(hc);
// var settings = new RunnerSettings
// {
// PoolId = 43242
// };
// var message1 = new TaskAgentMessage()
// {
// Body = JsonUtility.ToString(CreateJobRequestMessage("job1")),
// MessageId = 4234,
// MessageType = JobRequestMessageTypes.AgentJobRequest
// };
// var message2 = new TaskAgentMessage()
// {
// Body = JsonUtility.ToString(CreateJobRequestMessage("job1")),
// MessageId = 4235,
// MessageType = JobRequestMessageTypes.AgentJobRequest
// };
// var messages = new Queue<TaskAgentMessage>();
// messages.Enqueue(message1);
// messages.Enqueue(message2);
// _configurationManager.Setup(x => x.LoadSettings())
// .Returns(settings);
// _configurationManager.Setup(x => x.IsConfigured())
// .Returns(true);
// _messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
// .Returns(Task.FromResult<bool>(true));
// _messageListener.Setup(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()))
// .Returns(async () =>
// {
// if (0 == messages.Count)
// {
// await Task.Delay(2000);
// }
// return messages.Dequeue();
// });
// _messageListener.Setup(x => x.DeleteSessionAsync())
// .Returns(Task.CompletedTask);
// _messageListener.Setup(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()))
// .Returns(Task.CompletedTask);
// var runOnceJobCompleted = new TaskCompletionSource<bool>();
// _jobDispatcher.Setup(x => x.RunOnceJobCompleted)
// .Returns(runOnceJobCompleted);
// _jobDispatcher.Setup(x => x.Run(It.IsAny<Pipelines.AgentJobRequestMessage>(), It.IsAny<bool>()))
// .Callback(() =>
// {
// runOnceJobCompleted.TrySetResult(true);
// });
// _jobNotification.Setup(x => x.StartClient(It.IsAny<String>(), It.IsAny<String>(), It.IsAny<CancellationToken>()))
// .Callback(() =>
// {
// });
// _jobNotification.Setup(x => x.StartClient(It.IsAny<String>(), It.IsAny<String>()))
// .Callback(() =>
// {
// });
// hc.EnqueueInstance<IJobDispatcher>(_jobDispatcher.Object);
// _configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
// //Act
// var command = new CommandSettings(hc, new string[] { "run", "--once" });
// Task<int> agentTask = agent.ExecuteCommand(command);
// //Assert
// //wait for the agent to run one job and exit
// await Task.WhenAny(agentTask, Task.Delay(30000));
// Assert.True(agentTask.IsCompleted, $"{nameof(agent.ExecuteCommand)} timed out.");
// Assert.True(!agentTask.IsFaulted, agentTask.Exception?.ToString());
// Assert.True(agentTask.Result == Constants.Runner.ReturnCode.Success);
// _jobDispatcher.Verify(x => x.Run(It.IsAny<Pipelines.AgentJobRequestMessage>(), true), Times.Once(),
// $"{nameof(_jobDispatcher.Object.Run)} was not invoked.");
// _messageListener.Verify(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()), Times.AtLeastOnce());
// _messageListener.Verify(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()), Times.Once());
// _messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once());
// _messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.Once());
// }
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Agent")]
// public async void TestRunOnceHandleUpdateMessage()
// {
// using (var hc = new TestHostContext(this))
// {
// //Arrange
// var agent = new Runner.Listener.Runner();
// hc.SetSingleton<IConfigurationManager>(_configurationManager.Object);
// hc.SetSingleton<IJobNotification>(_jobNotification.Object);
// hc.SetSingleton<IMessageListener>(_messageListener.Object);
// hc.SetSingleton<IPromptManager>(_promptManager.Object);
// hc.SetSingleton<IRunnerServer>(_agentServer.Object);
// hc.SetSingleton<IRunnerWebProxy>(_proxy.Object);
// hc.SetSingleton<IRunnerCertificateManager>(_cert.Object);
// hc.SetSingleton<IConfigurationStore>(_configStore.Object);
// hc.SetSingleton<ISelfUpdater>(_updater.Object);
// agent.Initialize(hc);
// var settings = new RunnerSettings
// {
// PoolId = 43242,
// AgentId = 5678
// };
// var message1 = new TaskAgentMessage()
// {
// Body = JsonUtility.ToString(new AgentRefreshMessage(settings.AgentId, "2.123.0")),
// MessageId = 4234,
// MessageType = AgentRefreshMessage.MessageType
// };
// var messages = new Queue<TaskAgentMessage>();
// messages.Enqueue(message1);
// _updater.Setup(x => x.SelfUpdate(It.IsAny<AgentRefreshMessage>(), It.IsAny<IJobDispatcher>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
// .Returns(Task.FromResult(true));
// _configurationManager.Setup(x => x.LoadSettings())
// .Returns(settings);
// _configurationManager.Setup(x => x.IsConfigured())
// .Returns(true);
// _messageListener.Setup(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()))
// .Returns(Task.FromResult<bool>(true));
// _messageListener.Setup(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()))
// .Returns(async () =>
// {
// if (0 == messages.Count)
// {
// await Task.Delay(2000);
// }
// return messages.Dequeue();
// });
// _messageListener.Setup(x => x.DeleteSessionAsync())
// .Returns(Task.CompletedTask);
// _messageListener.Setup(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()))
// .Returns(Task.CompletedTask);
// _jobNotification.Setup(x => x.StartClient(It.IsAny<String>(), It.IsAny<String>(), It.IsAny<CancellationToken>()))
// .Callback(() =>
// {
// });
// _jobNotification.Setup(x => x.StartClient(It.IsAny<String>(), It.IsAny<String>()))
// .Callback(() =>
// {
// });
// hc.EnqueueInstance<IJobDispatcher>(_jobDispatcher.Object);
// _configStore.Setup(x => x.IsServiceConfigured()).Returns(false);
// //Act
// var command = new CommandSettings(hc, new string[] { "run", "--once" });
// Task<int> agentTask = agent.ExecuteCommand(command);
// //Assert
// //wait for the agent to exit with right return code
// await Task.WhenAny(agentTask, Task.Delay(30000));
// Assert.True(agentTask.IsCompleted, $"{nameof(agent.ExecuteCommand)} timed out.");
// Assert.True(!agentTask.IsFaulted, agentTask.Exception?.ToString());
// Assert.True(agentTask.Result == Constants.Runner.ReturnCode.RunOnceRunnerUpdating);
// _updater.Verify(x => x.SelfUpdate(It.IsAny<AgentRefreshMessage>(), It.IsAny<IJobDispatcher>(), false, It.IsAny<CancellationToken>()), Times.Once);
// _jobDispatcher.Verify(x => x.Run(It.IsAny<Pipelines.AgentJobRequestMessage>(), true), Times.Never());
// _messageListener.Verify(x => x.GetNextMessageAsync(It.IsAny<CancellationToken>()), Times.AtLeastOnce());
// _messageListener.Verify(x => x.CreateSessionAsync(It.IsAny<CancellationToken>()), Times.Once());
// _messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once());
// _messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny<TaskAgentMessage>()), Times.Once());
// }
// }
// }
// }

View File

@@ -0,0 +1,784 @@
using GitHub.Runner.Listener;
using GitHub.Runner.Listener.Configuration;
using GitHub.Runner.Common.Util;
using Moq;
using System;
using System.Runtime.CompilerServices;
using Xunit;
using GitHub.Runner.Sdk;
namespace GitHub.Runner.Common.Tests
{
public sealed class CommandSettingsL0
{
private readonly Mock<IPromptManager> _promptManager = new Mock<IPromptManager>();
// It is sufficient to test one arg only. All individual args are tested by the PromptsFor___ methods.
// The PromptsFor___ methods suffice to cover the interesting differences between each of the args.
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void GetsArg()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[] { "--agent", "some agent" });
// Act.
string actual = command.GetAgentName();
// Assert.
Assert.Equal("some agent", actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void GetsArgFromEnvVar()
{
using (TestHostContext hc = CreateTestContext())
{
try
{
// Arrange.
Environment.SetEnvironmentVariable("ACTIONS_RUNNER_INPUT_AGENT", "some agent");
var command = new CommandSettings(hc, args: new string[0]);
// Act.
string actual = command.GetAgentName();
// Assert.
Assert.Equal("some agent", actual);
Assert.Equal(string.Empty, Environment.GetEnvironmentVariable("ACTIONS_RUNNER_INPUT_AGENT") ?? string.Empty); // Should remove.
Assert.Equal(hc.SecretMasker.MaskSecrets("some agent"), "some agent");
}
finally
{
Environment.SetEnvironmentVariable("ACTIONS_RUNNER_INPUT_AGENT", null);
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void GetsArgSecretFromEnvVar()
{
using (TestHostContext hc = CreateTestContext())
{
try
{
// Arrange.
Environment.SetEnvironmentVariable("ACTIONS_RUNNER_INPUT_TOKEN", "some secret token value");
var command = new CommandSettings(hc, args: new string[0]);
// Act.
string actual = command.GetToken();
// Assert.
Assert.Equal("some secret token value", actual);
Assert.Equal(string.Empty, Environment.GetEnvironmentVariable("ACTIONS_RUNNER_INPUT_TOKEN") ?? string.Empty); // Should remove.
Assert.Equal(hc.SecretMasker.MaskSecrets("some secret token value"), "***");
}
finally
{
Environment.SetEnvironmentVariable("ACTIONS_RUNNER_INPUT_TOKEN", null);
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void GetsCommandConfigure()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[] { "configure" });
// Act.
bool actual = command.Configure;
// Assert.
Assert.True(actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void GetsCommandRun()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[] { "run" });
// Act.
bool actual = command.Run;
// Assert.
Assert.True(actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void GetsCommandUnconfigure()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[] { "remove" });
// Act.
bool actual = command.Remove;
// Assert.
Assert.True(actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void GetsFlagCommit()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[] { "--commit" });
// Act.
bool actual = command.Commit;
// Assert.
Assert.True(actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void GetsFlagHelp()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[] { "--help" });
// Act.
bool actual = command.Help;
// Assert.
Assert.True(actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void GetsFlagReplace()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[] { "--replace" });
// Act.
bool actual = command.GetReplace();
// Assert.
Assert.True(actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void GetsFlagRunAsService()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[] { "--runasservice" });
// Act.
bool actual = command.GetRunAsService();
// Assert.
Assert.True(actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void GetsFlagUnattended()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[] { "--unattended" });
// Act.
bool actual = command.Unattended;
// Assert.
Assert.True(actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void GetsFlagUnattendedFromEnvVar()
{
using (TestHostContext hc = CreateTestContext())
{
try
{
// Arrange.
Environment.SetEnvironmentVariable("ACTIONS_RUNNER_INPUT_UNATTENDED", "true");
var command = new CommandSettings(hc, args: new string[0]);
// Act.
bool actual = command.Unattended;
// Assert.
Assert.Equal(true, actual);
Assert.Equal(string.Empty, Environment.GetEnvironmentVariable("ACTIONS_RUNNER_INPUT_UNATTENDED") ?? string.Empty); // Should remove.
}
finally
{
Environment.SetEnvironmentVariable("ACTIONS_RUNNER_INPUT_UNATTENDED", null);
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void GetsFlagVersion()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[] { "--version" });
// Act.
bool actual = command.Version;
// Assert.
Assert.True(actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void PassesUnattendedToReadBool()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[] { "--unattended" });
_promptManager
.Setup(x => x.ReadBool(
Constants.Runner.CommandLine.Flags.Replace, // argName
"Would you like to replace the existing runner? (Y/N)", // description
false, // defaultValue
true)) // unattended
.Returns(true);
// Act.
bool actual = command.GetReplace();
// Assert.
Assert.True(actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void PassesUnattendedToReadValue()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[] { "--unattended" });
_promptManager
.Setup(x => x.ReadValue(
Constants.Runner.CommandLine.Args.Agent, // argName
"Enter the name of runner:", // description
false, // secret
Environment.MachineName, // defaultValue
Validators.NonEmptyValidator, // validator
true)) // unattended
.Returns("some agent");
// Act.
string actual = command.GetAgentName();
// Assert.
Assert.Equal("some agent", actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void PromptsForAgent()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[0]);
_promptManager
.Setup(x => x.ReadValue(
Constants.Runner.CommandLine.Args.Agent, // argName
"Enter the name of runner:", // description
false, // secret
Environment.MachineName, // defaultValue
Validators.NonEmptyValidator, // validator
false)) // unattended
.Returns("some agent");
// Act.
string actual = command.GetAgentName();
// Assert.
Assert.Equal("some agent", actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void PromptsForAuth()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[0]);
_promptManager
.Setup(x => x.ReadValue(
Constants.Runner.CommandLine.Args.Auth, // argName
"How would you like to authenticate?", // description
false, // secret
"some default auth", // defaultValue
Validators.AuthSchemeValidator, // validator
false)) // unattended
.Returns("some auth");
// Act.
string actual = command.GetAuth("some default auth");
// Assert.
Assert.Equal("some auth", actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void PromptsForPassword()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[0]);
_promptManager
.Setup(x => x.ReadValue(
Constants.Runner.CommandLine.Args.Password, // argName
"What is your GitHub password?", // description
true, // secret
string.Empty, // defaultValue
Validators.NonEmptyValidator, // validator
false)) // unattended
.Returns("some password");
// Act.
string actual = command.GetPassword();
// Assert.
Assert.Equal("some password", actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void PromptsForPool()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[0]);
_promptManager
.Setup(x => x.ReadValue(
Constants.Runner.CommandLine.Args.Pool, // argName
"Enter the name of your runner pool:", // description
false, // secret
"default", // defaultValue
Validators.NonEmptyValidator, // validator
false)) // unattended
.Returns("some pool");
// Act.
string actual = command.GetPool();
// Assert.
Assert.Equal("some pool", actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void PromptsForReplace()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[0]);
_promptManager
.Setup(x => x.ReadBool(
Constants.Runner.CommandLine.Flags.Replace, // argName
"Would you like to replace the existing runner? (Y/N)", // description
false, // defaultValue
false)) // unattended
.Returns(true);
// Act.
bool actual = command.GetReplace();
// Assert.
Assert.True(actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void PromptsForRunAsService()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[0]);
_promptManager
.Setup(x => x.ReadBool(
Constants.Runner.CommandLine.Flags.RunAsService, // argName
"Would you like to run the runner as service? (Y/N)", // description
false, // defaultValue
false)) // unattended
.Returns(true);
// Act.
bool actual = command.GetRunAsService();
// Assert.
Assert.True(actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void PromptsForToken()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[0]);
_promptManager
.Setup(x => x.ReadValue(
Constants.Runner.CommandLine.Args.Token, // argName
"Enter your personal access token:", // description
true, // secret
string.Empty, // defaultValue
Validators.NonEmptyValidator, // validator
false)) // unattended
.Returns("some token");
// Act.
string actual = command.GetToken();
// Assert.
Assert.Equal("some token", actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void PromptsForUrl()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[0]);
_promptManager
.Setup(x => x.ReadValue(
Constants.Runner.CommandLine.Args.Url, // argName
"What is the URL of your repository?", // description
false, // secret
string.Empty, // defaultValue
Validators.ServerUrlValidator, // validator
false)) // unattended
.Returns("some url");
// Act.
string actual = command.GetUrl();
// Assert.
Assert.Equal("some url", actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void PromptsForUserName()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[0]);
_promptManager
.Setup(x => x.ReadValue(
Constants.Runner.CommandLine.Args.UserName, // argName
"What is your GitHub username?", // description
false, // secret
string.Empty, // defaultValue
Validators.NonEmptyValidator, // validator
false)) // unattended
.Returns("some user name");
// Act.
string actual = command.GetUserName();
// Assert.
Assert.Equal("some user name", actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void PromptsForWindowsLogonAccount()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[0]);
_promptManager
.Setup(x => x.ReadValue(
Constants.Runner.CommandLine.Args.WindowsLogonAccount, // argName
"User account to use for the service", // description
false, // secret
"some default account", // defaultValue
Validators.NTAccountValidator, // validator
false)) // unattended
.Returns("some windows logon account");
// Act.
string actual = command.GetWindowsLogonAccount("some default account", "User account to use for the service");
// Assert.
Assert.Equal("some windows logon account", actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void PromptsForWindowsLogonPassword()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[0]);
string accountName = "somewindowsaccount";
_promptManager
.Setup(x => x.ReadValue(
Constants.Runner.CommandLine.Args.WindowsLogonPassword, // argName
string.Format("Password for the account {0}", accountName), // description
true, // secret
string.Empty, // defaultValue
Validators.NonEmptyValidator, // validator
false)) // unattended
.Returns("some windows logon password");
// Act.
string actual = command.GetWindowsLogonPassword(accountName);
// Assert.
Assert.Equal("some windows logon password", actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void PromptsForWork()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[0]);
_promptManager
.Setup(x => x.ReadValue(
Constants.Runner.CommandLine.Args.Work, // argName
"Enter name of work folder:", // description
false, // secret
"_work", // defaultValue
Validators.NonEmptyValidator, // validator
false)) // unattended
.Returns("some work");
// Act.
string actual = command.GetWork();
// Assert.
Assert.Equal("some work", actual);
}
}
// It is sufficient to test one arg only. All individual args are tested by the PromptsFor___ methods.
// The PromptsFor___ methods suffice to cover the interesting differences between each of the args.
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void PromptsWhenEmpty()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[] { "--url", "" });
_promptManager
.Setup(x => x.ReadValue(
Constants.Runner.CommandLine.Args.Url, // argName
"What is the URL of your repository?", // description
false, // secret
string.Empty, // defaultValue
Validators.ServerUrlValidator, // validator
false)) // unattended
.Returns("some url");
// Act.
string actual = command.GetUrl();
// Assert.
Assert.Equal("some url", actual);
}
}
// It is sufficient to test one arg only. All individual args are tested by the PromptsFor___ methods.
// The PromptsFor___ methods suffice to cover the interesting differences between each of the args.
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void PromptsWhenInvalid()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[] { "--url", "notValid" });
_promptManager
.Setup(x => x.ReadValue(
Constants.Runner.CommandLine.Args.Url, // argName
"What is the URL of your repository?", // description
false, // secret
string.Empty, // defaultValue
Validators.ServerUrlValidator, // validator
false)) // unattended
.Returns("some url");
// Act.
string actual = command.GetUrl();
// Assert.
Assert.Equal("some url", actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void ValidateCommands()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[] { "badcommand" });
// Assert.
Assert.True(command.Validate().Contains("badcommand"));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void ValidateFlags()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[] { "--badflag" });
// Assert.
Assert.True(command.Validate().Contains("badflag"));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void ValidateArgs()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc, args: new string[] { "--badargname", "bad arg value" });
// Assert.
Assert.True(command.Validate().Contains("badargname"));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", nameof(CommandSettings))]
public void ValidateGoodCommandline()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var command = new CommandSettings(hc,
args: new string[] {
"configure",
"--unattended",
"--agent",
"test agent" });
// Assert.
Assert.True(command.Validate().Count == 0);
}
}
private TestHostContext CreateTestContext([CallerMemberName] string testName = "")
{
TestHostContext hc = new TestHostContext(this, testName);
hc.SetSingleton<IPromptManager>(_promptManager.Object);
return hc;
}
}
}

View File

@@ -0,0 +1,79 @@
using GitHub.Runner.Common.Capabilities;
using GitHub.Runner.Listener.Configuration;
using Moq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace GitHub.Runner.Common.Tests.Listener
{
public sealed class AgentCapabilitiesProviderTestL0
{
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Agent")]
public async void TestGetCapabilities()
{
using (var hc = new TestHostContext(this))
using (var tokenSource = new CancellationTokenSource())
{
Mock<IConfigurationManager> configurationManager = new Mock<IConfigurationManager>();
hc.SetSingleton<IConfigurationManager>(configurationManager.Object);
// Arrange
var provider = new RunnerCapabilitiesProvider();
provider.Initialize(hc);
var settings = new RunnerSettings() { AgentName = "IAmAgent007" };
// Act
List<Capability> capabilities = await provider.GetCapabilitiesAsync(settings, tokenSource.Token);
// Assert
Assert.NotNull(capabilities);
Capability runnerNameCapability = capabilities.SingleOrDefault(x => string.Equals(x.Name, "Runner.Name", StringComparison.Ordinal));
Assert.NotNull(runnerNameCapability);
Assert.Equal("IAmAgent007", runnerNameCapability.Value);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Agent")]
public async void TestInteractiveSessionCapability()
{
using (var hc = new TestHostContext(this))
using (var tokenSource = new CancellationTokenSource())
{
hc.StartupType = StartupType.AutoStartup;
await VerifyInteractiveSessionCapability(hc, tokenSource.Token, true);
hc.StartupType = StartupType.Service;
await VerifyInteractiveSessionCapability(hc, tokenSource.Token, false);
hc.StartupType = StartupType.Manual;
await VerifyInteractiveSessionCapability(hc, tokenSource.Token, true);
}
}
private async Task VerifyInteractiveSessionCapability(IHostContext hc, CancellationToken token, bool expectedValue)
{
// Arrange
var provider = new RunnerCapabilitiesProvider();
provider.Initialize(hc);
var settings = new RunnerSettings() { AgentName = "IAmAgent007" };
// Act
List<Capability> capabilities = await provider.GetCapabilitiesAsync(settings, token);
// Assert
Assert.NotNull(capabilities);
Capability iSessionCapability = capabilities.SingleOrDefault(x => string.Equals(x.Name, "InteractiveSession", StringComparison.Ordinal));
Assert.NotNull(iSessionCapability);
bool.TryParse(iSessionCapability.Value, out bool isInteractive);
Assert.Equal(expectedValue, isInteractive);
}
}
}

View File

@@ -0,0 +1,26 @@
using GitHub.Runner.Listener;
using GitHub.Runner.Listener.Configuration;
using GitHub.Services.Client;
using GitHub.Services.Common;
namespace GitHub.Runner.Common.Tests.Listener.Configuration
{
public class TestAgentCredential : CredentialProvider
{
public TestAgentCredential(): base("TEST") {}
public override VssCredentials GetVssCredentials(IHostContext context)
{
Tracing trace = context.GetTrace("PersonalAccessToken");
trace.Info("GetVssCredentials()");
VssBasicCredential loginCred = new VssBasicCredential("test", "password");
VssCredentials creds = new VssCredentials(loginCred);
trace.Verbose("cred created");
return creds;
}
public override void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl)
{
}
}
}

View File

@@ -0,0 +1,60 @@
using GitHub.Runner.Listener.Configuration;
using Xunit;
namespace GitHub.Runner.Common.Tests.Listener.Configuration
{
public sealed class ArgumentValidatorTestsL0
{
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "ArgumentValidator")]
public void ServerUrlValidator()
{
using (TestHostContext hc = new TestHostContext(this))
{
Assert.True(Validators.ServerUrlValidator("http://servername"));
Assert.False(Validators.ServerUrlValidator("Fail"));
Assert.False(Validators.ServerUrlValidator("ftp://servername"));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "ArgumentValidator")]
public void AuthSchemeValidator()
{
using (TestHostContext hc = new TestHostContext(this))
{
Assert.True(Validators.AuthSchemeValidator("pat"));
Assert.False(Validators.AuthSchemeValidator("Fail"));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "ArgumentValidator")]
public void NonEmptyValidator()
{
using (TestHostContext hc = new TestHostContext(this))
{
Assert.True(Validators.NonEmptyValidator("test"));
Assert.False(Validators.NonEmptyValidator(string.Empty));
}
}
#if OS_WINDOWS
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "ArgumentValidator")]
#endif
public void WindowsLogonAccountValidator()
{
using (TestHostContext hc = new TestHostContext(this))
{
Assert.False(Validators.NTAccountValidator(string.Empty));
Assert.True(Validators.NTAccountValidator("NT AUTHORITY\\LOCAL SERVICE"));
}
}
}
}

View File

@@ -0,0 +1,220 @@
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Listener;
using GitHub.Runner.Common.Capabilities;
using GitHub.Runner.Listener.Configuration;
using GitHub.Runner.Common.Util;
using GitHub.Services.WebApi;
using Moq;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Threading.Tasks;
using Xunit;
using GitHub.Services.Location;
using GitHub.Services.Common;
namespace GitHub.Runner.Common.Tests.Listener.Configuration
{
public class ConfigurationManagerL0
{
private Mock<IRunnerServer> _agentServer;
private Mock<ILocationServer> _locationServer;
private Mock<ICredentialManager> _credMgr;
private Mock<IPromptManager> _promptManager;
private Mock<IConfigurationStore> _store;
private Mock<IExtensionManager> _extnMgr;
// private Mock<IDeploymentGroupServer> _machineGroupServer;
private Mock<IRunnerWebProxy> _runnerWebProxy;
private Mock<IRunnerCertificateManager> _cert;
#if OS_WINDOWS
private Mock<IWindowsServiceControlManager> _serviceControlManager;
#endif
#if !OS_WINDOWS
private Mock<ILinuxServiceControlManager> _serviceControlManager;
#endif
private Mock<IRSAKeyManager> _rsaKeyManager;
private ICapabilitiesManager _capabilitiesManager;
// private DeploymentGroupAgentConfigProvider _deploymentGroupAgentConfigProvider;
private string _expectedToken = "expectedToken";
private string _expectedServerUrl = "https://localhost";
private string _expectedAgentName = "expectedAgentName";
private string _expectedPoolName = "poolName";
private string _expectedCollectionName = "testCollectionName";
private string _expectedProjectName = "testProjectName";
private string _expectedProjectId = "edf3f94e-d251-49df-bfce-602d6c967409";
private string _expectedMachineGroupName = "testMachineGroupName";
private string _expectedAuthType = "pat";
private string _expectedWorkFolder = "_work";
private int _expectedPoolId = 1;
private int _expectedDeploymentMachineId = 81;
private RSACryptoServiceProvider rsa = null;
private RunnerSettings _configMgrAgentSettings = new RunnerSettings();
public ConfigurationManagerL0()
{
_agentServer = new Mock<IRunnerServer>();
_locationServer = new Mock<ILocationServer>();
_credMgr = new Mock<ICredentialManager>();
_promptManager = new Mock<IPromptManager>();
_store = new Mock<IConfigurationStore>();
_extnMgr = new Mock<IExtensionManager>();
_rsaKeyManager = new Mock<IRSAKeyManager>();
// _machineGroupServer = new Mock<IDeploymentGroupServer>();
_runnerWebProxy = new Mock<IRunnerWebProxy>();
_cert = new Mock<IRunnerCertificateManager>();
#if OS_WINDOWS
_serviceControlManager = new Mock<IWindowsServiceControlManager>();
#endif
#if !OS_WINDOWS
_serviceControlManager = new Mock<ILinuxServiceControlManager>();
#endif
_capabilitiesManager = new CapabilitiesManager();
var expectedAgent = new TaskAgent(_expectedAgentName) { Id = 1 };
var expectedDeploymentMachine = new DeploymentMachine() { Agent = expectedAgent, Id = _expectedDeploymentMachineId };
expectedAgent.Authorization = new TaskAgentAuthorization
{
ClientId = Guid.NewGuid(),
AuthorizationUrl = new Uri("http://localhost:8080/pipelines"),
};
var connectionData = new ConnectionData()
{
InstanceId = Guid.NewGuid(),
DeploymentType = DeploymentFlags.Hosted,
DeploymentId = Guid.NewGuid()
};
_agentServer.Setup(x => x.ConnectAsync(It.IsAny<Uri>(), It.IsAny<VssCredentials>())).Returns(Task.FromResult<object>(null));
_locationServer.Setup(x => x.ConnectAsync(It.IsAny<VssConnection>())).Returns(Task.FromResult<object>(null));
_locationServer.Setup(x => x.GetConnectionDataAsync()).Returns(Task.FromResult<ConnectionData>(connectionData));
// _machineGroupServer.Setup(x => x.ConnectAsync(It.IsAny<VssConnection>())).Returns(Task.FromResult<object>(null));
// _machineGroupServer.Setup(x => x.UpdateDeploymentTargetsAsync(It.IsAny<Guid>(), It.IsAny<int>(), It.IsAny<List<DeploymentMachine>>()));
// _machineGroupServer.Setup(x => x.AddDeploymentTargetAsync(It.IsAny<Guid>(), It.IsAny<int>(), It.IsAny<DeploymentMachine>())).Returns(Task.FromResult(expectedDeploymentMachine));
// _machineGroupServer.Setup(x => x.ReplaceDeploymentTargetAsync(It.IsAny<Guid>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<DeploymentMachine>())).Returns(Task.FromResult(expectedDeploymentMachine));
// _machineGroupServer.Setup(x => x.GetDeploymentTargetsAsync(It.IsAny<Guid>(), It.IsAny<int>(), It.IsAny<string>())).Returns(Task.FromResult(new List<DeploymentMachine>() { }));
// _machineGroupServer.Setup(x => x.DeleteDeploymentTargetAsync(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>())).Returns(Task.FromResult<object>(null));
_store.Setup(x => x.IsConfigured()).Returns(false);
_store.Setup(x => x.HasCredentials()).Returns(false);
_store.Setup(x => x.GetSettings()).Returns(() => _configMgrAgentSettings);
_store.Setup(x => x.SaveSettings(It.IsAny<RunnerSettings>()))
.Callback((RunnerSettings settings) =>
{
_configMgrAgentSettings = settings;
});
_credMgr.Setup(x => x.GetCredentialProvider(It.IsAny<string>())).Returns(new TestAgentCredential());
#if !OS_WINDOWS
_serviceControlManager.Setup(x => x.GenerateScripts(It.IsAny<RunnerSettings>()));
#endif
var expectedPools = new List<TaskAgentPool>() { new TaskAgentPool(_expectedPoolName) { Id = _expectedPoolId } };
_agentServer.Setup(x => x.GetAgentPoolsAsync(It.IsAny<string>(), It.IsAny<TaskAgentPoolType>())).Returns(Task.FromResult(expectedPools));
var expectedAgents = new List<TaskAgent>();
_agentServer.Setup(x => x.GetAgentsAsync(It.IsAny<int>(), It.IsAny<string>())).Returns(Task.FromResult(expectedAgents));
_agentServer.Setup(x => x.AddAgentAsync(It.IsAny<int>(), It.IsAny<TaskAgent>())).Returns(Task.FromResult(expectedAgent));
_agentServer.Setup(x => x.UpdateAgentAsync(It.IsAny<int>(), It.IsAny<TaskAgent>())).Returns(Task.FromResult(expectedAgent));
rsa = new RSACryptoServiceProvider(2048);
_rsaKeyManager.Setup(x => x.CreateKey()).Returns(rsa);
}
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
{
TestHostContext tc = new TestHostContext(this, testName);
tc.SetSingleton<ICredentialManager>(_credMgr.Object);
tc.SetSingleton<IPromptManager>(_promptManager.Object);
tc.SetSingleton<IConfigurationStore>(_store.Object);
tc.SetSingleton<IExtensionManager>(_extnMgr.Object);
tc.SetSingleton<IRunnerServer>(_agentServer.Object);
tc.SetSingleton<ILocationServer>(_locationServer.Object);
// tc.SetSingleton<IDeploymentGroupServer>(_machineGroupServer.Object);
tc.SetSingleton<ICapabilitiesManager>(_capabilitiesManager);
tc.SetSingleton<IRunnerWebProxy>(_runnerWebProxy.Object);
tc.SetSingleton<IRunnerCertificateManager>(_cert.Object);
#if OS_WINDOWS
tc.SetSingleton<IWindowsServiceControlManager>(_serviceControlManager.Object);
#else
tc.SetSingleton<ILinuxServiceControlManager>(_serviceControlManager.Object);
#endif
tc.SetSingleton<IRSAKeyManager>(_rsaKeyManager.Object);
return tc;
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "ConfigurationManagement")]
public async Task CanEnsureConfigure()
{
using (TestHostContext tc = CreateTestContext())
{
Tracing trace = tc.GetTrace();
trace.Info("Creating config manager");
IConfigurationManager configManager = new ConfigurationManager();
configManager.Initialize(tc);
trace.Info("Preparing command line arguments");
var command = new CommandSettings(
tc,
new[]
{
"configure",
#if !OS_WINDOWS
"--acceptteeeula",
#endif
"--url", _expectedServerUrl,
"--agent", _expectedAgentName,
"--pool", _expectedPoolName,
"--work", _expectedWorkFolder,
"--auth", _expectedAuthType,
"--token", _expectedToken
});
trace.Info("Constructed.");
_store.Setup(x => x.IsConfigured()).Returns(false);
_configMgrAgentSettings = null;
trace.Info("Ensuring all the required parameters are available in the command line parameter");
await configManager.ConfigureAsync(command);
_store.Setup(x => x.IsConfigured()).Returns(true);
trace.Info("Configured, verifying all the parameter value");
var s = configManager.LoadSettings();
Assert.NotNull(s);
Assert.True(s.ServerUrl.Equals(_expectedServerUrl));
Assert.True(s.AgentName.Equals(_expectedAgentName));
Assert.True(s.PoolId.Equals(_expectedPoolId));
Assert.True(s.WorkFolder.Equals(_expectedWorkFolder));
// validate GetAgentPoolsAsync gets called once with automation pool type
_agentServer.Verify(x => x.GetAgentPoolsAsync(It.IsAny<string>(), It.Is<TaskAgentPoolType>(p => p == TaskAgentPoolType.Automation)), Times.Once);
// validate GetAgentPoolsAsync not called with deployment pool type
_agentServer.Verify(x => x.GetAgentPoolsAsync(It.IsAny<string>(), It.Is<TaskAgentPoolType>(p => p == TaskAgentPoolType.Deployment)), Times.Never);
// For build and release agent / deployment pool, tags logic should not get trigger;
// _machineGroupServer.Verify(x =>
// x.UpdateDeploymentTargetsAsync(It.IsAny<Guid>(), It.IsAny<int>(), It.IsAny<List<DeploymentMachine>>()), Times.Never);
}
}
}
}

View File

@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using GitHub.Runner.Listener.Configuration;
using Moq;
using Xunit;
using System.Security.Principal;
using GitHub.Runner.Common;
using GitHub.Runner.Common.Tests;
namespace Test.L0.Listener.Configuration
{
public class NativeWindowsServiceHelperL0
{
#if OS_WINDOWS
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "ConfigurationManagement")]
public void EnsureGetDefaultServiceAccountShouldReturnNetworkServiceAccount()
{
using (TestHostContext tc = new TestHostContext(this, "EnsureGetDefaultServiceAccountShouldReturnNetworkServiceAccount"))
{
Tracing trace = tc.GetTrace();
trace.Info("Creating an instance of the NativeWindowsServiceHelper class");
var windowsServiceHelper = new NativeWindowsServiceHelper();
trace.Info("Trying to get the Default Service Account when a BuildRelease Agent is being configured");
var defaultServiceAccount = windowsServiceHelper.GetDefaultServiceAccount();
Assert.True(defaultServiceAccount.ToString().Equals(@"NT AUTHORITY\NETWORK SERVICE"), "If agent is getting configured as build-release agent, default service accout should be 'NT AUTHORITY\\NETWORK SERVICE'");
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "ConfigurationManagement")]
public void EnsureGetDefaultAdminServiceAccountShouldReturnLocalSystemAccount()
{
using (TestHostContext tc = new TestHostContext(this, "EnsureGetDefaultAdminServiceAccountShouldReturnLocalSystemAccount"))
{
Tracing trace = tc.GetTrace();
trace.Info("Creating an instance of the NativeWindowsServiceHelper class");
var windowsServiceHelper = new NativeWindowsServiceHelper();
trace.Info("Trying to get the Default Service Account when a DeploymentAgent is being configured");
var defaultServiceAccount = windowsServiceHelper.GetDefaultAdminServiceAccount();
Assert.True(defaultServiceAccount.ToString().Equals(@"NT AUTHORITY\SYSTEM"), "If agent is getting configured as deployment agent, default service accout should be 'NT AUTHORITY\\SYSTEM'");
}
}
#endif
}
}

View File

@@ -0,0 +1,228 @@
using GitHub.Runner.Listener.Configuration;
using GitHub.Runner.Common.Util;
using Moq;
using System;
using System.Collections.Generic;
using Xunit;
using GitHub.Runner.Sdk;
namespace GitHub.Runner.Common.Tests.Listener.Configuration
{
public class PromptManagerTestsL0
{
private readonly string _argName = "SomeArgName";
private readonly string _description = "Some description";
private readonly PromptManager _promptManager = new PromptManager();
private readonly Mock<ITerminal> _terminal = new Mock<ITerminal>();
private readonly string _unattendedExceptionMessage = "Invalid configuration provided for SomeArgName. Terminating unattended configuration.";
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "PromptManager")]
public void FallsBackToDefault()
{
using (TestHostContext hc = new TestHostContext(this))
{
// Arrange.
_terminal
.Setup(x => x.ReadLine())
.Returns(string.Empty);
_terminal
.Setup(x => x.ReadSecret())
.Throws<InvalidOperationException>();
hc.SetSingleton(_terminal.Object);
_promptManager.Initialize(hc);
// Act.
string actual = ReadValue(defaultValue: "Some default value");
// Assert.
Assert.Equal("Some default value", actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "PromptManager")]
public void FallsBackToDefaultWhenTrimmed()
{
using (TestHostContext hc = new TestHostContext(this))
{
// Arrange.
_terminal
.Setup(x => x.ReadLine())
.Returns(" ");
_terminal
.Setup(x => x.ReadSecret())
.Throws<InvalidOperationException>();
hc.SetSingleton(_terminal.Object);
_promptManager.Initialize(hc);
// Act.
string actual = ReadValue(defaultValue: "Some default value");
// Assert.
Assert.Equal("Some default value", actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "PromptManager")]
public void FallsBackToDefaultWhenUnattended()
{
using (TestHostContext hc = new TestHostContext(this))
{
// Arrange.
_terminal
.Setup(x => x.ReadLine())
.Throws<InvalidOperationException>();
_terminal
.Setup(x => x.ReadSecret())
.Throws<InvalidOperationException>();
hc.SetSingleton(_terminal.Object);
_promptManager.Initialize(hc);
// Act.
string actual = ReadValue(
defaultValue: "Some default value",
unattended: true);
// Assert.
Assert.Equal("Some default value", actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "PromptManager")]
public void Prompts()
{
using (TestHostContext hc = new TestHostContext(this))
{
// Arrange.
_terminal
.Setup(x => x.ReadLine())
.Returns("Some prompt value");
_terminal
.Setup(x => x.ReadSecret())
.Throws<InvalidOperationException>();
hc.SetSingleton(_terminal.Object);
_promptManager.Initialize(hc);
// Act.
string actual = ReadValue();
// Assert.
Assert.Equal("Some prompt value", actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "PromptManager")]
public void PromptsAgainWhenEmpty()
{
using (TestHostContext hc = new TestHostContext(this))
{
// Arrange.
var readLineValues = new Queue<string>(new[] { string.Empty, "Some prompt value" });
_terminal
.Setup(x => x.ReadLine())
.Returns(() => readLineValues.Dequeue());
_terminal
.Setup(x => x.ReadSecret())
.Throws<InvalidOperationException>();
hc.SetSingleton(_terminal.Object);
_promptManager.Initialize(hc);
// Act.
string actual = ReadValue();
// Assert.
Assert.Equal("Some prompt value", actual);
_terminal.Verify(x => x.ReadLine(), Times.Exactly(2));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "PromptManager")]
public void PromptsAgainWhenFailsValidation()
{
using (TestHostContext hc = new TestHostContext(this))
{
// Arrange.
var readLineValues = new Queue<string>(new[] { "Some invalid prompt value", "Some valid prompt value" });
_terminal
.Setup(x => x.ReadLine())
.Returns(() => readLineValues.Dequeue());
_terminal
.Setup(x => x.ReadSecret())
.Throws<InvalidOperationException>();
hc.SetSingleton(_terminal.Object);
_promptManager.Initialize(hc);
// Act.
string actual = ReadValue(validator: x => x == "Some valid prompt value");
// Assert.
Assert.Equal("Some valid prompt value", actual);
_terminal.Verify(x => x.ReadLine(), Times.Exactly(2));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "PromptManager")]
public void ThrowsWhenUnattended()
{
using (TestHostContext hc = new TestHostContext(this))
{
// Arrange.
_terminal
.Setup(x => x.ReadLine())
.Throws<InvalidOperationException>();
_terminal
.Setup(x => x.ReadSecret())
.Throws<InvalidOperationException>();
hc.SetSingleton(_terminal.Object);
_promptManager.Initialize(hc);
try
{
// Act.
string actual = ReadValue(unattended: true);
// Assert.
throw new InvalidOperationException();
}
catch (Exception ex)
{
// Assert.
Assert.Equal(_unattendedExceptionMessage, ex.Message);
}
}
}
private string ReadValue(
bool secret = false,
string defaultValue = null,
Func<string, bool> validator = null,
bool unattended = false)
{
return _promptManager.ReadValue(
argName: _argName,
description: _description,
secret: secret,
defaultValue: defaultValue,
validator: validator ?? DefaultValidator,
unattended: unattended);
}
private static bool DefaultValidator(string val)
{
return true;
}
}
}

View File

@@ -0,0 +1,502 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Listener;
using GitHub.Services.WebApi;
using Moq;
using Xunit;
using Pipelines = GitHub.DistributedTask.Pipelines;
namespace GitHub.Runner.Common.Tests.Listener
{
public sealed class JobDispatcherL0
{
private Mock<IProcessChannel> _processChannel;
private Mock<IProcessInvoker> _processInvoker;
private Mock<IRunnerServer> _agentServer;
private Mock<IConfigurationStore> _configurationStore;
public JobDispatcherL0()
{
_processChannel = new Mock<IProcessChannel>();
_processInvoker = new Mock<IProcessInvoker>();
_agentServer = new Mock<IRunnerServer>();
_configurationStore = new Mock<IConfigurationStore>();
}
private Pipelines.AgentJobRequestMessage CreateJobRequestMessage()
{
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
TimelineReference timeline = null;
JobEnvironment environment = new JobEnvironment();
List<TaskInstance> tasks = new List<TaskInstance>();
Guid JobId = Guid.NewGuid();
var jobRequest = new AgentJobRequestMessage(plan, timeline, JobId, "someJob", "someJob", environment, tasks);
var result = Pipelines.AgentJobRequestMessageUtil.Convert(jobRequest);
result.ContextData["github"] = new Pipelines.ContextData.DictionaryContextData();
return result;
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Agent")]
public async void DispatchesJobRequest()
{
//Arrange
using (var hc = new TestHostContext(this))
{
var jobDispatcher = new JobDispatcher();
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
hc.SetSingleton<IRunnerServer>(_agentServer.Object);
hc.EnqueueInstance<IProcessChannel>(_processChannel.Object);
hc.EnqueueInstance<IProcessInvoker>(_processInvoker.Object);
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
jobDispatcher.Initialize(hc);
var ts = new CancellationTokenSource();
Pipelines.AgentJobRequestMessage message = CreateJobRequestMessage();
string strMessage = JsonUtility.ToString(message);
_processInvoker.Setup(x => x.ExecuteAsync(It.IsAny<String>(), It.IsAny<String>(), "spawnclient 1 2", null, It.IsAny<CancellationToken>()))
.Returns(Task.FromResult<int>(56));
_processChannel.Setup(x => x.StartServer(It.IsAny<StartProcessDelegate>()))
.Callback((StartProcessDelegate startDel) => { startDel("1", "2"); });
_processChannel.Setup(x => x.SendAsync(MessageType.NewJobRequest, It.Is<string>(s => s.Equals(strMessage)), It.IsAny<CancellationToken>()))
.Returns(Task.CompletedTask);
var request = new TaskAgentJobRequest();
PropertyInfo sessionIdProperty = request.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
Assert.NotNull(sessionIdProperty);
sessionIdProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
_agentServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult<TaskAgentJobRequest>(request));
_agentServer.Setup(x => x.FinishAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<DateTime>(), It.IsAny<TaskResult>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult<TaskAgentJobRequest>(new TaskAgentJobRequest()));
//Actt
jobDispatcher.Run(message);
//Assert
await jobDispatcher.WaitAsync(CancellationToken.None);
Assert.False(jobDispatcher.RunOnceJobCompleted.Task.IsCompleted, "JobDispatcher should not set task complete token for regular agent.");
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Agent")]
public async void DispatcherRenewJobRequest()
{
//Arrange
using (var hc = new TestHostContext(this))
{
int poolId = 1;
Int64 requestId = 1000;
int count = 0;
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequest));
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
TaskAgentJobRequest request = new TaskAgentJobRequest();
PropertyInfo lockUntilProperty = request.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
Assert.NotNull(lockUntilProperty);
lockUntilProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
hc.SetSingleton<IRunnerServer>(_agentServer.Object);
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
_agentServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns(() =>
{
count++;
if (!firstJobRequestRenewed.Task.IsCompletedSuccessfully)
{
trace.Info("First renew happens.");
}
if (count < 5)
{
return Task.FromResult<TaskAgentJobRequest>(request);
}
else if (count == 5)
{
cancellationTokenSource.Cancel();
return Task.FromResult<TaskAgentJobRequest>(request);
}
else
{
throw new InvalidOperationException("Should not reach here.");
}
});
var jobDispatcher = new JobDispatcher();
jobDispatcher.Initialize(hc);
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, firstJobRequestRenewed, cancellationTokenSource.Token);
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully);
_agentServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()), Times.Exactly(5));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Agent")]
public async void DispatcherRenewJobRequestStopOnJobNotFoundExceptions()
{
//Arrange
using (var hc = new TestHostContext(this))
{
int poolId = 1;
Int64 requestId = 1000;
int count = 0;
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestStopOnJobNotFoundExceptions));
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
TaskAgentJobRequest request = new TaskAgentJobRequest();
PropertyInfo lockUntilProperty = request.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
Assert.NotNull(lockUntilProperty);
lockUntilProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
hc.SetSingleton<IRunnerServer>(_agentServer.Object);
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
_agentServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns(() =>
{
count++;
if (!firstJobRequestRenewed.Task.IsCompletedSuccessfully)
{
trace.Info("First renew happens.");
}
if (count < 5)
{
return Task.FromResult<TaskAgentJobRequest>(request);
}
else if (count == 5)
{
cancellationTokenSource.CancelAfter(10000);
throw new TaskAgentJobNotFoundException("");
}
else
{
throw new InvalidOperationException("Should not reach here.");
}
});
var jobDispatcher = new JobDispatcher();
jobDispatcher.Initialize(hc);
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, firstJobRequestRenewed, cancellationTokenSource.Token);
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
Assert.False(cancellationTokenSource.IsCancellationRequested);
_agentServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()), Times.Exactly(5));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Agent")]
public async void DispatcherRenewJobRequestStopOnJobTokenExpiredExceptions()
{
//Arrange
using (var hc = new TestHostContext(this))
{
int poolId = 1;
Int64 requestId = 1000;
int count = 0;
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestStopOnJobTokenExpiredExceptions));
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
TaskAgentJobRequest request = new TaskAgentJobRequest();
PropertyInfo lockUntilProperty = request.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
Assert.NotNull(lockUntilProperty);
lockUntilProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
hc.SetSingleton<IRunnerServer>(_agentServer.Object);
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
_agentServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns(() =>
{
count++;
if (!firstJobRequestRenewed.Task.IsCompletedSuccessfully)
{
trace.Info("First renew happens.");
}
if (count < 5)
{
return Task.FromResult<TaskAgentJobRequest>(request);
}
else if (count == 5)
{
cancellationTokenSource.CancelAfter(10000);
throw new TaskAgentJobTokenExpiredException("");
}
else
{
throw new InvalidOperationException("Should not reach here.");
}
});
var jobDispatcher = new JobDispatcher();
jobDispatcher.Initialize(hc);
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, firstJobRequestRenewed, cancellationTokenSource.Token);
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
Assert.False(cancellationTokenSource.IsCancellationRequested);
_agentServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()), Times.Exactly(5));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Agent")]
public async void DispatcherRenewJobRequestRecoverFromExceptions()
{
//Arrange
using (var hc = new TestHostContext(this))
{
int poolId = 1;
Int64 requestId = 1000;
int count = 0;
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestRecoverFromExceptions));
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
TaskAgentJobRequest request = new TaskAgentJobRequest();
PropertyInfo lockUntilProperty = request.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
Assert.NotNull(lockUntilProperty);
lockUntilProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
hc.SetSingleton<IRunnerServer>(_agentServer.Object);
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
_agentServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns(() =>
{
count++;
if (!firstJobRequestRenewed.Task.IsCompletedSuccessfully)
{
trace.Info("First renew happens.");
}
if (count < 5)
{
return Task.FromResult<TaskAgentJobRequest>(request);
}
else if (count == 5 || count == 6 || count == 7)
{
throw new TimeoutException("");
}
else
{
cancellationTokenSource.Cancel();
return Task.FromResult<TaskAgentJobRequest>(request);
}
});
var jobDispatcher = new JobDispatcher();
jobDispatcher.Initialize(hc);
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, firstJobRequestRenewed, cancellationTokenSource.Token);
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
Assert.True(cancellationTokenSource.IsCancellationRequested);
_agentServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()), Times.Exactly(8));
_agentServer.Verify(x => x.RefreshConnectionAsync(RunnerConnectionType.JobRequest, It.IsAny<TimeSpan>()), Times.Exactly(3));
_agentServer.Verify(x => x.SetConnectionTimeout(RunnerConnectionType.JobRequest, It.IsAny<TimeSpan>()), Times.Once);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Agent")]
public async void DispatcherRenewJobRequestFirstRenewRetrySixTimes()
{
//Arrange
using (var hc = new TestHostContext(this))
{
int poolId = 1;
Int64 requestId = 1000;
int count = 0;
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestFirstRenewRetrySixTimes));
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
TaskAgentJobRequest request = new TaskAgentJobRequest();
PropertyInfo lockUntilProperty = request.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
Assert.NotNull(lockUntilProperty);
lockUntilProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
hc.SetSingleton<IRunnerServer>(_agentServer.Object);
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
_agentServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns(() =>
{
count++;
if (!firstJobRequestRenewed.Task.IsCompletedSuccessfully)
{
trace.Info("First renew happens.");
}
if (count <= 5)
{
throw new TimeoutException("");
}
else
{
cancellationTokenSource.CancelAfter(10000);
throw new InvalidOperationException("Should not reach here.");
}
});
var jobDispatcher = new JobDispatcher();
jobDispatcher.Initialize(hc);
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, firstJobRequestRenewed, cancellationTokenSource.Token);
Assert.False(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should failed.");
Assert.False(cancellationTokenSource.IsCancellationRequested);
_agentServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()), Times.Exactly(6));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Agent")]
public async void DispatcherRenewJobRequestStopOnExpiredRequest()
{
//Arrange
using (var hc = new TestHostContext(this))
{
int poolId = 1;
Int64 requestId = 1000;
int count = 0;
var trace = hc.GetTrace(nameof(DispatcherRenewJobRequestStopOnExpiredRequest));
TaskCompletionSource<int> firstJobRequestRenewed = new TaskCompletionSource<int>();
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
TaskAgentJobRequest request = new TaskAgentJobRequest();
PropertyInfo lockUntilProperty = request.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
Assert.NotNull(lockUntilProperty);
lockUntilProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
hc.SetSingleton<IRunnerServer>(_agentServer.Object);
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
_agentServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.Returns(() =>
{
count++;
if (!firstJobRequestRenewed.Task.IsCompletedSuccessfully)
{
trace.Info("First renew happens.");
}
if (count == 1)
{
return Task.FromResult<TaskAgentJobRequest>(request);
}
else if (count < 5)
{
throw new TimeoutException("");
}
else if (count == 5)
{
lockUntilProperty.SetValue(request, DateTime.UtcNow.Subtract(TimeSpan.FromMinutes(11)));
throw new TimeoutException("");
}
else
{
cancellationTokenSource.CancelAfter(10000);
throw new InvalidOperationException("Should not reach here.");
}
});
var jobDispatcher = new JobDispatcher();
jobDispatcher.Initialize(hc);
await jobDispatcher.RenewJobRequestAsync(poolId, requestId, Guid.Empty, firstJobRequestRenewed, cancellationTokenSource.Token);
Assert.True(firstJobRequestRenewed.Task.IsCompletedSuccessfully, "First renew should succeed.");
Assert.False(cancellationTokenSource.IsCancellationRequested);
_agentServer.Verify(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>()), Times.Exactly(5));
_agentServer.Verify(x => x.RefreshConnectionAsync(RunnerConnectionType.JobRequest, It.IsAny<TimeSpan>()), Times.Exactly(3));
_agentServer.Verify(x => x.SetConnectionTimeout(RunnerConnectionType.JobRequest, It.IsAny<TimeSpan>()), Times.Never);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Agent")]
public async void DispatchesOneTimeJobRequest()
{
//Arrange
using (var hc = new TestHostContext(this))
{
var jobDispatcher = new JobDispatcher();
hc.SetSingleton<IConfigurationStore>(_configurationStore.Object);
hc.SetSingleton<IRunnerServer>(_agentServer.Object);
hc.EnqueueInstance<IProcessChannel>(_processChannel.Object);
hc.EnqueueInstance<IProcessInvoker>(_processInvoker.Object);
_configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings() { PoolId = 1 });
jobDispatcher.Initialize(hc);
var ts = new CancellationTokenSource();
Pipelines.AgentJobRequestMessage message = CreateJobRequestMessage();
string strMessage = JsonUtility.ToString(message);
_processInvoker.Setup(x => x.ExecuteAsync(It.IsAny<String>(), It.IsAny<String>(), "spawnclient 1 2", null, It.IsAny<CancellationToken>()))
.Returns(Task.FromResult<int>(56));
_processChannel.Setup(x => x.StartServer(It.IsAny<StartProcessDelegate>()))
.Callback((StartProcessDelegate startDel) => { startDel("1", "2"); });
_processChannel.Setup(x => x.SendAsync(MessageType.NewJobRequest, It.Is<string>(s => s.Equals(strMessage)), It.IsAny<CancellationToken>()))
.Returns(Task.CompletedTask);
var request = new TaskAgentJobRequest();
PropertyInfo sessionIdProperty = request.GetType().GetProperty("LockedUntil", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
Assert.NotNull(sessionIdProperty);
sessionIdProperty.SetValue(request, DateTime.UtcNow.AddMinutes(5));
_agentServer.Setup(x => x.RenewAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult<TaskAgentJobRequest>(request));
_agentServer.Setup(x => x.FinishAgentRequestAsync(It.IsAny<int>(), It.IsAny<long>(), It.IsAny<Guid>(), It.IsAny<DateTime>(), It.IsAny<TaskResult>(), It.IsAny<CancellationToken>())).Returns(Task.FromResult<TaskAgentJobRequest>(new TaskAgentJobRequest()));
//Act
jobDispatcher.Run(message, true);
//Assert
await jobDispatcher.WaitAsync(CancellationToken.None);
Assert.True(jobDispatcher.RunOnceJobCompleted.Task.IsCompleted, "JobDispatcher should set task complete token for one time agent.");
Assert.True(jobDispatcher.RunOnceJobCompleted.Task.Result, "JobDispatcher should set task complete token to 'TRUE' for one time agent.");
}
}
}
}

View File

@@ -0,0 +1,214 @@
using GitHub.DistributedTask.WebApi;
using GitHub.Services.Common;
using GitHub.Services.WebApi;
using GitHub.Runner.Listener;
using GitHub.Runner.Common.Capabilities;
using GitHub.Runner.Listener.Configuration;
using Moq;
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xunit;
using System.Threading;
using System.Reflection;
using System.Collections.Generic;
namespace GitHub.Runner.Common.Tests.Listener
{
public sealed class MessageListenerL0
{
private RunnerSettings _settings;
private Mock<IConfigurationManager> _config;
private Mock<IRunnerServer> _agentServer;
private Mock<ICredentialManager> _credMgr;
private Mock<ICapabilitiesManager> _capabilitiesManager;
public MessageListenerL0()
{
_settings = new RunnerSettings { AgentId = 1, AgentName = "myagent", PoolId = 123, PoolName = "default", ServerUrl = "http://myserver", WorkFolder = "_work" };
_config = new Mock<IConfigurationManager>();
_config.Setup(x => x.LoadSettings()).Returns(_settings);
_agentServer = new Mock<IRunnerServer>();
_credMgr = new Mock<ICredentialManager>();
_capabilitiesManager = new Mock<ICapabilitiesManager>();
}
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
{
TestHostContext tc = new TestHostContext(this, testName);
tc.SetSingleton<IConfigurationManager>(_config.Object);
tc.SetSingleton<IRunnerServer>(_agentServer.Object);
tc.SetSingleton<ICredentialManager>(_credMgr.Object);
tc.SetSingleton<ICapabilitiesManager>(_capabilitiesManager.Object);
return tc;
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Agent")]
public async void CreatesSession()
{
using (TestHostContext tc = CreateTestContext())
using (var tokenSource = new CancellationTokenSource())
{
Tracing trace = tc.GetTrace();
// Arrange.
var expectedSession = new TaskAgentSession();
_agentServer
.Setup(x => x.CreateAgentSessionAsync(
_settings.PoolId,
It.Is<TaskAgentSession>(y => y != null),
tokenSource.Token))
.Returns(Task.FromResult(expectedSession));
_capabilitiesManager.Setup(x => x.GetCapabilitiesAsync(_settings, It.IsAny<CancellationToken>())).Returns(Task.FromResult(new Dictionary<string, string>()));
_credMgr.Setup(x => x.LoadCredentials()).Returns(new VssCredentials());
// Act.
MessageListener listener = new MessageListener();
listener.Initialize(tc);
bool result = await listener.CreateSessionAsync(tokenSource.Token);
trace.Info("result: {0}", result);
// Assert.
Assert.True(result);
_agentServer
.Verify(x => x.CreateAgentSessionAsync(
_settings.PoolId,
It.Is<TaskAgentSession>(y => y != null),
tokenSource.Token), Times.Once());
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Agent")]
public async void DeleteSession()
{
using (TestHostContext tc = CreateTestContext())
using (var tokenSource = new CancellationTokenSource())
{
Tracing trace = tc.GetTrace();
// Arrange.
var expectedSession = new TaskAgentSession();
PropertyInfo sessionIdProperty = expectedSession.GetType().GetProperty("SessionId", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
Assert.NotNull(sessionIdProperty);
sessionIdProperty.SetValue(expectedSession, Guid.NewGuid());
_agentServer
.Setup(x => x.CreateAgentSessionAsync(
_settings.PoolId,
It.Is<TaskAgentSession>(y => y != null),
tokenSource.Token))
.Returns(Task.FromResult(expectedSession));
_capabilitiesManager.Setup(x => x.GetCapabilitiesAsync(_settings, It.IsAny<CancellationToken>())).Returns(Task.FromResult(new Dictionary<string, string>()));
_credMgr.Setup(x => x.LoadCredentials()).Returns(new VssCredentials());
// Act.
MessageListener listener = new MessageListener();
listener.Initialize(tc);
bool result = await listener.CreateSessionAsync(tokenSource.Token);
Assert.True(result);
_agentServer
.Setup(x => x.DeleteAgentSessionAsync(
_settings.PoolId, expectedSession.SessionId, It.IsAny<CancellationToken>()))
.Returns(Task.CompletedTask);
await listener.DeleteSessionAsync();
//Assert
_agentServer
.Verify(x => x.DeleteAgentSessionAsync(
_settings.PoolId, expectedSession.SessionId, It.IsAny<CancellationToken>()), Times.Once());
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Agent")]
public async void GetNextMessage()
{
using (TestHostContext tc = CreateTestContext())
using (var tokenSource = new CancellationTokenSource())
{
Tracing trace = tc.GetTrace();
// Arrange.
var expectedSession = new TaskAgentSession();
PropertyInfo sessionIdProperty = expectedSession.GetType().GetProperty("SessionId", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
Assert.NotNull(sessionIdProperty);
sessionIdProperty.SetValue(expectedSession, Guid.NewGuid());
_agentServer
.Setup(x => x.CreateAgentSessionAsync(
_settings.PoolId,
It.Is<TaskAgentSession>(y => y != null),
tokenSource.Token))
.Returns(Task.FromResult(expectedSession));
_capabilitiesManager.Setup(x => x.GetCapabilitiesAsync(_settings, It.IsAny<CancellationToken>())).Returns(Task.FromResult(new Dictionary<string, string>()));
_credMgr.Setup(x => x.LoadCredentials()).Returns(new VssCredentials());
// Act.
MessageListener listener = new MessageListener();
listener.Initialize(tc);
bool result = await listener.CreateSessionAsync(tokenSource.Token);
Assert.True(result);
var arMessages = new TaskAgentMessage[]
{
new TaskAgentMessage
{
Body = "somebody1",
MessageId = 4234,
MessageType = JobRequestMessageTypes.AgentJobRequest
},
new TaskAgentMessage
{
Body = "somebody2",
MessageId = 4235,
MessageType = JobCancelMessage.MessageType
},
null, //should be skipped by GetNextMessageAsync implementation
null,
new TaskAgentMessage
{
Body = "somebody3",
MessageId = 4236,
MessageType = JobRequestMessageTypes.AgentJobRequest
}
};
var messages = new Queue<TaskAgentMessage>(arMessages);
_agentServer
.Setup(x => x.GetAgentMessageAsync(
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), tokenSource.Token))
.Returns(async (Int32 poolId, Guid sessionId, Int64? lastMessageId, CancellationToken cancellationToken) =>
{
await Task.Yield();
return messages.Dequeue();
});
TaskAgentMessage message1 = await listener.GetNextMessageAsync(tokenSource.Token);
TaskAgentMessage message2 = await listener.GetNextMessageAsync(tokenSource.Token);
TaskAgentMessage message3 = await listener.GetNextMessageAsync(tokenSource.Token);
Assert.Equal(arMessages[0], message1);
Assert.Equal(arMessages[1], message2);
Assert.Equal(arMessages[4], message3);
//Assert
_agentServer
.Verify(x => x.GetAgentMessageAsync(
_settings.PoolId, expectedSession.SessionId, It.IsAny<long?>(), tokenSource.Token), Times.Exactly(arMessages.Length));
}
}
}
}

View File

@@ -0,0 +1,131 @@
using GitHub.Runner.Common.Util;
using Moq;
using System;
using System.IO;
using Xunit;
namespace GitHub.Runner.Common.Tests.Listener
{
public sealed class PagingLoggerL0
{
private const string LogData = "messagemessagemessagemessagemessagemessagemessagemessageXPLATmessagemessagemessagemessagemessagemessagemessagemessage";
private const int PagesToWrite = 2;
private Mock<IJobServerQueue> _jobServerQueue;
public PagingLoggerL0()
{
_jobServerQueue = new Mock<IJobServerQueue>();
PagingLogger.PagingFolder = "pages_" + Guid.NewGuid().ToString();
}
private void CleanLogFolder()
{
using (TestHostContext hc = new TestHostContext(this))
{
//clean test data if any old test forgot
string pagesFolder = Path.Combine(hc.GetDirectory(WellKnownDirectory.Diag), PagingLogger.PagingFolder);
if (Directory.Exists(pagesFolder))
{
Directory.Delete(pagesFolder, true);
}
}
}
//WriteAndShipLog test will write "PagesToWrite" pages of data,
//verify file content on the disk and check if API to ship data is invoked
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void WriteAndShipLog()
{
CleanLogFolder();
try
{
//Arrange
using (var hc = new TestHostContext(this))
{
var pagingLogger = new PagingLogger();
hc.SetSingleton<IJobServerQueue>(_jobServerQueue.Object);
pagingLogger.Initialize(hc);
Guid timeLineId = Guid.NewGuid();
Guid timeLineRecordId = Guid.NewGuid();
int totalBytes = PagesToWrite * PagingLogger.PageSize;
int bytesWritten = 0;
int logDataSize = System.Text.Encoding.UTF8.GetByteCount(LogData);
_jobServerQueue.Setup(x => x.QueueFileUpload(timeLineId, timeLineRecordId, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), true))
.Callback((Guid timelineId, Guid timelineRecordId, string type, string name, string path, bool deleteSource) =>
{
bool fileExists = File.Exists(path);
Assert.True(fileExists);
using (var freader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read), System.Text.Encoding.UTF8))
{
string line;
while ((line = freader.ReadLine()) != null)
{
Assert.True(line.EndsWith(LogData));
bytesWritten += logDataSize;
}
}
File.Delete(path);
});
//Act
int bytesSent = 0;
pagingLogger.Setup(timeLineId, timeLineRecordId);
while (bytesSent < totalBytes)
{
pagingLogger.Write(LogData);
bytesSent += logDataSize;
}
pagingLogger.End();
//Assert
_jobServerQueue.Verify(x => x.QueueFileUpload(timeLineId, timeLineRecordId, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), true), Times.AtLeast(PagesToWrite));
Assert.Equal(bytesSent, bytesWritten);
}
}
finally
{
//cleanup
CleanLogFolder();
}
}
//Try to ship empty log
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void ShipEmptyLog()
{
CleanLogFolder();
try
{
//Arrange
using (var hc = new TestHostContext(this))
{
var pagingLogger = new PagingLogger();
hc.SetSingleton<IJobServerQueue>(_jobServerQueue.Object);
pagingLogger.Initialize(hc);
Guid timeLineId = Guid.NewGuid();
Guid timeLineRecordId = Guid.NewGuid();
_jobServerQueue.Setup(x => x.QueueFileUpload(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), true));
//Act
pagingLogger.Setup(timeLineId, timeLineRecordId);
pagingLogger.End();
//Assert
_jobServerQueue.Verify(x => x.QueueFileUpload(It.IsAny<Guid>(), It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), true), Times.Exactly(0));
}
}
finally
{
//cleanup
CleanLogFolder();
}
}
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using Xunit;
namespace GitHub.Runner.Common.Tests
{
public sealed class ProcessExtensionL0
{
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public async Task SuccessReadProcessEnv()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
string envName = Guid.NewGuid().ToString();
string envValue = Guid.NewGuid().ToString();
Process sleep = null;
try
{
#if OS_WINDOWS
string node = Path.Combine(TestUtil.GetSrcPath(), @"..\_layout\externals\node12\bin\node");
#else
string node = Path.Combine(TestUtil.GetSrcPath(), @"../_layout/externals/node12/bin/node");
hc.EnqueueInstance<IProcessInvoker>(new ProcessInvokerWrapper());
#endif
var startInfo = new ProcessStartInfo(node, "-e \"setTimeout(function(){{}}, 15 * 1000);\"");
startInfo.Environment[envName] = envValue;
sleep = Process.Start(startInfo);
var timeout = Process.GetProcessById(sleep.Id);
while (timeout == null)
{
await Task.Delay(500);
timeout = Process.GetProcessById(sleep.Id);
}
try
{
trace.Info($"Read env from {timeout.Id}");
var value = timeout.GetEnvironmentVariable(hc, envName);
if (string.Equals(value, envValue, StringComparison.OrdinalIgnoreCase))
{
trace.Info($"Find the env.");
return;
}
}
catch (Exception ex)
{
trace.Error(ex);
}
Assert.True(false, "Fail to retrive process environment variable.");
}
finally
{
sleep?.Kill();
}
}
}
}
}

View File

@@ -0,0 +1,350 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
using GitHub.Runner.Common.Util;
using System.Threading.Channels;
using GitHub.Runner.Sdk;
namespace GitHub.Runner.Common.Tests
{
public sealed class ProcessInvokerL0
{
// #if OS_WINDOWS
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Common")]
// public async Task DefaultsToCurrentSystemOemEncoding()
// {
// // This test verifies that the additional code pages encoding provider is registered.
// // By default, only Unicode encodings, ASCII, and code page 28591 are supported. An
// // additional provider must be registered to support the full set of encodings that
// // were included in Full .NET prior to 4.6.
// //
// // For example, on an en-US box, this is required for loading the encoding for the
// // default console output code page '437'. Without loading the correct encoding for
// // code page IBM437, some characters cannot be translated correctly, e.g. write 'ç'
// // from powershell.exe.
// using (TestHostContext hc = new TestHostContext(this))
// {
// Tracing trace = hc.GetTrace();
// var processInvoker = new ProcessInvokerWrapper();
// processInvoker.Initialize(hc);
// var stdout = new List<string>();
// var stderr = new List<string>();
// processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs e) =>
// {
// stdout.Add(e.Data);
// };
// processInvoker.ErrorDataReceived += (object sender, ProcessDataReceivedEventArgs e) =>
// {
// stderr.Add(e.Data);
// };
// await processInvoker.ExecuteAsync(
// workingDirectory: "",
// fileName: "powershell.exe",
// arguments: $@"-NoLogo -Sta -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command ""Write-Host 'From STDOUT ''ç''' ; Write-Error 'From STDERR ''ç'''""",
// environment: null,
// requireExitCodeZero: false,
// cancellationToken: CancellationToken.None);
// Assert.Equal(1, stdout.Count);
// Assert.Equal("From STDOUT 'ç'", stdout[0]);
// Assert.True(stderr.Count > 0);
// Assert.True(stderr[0].Contains("From STDERR 'ç'"));
// }
// }
// #endif
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public async Task SuccessExitsWithCodeZero()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
Int32 exitCode = -1;
var processInvoker = new ProcessInvokerWrapper();
processInvoker.Initialize(hc);
#if OS_WINDOWS
exitCode = await processInvoker.ExecuteAsync("", "cmd.exe", "/c \"dir >nul\"", null, CancellationToken.None);
#else
exitCode = await processInvoker.ExecuteAsync("", "bash", "-c echo .", null, CancellationToken.None);
#endif
trace.Info("Exit Code: {0}", exitCode);
Assert.Equal(0, exitCode);
}
}
#if !OS_WINDOWS
//Run a process that normally takes 20sec to finish and cancel it.
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public async Task TestCancel()
{
const int SecondsToRun = 20;
using (TestHostContext hc = new TestHostContext(this))
using (var tokenSource = new CancellationTokenSource())
{
Tracing trace = hc.GetTrace();
var processInvoker = new ProcessInvokerWrapper();
processInvoker.Initialize(hc);
Stopwatch watch = Stopwatch.StartNew();
Task execTask;
#if OS_WINDOWS
execTask = processInvoker.ExecuteAsync("", "cmd.exe", $"/c \"choice /T {SecondsToRun} /D y\"", null, tokenSource.Token);
#else
execTask = processInvoker.ExecuteAsync("", "bash", $"-c \"sleep {SecondsToRun}s\"", null, tokenSource.Token);
#endif
await Task.Delay(500);
tokenSource.Cancel();
try
{
await execTask;
}
catch (OperationCanceledException)
{
trace.Info("Get expected OperationCanceledException.");
}
Assert.True(execTask.IsCompleted);
Assert.True(!execTask.IsFaulted);
Assert.True(execTask.IsCanceled);
watch.Stop();
long elapsedSeconds = watch.ElapsedMilliseconds / 1000;
#if ARM
// if cancellation fails, then execution time is more than 15 seconds
// longer time to compensate for a slower ARM environment (e.g. Raspberry Pi)
long expectedSeconds = (SecondsToRun * 3) / 4;
#else
// if cancellation fails, then execution time is more than 10 seconds
long expectedSeconds = SecondsToRun / 2;
#endif
Assert.True(elapsedSeconds <= expectedSeconds, $"cancellation failed, because task took too long to run. {elapsedSeconds}");
}
}
#endif
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public async Task RedirectSTDINCloseStream()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
Int32 exitCode = -1;
Channel<string> redirectSTDIN = Channel.CreateUnbounded<string>(new UnboundedChannelOptions() { SingleReader = true, SingleWriter = true });
List<string> stdout = new List<string>();
redirectSTDIN.Writer.TryWrite("Single line of STDIN");
var processInvoker = new ProcessInvokerWrapper();
processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs e) =>
{
stdout.Add(e.Data);
};
processInvoker.Initialize(hc);
#if OS_WINDOWS
var proc = processInvoker.ExecuteAsync("", "cmd.exe", "/c more", null, false, null, false, redirectSTDIN, false, false, cancellationTokenSource.Token);
#else
var proc = processInvoker.ExecuteAsync("", "bash", "-c \"read input; echo $input; read input; echo $input; read input; echo $input;\"", null, false, null, false, redirectSTDIN, false, false, cancellationTokenSource.Token);
#endif
redirectSTDIN.Writer.TryWrite("More line of STDIN");
redirectSTDIN.Writer.TryWrite("More line of STDIN");
await Task.Delay(100);
redirectSTDIN.Writer.TryWrite("More line of STDIN");
redirectSTDIN.Writer.TryWrite("More line of STDIN");
await Task.Delay(100);
redirectSTDIN.Writer.TryWrite("More line of STDIN");
cancellationTokenSource.CancelAfter(100);
try
{
exitCode = await proc;
trace.Info("Exit Code: {0}", exitCode);
}
catch (Exception ex)
{
trace.Error(ex);
}
trace.Info("STDOUT: {0}", string.Join(Environment.NewLine, stdout));
Assert.False(stdout.Contains("More line of STDIN"), "STDIN should be closed after first input line.");
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public async Task RedirectSTDINKeepStreamOpen()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
Int32 exitCode = -1;
Channel<string> redirectSTDIN = Channel.CreateUnbounded<string>(new UnboundedChannelOptions() { SingleReader = true, SingleWriter = true });
List<string> stdout = new List<string>();
redirectSTDIN.Writer.TryWrite("Single line of STDIN");
var processInvoker = new ProcessInvokerWrapper();
processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs e) =>
{
stdout.Add(e.Data);
};
processInvoker.Initialize(hc);
#if OS_WINDOWS
var proc = processInvoker.ExecuteAsync("", "cmd.exe", "/c more", null, false, null, false, redirectSTDIN, false, true, cancellationTokenSource.Token);
#else
var proc = processInvoker.ExecuteAsync("", "bash", "-c \"read input; echo $input; read input; echo $input; read input; echo $input;\"", null, false, null, false, redirectSTDIN, false, true, cancellationTokenSource.Token);
#endif
redirectSTDIN.Writer.TryWrite("More line of STDIN");
redirectSTDIN.Writer.TryWrite("More line of STDIN");
await Task.Delay(100);
redirectSTDIN.Writer.TryWrite("More line of STDIN");
redirectSTDIN.Writer.TryWrite("More line of STDIN");
await Task.Delay(100);
redirectSTDIN.Writer.TryWrite("More line of STDIN");
cancellationTokenSource.CancelAfter(100);
try
{
exitCode = await proc;
trace.Info("Exit Code: {0}", exitCode);
}
catch (Exception ex)
{
trace.Error(ex);
}
trace.Info("STDOUT: {0}", string.Join(Environment.NewLine, stdout));
Assert.True(stdout.Contains("More line of STDIN"), "STDIN should keep open and accept more inputs after first input line.");
}
}
#if OS_LINUX
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public async Task OomScoreAdjIsWriten_Default()
{
// We are on a system that supports oom_score_adj in procfs as assumed by ProcessInvoker
string testProcPath = $"/proc/{Process.GetCurrentProcess().Id}/oom_score_adj";
if (File.Exists(testProcPath))
{
using (TestHostContext hc = new TestHostContext(this))
using (var tokenSource = new CancellationTokenSource())
{
Tracing trace = hc.GetTrace();
var processInvoker = new ProcessInvokerWrapper();
processInvoker.Initialize(hc);
int oomScoreAdj = -9999;
processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs e) =>
{
oomScoreAdj = int.Parse(e.Data);
tokenSource.Cancel();
};
try
{
var proc = await processInvoker.ExecuteAsync("", "bash", "-c \"cat /proc/$$/oom_score_adj\"", null, false, null, false, null, false, false,
highPriorityProcess: false,
cancellationToken: tokenSource.Token);
Assert.Equal(oomScoreAdj, 500);
}
catch (OperationCanceledException)
{
trace.Info("Caught expected OperationCanceledException");
}
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public async Task OomScoreAdjIsWriten_FromEnv()
{
// We are on a system that supports oom_score_adj in procfs as assumed by ProcessInvoker
string testProcPath = $"/proc/{Process.GetCurrentProcess().Id}/oom_score_adj";
if (File.Exists(testProcPath))
{
using (TestHostContext hc = new TestHostContext(this))
using (var tokenSource = new CancellationTokenSource())
{
Tracing trace = hc.GetTrace();
var processInvoker = new ProcessInvokerWrapper();
processInvoker.Initialize(hc);
int oomScoreAdj = -9999;
processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs e) =>
{
oomScoreAdj = int.Parse(e.Data);
tokenSource.Cancel();
};
try
{
var proc = await processInvoker.ExecuteAsync("", "bash", "-c \"cat /proc/$$/oom_score_adj\"",
new Dictionary<string, string> { {"PIPELINE_JOB_OOMSCOREADJ", "1234"} },
false, null, false, null, false, false,
highPriorityProcess: false,
cancellationToken: tokenSource.Token);
Assert.Equal(oomScoreAdj, 1234);
}
catch (OperationCanceledException)
{
trace.Info("Caught expected OperationCanceledException");
}
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public async Task OomScoreAdjIsInherited()
{
// We are on a system that supports oom_score_adj in procfs as assumed by ProcessInvoker
string testProcPath = $"/proc/{Process.GetCurrentProcess().Id}/oom_score_adj";
if (File.Exists(testProcPath))
{
int testProcOomScoreAdj = 123;
File.WriteAllText(testProcPath, testProcOomScoreAdj.ToString());
using (TestHostContext hc = new TestHostContext(this))
using (var tokenSource = new CancellationTokenSource())
{
Tracing trace = hc.GetTrace();
var processInvoker = new ProcessInvokerWrapper();
processInvoker.Initialize(hc);
int oomScoreAdj = -9999;
processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs e) =>
{
oomScoreAdj = int.Parse(e.Data);
tokenSource.Cancel();
};
try
{
var proc = await processInvoker.ExecuteAsync("", "bash", "-c \"cat /proc/$$/oom_score_adj\"", null, false, null, false, null, false, false,
highPriorityProcess: true,
cancellationToken: tokenSource.Token);
Assert.Equal(oomScoreAdj, 123);
}
catch (OperationCanceledException)
{
trace.Info("Caught expected OperationCanceledException");
}
}
}
}
#endif
}
}

View File

@@ -0,0 +1,116 @@
using GitHub.Runner.Common.Util;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Xunit;
using System;
using GitHub.Runner.Sdk;
namespace GitHub.Runner.Common.Tests
{
public sealed class ProxyConfigL0
{
private static readonly Regex NewHttpClientHandlerRegex = new Regex("New\\s+HttpClientHandler\\s*\\(", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex NewHttpClientRegex = new Regex("New\\s+HttpClient\\s*\\(\\s*\\)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly List<string> SkippedFiles = new List<string>()
{
"Runner.Common\\HostContext.cs",
"Runner.Common/HostContext.cs"
};
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void IsNotUseRawHttpClientHandler()
{
List<string> sourceFiles = Directory.GetFiles(
TestUtil.GetProjectPath("Runner.Common"),
"*.cs",
SearchOption.AllDirectories).ToList();
sourceFiles.AddRange(Directory.GetFiles(
TestUtil.GetProjectPath("Runner.Listener"),
"*.cs",
SearchOption.AllDirectories));
sourceFiles.AddRange(Directory.GetFiles(
TestUtil.GetProjectPath("Runner.Worker"),
"*.cs",
SearchOption.AllDirectories));
List<string> badCode = new List<string>();
foreach (string sourceFile in sourceFiles)
{
// Skip skipped files.
if (SkippedFiles.Any(s => sourceFile.Contains(s)))
{
continue;
}
// Skip files in the obj directory.
if (sourceFile.Contains(StringUtil.Format("{0}obj{0}", Path.DirectorySeparatorChar)))
{
continue;
}
int lineCount = 0;
foreach (string line in File.ReadAllLines(sourceFile))
{
lineCount++;
if (NewHttpClientHandlerRegex.IsMatch(line))
{
badCode.Add($"{sourceFile} (line {lineCount})");
}
}
}
Assert.True(badCode.Count == 0, $"The following code is using Raw HttpClientHandler() which will not follow the proxy setting agent have. Please use HostContext.CreateHttpClientHandler() instead.\n {string.Join("\n", badCode)}");
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void IsNotUseRawHttpClient()
{
List<string> sourceFiles = Directory.GetFiles(
TestUtil.GetProjectPath("Runner.Common"),
"*.cs",
SearchOption.AllDirectories).ToList();
sourceFiles.AddRange(Directory.GetFiles(
TestUtil.GetProjectPath("Runner.Listener"),
"*.cs",
SearchOption.AllDirectories));
sourceFiles.AddRange(Directory.GetFiles(
TestUtil.GetProjectPath("Runner.Worker"),
"*.cs",
SearchOption.AllDirectories));
List<string> badCode = new List<string>();
foreach (string sourceFile in sourceFiles)
{
// Skip skipped files.
if (SkippedFiles.Any(s => sourceFile.Contains(s)))
{
continue;
}
// Skip files in the obj directory.
if (sourceFile.Contains(StringUtil.Format("{0}obj{0}", Path.DirectorySeparatorChar)))
{
continue;
}
int lineCount = 0;
foreach (string line in File.ReadAllLines(sourceFile))
{
lineCount++;
if (NewHttpClientRegex.IsMatch(line))
{
badCode.Add($"{sourceFile} (line {lineCount})");
}
}
}
Assert.True(badCode.Count == 0, $"The following code is using Raw HttpClient() which will not follow the proxy setting agent have. Please use New HttpClient(HostContext.CreateHttpClientHandler()) instead.\n {string.Join("\n", badCode)}");
}
}
}

View File

@@ -0,0 +1,115 @@
using GitHub.Runner.Listener;
using GitHub.Runner.Common.Capabilities;
using GitHub.Runner.Listener.Configuration;
using GitHub.Runner.Worker;
using GitHub.Runner.Worker.Handlers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Xunit;
namespace GitHub.Runner.Common.Tests
{
public sealed class ServiceInterfacesL0
{
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Agent")]
public void AgentInterfacesSpecifyDefaultImplementation()
{
// Validate all interfaces in the Listener assembly define a valid service locator attribute.
// Otherwise, the interface needs to whitelisted.
var whitelist = new[]
{
typeof(ICredentialProvider)
};
Validate(
assembly: typeof(IMessageListener).GetTypeInfo().Assembly,
whitelist: whitelist);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void CommonInterfacesSpecifyDefaultImplementation()
{
// Validate all interfaces in the Common assembly define a valid service locator attribute.
// Otherwise, the interface needs to whitelisted.
var whitelist = new[]
{
typeof(IRunnerService),
typeof(ICredentialProvider),
typeof(IExtension),
typeof(IHostContext),
typeof(ITraceManager),
typeof(IThrottlingReporter),
typeof(ICapabilitiesProvider)
};
Validate(
assembly: typeof(IHostContext).GetTypeInfo().Assembly,
whitelist: whitelist);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void WorkerInterfacesSpecifyDefaultImplementation()
{
// Validate all interfaces in the Worker assembly define a valid service locator attribute.
// Otherwise, the interface needs to whitelisted.
var whitelist = new[]
{
typeof(IActionCommandExtension),
typeof(IExecutionContext),
typeof(IHandler),
typeof(IJobExtension),
typeof(IStep),
typeof(IStepHost),
typeof(IDiagnosticLogManager),
typeof(IEnvironmentContextData)
};
Validate(
assembly: typeof(IStepsRunner).GetTypeInfo().Assembly,
whitelist: whitelist);
}
private static void Validate(Assembly assembly, params Type[] whitelist)
{
// Iterate over all non-whitelisted interfaces contained within the assembly.
IDictionary<TypeInfo, Type> w = whitelist.ToDictionary(x => x.GetTypeInfo());
foreach (TypeInfo interfaceTypeInfo in assembly.DefinedTypes.Where(x => x.IsInterface && !w.ContainsKey(x)))
{
// Temporary hack due to shared code copied in two places.
if (interfaceTypeInfo.FullName.StartsWith("GitHub.DistributedTask"))
{
continue;
}
if (interfaceTypeInfo.FullName.Contains("IConverter")){
continue;
}
// Assert the ServiceLocatorAttribute is defined on the interface.
CustomAttributeData attribute =
interfaceTypeInfo
.CustomAttributes
.SingleOrDefault(x => x.AttributeType == typeof(ServiceLocatorAttribute));
Assert.True(attribute != null, $"Missing {nameof(ServiceLocatorAttribute)} for interface '{interfaceTypeInfo.FullName}'. Add the attribute to the interface or whitelist the interface in the test.");
// Assert the interface is mapped to a concrete type.
CustomAttributeNamedArgument defaultArg =
attribute
.NamedArguments
.SingleOrDefault(x => String.Equals(x.MemberName, ServiceLocatorAttribute.DefaultPropertyName, StringComparison.Ordinal));
Type concreteType = defaultArg.TypedValue.Value as Type;
string invalidConcreteTypeMessage = $"Invalid Default parameter on {nameof(ServiceLocatorAttribute)} for the interface '{interfaceTypeInfo.FullName}'. The default implementation must not be null, must not be an interface, must be a class, and must implement the interface '{interfaceTypeInfo.FullName}'.";
Assert.True(concreteType != null, invalidConcreteTypeMessage);
TypeInfo concreteTypeInfo = concreteType.GetTypeInfo();
Assert.False(concreteTypeInfo.IsInterface, invalidConcreteTypeMessage);
Assert.True(concreteTypeInfo.IsClass, invalidConcreteTypeMessage);
Assert.True(concreteTypeInfo.ImplementedInterfaces.Any(x => x.GetTypeInfo() == interfaceTypeInfo), invalidConcreteTypeMessage);
}
}
}
}

View File

@@ -0,0 +1,373 @@
using GitHub.Runner.Common.Util;
using System;
using System.Collections.Concurrent;
using System.Globalization;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.Loader;
using System.Reflection;
using System.Collections.Generic;
using GitHub.DistributedTask.Logging;
using System.Net.Http.Headers;
using GitHub.Runner.Sdk;
namespace GitHub.Runner.Common.Tests
{
public sealed class TestHostContext : IHostContext, IDisposable
{
private readonly ConcurrentDictionary<Type, ConcurrentQueue<object>> _serviceInstances = new ConcurrentDictionary<Type, ConcurrentQueue<object>>();
private readonly ConcurrentDictionary<Type, object> _serviceSingletons = new ConcurrentDictionary<Type, object>();
private readonly ITraceManager _traceManager;
private readonly Terminal _term;
private readonly SecretMasker _secretMasker;
private CancellationTokenSource _agentShutdownTokenSource = new CancellationTokenSource();
private string _suiteName;
private string _testName;
private Tracing _trace;
private AssemblyLoadContext _loadContext;
private string _tempDirectoryRoot = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("D"));
private StartupType _startupType;
public event EventHandler Unloading;
public CancellationToken RunnerShutdownToken => _agentShutdownTokenSource.Token;
public ShutdownReason RunnerShutdownReason { get; private set; }
public ISecretMasker SecretMasker => _secretMasker;
public TestHostContext(object testClass, [CallerMemberName] string testName = "")
{
ArgUtil.NotNull(testClass, nameof(testClass));
ArgUtil.NotNullOrEmpty(testName, nameof(testName));
_loadContext = AssemblyLoadContext.GetLoadContext(typeof(TestHostContext).GetTypeInfo().Assembly);
_loadContext.Unloading += LoadContext_Unloading;
_testName = testName;
// Trim the test assembly's root namespace from the test class's full name.
_suiteName = testClass.GetType().FullName.Substring(
startIndex: typeof(Tests.TestHostContext).FullName.LastIndexOf(nameof(TestHostContext)));
_suiteName = _suiteName.Replace(".", "_");
// Setup the trace manager.
TraceFileName = Path.Combine(
Path.Combine(TestUtil.GetSrcPath(), "Test", "TestLogs"),
$"trace_{_suiteName}_{_testName}.log");
if (File.Exists(TraceFileName))
{
File.Delete(TraceFileName);
}
var traceListener = new HostTraceListener(TraceFileName);
_secretMasker = new SecretMasker();
_secretMasker.AddValueEncoder(ValueEncoders.JsonStringEscape);
_secretMasker.AddValueEncoder(ValueEncoders.UriDataEscape);
_traceManager = new TraceManager(traceListener, _secretMasker);
_trace = GetTrace(nameof(TestHostContext));
// inject a terminal in silent mode so all console output
// goes to the test trace file
_term = new Terminal();
_term.Silent = true;
SetSingleton<ITerminal>(_term);
EnqueueInstance<ITerminal>(_term);
#if !OS_WINDOWS
string eulaFile = Path.Combine(GetDirectory(WellKnownDirectory.Externals), Constants.Path.TeeDirectory, "license.html");
Directory.CreateDirectory(GetDirectory(WellKnownDirectory.Externals));
Directory.CreateDirectory(Path.Combine(GetDirectory(WellKnownDirectory.Externals), Constants.Path.TeeDirectory));
File.WriteAllText(eulaFile, "testeulafile");
#endif
}
public CultureInfo DefaultCulture { get; private set; }
public RunMode RunMode { get; set; }
public string TraceFileName { get; private set; }
public StartupType StartupType
{
get
{
return _startupType;
}
set
{
_startupType = value;
}
}
public ProductInfoHeaderValue UserAgent => new ProductInfoHeaderValue("L0Test", "0.0");
public async Task Delay(TimeSpan delay, CancellationToken token)
{
await Task.Delay(TimeSpan.Zero);
}
public T CreateService<T>() where T : class, IRunnerService
{
_trace.Verbose($"Create service: '{typeof(T).Name}'");
// Dequeue a registered instance.
object service;
ConcurrentQueue<object> queue;
if (!_serviceInstances.TryGetValue(typeof(T), out queue) ||
!queue.TryDequeue(out service))
{
throw new Exception($"Unable to dequeue a registered instance for type '{typeof(T).FullName}'.");
}
var s = service as T;
s.Initialize(this);
return s;
}
public T GetService<T>() where T : class, IRunnerService
{
_trace.Verbose($"Get service: '{typeof(T).Name}'");
// Get the registered singleton instance.
object service;
if (!_serviceSingletons.TryGetValue(typeof(T), out service))
{
throw new Exception($"Singleton instance not registered for type '{typeof(T).FullName}'.");
}
T s = service as T;
s.Initialize(this);
return s;
}
public void EnqueueInstance<T>(T instance) where T : class, IRunnerService
{
// Enqueue a service instance to be returned by CreateService.
if (object.ReferenceEquals(instance, null))
{
throw new ArgumentNullException(nameof(instance));
}
ConcurrentQueue<object> queue = _serviceInstances.GetOrAdd(
key: typeof(T),
valueFactory: x => new ConcurrentQueue<object>());
queue.Enqueue(instance);
}
public void SetDefaultCulture(string name)
{
DefaultCulture = new CultureInfo(name);
}
public void SetSingleton<T>(T singleton) where T : class, IRunnerService
{
// Set the singleton instance to be returned by GetService.
if (object.ReferenceEquals(singleton, null))
{
throw new ArgumentNullException(nameof(singleton));
}
_serviceSingletons[typeof(T)] = singleton;
}
public string GetDirectory(WellKnownDirectory directory)
{
string path;
switch (directory)
{
case WellKnownDirectory.Bin:
path = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
break;
case WellKnownDirectory.Diag:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
Constants.Path.DiagDirectory);
break;
case WellKnownDirectory.Externals:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
Constants.Path.ExternalsDirectory);
break;
case WellKnownDirectory.Root:
path = new DirectoryInfo(GetDirectory(WellKnownDirectory.Bin)).Parent.FullName;
break;
case WellKnownDirectory.Temp:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Work),
Constants.Path.TempDirectory);
break;
case WellKnownDirectory.Actions:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Work),
Constants.Path.ActionsDirectory);
break;
case WellKnownDirectory.Tools:
path = Environment.GetEnvironmentVariable("AGENT_TOOLSDIRECTORY") ?? Environment.GetEnvironmentVariable(Constants.Variables.Agent.ToolsDirectory);
if (string.IsNullOrEmpty(path))
{
path = Path.Combine(
GetDirectory(WellKnownDirectory.Work),
Constants.Path.ToolDirectory);
}
break;
case WellKnownDirectory.Update:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Work),
Constants.Path.UpdateDirectory);
break;
case WellKnownDirectory.Work:
path = Path.Combine(
_tempDirectoryRoot,
WellKnownDirectory.Work.ToString());
break;
default:
throw new NotSupportedException($"Unexpected well known directory: '{directory}'");
}
_trace.Info($"Well known directory '{directory}': '{path}'");
return path;
}
public string GetConfigFile(WellKnownConfigFile configFile)
{
string path;
switch (configFile)
{
case WellKnownConfigFile.Runner:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".agent");
break;
case WellKnownConfigFile.Credentials:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".credentials");
break;
case WellKnownConfigFile.RSACredentials:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".credentials_rsaparams");
break;
case WellKnownConfigFile.Service:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".service");
break;
case WellKnownConfigFile.CredentialStore:
#if OS_OSX
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".credential_store.keychain");
#else
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".credential_store");
#endif
break;
case WellKnownConfigFile.Certificates:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".certificates");
break;
case WellKnownConfigFile.Proxy:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".proxy");
break;
case WellKnownConfigFile.ProxyCredentials:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".proxycredentials");
break;
case WellKnownConfigFile.ProxyBypass:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".proxybypass");
break;
case WellKnownConfigFile.Options:
path = Path.Combine(
GetDirectory(WellKnownDirectory.Root),
".options");
break;
default:
throw new NotSupportedException($"Unexpected well known config file: '{configFile}'");
}
_trace.Info($"Well known config file '{configFile}': '{path}'");
return path;
}
// simple convenience factory so each suite/test gets a different trace file per run
public Tracing GetTrace()
{
Tracing trace = GetTrace($"{_suiteName}_{_testName}");
trace.Info($"Starting {_testName}");
return trace;
}
public Tracing GetTrace(string name)
{
return _traceManager[name];
}
public void ShutdownRunner(ShutdownReason reason)
{
ArgUtil.NotNull(reason, nameof(reason));
RunnerShutdownReason = reason;
_agentShutdownTokenSource.Cancel();
}
public void WritePerfCounter(string counter)
{
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (disposing)
{
if (_loadContext != null)
{
_loadContext.Unloading -= LoadContext_Unloading;
_loadContext = null;
}
_traceManager?.Dispose();
try
{
Directory.Delete(_tempDirectoryRoot);
}
catch (Exception)
{
// eat exception on dispose
}
}
}
private void LoadContext_Unloading(AssemblyLoadContext obj)
{
if (Unloading != null)
{
Unloading(this, null);
}
}
}
}

39
src/Test/L0/TestUtil.cs Normal file
View File

@@ -0,0 +1,39 @@
using GitHub.Runner.Common.Util;
using System.IO;
using Xunit;
using System;
using GitHub.Runner.Sdk;
namespace GitHub.Runner.Common.Tests
{
public static class TestUtil
{
private const string Src = "src";
private const string TestData = "TestData";
public static string GetProjectPath(string name = "Test")
{
ArgUtil.NotNullOrEmpty(name, nameof(name));
string projectDir = Path.Combine(
GetSrcPath(),
name);
Assert.True(Directory.Exists(projectDir));
return projectDir;
}
public static string GetSrcPath()
{
string srcDir = Environment.GetEnvironmentVariable("GITHUB_RUNNER_SRC_DIR");
ArgUtil.Directory(srcDir, nameof(srcDir));
Assert.Equal(Src, Path.GetFileName(srcDir));
return srcDir;
}
public static string GetTestDataPath()
{
string testDataDir = Path.Combine(GetProjectPath(), TestData);
Assert.True(Directory.Exists(testDataDir));
return testDataDir;
}
}
}

View File

@@ -0,0 +1,148 @@
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using System;
using Xunit;
namespace GitHub.Runner.Common.Tests.Util
{
public sealed class ArgUtilL0
{
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void Equal_MatchesObjectEquality()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange.
string expected = "Some string".ToLower(); // ToLower is required to avoid reference equality
string actual = "Some string".ToLower(); // due to compile-time string interning.
// Act/Assert.
ArgUtil.Equal(expected: expected, actual: actual, name: "Some parameter");
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void Equal_MatchesReferenceEquality()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange.
object expected = new object();
object actual = expected;
// Act/Assert.
ArgUtil.Equal(expected: expected, actual: actual, name: "Some parameter");
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void Equal_MatchesStructEquality()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange.
int expected = 123;
int actual = expected;
// Act/Assert.
ArgUtil.Equal(expected: expected, actual: actual, name: "Some parameter");
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void Equal_ThrowsWhenActualObjectIsNull()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange.
object expected = new object();
object actual = null;
// Act/Assert.
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
ArgUtil.Equal(expected: expected, actual: actual, name: "Some parameter");
});
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void Equal_ThrowsWhenExpectedObjectIsNull()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange.
object expected = null;
object actual = new object();
// Act/Assert.
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
ArgUtil.Equal(expected: expected, actual: actual, name: "Some parameter");
});
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void Equal_ThrowsWhenObjectsAreNotEqual()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange.
object expected = new object();
object actual = new object();
// Act/Assert.
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
ArgUtil.Equal(expected: expected, actual: actual, name: "Some parameter");
});
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void Equal_ThrowsWhenStructsAreNotEqual()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange.
int expected = 123;
int actual = 456;
// Act/Assert.
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
ArgUtil.Equal(expected: expected, actual: actual, name: "Some parameter");
});
}
}
}
}

View File

@@ -0,0 +1,957 @@
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
namespace GitHub.Runner.Common.Tests.Util
{
public sealed class IOUtilL0
{
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void Delete_DeletesDirectory()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange: Create a directory with a file.
string directory = Path.Combine(hc.GetDirectory(WellKnownDirectory.Bin), Path.GetRandomFileName());
string file = Path.Combine(directory, "some file");
try
{
Directory.CreateDirectory(directory);
File.WriteAllText(path: file, contents: "some contents");
// Act.
IOUtil.Delete(directory, CancellationToken.None);
// Assert.
Assert.False(Directory.Exists(directory));
}
finally
{
// Cleanup.
if (Directory.Exists(directory))
{
Directory.Delete(directory, recursive: true);
}
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void Delete_DeletesFile()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange: Create a directory with a file.
string directory = Path.Combine(hc.GetDirectory(WellKnownDirectory.Bin), Path.GetRandomFileName());
string file = Path.Combine(directory, "some file");
try
{
Directory.CreateDirectory(directory);
File.WriteAllText(path: file, contents: "some contents");
// Act.
IOUtil.Delete(file, CancellationToken.None);
// Assert.
Assert.False(File.Exists(file));
}
finally
{
// Cleanup.
if (Directory.Exists(directory))
{
Directory.Delete(directory, recursive: true);
}
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void DeleteDirectory_DeletesDirectoriesRecursively()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange: Create a directory with a grandchild directory.
string directory = Path.Combine(hc.GetDirectory(WellKnownDirectory.Bin), Path.GetRandomFileName());
try
{
Directory.CreateDirectory(Path.Combine(directory, "some child directory", "some grandchild directory"));
// Act.
IOUtil.DeleteDirectory(directory, CancellationToken.None);
// Assert.
Assert.False(Directory.Exists(directory));
}
finally
{
// Cleanup.
if (Directory.Exists(directory))
{
Directory.Delete(directory, recursive: true);
}
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public async Task DeleteDirectory_DeletesDirectoryReparsePointChain()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange: Create the following structure:
// randomDir
// randomDir/<guid 1> -> <guid 2>
// randomDir/<guid 2> -> <guid 3>
// randomDir/<guid 3> -> <guid 4>
// randomDir/<guid 4> -> <guid 5>
// randomDir/<guid 5> -> targetDir
// randomDir/targetDir
// randomDir/targetDir/file.txt
//
// The purpose of this test is to verify that DirectoryNotFoundException is gracefully handled when
// deleting a chain of reparse point directories. Since the reparse points are named in a random order,
// the DirectoryNotFoundException case is likely to be encountered.
string randomDir = Path.Combine(hc.GetDirectory(WellKnownDirectory.Bin), Path.GetRandomFileName());
try
{
string targetDir = Directory.CreateDirectory(Path.Combine(randomDir, "targetDir")).FullName;
string file = Path.Combine(targetDir, "file.txt");
File.WriteAllText(path: file, contents: "some contents");
string linkDir1 = Path.Combine(randomDir, $"{Guid.NewGuid()}_linkDir1");
string linkDir2 = Path.Combine(randomDir, $"{Guid.NewGuid()}_linkDir2");
string linkDir3 = Path.Combine(randomDir, $"{Guid.NewGuid()}_linkDir3");
string linkDir4 = Path.Combine(randomDir, $"{Guid.NewGuid()}_linkDir4");
string linkDir5 = Path.Combine(randomDir, $"{Guid.NewGuid()}_linkDir5");
await CreateDirectoryReparsePoint(context: hc, link: linkDir1, target: linkDir2);
await CreateDirectoryReparsePoint(context: hc, link: linkDir2, target: linkDir3);
await CreateDirectoryReparsePoint(context: hc, link: linkDir3, target: linkDir4);
await CreateDirectoryReparsePoint(context: hc, link: linkDir4, target: linkDir5);
await CreateDirectoryReparsePoint(context: hc, link: linkDir5, target: targetDir);
// Sanity check to verify the link was created properly:
Assert.True(Directory.Exists(linkDir1));
Assert.True(new DirectoryInfo(linkDir1).Attributes.HasFlag(FileAttributes.ReparsePoint));
Assert.True(File.Exists(Path.Combine(linkDir1, "file.txt")));
// Act.
IOUtil.DeleteDirectory(randomDir, CancellationToken.None);
// Assert.
Assert.False(Directory.Exists(linkDir1));
Assert.False(Directory.Exists(targetDir));
Assert.False(File.Exists(file));
Assert.False(Directory.Exists(randomDir));
}
finally
{
// Cleanup.
if (Directory.Exists(randomDir))
{
Directory.Delete(randomDir, recursive: true);
}
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public async Task DeleteDirectory_DeletesDirectoryReparsePointsBeforeDirectories()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange: Create the following structure:
// randomDir
// randomDir/linkDir -> targetDir
// randomDir/targetDir
// randomDir/targetDir/file.txt
//
// The accuracy of this test relies on an assumption that IOUtil sorts the directories in
// descending order before deleting them - either by length or by default sort order.
string randomDir = Path.Combine(hc.GetDirectory(WellKnownDirectory.Bin), Path.GetRandomFileName());
try
{
string targetDir = Directory.CreateDirectory(Path.Combine(randomDir, "targetDir")).FullName;
string file = Path.Combine(targetDir, "file.txt");
File.WriteAllText(path: file, contents: "some contents");
string linkDir = Path.Combine(randomDir, "linkDir");
await CreateDirectoryReparsePoint(context: hc, link: linkDir, target: targetDir);
// Sanity check to verify the link was created properly:
Assert.True(Directory.Exists(linkDir));
Assert.True(new DirectoryInfo(linkDir).Attributes.HasFlag(FileAttributes.ReparsePoint));
Assert.True(File.Exists(Path.Combine(linkDir, "file.txt")));
// Act.
IOUtil.DeleteDirectory(randomDir, CancellationToken.None);
// Assert.
Assert.False(Directory.Exists(linkDir));
Assert.False(Directory.Exists(targetDir));
Assert.False(File.Exists(file));
Assert.False(Directory.Exists(randomDir));
}
finally
{
// Cleanup.
if (Directory.Exists(randomDir))
{
Directory.Delete(randomDir, recursive: true);
}
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void DeleteDirectory_DeletesFilesRecursively()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange: Create a directory with a grandchild file.
string directory = Path.Combine(hc.GetDirectory(WellKnownDirectory.Bin), Path.GetRandomFileName());
try
{
string file = Path.Combine(directory, "some subdirectory", "some file");
Directory.CreateDirectory(Path.GetDirectoryName(file));
File.WriteAllText(path: file, contents: "some contents");
// Act.
IOUtil.DeleteDirectory(directory, CancellationToken.None);
// Assert.
Assert.False(Directory.Exists(directory));
}
finally
{
// Cleanup.
if (Directory.Exists(directory))
{
Directory.Delete(directory, recursive: true);
}
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void DeleteDirectory_DeletesReadOnlyDirectories()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange: Create a directory with a read-only subdirectory.
string directory = Path.Combine(hc.GetDirectory(WellKnownDirectory.Bin), Path.GetRandomFileName());
string subdirectory = Path.Combine(directory, "some subdirectory");
try
{
var subdirectoryInfo = new DirectoryInfo(subdirectory);
subdirectoryInfo.Create();
subdirectoryInfo.Attributes = subdirectoryInfo.Attributes | FileAttributes.ReadOnly;
// Act.
IOUtil.DeleteDirectory(directory, CancellationToken.None);
// Assert.
Assert.False(Directory.Exists(directory));
}
finally
{
// Cleanup.
var subdirectoryInfo = new DirectoryInfo(subdirectory);
if (subdirectoryInfo.Exists)
{
subdirectoryInfo.Attributes = subdirectoryInfo.Attributes & ~FileAttributes.ReadOnly;
}
if (Directory.Exists(directory))
{
Directory.Delete(directory, recursive: true);
}
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void DeleteDirectory_DeletesReadOnlyRootDirectory()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange: Create a read-only directory.
string directory = Path.Combine(hc.GetDirectory(WellKnownDirectory.Bin), Path.GetRandomFileName());
try
{
var directoryInfo = new DirectoryInfo(directory);
directoryInfo.Create();
directoryInfo.Attributes = directoryInfo.Attributes | FileAttributes.ReadOnly;
// Act.
IOUtil.DeleteDirectory(directory, CancellationToken.None);
// Assert.
Assert.False(Directory.Exists(directory));
}
finally
{
// Cleanup.
var directoryInfo = new DirectoryInfo(directory);
if (directoryInfo.Exists)
{
directoryInfo.Attributes = directoryInfo.Attributes & ~FileAttributes.ReadOnly;
directoryInfo.Delete();
}
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void DeleteDirectory_DeletesReadOnlyFiles()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange: Create a directory with a read-only file.
string directory = Path.Combine(hc.GetDirectory(WellKnownDirectory.Bin), Path.GetRandomFileName());
string file = Path.Combine(directory, "some file");
try
{
Directory.CreateDirectory(directory);
File.WriteAllText(path: file, contents: "some contents");
File.SetAttributes(file, File.GetAttributes(file) | FileAttributes.ReadOnly);
// Act.
IOUtil.DeleteDirectory(directory, CancellationToken.None);
// Assert.
Assert.False(Directory.Exists(directory));
}
finally
{
// Cleanup.
if (File.Exists(file))
{
File.SetAttributes(file, File.GetAttributes(file) & ~FileAttributes.ReadOnly);
}
if (Directory.Exists(directory))
{
Directory.Delete(directory, recursive: true);
}
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public async Task DeleteDirectory_DoesNotFollowDirectoryReparsePoint()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange: Create the following structure:
// randomDir
// randomDir/targetDir
// randomDir/targetDir/file.txt
// randomDir/linkDir -> targetDir
string randomDir = Path.Combine(hc.GetDirectory(WellKnownDirectory.Bin), Path.GetRandomFileName());
try
{
string targetDir = Directory.CreateDirectory(Path.Combine(randomDir, "targetDir")).FullName;
string file = Path.Combine(targetDir, "file.txt");
File.WriteAllText(path: file, contents: "some contents");
string linkDir = Path.Combine(randomDir, "linkDir");
await CreateDirectoryReparsePoint(context: hc, link: linkDir, target: targetDir);
// Sanity check to verify the link was created properly:
Assert.True(Directory.Exists(linkDir));
Assert.True(new DirectoryInfo(linkDir).Attributes.HasFlag(FileAttributes.ReparsePoint));
Assert.True(File.Exists(Path.Combine(linkDir, "file.txt")));
// Act.
IOUtil.DeleteDirectory(linkDir, CancellationToken.None);
// Assert.
Assert.False(Directory.Exists(linkDir));
Assert.True(Directory.Exists(targetDir));
Assert.True(File.Exists(file));
}
finally
{
// Cleanup.
if (Directory.Exists(randomDir))
{
Directory.Delete(randomDir, recursive: true);
}
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public async Task DeleteDirectory_DoesNotFollowNestLevel1DirectoryReparsePoint()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange: Create the following structure:
// randomDir
// randomDir/targetDir
// randomDir/targetDir/file.txt
// randomDir/subDir
// randomDir/subDir/linkDir -> ../targetDir
string randomDir = Path.Combine(hc.GetDirectory(WellKnownDirectory.Bin), Path.GetRandomFileName());
try
{
string targetDir = Directory.CreateDirectory(Path.Combine(randomDir, "targetDir")).FullName;
string file = Path.Combine(targetDir, "file.txt");
File.WriteAllText(path: file, contents: "some contents");
string subDir = Directory.CreateDirectory(Path.Combine(randomDir, "subDir")).FullName;
string linkDir = Path.Combine(subDir, "linkDir");
await CreateDirectoryReparsePoint(context: hc, link: linkDir, target: targetDir);
// Sanity check to verify the link was created properly:
Assert.True(Directory.Exists(linkDir));
Assert.True(new DirectoryInfo(linkDir).Attributes.HasFlag(FileAttributes.ReparsePoint));
Assert.True(File.Exists(Path.Combine(linkDir, "file.txt")));
// Act.
IOUtil.DeleteDirectory(subDir, CancellationToken.None);
// Assert.
Assert.False(Directory.Exists(subDir));
Assert.True(Directory.Exists(targetDir));
Assert.True(File.Exists(file));
}
finally
{
// Cleanup.
if (Directory.Exists(randomDir))
{
Directory.Delete(randomDir, recursive: true);
}
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public async Task DeleteDirectory_DoesNotFollowNestLevel2DirectoryReparsePoint()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange: Create the following structure:
// randomDir
// randomDir/targetDir
// randomDir/targetDir/file.txt
// randomDir/subDir1
// randomDir/subDir1/subDir2
// randomDir/subDir1/subDir2/linkDir -> ../../targetDir
string randomDir = Path.Combine(hc.GetDirectory(WellKnownDirectory.Bin), Path.GetRandomFileName());
try
{
string targetDir = Directory.CreateDirectory(Path.Combine(randomDir, "targetDir")).FullName;
string file = Path.Combine(targetDir, "file.txt");
File.WriteAllText(path: file, contents: "some contents");
string subDir1 = Directory.CreateDirectory(Path.Combine(randomDir, "subDir1")).FullName;
string subDir2 = Directory.CreateDirectory(Path.Combine(subDir1, "subDir2")).FullName;
string linkDir = Path.Combine(subDir2, "linkDir");
await CreateDirectoryReparsePoint(context: hc, link: linkDir, target: targetDir);
// Sanity check to verify the link was created properly:
Assert.True(Directory.Exists(linkDir));
Assert.True(new DirectoryInfo(linkDir).Attributes.HasFlag(FileAttributes.ReparsePoint));
Assert.True(File.Exists(Path.Combine(linkDir, "file.txt")));
// Act.
IOUtil.DeleteDirectory(subDir1, CancellationToken.None);
// Assert.
Assert.False(Directory.Exists(subDir1));
Assert.True(Directory.Exists(targetDir));
Assert.True(File.Exists(file));
}
finally
{
// Cleanup.
if (Directory.Exists(randomDir))
{
Directory.Delete(randomDir, recursive: true);
}
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void DeleteDirectory_IgnoresFile()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange: Create a directory with a file.
string directory = Path.Combine(hc.GetDirectory(WellKnownDirectory.Bin), Path.GetRandomFileName());
string file = Path.Combine(directory, "some file");
try
{
Directory.CreateDirectory(directory);
File.WriteAllText(path: file, contents: "some contents");
// Act: Call "DeleteDirectory" against the file. The method should not blow up and
// should simply ignore the file since it is not a directory.
IOUtil.DeleteDirectory(file, CancellationToken.None);
// Assert.
Assert.True(File.Exists(file));
}
finally
{
// Cleanup.
if (Directory.Exists(directory))
{
Directory.Delete(directory, recursive: true);
}
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void DeleteFile_DeletesFile()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange: Create a directory with a file.
string directory = Path.Combine(hc.GetDirectory(WellKnownDirectory.Bin), Path.GetRandomFileName());
string file = Path.Combine(directory, "some file");
try
{
Directory.CreateDirectory(directory);
File.WriteAllText(path: file, contents: "some contents");
// Act.
IOUtil.DeleteFile(file);
// Assert.
Assert.False(File.Exists(file));
}
finally
{
// Cleanup.
if (Directory.Exists(directory))
{
Directory.Delete(directory, recursive: true);
}
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void DeleteFile_DeletesReadOnlyFile()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange: Create a directory with a read-only file.
string directory = Path.Combine(hc.GetDirectory(WellKnownDirectory.Bin), Path.GetRandomFileName());
string file = Path.Combine(directory, "some file");
try
{
Directory.CreateDirectory(directory);
File.WriteAllText(path: file, contents: "some contents");
File.SetAttributes(file, File.GetAttributes(file) | FileAttributes.ReadOnly);
// Act.
IOUtil.DeleteFile(file);
// Assert.
Assert.False(File.Exists(file));
}
finally
{
// Cleanup.
if (File.Exists(file))
{
File.SetAttributes(file, File.GetAttributes(file) & ~FileAttributes.ReadOnly);
}
if (Directory.Exists(directory))
{
Directory.Delete(directory, recursive: true);
}
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void DeleteFile_IgnoresDirectory()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange: Create a directory.
string directory = Path.Combine(hc.GetDirectory(WellKnownDirectory.Bin), Path.GetRandomFileName());
try
{
Directory.CreateDirectory(directory);
// Act: Call "DeleteFile" against a directory. The method should not blow up and
// should simply ignore the directory since it is not a file.
IOUtil.DeleteFile(directory);
// Assert.
Assert.True(Directory.Exists(directory));
}
finally
{
// Cleanup.
if (Directory.Exists(directory))
{
Directory.Delete(directory, recursive: true);
}
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void GetRelativePath()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
string relativePath;
#if OS_WINDOWS
/// MakeRelative(@"d:\src\project\foo.cpp", @"d:\src") -> @"project\foo.cpp"
// Act.
relativePath = IOUtil.MakeRelative(@"d:\src\project\foo.cpp", @"d:\src");
// Assert.
Assert.True(string.Equals(relativePath, @"project\foo.cpp", StringComparison.OrdinalIgnoreCase), $"RelativePath does not expected: {relativePath}");
/// MakeRelative(@"d:\", @"d:\specs") -> @"d:\"
// Act.
relativePath = IOUtil.MakeRelative(@"d:\", @"d:\specs");
// Assert.
Assert.True(string.Equals(relativePath, @"d:\", StringComparison.OrdinalIgnoreCase), $"RelativePath does not expected: {relativePath}");
/// MakeRelative(@"d:\src\project\foo.cpp", @"d:\src\proj") -> @"d:\src\project\foo.cpp"
// Act.
relativePath = IOUtil.MakeRelative(@"d:\src\project\foo.cpp", @"d:\src\proj");
// Assert.
Assert.True(string.Equals(relativePath, @"d:\src\project\foo.cpp", StringComparison.OrdinalIgnoreCase), $"RelativePath does not expected: {relativePath}");
/// MakeRelative(@"d:\src\project\foo", @"d:\src") -> @"project\foo"
// Act.
relativePath = IOUtil.MakeRelative(@"d:\src\project\foo", @"d:\src");
// Assert.
Assert.True(string.Equals(relativePath, @"project\foo", StringComparison.OrdinalIgnoreCase), $"RelativePath does not expected: {relativePath}");
/// MakeRelative(@"d:\src\project\foo.cpp", @"d:\src\project\foo.cpp") -> @""
// Act.
relativePath = IOUtil.MakeRelative(@"d:\src\project", @"d:\src\project");
// Assert.
Assert.True(string.Equals(relativePath, string.Empty, StringComparison.OrdinalIgnoreCase), $"RelativePath does not expected: {relativePath}");
/// MakeRelative(@"d:/src/project/foo.cpp", @"d:/src") -> @"project/foo.cpp"
// Act.
relativePath = IOUtil.MakeRelative(@"d:/src/project/foo.cpp", @"d:/src");
// Assert.
Assert.True(string.Equals(relativePath, @"project\foo.cpp", StringComparison.OrdinalIgnoreCase), $"RelativePath does not expected: {relativePath}");
/// MakeRelative(@"d:/src/project/foo.cpp", @"d:\src") -> @"d:/src/project/foo.cpp"
// Act.
relativePath = IOUtil.MakeRelative(@"d:/src/project/foo.cpp", @"d:/src");
// Assert.
Assert.True(string.Equals(relativePath, @"project\foo.cpp", StringComparison.OrdinalIgnoreCase), $"RelativePath does not expected: {relativePath}");
/// MakeRelative(@"d:/src/project/foo", @"d:/src") -> @"project/foo"
// Act.
relativePath = IOUtil.MakeRelative(@"d:/src/project/foo", @"d:/src");
// Assert.
Assert.True(string.Equals(relativePath, @"project\foo", StringComparison.OrdinalIgnoreCase), $"RelativePath does not expected: {relativePath}");
/// MakeRelative(@"d\src\project", @"d:/src/project") -> @""
// Act.
relativePath = IOUtil.MakeRelative(@"d:\src\project", @"d:/src/project");
// Assert.
Assert.True(string.Equals(relativePath, string.Empty, StringComparison.OrdinalIgnoreCase), $"RelativePath does not expected: {relativePath}");
#else
/// MakeRelative(@"/user/src/project/foo.cpp", @"/user/src") -> @"project/foo.cpp"
// Act.
relativePath = IOUtil.MakeRelative(@"/user/src/project/foo.cpp", @"/user/src");
// Assert.
Assert.True(string.Equals(relativePath, @"project/foo.cpp", StringComparison.OrdinalIgnoreCase), $"RelativePath does not expected: {relativePath}");
/// MakeRelative(@"/user", @"/user/specs") -> @"/user"
// Act.
relativePath = IOUtil.MakeRelative(@"/user", @"/user/specs");
// Assert.
Assert.True(string.Equals(relativePath, @"/user", StringComparison.OrdinalIgnoreCase), $"RelativePath does not expected: {relativePath}");
/// MakeRelative(@"/user/src/project/foo.cpp", @"/user/src/proj") -> @"/user/src/project/foo.cpp"
// Act.
relativePath = IOUtil.MakeRelative(@"/user/src/project/foo.cpp", @"/user/src/proj");
// Assert.
Assert.True(string.Equals(relativePath, @"/user/src/project/foo.cpp", StringComparison.OrdinalIgnoreCase), $"RelativePath does not expected: {relativePath}");
/// MakeRelative(@"/user/src/project/foo", @"/user/src") -> @"project/foo"
// Act.
relativePath = IOUtil.MakeRelative(@"/user/src/project/foo", @"/user/src");
// Assert.
Assert.True(string.Equals(relativePath, @"project/foo", StringComparison.OrdinalIgnoreCase), $"RelativePath does not expected: {relativePath}");
/// MakeRelative(@"/user/src/project", @"/user/src/project") -> @""
// Act.
relativePath = IOUtil.MakeRelative(@"/user/src/project", @"/user/src/project");
// Assert.
Assert.True(string.Equals(relativePath, string.Empty, StringComparison.OrdinalIgnoreCase), $"RelativePath does not expected: {relativePath}");
#endif
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void ResolvePath()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
string resolvePath;
#if OS_WINDOWS
// Act.
resolvePath = IOUtil.ResolvePath(@"d:\src\project\", @"foo");
// Assert.
Assert.True(string.Equals(resolvePath, @"d:\src\project\foo", StringComparison.OrdinalIgnoreCase), $"resolvePath does not expected: {resolvePath}");
// Act.
resolvePath = IOUtil.ResolvePath(@"d:\", @"specs");
// Assert.
Assert.True(string.Equals(resolvePath, @"d:\specs", StringComparison.OrdinalIgnoreCase), $"resolvePath does not expected: {resolvePath}");
// Act.
resolvePath = IOUtil.ResolvePath(@"d:\src\project\", @"src\proj");
// Assert.
Assert.True(string.Equals(resolvePath, @"d:\src\project\src\proj", StringComparison.OrdinalIgnoreCase), $"resolvePath does not expected: {resolvePath}");
// Act.
resolvePath = IOUtil.ResolvePath(@"d:\src\project\foo", @"..");
// Assert.
Assert.True(string.Equals(resolvePath, @"d:\src\project", StringComparison.OrdinalIgnoreCase), $"resolvePath does not expected: {resolvePath}");
// Act.
resolvePath = IOUtil.ResolvePath(@"d:\src\project", @"..\..\");
// Assert.
Assert.True(string.Equals(resolvePath, @"d:\", StringComparison.OrdinalIgnoreCase), $"resolvePath does not expected: {resolvePath}");
// Act.
resolvePath = IOUtil.ResolvePath(@"d:/src/project", @"../.");
// Assert.
Assert.True(string.Equals(resolvePath, @"d:\src", StringComparison.OrdinalIgnoreCase), $"resolvePath does not expected: {resolvePath}");
// Act.
resolvePath = IOUtil.ResolvePath(@"d:/src/project/", @"../../foo");
// Assert.
Assert.True(string.Equals(resolvePath, @"d:\foo", StringComparison.OrdinalIgnoreCase), $"resolvePath does not expected: {resolvePath}");
// Act.
resolvePath = IOUtil.ResolvePath(@"d:/src/project/foo", @".././bar/.././../foo");
// Assert.
Assert.True(string.Equals(resolvePath, @"d:\src\foo", StringComparison.OrdinalIgnoreCase), $"resolvePath does not expected: {resolvePath}");
// Act.
resolvePath = IOUtil.ResolvePath(@"d:\", @".");
// Assert.
Assert.True(string.Equals(resolvePath, @"d:\", StringComparison.OrdinalIgnoreCase), $"resolvePath does not expected: {resolvePath}");
#else
// Act.
resolvePath = IOUtil.ResolvePath(@"/user/src/project", @"foo");
// Assert.
Assert.True(string.Equals(resolvePath, @"/user/src/project/foo", StringComparison.OrdinalIgnoreCase), $"RelativePath does not expected: {resolvePath}");
// Act.
resolvePath = IOUtil.ResolvePath(@"/root", @"./user/./specs");
// Assert.
Assert.True(string.Equals(resolvePath, @"/root/user/specs", StringComparison.OrdinalIgnoreCase), $"RelativePath does not expected: {resolvePath}");
// Act.
resolvePath = IOUtil.ResolvePath(@"/", @"user/specs/.");
// Assert.
Assert.True(string.Equals(resolvePath, @"/user/specs", StringComparison.OrdinalIgnoreCase), $"RelativePath does not expected: {resolvePath}");
// Act.
resolvePath = IOUtil.ResolvePath(@"/user/src/project", @"../");
// Assert.
Assert.True(string.Equals(resolvePath, @"/user/src", StringComparison.OrdinalIgnoreCase), $"RelativePath does not expected: {resolvePath}");
// Act.
resolvePath = IOUtil.ResolvePath(@"/user/src/project", @"../../");
// Assert.
Assert.True(string.Equals(resolvePath, @"/user", StringComparison.OrdinalIgnoreCase), $"RelativePath does not expected: {resolvePath}");
// Act.
resolvePath = IOUtil.ResolvePath(@"/user/src/project/foo", @"../../../../user/./src");
// Assert.
Assert.True(string.Equals(resolvePath, @"/user/src", StringComparison.OrdinalIgnoreCase), $"RelativePath does not expected: {resolvePath}");
// Act.
resolvePath = IOUtil.ResolvePath(@"/user/src", @"../../.");
// Assert.
Assert.True(string.Equals(resolvePath, @"/", StringComparison.OrdinalIgnoreCase), $"RelativePath does not expected: {resolvePath}");
// Act.
resolvePath = IOUtil.ResolvePath(@"/", @"./");
// Assert.
Assert.True(string.Equals(resolvePath, @"/", StringComparison.OrdinalIgnoreCase), $"RelativePath does not expected: {resolvePath}");
#endif
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void ValidateExecutePermission_DoesNotExceedFailsafe()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange: Create a directory.
string directory = Path.Combine(hc.GetDirectory(WellKnownDirectory.Bin), Path.GetRandomFileName());
try
{
Directory.CreateDirectory(directory);
// Act/Assert: Call "ValidateExecutePermission". The method should not blow up.
IOUtil.ValidateExecutePermission(directory);
}
finally
{
// Cleanup.
if (Directory.Exists(directory))
{
Directory.Delete(directory, recursive: true);
}
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void ValidateExecutePermission_ExceedsFailsafe()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange: Create a deep directory.
string directory = Path.Combine(hc.GetDirectory(WellKnownDirectory.Bin), Path.GetRandomFileName(), "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20");
try
{
Directory.CreateDirectory(directory);
Environment.SetEnvironmentVariable("AGENT_TEST_VALIDATE_EXECUTE_PERMISSIONS_FAILSAFE", "20");
try
{
// Act: Call "ValidateExecutePermission". The method should throw since
// it exceeds the failsafe recursion depth.
IOUtil.ValidateExecutePermission(directory);
// Assert.
throw new Exception("Should have thrown not supported exception.");
}
catch (NotSupportedException)
{
}
}
finally
{
// Cleanup.
if (Directory.Exists(directory))
{
Directory.Delete(directory, recursive: true);
}
}
}
}
private static async Task CreateDirectoryReparsePoint(IHostContext context, string link, string target)
{
#if OS_WINDOWS
string fileName = Environment.GetEnvironmentVariable("ComSpec");
string arguments = $@"/c ""mklink /J ""{link}"" {target}""""";
#else
string fileName = "/bin/ln";
string arguments = $@"-s ""{target}"" ""{link}""";
#endif
ArgUtil.File(fileName, nameof(fileName));
using (var processInvoker = new ProcessInvokerWrapper())
{
processInvoker.Initialize(context);
await processInvoker.ExecuteAsync(
workingDirectory: context.GetDirectory(WellKnownDirectory.Bin),
fileName: fileName,
arguments: arguments,
environment: null,
requireExitCodeZero: true,
cancellationToken: CancellationToken.None);
}
}
}
}

View File

@@ -0,0 +1,190 @@
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using System.Globalization;
using Xunit;
namespace GitHub.Runner.Common.Tests.Util
{
public class StringUtilL0
{
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void FormatAlwaysCallsFormat()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange.
var variableSets = new[]
{
new { Format = null as string, Args = null as object[], Expected = string.Empty },
new { Format = null as string, Args = new object[0], Expected = string.Empty },
new { Format = null as string, Args = new object[] { 123 }, Expected = string.Empty },
new { Format = "Some message", Args = null as object[], Expected = "Some message" },
new { Format = "Some message", Args = new object[0], Expected = "Some message" },
new { Format = "Some message", Args = new object[] { 123 }, Expected = "Some message" },
new { Format = "Some format '{0}'", Args = null as object[], Expected = "Some format ''" },
new { Format = "Some format '{0}'", Args = new object[0], Expected = "Some format ''" },
new { Format = "Some format '{0}'", Args = new object[] { 123 }, Expected = "Some format '123'" },
};
foreach (var variableSet in variableSets)
{
trace.Info($"{nameof(variableSet)}:");
trace.Info(variableSet);
// Act.
string actual = StringUtil.Format(variableSet.Format, variableSet.Args);
// Assert.
Assert.Equal(variableSet.Expected, actual);
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void FormatHandlesFormatException()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Arrange.
var variableSets = new[]
{
new { Format = "Bad format { 0}", Args = null as object[], Expected = "Bad format { 0}" },
new { Format = "Bad format { 0}", Args = new object[0], Expected = "Bad format { 0} " },
new { Format = "Bad format { 0}", Args = new object[] { null }, Expected = "Bad format { 0} " },
new { Format = "Bad format { 0}", Args = new object[] { 123, 456 }, Expected = "Bad format { 0} 123, 456" },
};
foreach (var variableSet in variableSets)
{
trace.Info($"{nameof(variableSet)}:");
trace.Info(variableSet);
// Act.
string actual = StringUtil.Format(variableSet.Format, variableSet.Args);
// Assert.
Assert.Equal(variableSet.Expected, actual);
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void FormatUsesInvariantCulture()
{
using (TestHostContext hc = new TestHostContext(this))
{
// Arrange.
CultureInfo originalCulture = CultureInfo.CurrentCulture;
try
{
CultureInfo.CurrentCulture = new CultureInfo("it-IT");
// Act.
string actual = StringUtil.Format("{0:N2}", 123456.789);
// Actual
Assert.Equal("123,456.79", actual);
}
finally
{
CultureInfo.CurrentCulture = originalCulture;
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void ConvertNullOrEmptryStringToBool()
{
using (TestHostContext hc = new TestHostContext(this))
{
// Arrange.
string nullString = null;
string emptyString = string.Empty;
// Act.
bool result1 = StringUtil.ConvertToBoolean(nullString);
bool result2 = StringUtil.ConvertToBoolean(emptyString);
// Actual
Assert.False(result1, "Null String should convert to false.");
Assert.False(result2, "Empty String should convert to false.");
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void ConvertNullOrEmptryStringToDefaultBool()
{
using (TestHostContext hc = new TestHostContext(this))
{
// Arrange.
string nullString = null;
string emptyString = string.Empty;
// Act.
bool result1 = StringUtil.ConvertToBoolean(nullString, true);
bool result2 = StringUtil.ConvertToBoolean(emptyString, true);
// Actual
Assert.True(result1, "Null String should convert to true since default value is set to true.");
Assert.True(result2, "Empty String should convert to true since default value is set to true.");
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void ConvertStringToBool()
{
using (TestHostContext hc = new TestHostContext(this))
{
// Arrange.
string trueString1 = "1";
string trueString2 = "True";
string trueString3 = "$TRUE";
string falseString1 = "0";
string falseString2 = "false";
string falseString3 = "$False";
string undefineString1 = "-1";
string undefineString2 = "sometext";
string undefineString3 = "2015-03-21";
// Act.
bool result1 = StringUtil.ConvertToBoolean(trueString1, false);
bool result2 = StringUtil.ConvertToBoolean(trueString2);
bool result3 = StringUtil.ConvertToBoolean(trueString3, true);
bool result4 = StringUtil.ConvertToBoolean(falseString1, true);
bool result5 = StringUtil.ConvertToBoolean(falseString2);
bool result6 = StringUtil.ConvertToBoolean(falseString3, false);
bool result7 = StringUtil.ConvertToBoolean(undefineString1, true);
bool result8 = StringUtil.ConvertToBoolean(undefineString2);
bool result9 = StringUtil.ConvertToBoolean(undefineString3, false);
// Actual
Assert.True(result1, $"'{trueString1}' should convert to true.");
Assert.True(result2, $"'{trueString2}' should convert to true.");
Assert.True(result3, $"'{trueString3}' should convert to true.");
Assert.False(result4, $"'{falseString1}' should convert to false.");
Assert.False(result5, $"'{falseString2}' should convert to false.");
Assert.False(result6, $"'{falseString3}' should convert to false.");
Assert.True(result7, $"'{undefineString1}' should convert to true, since default is true.");
Assert.False(result8, $"'{undefineString2}' should convert to false.");
Assert.False(result9, $"'{undefineString3}' should convert to false.");
}
}
}
}

View File

@@ -0,0 +1,206 @@
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common.Util;
using Xunit;
namespace GitHub.Runner.Common.Tests.Util
{
public class TaskResultUtilL0
{
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void TaskResultReturnCodeTranslate()
{
// Arrange.
using (TestHostContext hc = new TestHostContext(this))
{
// Act.
TaskResult abandon = TaskResultUtil.TranslateFromReturnCode(TaskResultUtil.TranslateToReturnCode(TaskResult.Abandoned));
// Actual
Assert.Equal(TaskResult.Abandoned, abandon);
// Act.
TaskResult canceled = TaskResultUtil.TranslateFromReturnCode(TaskResultUtil.TranslateToReturnCode(TaskResult.Canceled));
// Actual
Assert.Equal(TaskResult.Canceled, canceled);
// Act.
TaskResult failed = TaskResultUtil.TranslateFromReturnCode(TaskResultUtil.TranslateToReturnCode(TaskResult.Failed));
// Actual
Assert.Equal(TaskResult.Failed, failed);
// Act.
TaskResult skipped = TaskResultUtil.TranslateFromReturnCode(TaskResultUtil.TranslateToReturnCode(TaskResult.Skipped));
// Actual
Assert.Equal(TaskResult.Skipped, skipped);
// Act.
TaskResult succeeded = TaskResultUtil.TranslateFromReturnCode(TaskResultUtil.TranslateToReturnCode(TaskResult.Succeeded));
// Actual
Assert.Equal(TaskResult.Succeeded, succeeded);
// Act.
TaskResult unknowReturnCode1 = TaskResultUtil.TranslateFromReturnCode(0);
// Actual
Assert.Equal(TaskResult.Failed, unknowReturnCode1);
// Act.
TaskResult unknowReturnCode2 = TaskResultUtil.TranslateFromReturnCode(1);
// Actual
Assert.Equal(TaskResult.Failed, unknowReturnCode2);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void TaskResultsMerge()
{
// Arrange.
using (TestHostContext hc = new TestHostContext(this))
{
TaskResult merged;
//
// No current result merge.
//
// Act.
merged = TaskResultUtil.MergeTaskResults(null, TaskResult.Succeeded);
// Actual
Assert.Equal(TaskResult.Succeeded, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(null, TaskResult.Abandoned);
// Actual
Assert.Equal(TaskResult.Abandoned, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(null, TaskResult.Canceled);
// Actual
Assert.Equal(TaskResult.Canceled, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(null, TaskResult.Failed);
// Actual
Assert.Equal(TaskResult.Failed, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(null, TaskResult.Skipped);
// Actual
Assert.Equal(TaskResult.Skipped, merged);
//
// Same result merge.
//
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Succeeded, TaskResult.Succeeded);
// Actual
Assert.Equal(TaskResult.Succeeded, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Abandoned, TaskResult.Abandoned);
// Actual
Assert.Equal(TaskResult.Abandoned, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Canceled, TaskResult.Canceled);
// Actual
Assert.Equal(TaskResult.Canceled, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Failed, TaskResult.Failed);
// Actual
Assert.Equal(TaskResult.Failed, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Skipped, TaskResult.Skipped);
// Actual
Assert.Equal(TaskResult.Skipped, merged);
//
// Forward result merge
//
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Succeeded, TaskResult.Abandoned);
// Actual
Assert.Equal(TaskResult.Abandoned, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Succeeded, TaskResult.Canceled);
// Actual
Assert.Equal(TaskResult.Canceled, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Succeeded, TaskResult.Failed);
// Actual
Assert.Equal(TaskResult.Failed, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Succeeded, TaskResult.Skipped);
// Actual
Assert.Equal(TaskResult.Skipped, merged);
//
// No backward merge
//
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Abandoned, TaskResult.Succeeded);
// Actual
Assert.Equal(TaskResult.Abandoned, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Canceled, TaskResult.Succeeded);
// Actual
Assert.Equal(TaskResult.Canceled, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Failed, TaskResult.Succeeded);
// Actual
Assert.Equal(TaskResult.Failed, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Skipped, TaskResult.Succeeded);
// Actual
Assert.Equal(TaskResult.Skipped, merged);
//
// Worst result no change
//
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Abandoned, TaskResult.Canceled);
// Actual
Assert.Equal(TaskResult.Abandoned, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Abandoned, TaskResult.Failed);
// Actual
Assert.Equal(TaskResult.Abandoned, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Abandoned, TaskResult.Skipped);
// Actual
Assert.Equal(TaskResult.Abandoned, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Canceled, TaskResult.Abandoned);
// Actual
Assert.Equal(TaskResult.Canceled, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Canceled, TaskResult.Failed);
// Actual
Assert.Equal(TaskResult.Canceled, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Canceled, TaskResult.Skipped);
// Actual
Assert.Equal(TaskResult.Canceled, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Failed, TaskResult.Abandoned);
// Actual
Assert.Equal(TaskResult.Abandoned, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Failed, TaskResult.Canceled);
// Actual
Assert.Equal(TaskResult.Canceled, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Failed, TaskResult.Skipped);
// Actual
Assert.Equal(TaskResult.Skipped, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Skipped, TaskResult.Abandoned);
// Actual
Assert.Equal(TaskResult.Skipped, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Skipped, TaskResult.Canceled);
// Actual
Assert.Equal(TaskResult.Skipped, merged);
// Act.
merged = TaskResultUtil.MergeTaskResults(TaskResult.Skipped, TaskResult.Failed);
// Actual
Assert.Equal(TaskResult.Skipped, merged);
}
}
}
}

View File

@@ -0,0 +1,65 @@
using System;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using Xunit;
namespace GitHub.Runner.Common.Tests.Util
{
public class UrlUtilL0
{
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void GetCredentialEmbeddedUrl_NoUsernameAndPassword()
{
// Act.
Uri result = UrlUtil.GetCredentialEmbeddedUrl(new Uri("https://github.com/actions/runner.git"), string.Empty, string.Empty);
// Actual
Assert.Equal("https://github.com/actions/runner.git", result.AbsoluteUri);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void GetCredentialEmbeddedUrl_NoUsername()
{
// Act.
Uri result = UrlUtil.GetCredentialEmbeddedUrl(new Uri("https://github.com/actions/runner.git"), string.Empty, "password123");
// Actual
Assert.Equal("https://emptyusername:password123@github.com/actions/runner.git", result.AbsoluteUri);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void GetCredentialEmbeddedUrl_NoPassword()
{
// Act.
Uri result = UrlUtil.GetCredentialEmbeddedUrl(new Uri("https://github.com/actions/runner.git"), "user123", string.Empty);
// Actual
Assert.Equal("https://user123@github.com/actions/runner.git", result.AbsoluteUri);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void GetCredentialEmbeddedUrl_HasUsernameAndPassword()
{
// Act.
Uri result = UrlUtil.GetCredentialEmbeddedUrl(new Uri("https://github.com/actions/runner.git"), "user123", "password123");
// Actual
Assert.Equal("https://user123:password123@github.com/actions/runner.git", result.AbsoluteUri);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void GetCredentialEmbeddedUrl_UsernameAndPasswordEncoding()
{
// Act.
Uri result = UrlUtil.GetCredentialEmbeddedUrl(new Uri("https://github.com/actions/runner.git"), "user 123", "password 123");
// Actual
Assert.Equal("https://user%20123:password%20123@github.com/actions/runner.git", result.AbsoluteUri);
}
}
}

View File

@@ -0,0 +1,56 @@
using GitHub.Runner.Common.Util;
using GitHub.Services.Common;
using System;
using System.Collections.Generic;
using System.Net.Http.Headers;
using Xunit;
using System.Text.RegularExpressions;
using GitHub.Runner.Sdk;
namespace GitHub.Runner.Common.Tests.Util
{
public sealed class VssUtilL0
{
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void VerifyOverwriteVssConnectionSetting()
{
using (TestHostContext hc = new TestHostContext(this))
{
Tracing trace = hc.GetTrace();
// Act.
try
{
trace.Info("Set httpretry to 10.");
Environment.SetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_HTTP_RETRY", "10");
trace.Info("Set httptimeout to 360.");
Environment.SetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_HTTP_TIMEOUT", "360");
var connect = VssUtil.CreateConnection(new Uri("https://github.com/actions/runner"), new VssCredentials());
// Assert.
Assert.Equal(connect.Settings.MaxRetryRequest.ToString(), "10");
Assert.Equal(connect.Settings.SendTimeout.TotalSeconds.ToString(), "360");
trace.Info("Set httpretry to 100.");
Environment.SetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_HTTP_RETRY", "100");
trace.Info("Set httptimeout to 3600.");
Environment.SetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_HTTP_TIMEOUT", "3600");
connect = VssUtil.CreateConnection(new Uri("https://github.com/actions/runner"), new VssCredentials());
// Assert.
Assert.Equal(connect.Settings.MaxRetryRequest.ToString(), "10");
Assert.Equal(connect.Settings.SendTimeout.TotalSeconds.ToString(), "1200");
}
finally
{
Environment.SetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_HTTP_RETRY", "");
Environment.SetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_HTTP_TIMEOUT", "");
}
}
}
}
}

View File

@@ -0,0 +1,74 @@
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using System;
using System.IO;
using Xunit;
namespace GitHub.Runner.Common.Tests.Util
{
public sealed class WhichUtilL0
{
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void UseWhichFindGit()
{
using (TestHostContext hc = new TestHostContext(this))
{
//Arrange
Tracing trace = hc.GetTrace();
// Act.
string gitPath = WhichUtil.Which("git", trace: trace);
trace.Info($"Which(\"git\") returns: {gitPath ?? string.Empty}");
// Assert.
Assert.True(!string.IsNullOrEmpty(gitPath) && File.Exists(gitPath), $"Unable to find Git through: {nameof(WhichUtil.Which)}");
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void WhichReturnsNullWhenNotFound()
{
using (TestHostContext hc = new TestHostContext(this))
{
//Arrange
Tracing trace = hc.GetTrace();
// Act.
string nosuch = WhichUtil.Which("no-such-file-cf7e351f", trace: trace);
trace.Info($"result: {nosuch ?? string.Empty}");
// Assert.
Assert.True(string.IsNullOrEmpty(nosuch), "Path should not be resolved");
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void WhichThrowsWhenRequireAndNotFound()
{
using (TestHostContext hc = new TestHostContext(this))
{
//Arrange
Tracing trace = hc.GetTrace();
// Act.
try
{
WhichUtil.Which("no-such-file-cf7e351f", require: true, trace: trace);
throw new Exception("which should have thrown");
}
catch (FileNotFoundException ex)
{
Assert.Equal("no-such-file-cf7e351f", ex.FileName);
}
}
}
}
}

View File

@@ -0,0 +1,211 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace GitHub.Runner.Common.Tests.Worker
{
public class LoggingCommandL0
{
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "LoggingCommand")]
public void CommandParserTest()
{
//Arrange
using (var hc = new TestHostContext(this))
{
string message;
ActionCommand test;
ActionCommand verify;
HashSet<string> commands = new HashSet<string>() { "do-something" };
//##[do-something k1=v1;]msg
message = "##[do-something k1=v1;]msg";
test = new ActionCommand("do-something")
{
Data = "msg",
};
test.Properties.Add("k1", "v1");
Assert.True(ActionCommand.TryParse(message, commands, out verify));
Assert.True(IsEqualCommand(hc, test, verify));
message = "";
test = null;
verify = null;
//##[do-something]
message = "##[do-something]";
test = new ActionCommand("do-something");
Assert.True(ActionCommand.TryParse(message, commands, out verify));
Assert.True(IsEqualCommand(hc, test, verify));
message = "";
test = null;
verify = null;
//##[do-something k1=%3B=%0D=%0A=%5D;]%3B-%0D-%0A-%5D
message = "##[do-something k1=%3B=%0D=%0A=%5D;]%3B-%0D-%0A-%5D";
test = new ActionCommand("do-something")
{
Data = ";-\r-\n-]",
};
test.Properties.Add("k1", ";=\r=\n=]");
Assert.True(ActionCommand.TryParse(message, commands, out verify));
Assert.True(IsEqualCommand(hc, test, verify));
message = "";
test = null;
verify = null;
//##[do-something k1=;k2=;]
message = "##[do-something k1=;k2=;]";
test = new ActionCommand("do-something");
Assert.True(ActionCommand.TryParse(message, commands, out verify));
Assert.True(IsEqualCommand(hc, test, verify));
message = "";
test = null;
verify = null;
//>>> ##[do-something k1=;k2=;]
message = ">>> ##[do-something k1=v1;]msg";
test = new ActionCommand("do-something")
{
Data = "msg",
};
test.Properties.Add("k1", "v1");
Assert.True(ActionCommand.TryParse(message, commands, out verify));
Assert.True(IsEqualCommand(hc, test, verify));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "LoggingCommand")]
public void CommandParserV2Test()
{
//Arrange
using (var hc = new TestHostContext(this))
{
string message;
ActionCommand test;
ActionCommand verify;
HashSet<string> commands = new HashSet<string>() { "do-something" };
//::do-something k1=v1;]msg
message = "::do-something k1=v1,::msg";
test = new ActionCommand("do-something")
{
Data = "msg",
};
test.Properties.Add("k1", "v1");
Assert.True(ActionCommand.TryParseV2(message, commands, out verify));
Assert.True(IsEqualCommand(hc, test, verify));
message = "";
test = null;
verify = null;
//::do-something::
message = "::do-something::";
test = new ActionCommand("do-something");
Assert.True(ActionCommand.TryParseV2(message, commands, out verify));
Assert.True(IsEqualCommand(hc, test, verify));
message = "";
test = null;
verify = null;
//::do-something k1=%3B=%0D=%0A=%5D;::%3B-%0D-%0A-%5D
message = "::do-something k1=;=%2C=%0D=%0A=]=%3A,::;-%0D-%0A-]-:-,";
test = new ActionCommand("do-something")
{
Data = ";-\r-\n-]-:-,",
};
test.Properties.Add("k1", ";=,=\r=\n=]=:");
Assert.True(ActionCommand.TryParseV2(message, commands, out verify));
Assert.True(IsEqualCommand(hc, test, verify));
message = "";
test = null;
verify = null;
//::do-something k1=,k2=,::
message = "::do-something k1=,k2=,::";
test = new ActionCommand("do-something");
Assert.True(ActionCommand.TryParseV2(message, commands, out verify));
Assert.True(IsEqualCommand(hc, test, verify));
message = "";
test = null;
verify = null;
//::do-something k1=v1::
message = "::do-something k1=v1::";
test = new ActionCommand("do-something");
test.Properties.Add("k1", "v1");
Assert.True(ActionCommand.TryParseV2(message, commands, out verify));
Assert.True(IsEqualCommand(hc, test, verify));
message = "";
test = null;
verify = null;
// ::do-something k1=v1,::
message = " ::do-something k1=v1,::msg";
test = new ActionCommand("do-something")
{
Data = "msg",
};
test.Properties.Add("k1", "v1");
Assert.True(ActionCommand.TryParseV2(message, commands, out verify));
Assert.True(IsEqualCommand(hc, test, verify));
message = "";
verify = null;
// >>> ::do-something k1=v1,::
message = " >>> ::do-something k1=v1,::msg";
Assert.False(ActionCommand.TryParseV2(message, commands, out verify));
}
}
private bool IsEqualCommand(IHostContext hc, ActionCommand e1, ActionCommand e2)
{
try
{
if (!string.Equals(e1.Command, e2.Command, StringComparison.OrdinalIgnoreCase))
{
hc.GetTrace("CommandEqual").Info("Command 1={0}, Command 2={1}", e1.Command, e2.Command);
return false;
}
if (!string.Equals(e1.Data, e2.Data, StringComparison.OrdinalIgnoreCase) && (!string.IsNullOrEmpty(e1.Data) && !string.IsNullOrEmpty(e2.Data)))
{
hc.GetTrace("CommandEqual").Info("Data 1={0}, Data 2={1}", e1.Data, e2.Data);
return false;
}
if (e1.Properties.Count != e2.Properties.Count)
{
hc.GetTrace("CommandEqual").Info("Logging events contain different numbers of Properties,{0} to {1}", e1.Properties.Count, e2.Properties.Count);
return false;
}
if (!e1.Properties.SequenceEqual(e2.Properties))
{
hc.GetTrace("CommandEqual").Info("Logging events contain different Properties");
hc.GetTrace("CommandEqual").Info("Properties for event 1:");
foreach (var data in e1.Properties)
{
hc.GetTrace("CommandEqual").Info("Key={0}, Value={1}", data.Key, data.Value);
}
hc.GetTrace("CommandEqual").Info("Properties for event 2:");
foreach (var data in e2.Properties)
{
hc.GetTrace("CommandEqual").Info("Key={0}, Value={1}", data.Key, data.Value);
}
return false;
}
}
catch (Exception ex)
{
hc.GetTrace("CommandEqual").Info("Catch Exception during compare:{0}", ex.ToString());
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Worker;
using Moq;
using Xunit;
namespace GitHub.Runner.Common.Tests.Worker
{
public sealed class ActionCommandManagerL0
{
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void EnablePluginInternalCommand()
{
using (TestHostContext _hc = new TestHostContext(this))
{
var extensionManger = new Mock<IExtensionManager>();
var directoryManager = new Mock<IPipelineDirectoryManager>();
var pluginCommand = new InternalPluginSetRepoPathCommandExtension();
pluginCommand.Initialize(_hc);
var envCommand = new SetEnvCommandExtension();
envCommand.Initialize(_hc);
extensionManger.Setup(x => x.GetExtensions<IActionCommandExtension>())
.Returns(new List<IActionCommandExtension>() { pluginCommand, envCommand });
_hc.SetSingleton<IExtensionManager>(extensionManger.Object);
_hc.SetSingleton<IPipelineDirectoryManager>(directoryManager.Object);
Mock<IExecutionContext> _ec = new Mock<IExecutionContext>();
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
.Returns((string tag, string line) =>
{
_hc.GetTrace().Info($"{tag} {line}");
return 1;
});
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>()))
.Callback((Issue issue, string message) =>
{
_hc.GetTrace().Info($"{issue.Type} {issue.Message} {message ?? string.Empty}");
});
ActionCommandManager commandManager = new ActionCommandManager();
commandManager.Initialize(_hc);
commandManager.EnablePluginInternalCommand();
Assert.True(commandManager.TryProcessCommand(_ec.Object, "##[internal-set-repo-path repoFullName=actions/runner;workspaceRepo=true]somepath"));
directoryManager.Verify(x => x.UpdateRepositoryDirectory(_ec.Object, "actions/runner", "somepath", true), Times.Once);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void DisablePluginInternalCommand()
{
using (TestHostContext _hc = new TestHostContext(this))
{
var extensionManger = new Mock<IExtensionManager>();
var directoryManager = new Mock<IPipelineDirectoryManager>();
var pluginCommand = new InternalPluginSetRepoPathCommandExtension();
pluginCommand.Initialize(_hc);
var envCommand = new SetEnvCommandExtension();
envCommand.Initialize(_hc);
extensionManger.Setup(x => x.GetExtensions<IActionCommandExtension>())
.Returns(new List<IActionCommandExtension>() { pluginCommand, envCommand });
_hc.SetSingleton<IExtensionManager>(extensionManger.Object);
_hc.SetSingleton<IPipelineDirectoryManager>(directoryManager.Object);
Mock<IExecutionContext> _ec = new Mock<IExecutionContext>();
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
.Returns((string tag, string line) =>
{
_hc.GetTrace().Info($"{tag} {line}");
return 1;
});
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>()))
.Callback((Issue issue, string message) =>
{
_hc.GetTrace().Info($"{issue.Type} {issue.Message} {message ?? string.Empty}");
});
ActionCommandManager commandManager = new ActionCommandManager();
commandManager.Initialize(_hc);
commandManager.EnablePluginInternalCommand();
Assert.True(commandManager.TryProcessCommand(_ec.Object, "##[internal-set-repo-path repoFullName=actions/runner;workspaceRepo=true]somepath"));
commandManager.DisablePluginInternalCommand();
Assert.False(commandManager.TryProcessCommand(_ec.Object, "##[internal-set-repo-path repoFullName=actions/runner;workspaceRepo=true]somepath"));
directoryManager.Verify(x => x.UpdateRepositoryDirectory(_ec.Object, "actions/runner", "somepath", true), Times.Once);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void StopProcessCommand()
{
using (TestHostContext _hc = new TestHostContext(this))
{
var extensionManger = new Mock<IExtensionManager>();
var pluginCommand = new InternalPluginSetRepoPathCommandExtension();
pluginCommand.Initialize(_hc);
var envCommand = new SetEnvCommandExtension();
envCommand.Initialize(_hc);
extensionManger.Setup(x => x.GetExtensions<IActionCommandExtension>())
.Returns(new List<IActionCommandExtension>() { pluginCommand, envCommand });
_hc.SetSingleton<IExtensionManager>(extensionManger.Object);
Mock<IExecutionContext> _ec = new Mock<IExecutionContext>();
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
.Returns((string tag, string line) =>
{
_hc.GetTrace().Info($"{tag} {line}");
return 1;
});
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>()))
.Callback((Issue issue, string message) =>
{
_hc.GetTrace().Info($"{issue.Type} {issue.Message} {message ?? string.Empty}");
});
_ec.Setup(x => x.EnvironmentVariables).Returns(new Dictionary<string, string>());
ActionCommandManager commandManager = new ActionCommandManager();
commandManager.Initialize(_hc);
Assert.True(commandManager.TryProcessCommand(_ec.Object, "##[stop-commands]stopToken"));
Assert.False(commandManager.TryProcessCommand(_ec.Object, "##[set-env name=foo]bar"));
Assert.True(commandManager.TryProcessCommand(_ec.Object, "##[stopToken]"));
Assert.True(commandManager.TryProcessCommand(_ec.Object, "##[set-env name=foo]bar"));
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,497 @@
using GitHub.DistributedTask.ObjectTemplating.Tokens;
using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Worker;
using Moq;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using Xunit;
namespace GitHub.Runner.Common.Tests.Worker
{
public sealed class ActionManifestManagerL0
{
private CancellationTokenSource _ecTokenSource;
private Mock<IExecutionContext> _ec;
private TestHostContext _hc;
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Load_ContainerAction_Dockerfile()
{
try
{
//Arrange
Setup();
var actionManifest = new ActionManifestManager();
actionManifest.Initialize(_hc);
//Act
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "dockerfileaction.yml"));
//Assert
Assert.Equal(result.Name, "Hello World");
Assert.Equal(result.Description, "Greet the world and record the time");
Assert.Equal(result.Inputs.Count, 2);
Assert.Equal(result.Inputs[0].Key.AssertString("key").Value, "greeting");
Assert.Equal(result.Inputs[0].Value.AssertString("value").Value, "Hello");
Assert.Equal(result.Inputs[1].Key.AssertString("key").Value, "entryPoint");
Assert.Equal(result.Inputs[1].Value.AssertString("value").Value, "");
Assert.Equal(result.Execution.ExecutionType, ActionExecutionType.Container);
var containerAction = result.Execution as ContainerActionExecutionData;
Assert.Equal(containerAction.Image, "Dockerfile");
Assert.Equal(containerAction.EntryPoint, "main.sh");
Assert.Equal(containerAction.Arguments[0].ToString(), "bzz");
Assert.Equal(containerAction.Environment[0].Key.ToString(), "Token");
Assert.Equal(containerAction.Environment[0].Value.ToString(), "foo");
Assert.Equal(containerAction.Environment[1].Key.ToString(), "Url");
Assert.Equal(containerAction.Environment[1].Value.ToString(), "bar");
}
finally
{
Teardown();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Load_ContainerAction_Dockerfile_Post()
{
try
{
//Arrange
Setup();
var actionManifest = new ActionManifestManager();
actionManifest.Initialize(_hc);
//Act
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "dockerfileaction_cleanup.yml"));
//Assert
Assert.Equal(result.Name, "Hello World");
Assert.Equal(result.Description, "Greet the world and record the time");
Assert.Equal(result.Inputs.Count, 2);
Assert.Equal(result.Inputs[0].Key.AssertString("key").Value, "greeting");
Assert.Equal(result.Inputs[0].Value.AssertString("value").Value, "Hello");
Assert.Equal(result.Inputs[1].Key.AssertString("key").Value, "entryPoint");
Assert.Equal(result.Inputs[1].Value.AssertString("value").Value, "");
Assert.Equal(result.Execution.ExecutionType, ActionExecutionType.Container);
var containerAction = result.Execution as ContainerActionExecutionData;
Assert.Equal(containerAction.Image, "Dockerfile");
Assert.Equal(containerAction.EntryPoint, "main.sh");
Assert.Equal(containerAction.Cleanup, "cleanup.sh");
Assert.Equal(containerAction.CleanupCondition, "failure()");
Assert.Equal(containerAction.Arguments[0].ToString(), "bzz");
Assert.Equal(containerAction.Environment[0].Key.ToString(), "Token");
Assert.Equal(containerAction.Environment[0].Value.ToString(), "foo");
Assert.Equal(containerAction.Environment[1].Key.ToString(), "Url");
Assert.Equal(containerAction.Environment[1].Value.ToString(), "bar");
}
finally
{
Teardown();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Load_ContainerAction_NoArgsNoEnv()
{
try
{
//Arrange
Setup();
var actionManifest = new ActionManifestManager();
actionManifest.Initialize(_hc);
//Act
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "dockerfileaction_noargs_noenv_noentrypoint.yml"));
//Assert
Assert.Equal(result.Name, "Hello World");
Assert.Equal(result.Description, "Greet the world and record the time");
Assert.Equal(result.Inputs.Count, 2);
Assert.Equal(result.Inputs[0].Key.AssertString("key").Value, "greeting");
Assert.Equal(result.Inputs[0].Value.AssertString("value").Value, "Hello");
Assert.Equal(result.Inputs[1].Key.AssertString("key").Value, "entryPoint");
Assert.Equal(result.Inputs[1].Value.AssertString("value").Value, "");
Assert.Equal(result.Execution.ExecutionType, ActionExecutionType.Container);
var containerAction = result.Execution as ContainerActionExecutionData;
Assert.Equal(containerAction.Image, "Dockerfile");
}
finally
{
Teardown();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Load_ContainerAction_Dockerfile_Expression()
{
try
{
//Arrange
Setup();
var actionManifest = new ActionManifestManager();
actionManifest.Initialize(_hc);
//Act
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "dockerfileaction_arg_env_expression.yml"));
//Assert
Assert.Equal(result.Name, "Hello World");
Assert.Equal(result.Description, "Greet the world and record the time");
Assert.Equal(result.Inputs.Count, 2);
Assert.Equal(result.Inputs[0].Key.AssertString("key").Value, "greeting");
Assert.Equal(result.Inputs[0].Value.AssertString("value").Value, "Hello");
Assert.Equal(result.Inputs[1].Key.AssertString("key").Value, "entryPoint");
Assert.Equal(result.Inputs[1].Value.AssertString("value").Value, "");
Assert.Equal(result.Execution.ExecutionType, ActionExecutionType.Container);
var containerAction = result.Execution as ContainerActionExecutionData;
Assert.Equal(containerAction.Image, "Dockerfile");
Assert.Equal(containerAction.EntryPoint, "main.sh");
Assert.Equal(containerAction.Arguments[0].ToString(), "${{ inputs.greeting }}");
Assert.Equal(containerAction.Environment[0].Key.ToString(), "Token");
Assert.Equal(containerAction.Environment[0].Value.ToString(), "foo");
Assert.Equal(containerAction.Environment[1].Key.ToString(), "Url");
Assert.Equal(containerAction.Environment[1].Value.ToString(), "${{ inputs.entryPoint }}");
}
finally
{
Teardown();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Load_ContainerAction_DockerHub()
{
try
{
//Arrange
Setup();
var actionManifest = new ActionManifestManager();
actionManifest.Initialize(_hc);
//Act
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "dockerhubaction.yml"));
//Assert
Assert.Equal(result.Name, "Hello World");
Assert.Equal(result.Description, "Greet the world and record the time");
Assert.Equal(result.Inputs.Count, 2);
Assert.Equal(result.Inputs[0].Key.AssertString("key").Value, "greeting");
Assert.Equal(result.Inputs[0].Value.AssertString("value").Value, "Hello");
Assert.Equal(result.Inputs[1].Key.AssertString("key").Value, "entryPoint");
Assert.Equal(result.Inputs[1].Value.AssertString("value").Value, "");
Assert.Equal(result.Execution.ExecutionType, ActionExecutionType.Container);
var containerAction = result.Execution as ContainerActionExecutionData;
Assert.Equal(containerAction.Image, "docker://ubuntu:18.04");
Assert.Equal(containerAction.EntryPoint, "main.sh");
Assert.Equal(containerAction.Arguments[0].ToString(), "bzz");
Assert.Equal(containerAction.Environment[0].Key.ToString(), "Token");
Assert.Equal(containerAction.Environment[0].Value.ToString(), "foo");
Assert.Equal(containerAction.Environment[1].Key.ToString(), "Url");
Assert.Equal(containerAction.Environment[1].Value.ToString(), "bar");
}
finally
{
Teardown();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Load_NodeAction()
{
try
{
//Arrange
Setup();
var actionManifest = new ActionManifestManager();
actionManifest.Initialize(_hc);
//Act
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "nodeaction.yml"));
//Assert
Assert.Equal(result.Name, "Hello World");
Assert.Equal(result.Description, "Greet the world and record the time");
Assert.Equal(result.Inputs.Count, 2);
Assert.Equal(result.Inputs[0].Key.AssertString("key").Value, "greeting");
Assert.Equal(result.Inputs[0].Value.AssertString("value").Value, "Hello");
Assert.Equal(result.Inputs[1].Key.AssertString("key").Value, "entryPoint");
Assert.Equal(result.Inputs[1].Value.AssertString("value").Value, "");
Assert.Equal(result.Deprecated.Count, 1);
Assert.True(result.Deprecated.ContainsKey("greeting"));
result.Deprecated.TryGetValue("greeting", out string value);
Assert.Equal(value, "This property has been deprecated");
Assert.Equal(result.Execution.ExecutionType, ActionExecutionType.NodeJS);
var nodeAction = result.Execution as NodeJSActionExecutionData;
Assert.Equal(nodeAction.Script, "main.js");
}
finally
{
Teardown();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Load_NodeAction_Cleanup()
{
try
{
//Arrange
Setup();
var actionManifest = new ActionManifestManager();
actionManifest.Initialize(_hc);
//Act
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "nodeaction_cleanup.yml"));
//Assert
Assert.Equal(result.Name, "Hello World");
Assert.Equal(result.Description, "Greet the world and record the time");
Assert.Equal(result.Inputs.Count, 2);
Assert.Equal(result.Inputs[0].Key.AssertString("key").Value, "greeting");
Assert.Equal(result.Inputs[0].Value.AssertString("value").Value, "Hello");
Assert.Equal(result.Inputs[1].Key.AssertString("key").Value, "entryPoint");
Assert.Equal(result.Inputs[1].Value.AssertString("value").Value, "");
Assert.Equal(result.Deprecated.Count, 1);
Assert.True(result.Deprecated.ContainsKey("greeting"));
result.Deprecated.TryGetValue("greeting", out string value);
Assert.Equal(value, "This property has been deprecated");
Assert.Equal(result.Execution.ExecutionType, ActionExecutionType.NodeJS);
var nodeAction = result.Execution as NodeJSActionExecutionData;
Assert.Equal(nodeAction.Script, "main.js");
Assert.Equal(nodeAction.Cleanup, "cleanup.js");
Assert.Equal(nodeAction.CleanupCondition, "cancelled()");
}
finally
{
Teardown();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Load_PluginAction()
{
try
{
//Arrange
Setup();
var actionManifest = new ActionManifestManager();
actionManifest.Initialize(_hc);
//Act
var result = actionManifest.Load(_ec.Object, Path.Combine(TestUtil.GetTestDataPath(), "pluginaction.yml"));
//Assert
Assert.Equal(result.Name, "Hello World");
Assert.Equal(result.Description, "Greet the world and record the time");
Assert.Equal(result.Inputs.Count, 2);
Assert.Equal(result.Inputs[0].Key.AssertString("key").Value, "greeting");
Assert.Equal(result.Inputs[0].Value.AssertString("value").Value, "Hello");
Assert.Equal(result.Inputs[1].Key.AssertString("key").Value, "entryPoint");
Assert.Equal(result.Inputs[1].Value.AssertString("value").Value, "");
Assert.Equal(result.Execution.ExecutionType, ActionExecutionType.Plugin);
var pluginAction = result.Execution as PluginActionExecutionData;
Assert.Equal(pluginAction.Plugin, "someplugin");
}
finally
{
Teardown();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Evaluate_ContainerAction_Args()
{
try
{
//Arrange
Setup();
var actionManifest = new ActionManifestManager();
actionManifest.Initialize(_hc);
var arguments = new SequenceToken(null, null, null);
arguments.Add(new BasicExpressionToken(null, null, null, "inputs.greeting"));
arguments.Add(new StringToken(null, null, null, "test"));
var inputsContext = new DictionaryContextData();
inputsContext.Add("greeting", new StringContextData("hello"));
var evaluateContext = new Dictionary<string, PipelineContextData>(StringComparer.OrdinalIgnoreCase);
evaluateContext["inputs"] = inputsContext;
//Act
var result = actionManifest.EvaluateContainerArguments(_ec.Object, arguments, evaluateContext);
//Assert
Assert.Equal(result[0], "hello");
Assert.Equal(result[1], "test");
Assert.Equal(result.Count, 2);
}
finally
{
Teardown();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Evaluate_ContainerAction_Env()
{
try
{
//Arrange
Setup();
var actionManifest = new ActionManifestManager();
actionManifest.Initialize(_hc);
var environment = new MappingToken(null, null, null);
environment.Add(new StringToken(null, null, null, "hello"), new BasicExpressionToken(null, null, null, "inputs.greeting"));
environment.Add(new StringToken(null, null, null, "test"), new StringToken(null, null, null, "test"));
var inputsContext = new DictionaryContextData();
inputsContext.Add("greeting", new StringContextData("hello"));
var evaluateContext = new Dictionary<string, PipelineContextData>(StringComparer.OrdinalIgnoreCase);
evaluateContext["inputs"] = inputsContext;
//Act
var result = actionManifest.EvaluateContainerEnvironment(_ec.Object, environment, evaluateContext);
//Assert
Assert.Equal(result["hello"], "hello");
Assert.Equal(result["test"], "test");
Assert.Equal(result.Count, 2);
}
finally
{
Teardown();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Evaluate_Default_Input()
{
try
{
//Arrange
Setup();
var actionManifest = new ActionManifestManager();
actionManifest.Initialize(_hc);
var githubContext = new DictionaryContextData();
githubContext.Add("ref", new StringContextData("refs/heads/master"));
var evaluateContext = new Dictionary<string, PipelineContextData>(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();
//Act
var result = actionManifest.EvaluateDefaultInput(_ec.Object, "testInput", new StringToken(null, null, null, "defaultValue"), evaluateContext);
//Assert
Assert.Equal(result, "defaultValue");
//Act
result = actionManifest.EvaluateDefaultInput(_ec.Object, "testInput", new BasicExpressionToken(null, null, null, "github.ref"), evaluateContext);
//Assert
Assert.Equal(result, "refs/heads/master");
}
finally
{
Teardown();
}
}
private void Setup([CallerMemberName] string name = "")
{
_ecTokenSource?.Dispose();
_ecTokenSource = new CancellationTokenSource();
// Test host context.
_hc = new TestHostContext(this, name);
_ec = new Mock<IExecutionContext>();
_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<string, VariableValue>()));
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"{tag}{message}"); });
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); });
}
private void Teardown()
{
_hc?.Dispose();
}
}
}

View File

@@ -0,0 +1,359 @@
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<IHandlerFactory> _handlerFactory;
private Mock<IActionManager> _actionManager;
private Mock<IDefaultStepHost> _defaultStepHost;
private Mock<IExecutionContext> _ec;
private TestHostContext _hc;
private ActionRunner _actionRunner;
private IActionManifestManager _actionManifestManager;
private string _workFolder;
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<string, string> finialInputs = new Dictionary<string, string>();
_handlerFactory.Setup(x => x.Create(It.IsAny<IExecutionContext>(), It.IsAny<ActionStepDefinitionReference>(), It.IsAny<IStepHost>(), It.IsAny<ActionExecutionData>(), It.IsAny<Dictionary<string, string>>(), It.IsAny<Dictionary<string, string>>(), It.IsAny<Variables>(), It.IsAny<string>()))
.Callback((IExecutionContext executionContext, Pipelines.ActionStepDefinitionReference actionReference, IStepHost stepHost, ActionExecutionData data, Dictionary<string, string> inputs, Dictionary<string, string> environment, Variables runtimeVariables, string taskDirectory) =>
{
finialInputs = inputs;
})
.Returns(new Mock<IHandler>().Object);
//Act
await _actionRunner.RunAsync();
foreach (var input in finialInputs)
{
_hc.GetTrace().Info($"Input: {input.Key}={input.Value}");
}
//Assert
Assert.Equal(finialInputs["input1"], "test1");
Assert.Equal(finialInputs["input2"], "test2");
Assert.Equal(finialInputs["input3"], "github");
}
[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<string, string> finialInputs = new Dictionary<string, string>();
_handlerFactory.Setup(x => x.Create(It.IsAny<IExecutionContext>(), It.IsAny<ActionStepDefinitionReference>(), It.IsAny<IStepHost>(), It.IsAny<ActionExecutionData>(), It.IsAny<Dictionary<string, string>>(), It.IsAny<Dictionary<string, string>>(), It.IsAny<Variables>(), It.IsAny<string>()))
.Callback((IExecutionContext executionContext, Pipelines.ActionStepDefinitionReference actionReference, IStepHost stepHost, ActionExecutionData data, Dictionary<string, string> inputs, Dictionary<string, string> environment, Variables runtimeVariables, string taskDirectory) =>
{
finialInputs = inputs;
})
.Returns(new Mock<IHandler>().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);
}
private void CreateAction(string yamlContent, out Pipelines.ActionStep instance, out string directory)
{
directory = Path.Combine(_workFolder, Constants.Path.ActionsDirectory, "GitHub/actions".Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar), "master");
string file = Path.Combine(directory, Constants.Path.ActionManifestFile);
Directory.CreateDirectory(Path.GetDirectoryName(file));
File.WriteAllText(file, yamlContent);
instance = new Pipelines.ActionStep()
{
Id = Guid.NewGuid(),
Reference = new Pipelines.RepositoryPathReference()
{
Name = "GitHub/actions",
Ref = "master",
RepositoryType = Pipelines.RepositoryTypes.GitHub
}
};
}
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<IActionManager>();
_actionManager.Setup(x => x.LoadAction(It.IsAny<IExecutionContext>(), It.IsAny<ActionStep>())).Returns(actionDefinition);
_handlerFactory = new Mock<IHandlerFactory>();
_defaultStepHost = new Mock<IDefaultStepHost>();
_actionManifestManager = new ActionManifestManager();
_actionManifestManager.Initialize(_hc);
var githubContext = new GitHubContext();
githubContext.Add("event", JToken.Parse("{\"foo\":\"bar\"}").ToPipelineContextData());
_context.Add("github", githubContext);
_ec = new Mock<IExecutionContext>();
_ec.Setup(x => x.ExpressionValues).Returns(_context);
_ec.Setup(x => x.IntraActionState).Returns(new Dictionary<string, string>());
_ec.Setup(x => x.EnvironmentVariables).Returns(new Dictionary<string, string>());
_ec.Setup(x => x.SetGitHubContext(It.IsAny<string>(), It.IsAny<string>()));
_ec.Setup(x => x.GetGitHubContext(It.IsAny<string>())).Returns("{\"foo\":\"bar\"}");
_ec.Setup(x => x.CancellationToken).Returns(_ecTokenSource.Token);
_ec.Setup(x => x.Variables).Returns(new Variables(_hc, new Dictionary<string, VariableValue>()));
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); });
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); });
_hc.SetSingleton<IActionManager>(_actionManager.Object);
_hc.SetSingleton<IHandlerFactory>(_handlerFactory.Object);
_hc.SetSingleton<IActionManifestManager>(_actionManifestManager);
_hc.EnqueueInstance<IDefaultStepHost>(_defaultStepHost.Object);
// Instance to test.
_actionRunner = new ActionRunner();
_actionRunner.Initialize(_hc);
_actionRunner.ExecutionContext = _ec.Object;
}
}
}

View File

@@ -0,0 +1,259 @@
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Worker;
using Moq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using Xunit;
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 TaskOrchestrationPlanReference();
TimelineReference timeline = new TimelineReference();
JobEnvironment environment = new JobEnvironment();
environment.SystemConnection = new ServiceEndpoint();
List<TaskInstance> tasks = new List<TaskInstance>();
Guid JobId = Guid.NewGuid();
string jobName = "some job name";
var jobRequest = Pipelines.AgentJobRequestMessageUtil.Convert(new AgentJobRequestMessage(plan, timeline, JobId, jobName, jobName, environment, tasks));
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<IPagingLogger>();
var jobServerQueue = new Mock<IJobServerQueue>();
jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.IsAny<TimelineRecord>()));
hc.EnqueueInstance(pagingLogger.Object);
hc.SetSingleton(jobServerQueue.Object);
var ec = new Runner.Worker.ExecutionContext();
ec.Initialize(hc);
// Act.
ec.InitializeJob(jobRequest, CancellationToken.None);
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Error, Message = "error" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.AddIssue(new Issue() { Type = IssueType.Warning, Message = "warning" });
ec.Complete();
// Assert.
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.ErrorCount == 15)), Times.AtLeastOnce);
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.WarningCount == 14)), Times.AtLeastOnce);
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Type == IssueType.Error).Count() == 10)), Times.AtLeastOnce);
jobServerQueue.Verify(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.Is<TimelineRecord>(t => t.Issues.Where(i => i.Type == IssueType.Warning).Count() == 10)), Times.AtLeastOnce);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Debug_Multilines()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange: Create a job request message.
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
TimelineReference timeline = new TimelineReference();
JobEnvironment environment = new JobEnvironment();
environment.SystemConnection = new ServiceEndpoint();
List<TaskInstance> tasks = new List<TaskInstance>();
Guid JobId = Guid.NewGuid();
string jobName = "some job name";
var jobRequest = Pipelines.AgentJobRequestMessageUtil.Convert(new AgentJobRequestMessage(plan, timeline, JobId, jobName, jobName, environment, tasks));
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<IPagingLogger>();
var jobServerQueue = new Mock<IJobServerQueue>();
jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.IsAny<TimelineRecord>()));
jobServerQueue.Setup(x => x.QueueWebConsoleLine(It.IsAny<Guid>(), It.IsAny<string>())).Callback((Guid id, string msg) => { 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<Guid>(), It.IsAny<string>()), 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 TaskOrchestrationPlanReference();
TimelineReference timeline = new TimelineReference();
JobEnvironment environment = new JobEnvironment();
environment.SystemConnection = new ServiceEndpoint();
List<TaskInstance> tasks = new List<TaskInstance>();
Guid JobId = Guid.NewGuid();
string jobName = "some job name";
var jobRequest = Pipelines.AgentJobRequestMessageUtil.Convert(new AgentJobRequestMessage(plan, timeline, JobId, jobName, jobName, environment, tasks));
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<IPagingLogger>();
var pagingLogger2 = new Mock<IPagingLogger>();
var pagingLogger3 = new Mock<IPagingLogger>();
var pagingLogger4 = new Mock<IPagingLogger>();
var pagingLogger5 = new Mock<IPagingLogger>();
var jobServerQueue = new Mock<IJobServerQueue>();
jobServerQueue.Setup(x => x.QueueTimelineRecordUpdate(It.IsAny<Guid>(), It.IsAny<TimelineRecord>()));
jobServerQueue.Setup(x => x.QueueWebConsoleLine(It.IsAny<Guid>(), It.IsAny<string>())).Callback((Guid id, string msg) => { 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);
action1.IntraActionState["state"] = "1";
var action2 = jobContext.CreateChild(Guid.NewGuid(), "action_2", "action_2", null, null);
action2.IntraActionState["state"] = "2";
action1.RegisterPostJobAction("post1", "always()", new Pipelines.ActionStep() { Name = "post1", DisplayName = "Test 1", Reference = new Pipelines.RepositoryPathReference() { Name = "actions/action" } });
action2.RegisterPostJobAction("post2", "always()", new Pipelines.ActionStep() { Name = "post2", DisplayName = "Test 2", Reference = new Pipelines.RepositoryPathReference() { Name = "actions/action" } });
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"]);
}
}
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
{
var hc = new TestHostContext(this, testName);
// Arrange: Setup the configation store.
var configurationStore = new Mock<IConfigurationStore>();
configurationStore.Setup(x => x.GetSettings()).Returns(new RunnerSettings());
hc.SetSingleton(configurationStore.Object);
// Arrange: Setup the proxy configation.
var proxy = new Mock<IRunnerWebProxy>();
hc.SetSingleton(proxy.Object);
// Arrange: Setup the cert configation.
var cert = new Mock<IRunnerCertificateManager>();
hc.SetSingleton(cert.Object);
// Arrange: Create the execution context.
hc.SetSingleton(new Mock<IJobServerQueue>().Object);
return hc;
}
}
}

View File

@@ -0,0 +1,186 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Worker;
using Moq;
using Xunit;
using GitHub.DistributedTask.Expressions2;
using GitHub.DistributedTask.Pipelines.ContextData;
namespace GitHub.Runner.Common.Tests.Worker
{
public sealed class ExpressionManagerL0
{
private Mock<IExecutionContext> _ec;
private ExpressionManager _expressionManager;
private DictionaryContextData _expressions;
private JobContext _jobContext;
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void AlwaysFunction()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var variableSets = new[]
{
new { JobStatus = (ActionResult?)null, Expected = true },
new { JobStatus = (ActionResult?)ActionResult.Cancelled, Expected = true },
new { JobStatus = (ActionResult?)ActionResult.Failure, Expected = true },
new { JobStatus = (ActionResult?)ActionResult.Success, Expected = true },
};
foreach (var variableSet in variableSets)
{
InitializeExecutionContext(hc);
_jobContext.Status = variableSet.JobStatus;
// Act.
bool actual = _expressionManager.Evaluate(_ec.Object, "always()").Value;
// Assert.
Assert.Equal(variableSet.Expected, actual);
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void CancelledFunction()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var variableSets = new[]
{
new { JobStatus = (ActionResult?)ActionResult.Cancelled, Expected = true },
new { JobStatus = (ActionResult?)null, Expected = false },
new { JobStatus = (ActionResult?)ActionResult.Failure, Expected = false },
new { JobStatus = (ActionResult?)ActionResult.Success, Expected = false },
};
foreach (var variableSet in variableSets)
{
InitializeExecutionContext(hc);
_jobContext.Status = variableSet.JobStatus;
// Act.
bool actual = _expressionManager.Evaluate(_ec.Object, "cancelled()").Value;
// Assert.
Assert.Equal(variableSet.Expected, actual);
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void FailureFunction()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var variableSets = new[]
{
new { JobStatus = (ActionResult?)ActionResult.Failure, Expected = true },
new { JobStatus = (ActionResult?)null, Expected = false },
new { JobStatus = (ActionResult?)ActionResult.Cancelled, Expected = false },
new { JobStatus = (ActionResult?)ActionResult.Success, Expected = false },
};
foreach (var variableSet in variableSets)
{
InitializeExecutionContext(hc);
_jobContext.Status = variableSet.JobStatus;
// Act.
bool actual = _expressionManager.Evaluate(_ec.Object, "failure()").Value;
// Assert.
Assert.Equal(variableSet.Expected, actual);
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void SuccessFunction()
{
using (TestHostContext hc = CreateTestContext())
{
// Arrange.
var variableSets = new[]
{
new { JobStatus = (ActionResult?)null, Expected = true },
new { JobStatus = (ActionResult?)ActionResult.Success, Expected = true },
new { JobStatus = (ActionResult?)ActionResult.Cancelled, Expected = false },
new { JobStatus = (ActionResult?)ActionResult.Failure, Expected = false },
};
foreach (var variableSet in variableSets)
{
InitializeExecutionContext(hc);
_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;
// Assert.
Assert.Equal(variableSet.Expected, actual);
}
}
}
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
{
var hc = new TestHostContext(this, testName);
_expressionManager = new ExpressionManager();
_expressionManager.Initialize(hc);
return hc;
}
private void InitializeExecutionContext(TestHostContext hc)
{
_expressions = new DictionaryContextData();
_jobContext = new JobContext();
_ec = new Mock<IExecutionContext>();
_ec.SetupAllProperties();
_ec.Setup(x => x.ExpressionValues).Returns(_expressions);
_ec.Setup(x => x.JobContext).Returns(_jobContext);
}
}
}

View File

@@ -0,0 +1,795 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using GitHub.Runner.Worker;
using GitHub.Services.WebApi;
using Xunit;
namespace GitHub.Runner.Common.Tests.Worker
{
public sealed class IssueMatcherL0
{
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Config_Validate_Loop_MayNotBeSetOnSinglePattern()
{
var config = JsonUtility.FromString<IssueMatchersConfig>(@"
{
""problemMatcher"": [
{
""owner"": ""myMatcher"",
""pattern"": [
{
""regexp"": ""^error: (.+)$"",
""message"": 1,
""loop"": true
}
]
}
]
}
");
Assert.Throws<ArgumentException>(() => config.Validate());
// Sanity test
config.Matchers[0].Patterns = new[]
{
new IssuePatternConfig
{
Pattern = "^file: (.+)$",
File = 1,
},
config.Matchers[0].Patterns[0],
};
config.Validate();
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Config_Validate_Loop_OnlyAllowedOnLastPattern()
{
var config = JsonUtility.FromString<IssueMatchersConfig>(@"
{
""problemMatcher"": [
{
""owner"": ""myMatcher"",
""pattern"": [
{
""regexp"": ""^(error)$"",
""severity"": 1
},
{
""regexp"": ""^file: (.+)$"",
""file"": 1,
""loop"": true
},
{
""regexp"": ""^error: (.+)$"",
""message"": 1
}
]
}
]
}
");
Assert.Throws<ArgumentException>(() => config.Validate());
// Sanity test
config.Matchers[0].Patterns[1].Loop = false;
config.Matchers[0].Patterns[2].Loop = true;
config.Validate();
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Config_Validate_Loop_MustSetMessage()
{
var config = JsonUtility.FromString<IssueMatchersConfig>(@"
{
""problemMatcher"": [
{
""owner"": ""myMatcher"",
""pattern"": [
{
""regexp"": ""^file: (.+)$"",
""message"": 1
},
{
""regexp"": ""^file: (.+)$"",
""file"": 1,
""loop"": true
}
]
}
]
}
");
Assert.Throws<ArgumentException>(() => config.Validate());
config.Matchers[0].Patterns[1].Loop = false;
config.Validate();
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Config_Validate_Message_AllowedInFirstPattern()
{
var config = JsonUtility.FromString<IssueMatchersConfig>(@"
{
""problemMatcher"": [
{
""owner"": ""myMatcher"",
""pattern"": [
{
""regexp"": ""^file: (.+)$"",
""message"": 1
},
{
""regexp"": ""^error: (.+)$"",
""file"": 1
}
]
}
]
}
");
config.Validate();
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Config_Validate_Message_Required()
{
var config = JsonUtility.FromString<IssueMatchersConfig>(@"
{
""problemMatcher"": [
{
""owner"": ""myMatcher"",
""pattern"": [
{
""regexp"": ""^error: (.+)$"",
""file"": 1
}
]
}
]
}
");
Assert.Throws<ArgumentException>(() => config.Validate());
// Sanity test
config.Matchers[0].Patterns[0].File = null;
config.Matchers[0].Patterns[0].Message = 1;
config.Validate();
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Config_Validate_Owner_Distinct()
{
var config = JsonUtility.FromString<IssueMatchersConfig>(@"
{
""problemMatcher"": [
{
""owner"": ""myMatcher"",
""pattern"": [
{
""regexp"": ""^error: (.+)$"",
""message"": 1
}
]
},
{
""owner"": ""MYmatcher"",
""pattern"": [
{
""regexp"": ""^ERR: (.+)$"",
""message"": 1
}
]
}
]
}
");
Assert.Throws<ArgumentException>(() => config.Validate());
// Sanity test
config.Matchers[0].Owner = "asdf";
config.Validate();
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Config_Validate_Owner_Required()
{
var config = JsonUtility.FromString<IssueMatchersConfig>(@"
{
""problemMatcher"": [
{
""owner"": """",
""pattern"": [
{
""regexp"": ""^error: (.+)$"",
""message"": 1
}
]
}
]
}
");
Assert.Throws<ArgumentException>(() => config.Validate());
// Sanity test
config.Matchers[0].Owner = "asdf";
config.Validate();
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Config_Validate_Pattern_Required()
{
var config = JsonUtility.FromString<IssueMatchersConfig>(@"
{
""problemMatcher"": [
{
""owner"": ""myMatcher"",
""pattern"": [
]
}
]
}
");
Assert.Throws<ArgumentException>(() => config.Validate());
// Sanity test
config.Matchers[0].Patterns = new[]
{
new IssuePatternConfig
{
Pattern = "^error: (.+)$",
Message = 1,
}
};
config.Validate();
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Config_Validate_PropertyMayNotBeSetTwice()
{
var config = JsonUtility.FromString<IssueMatchersConfig>(@"
{
""problemMatcher"": [
{
""owner"": ""myMatcher"",
""pattern"": [
{
""regexp"": ""^severity: (.+)$"",
""file"": 1
},
{
""regexp"": ""^file: (.+)$"",
""file"": 1
},
{
""regexp"": ""^(.+)$"",
""message"": 1
}
]
}
]
}
");
Assert.Throws<ArgumentException>(() => config.Validate());
// Sanity test
config.Matchers[0].Patterns[0].File = null;
config.Matchers[0].Patterns[0].Severity = 1;
config.Validate();
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Config_Validate_PropertyOutOfRange()
{
var config = JsonUtility.FromString<IssueMatchersConfig>(@"
{
""problemMatcher"": [
{
""owner"": ""myMatcher"",
""pattern"": [
{
""regexp"": ""^(.+)$"",
""message"": 2
}
]
}
]
}
");
Assert.Throws<ArgumentException>(() => config.Validate());
// Sanity test
config.Matchers[0].Patterns[0].Message = 1;
config.Validate();
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Config_Validate_PropertyOutOfRange_LessThanZero()
{
var config = JsonUtility.FromString<IssueMatchersConfig>(@"
{
""problemMatcher"": [
{
""owner"": ""myMatcher"",
""pattern"": [
{
""regexp"": ""^(.+)$"",
""message"": -1
}
]
}
]
}
");
Assert.Throws<ArgumentException>(() => config.Validate());
// Sanity test
config.Matchers[0].Patterns[0].Message = 1;
config.Validate();
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Matcher_MultiplePatterns_Loop_AccumulatesStatePerLine()
{
var config = JsonUtility.FromString<IssueMatchersConfig>(@"
{
""problemMatcher"": [
{
""owner"": ""myMatcher"",
""pattern"": [
{
""regexp"": ""^(.+)$"",
""file"": 1
},
{
""regexp"": ""^(.+)$"",
""code"": 1
},
{
""regexp"": ""^message:(.+)$"",
""message"": 1,
""loop"": true
}
]
}
]
}
");
config.Validate();
var matcher = new IssueMatcher(config.Matchers[0], TimeSpan.FromSeconds(1));
var match = matcher.Match("file1");
Assert.Null(match);
match = matcher.Match("code1");
Assert.Null(match);
match = matcher.Match("message:message1");
Assert.Equal("file1", match.File);
Assert.Equal("code1", match.Code);
Assert.Equal("message1", match.Message);
match = matcher.Match("message:message1-2"); // sanity check loop
Assert.Equal("file1", match.File);
Assert.Equal("code1", match.Code);
Assert.Equal("message1-2", match.Message);
match = matcher.Match("abc"); // discarded
match = matcher.Match("file2");
Assert.Null(match);
match = matcher.Match("code2");
Assert.Null(match);
match = matcher.Match("message:message2");
Assert.Equal("file2", match.File);
Assert.Equal("code2", match.Code);
Assert.Equal("message2", match.Message);
match = matcher.Match("abc"); // discarded
match = matcher.Match("abc"); // discarded
match = matcher.Match("file3");
Assert.Null(match);
match = matcher.Match("code3");
Assert.Null(match);
match = matcher.Match("message:message3");
Assert.Equal("file3", match.File);
Assert.Equal("code3", match.Code);
Assert.Equal("message3", match.Message);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Matcher_MultiplePatterns_Loop_BrokenMatchClearsState()
{
var config = JsonUtility.FromString<IssueMatchersConfig>(@"
{
""problemMatcher"": [
{
""owner"": ""myMatcher"",
""pattern"": [
{
""regexp"": ""^(.+)$"",
""file"": 1
},
{
""regexp"": ""^(.+)$"",
""severity"": 1
},
{
""regexp"": ""^message:(.+)$"",
""message"": 1,
""loop"": true
}
]
}
]
}
");
config.Validate();
var matcher = new IssueMatcher(config.Matchers[0], TimeSpan.FromSeconds(1));
var match = matcher.Match("my-file.cs"); // file
Assert.Null(match);
match = matcher.Match("real-bad"); // severity
Assert.Null(match);
match = matcher.Match("message:not-working"); // message
Assert.Equal("my-file.cs", match.File);
Assert.Equal("real-bad", match.Severity);
Assert.Equal("not-working", match.Message);
match = matcher.Match("message:problem"); // message
Assert.Equal("my-file.cs", match.File);
Assert.Equal("real-bad", match.Severity);
Assert.Equal("problem", match.Message);
match = matcher.Match("other-file.cs"); // file - breaks the loop
Assert.Null(match);
match = matcher.Match("message:not-good"); // severity - also matches the message pattern, therefore
Assert.Null(match); // guarantees sufficient previous state has been cleared
match = matcher.Match("message:broken"); // message
Assert.Equal("other-file.cs", match.File);
Assert.Equal("message:not-good", match.Severity);
Assert.Equal("broken", match.Message);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Matcher_MultiplePatterns_Loop_ExtractsProperties()
{
var config = JsonUtility.FromString<IssueMatchersConfig>(@"
{
""problemMatcher"": [
{
""owner"": ""myMatcher"",
""pattern"": [
{
""regexp"": ""^file:(.+) fromPath:(.+)$"",
""file"": 1,
""fromPath"": 2
},
{
""regexp"": ""^severity:(.+)$"",
""severity"": 1
},
{
""regexp"": ""^line:(.+) column:(.+) code:(.+) message:(.+)$"",
""line"": 1,
""column"": 2,
""code"": 3,
""message"": 4,
""loop"": true
}
]
}
]
}
");
config.Validate();
var matcher = new IssueMatcher(config.Matchers[0], TimeSpan.FromSeconds(1));
var match = matcher.Match("file:my-file.cs fromPath:my-project.proj");
Assert.Null(match);
match = matcher.Match("severity:real-bad");
Assert.Null(match);
match = matcher.Match("line:123 column:45 code:uh-oh message:not-working");
Assert.Equal("my-file.cs", match.File);
Assert.Equal("my-project.proj", match.FromPath);
Assert.Equal("real-bad", match.Severity);
Assert.Equal("123", match.Line);
Assert.Equal("45", match.Column);
Assert.Equal("uh-oh", match.Code);
Assert.Equal("not-working", match.Message);
match = matcher.Match("line:234 column:56 code:yikes message:broken");
Assert.Equal("my-file.cs", match.File);
Assert.Equal("my-project.proj", match.FromPath);
Assert.Equal("real-bad", match.Severity);
Assert.Equal("234", match.Line);
Assert.Equal("56", match.Column);
Assert.Equal("yikes", match.Code);
Assert.Equal("broken", match.Message);
match = matcher.Match("line:345 column:67 code:failed message:cant-do-that");
Assert.Equal("my-file.cs", match.File);
Assert.Equal("my-project.proj", match.FromPath);
Assert.Equal("real-bad", match.Severity);
Assert.Equal("345", match.Line);
Assert.Equal("67", match.Column);
Assert.Equal("failed", match.Code);
Assert.Equal("cant-do-that", match.Message);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Matcher_MultiplePatterns_NonLoop_AccumulatesStatePerLine()
{
var config = JsonUtility.FromString<IssueMatchersConfig>(@"
{
""problemMatcher"": [
{
""owner"": ""myMatcher"",
""pattern"": [
{
""regexp"": ""^(.+)$"",
""file"": 1
},
{
""regexp"": ""^(.+)$"",
""code"": 1
},
{
""regexp"": ""^message:(.+)$"",
""message"": 1
}
]
}
]
}
");
config.Validate();
var matcher = new IssueMatcher(config.Matchers[0], TimeSpan.FromSeconds(1));
var match = matcher.Match("file1");
Assert.Null(match);
match = matcher.Match("code1");
Assert.Null(match);
match = matcher.Match("message:message1");
Assert.Equal("file1", match.File);
Assert.Equal("code1", match.Code);
Assert.Equal("message1", match.Message);
match = matcher.Match("abc"); // discarded
match = matcher.Match("file2");
Assert.Null(match);
match = matcher.Match("code2");
Assert.Null(match);
match = matcher.Match("message:message2");
Assert.Equal("file2", match.File);
Assert.Equal("code2", match.Code);
Assert.Equal("message2", match.Message);
match = matcher.Match("abc"); // discarded
match = matcher.Match("abc"); // discarded
match = matcher.Match("file3");
Assert.Null(match);
match = matcher.Match("code3");
Assert.Null(match);
match = matcher.Match("message:message3");
Assert.Equal("file3", match.File);
Assert.Equal("code3", match.Code);
Assert.Equal("message3", match.Message);
match = matcher.Match("message:message3"); // sanity check not loop
Assert.Null(match);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Matcher_MultiplePatterns_NonLoop_DoesNotLoop()
{
var config = JsonUtility.FromString<IssueMatchersConfig>(@"
{
""problemMatcher"": [
{
""owner"": ""myMatcher"",
""pattern"": [
{
""regexp"": ""^file:(.+)$"",
""file"": 1
},
{
""regexp"": ""^message:(.+)$"",
""message"": 1
}
]
}
]
}
");
config.Validate();
var matcher = new IssueMatcher(config.Matchers[0], TimeSpan.FromSeconds(1));
var match = matcher.Match("file:my-file.cs");
Assert.Null(match);
match = matcher.Match("message:not-working");
Assert.Equal("my-file.cs", match.File);
Assert.Equal("not-working", match.Message);
match = matcher.Match("message:not-working");
Assert.Null(match);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Matcher_MultiplePatterns_NonLoop_ExtractsProperties()
{
var config = JsonUtility.FromString<IssueMatchersConfig>(@"
{
""problemMatcher"": [
{
""owner"": ""myMatcher"",
""pattern"": [
{
""regexp"": ""^file:(.+) fromPath:(.+)$"",
""file"": 1,
""fromPath"": 2
},
{
""regexp"": ""^severity:(.+)$"",
""severity"": 1
},
{
""regexp"": ""^line:(.+) column:(.+) code:(.+) message:(.+)$"",
""line"": 1,
""column"": 2,
""code"": 3,
""message"": 4
}
]
}
]
}
");
config.Validate();
var matcher = new IssueMatcher(config.Matchers[0], TimeSpan.FromSeconds(1));
var match = matcher.Match("file:my-file.cs fromPath:my-project.proj");
Assert.Null(match);
match = matcher.Match("severity:real-bad");
Assert.Null(match);
match = matcher.Match("line:123 column:45 code:uh-oh message:not-working");
Assert.Equal("my-file.cs", match.File);
Assert.Equal("my-project.proj", match.FromPath);
Assert.Equal("real-bad", match.Severity);
Assert.Equal("123", match.Line);
Assert.Equal("45", match.Column);
Assert.Equal("uh-oh", match.Code);
Assert.Equal("not-working", match.Message);
match = matcher.Match("line:123 column:45 code:uh-oh message:not-working"); // sanity check not loop
Assert.Null(match);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Matcher_MultiplePatterns_NonLoop_MatchClearsState()
{
var config = JsonUtility.FromString<IssueMatchersConfig>(@"
{
""problemMatcher"": [
{
""owner"": ""myMatcher"",
""pattern"": [
{
""regexp"": ""^(.+)$"",
""file"": 1
},
{
""regexp"": ""^(.+)$"",
""severity"": 1
},
{
""regexp"": ""^(.+)$"",
""message"": 1
}
]
}
]
}
");
config.Validate();
var matcher = new IssueMatcher(config.Matchers[0], TimeSpan.FromSeconds(1));
var match = matcher.Match("my-file.cs"); // file
Assert.Null(match);
match = matcher.Match("real-bad"); // severity
Assert.Null(match);
match = matcher.Match("not-working"); // message
Assert.Equal("my-file.cs", match.File);
Assert.Equal("real-bad", match.Severity);
Assert.Equal("not-working", match.Message);
match = matcher.Match("other-file.cs"); // file
Assert.Null(match);
match = matcher.Match("not-good"); // severity
Assert.Null(match);
match = matcher.Match("broken"); // message
Assert.Equal("other-file.cs", match.File);
Assert.Equal("not-good", match.Severity);
Assert.Equal("broken", match.Message);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Matcher_SetsOwner()
{
var config = JsonUtility.FromString<IssueMatchersConfig>(@"
{
""problemMatcher"": [
{
""owner"": ""myMatcher"",
""pattern"": [
{
""regexp"": ""^(.+)$"",
""message"": 1
}
]
}
]
}
");
config.Validate();
var matcher = new IssueMatcher(config.Matchers[0], TimeSpan.FromSeconds(1));
Assert.Equal("myMatcher", matcher.Owner);
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Matcher_SinglePattern_ExtractsProperties()
{
var config = JsonUtility.FromString<IssueMatchersConfig>(@"
{
""problemMatcher"": [
{
""owner"": ""myMatcher"",
""pattern"": [
{
""regexp"": ""^file:(.+) line:(.+) column:(.+) severity:(.+) code:(.+) message:(.+) fromPath:(.+)$"",
""file"": 1,
""line"": 2,
""column"": 3,
""severity"": 4,
""code"": 5,
""message"": 6,
""fromPath"": 7
}
]
}
]
}
");
config.Validate();
var matcher = new IssueMatcher(config.Matchers[0], TimeSpan.FromSeconds(1));
var match = matcher.Match("file:my-file.cs line:123 column:45 severity:real-bad code:uh-oh message:not-working fromPath:my-project.proj");
Assert.Equal("my-file.cs", match.File);
Assert.Equal("123", match.Line);
Assert.Equal("45", match.Column);
Assert.Equal("real-bad", match.Severity);
Assert.Equal("uh-oh", match.Code);
Assert.Equal("not-working", match.Message);
Assert.Equal("my-project.proj", match.FromPath);
}
}
}

View File

@@ -0,0 +1,386 @@
// using GitHub.DistributedTask.WebApi;
// using GitHub.Runner.Worker;
// using Moq;
// using System;
// using System.Collections.Generic;
// using System.Linq;
// using System.Runtime.CompilerServices;
// using System.Threading.Tasks;
// using Xunit;
// using System.Threading;
// using Pipelines = GitHub.DistributedTask.Pipelines;
// namespace GitHub.Runner.Common.Tests.Worker
// {
// public sealed class JobExtensionL0
// {
// private class TestJobExtension : JobExtension
// {
// public override HostTypes HostType => HostTypes.None;
// public override Type ExtensionType => typeof(IJobExtension);
// public override void ConvertLocalPath(IExecutionContext context, string localPath, out string repoName, out string sourcePath)
// {
// repoName = "";
// sourcePath = "";
// }
// public override IStep GetExtensionPostJobStep(IExecutionContext jobContext)
// {
// return null;
// }
// public override IStep GetExtensionPreJobStep(IExecutionContext jobContext)
// {
// return null;
// }
// public override string GetRootedPath(IExecutionContext context, string path)
// {
// return path;
// }
// public override void InitializeJobExtension(IExecutionContext context, IList<Pipelines.JobStep> steps, Pipelines.WorkspaceOptions workspace)
// {
// return;
// }
// }
// private IExecutionContext _jobEc;
// private Pipelines.AgentJobRequestMessage _message;
// private Mock<ITaskManager> _taskManager;
// private Mock<IAgentLogPlugin> _logPlugin;
// private Mock<IJobServerQueue> _jobServerQueue;
// private Mock<IVstsAgentWebProxy> _proxy;
// private Mock<IAgentCertificateManager> _cert;
// private Mock<IConfigurationStore> _config;
// private Mock<IPagingLogger> _logger;
// private Mock<IExpressionManager> _express;
// private Mock<IContainerOperationProvider> _containerProvider;
// private CancellationTokenSource _tokenSource;
// private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
// {
// var hc = new TestHostContext(this, testName);
// _jobEc = new Runner.Worker.ExecutionContext();
// _taskManager = new Mock<ITaskManager>();
// _jobServerQueue = new Mock<IJobServerQueue>();
// _config = new Mock<IConfigurationStore>();
// _logger = new Mock<IPagingLogger>();
// _proxy = new Mock<IVstsAgentWebProxy>();
// _cert = new Mock<IAgentCertificateManager>();
// _express = new Mock<IExpressionManager>();
// _containerProvider = new Mock<IContainerOperationProvider>();
// _logPlugin = new Mock<IAgentLogPlugin>();
// TaskRunner step1 = new TaskRunner();
// TaskRunner step2 = new TaskRunner();
// TaskRunner step3 = new TaskRunner();
// TaskRunner step4 = new TaskRunner();
// TaskRunner step5 = new TaskRunner();
// TaskRunner step6 = new TaskRunner();
// TaskRunner step7 = new TaskRunner();
// TaskRunner step8 = new TaskRunner();
// TaskRunner step9 = new TaskRunner();
// TaskRunner step10 = new TaskRunner();
// TaskRunner step11 = new TaskRunner();
// TaskRunner step12 = new TaskRunner();
// _logger.Setup(x => x.Setup(It.IsAny<Guid>(), It.IsAny<Guid>()));
// var settings = new AgentSettings
// {
// AgentId = 1,
// AgentName = "agent1",
// ServerUrl = "https://test.visualstudio.com",
// WorkFolder = "_work",
// };
// _config.Setup(x => x.GetSettings())
// .Returns(settings);
// _proxy.Setup(x => x.ProxyAddress)
// .Returns(string.Empty);
// if (_tokenSource != null)
// {
// _tokenSource.Dispose();
// _tokenSource = null;
// }
// _tokenSource = new CancellationTokenSource();
// TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
// TimelineReference timeline = new Timeline(Guid.NewGuid());
// JobEnvironment environment = new JobEnvironment();
// environment.Variables[Constants.Variables.System.Culture] = "en-US";
// environment.SystemConnection = new ServiceEndpoint()
// {
// Name = WellKnownServiceEndpointNames.SystemVssConnection,
// Url = new Uri("https://test.visualstudio.com"),
// Authorization = new EndpointAuthorization()
// {
// Scheme = "Test",
// }
// };
// environment.SystemConnection.Authorization.Parameters["AccessToken"] = "token";
// List<TaskInstance> tasks = new List<TaskInstance>()
// {
// new TaskInstance()
// {
// InstanceId = Guid.NewGuid(),
// DisplayName = "task1",
// },
// new TaskInstance()
// {
// InstanceId = Guid.NewGuid(),
// DisplayName = "task2",
// },
// new TaskInstance()
// {
// InstanceId = Guid.NewGuid(),
// DisplayName = "task3",
// },
// new TaskInstance()
// {
// InstanceId = Guid.NewGuid(),
// DisplayName = "task4",
// },
// new TaskInstance()
// {
// InstanceId = Guid.NewGuid(),
// DisplayName = "task5",
// },
// new TaskInstance()
// {
// InstanceId = Guid.NewGuid(),
// DisplayName = "task6",
// },
// new TaskInstance()
// {
// InstanceId = Guid.NewGuid(),
// DisplayName = "task7",
// },
// };
// Guid JobId = Guid.NewGuid();
// _message = Pipelines.AgentJobRequestMessageUtil.Convert(new AgentJobRequestMessage(plan, timeline, JobId, testName, testName, environment, tasks));
// _taskManager.Setup(x => x.DownloadAsync(It.IsAny<IExecutionContext>(), It.IsAny<IEnumerable<Pipelines.TaskStep>>()))
// .Returns(Task.CompletedTask);
// _taskManager.Setup(x => x.Load(It.Is<Pipelines.TaskStep>(t => t.DisplayName == "task1")))
// .Returns(new Definition()
// {
// Data = new DefinitionData()
// {
// PreJobExecution = null,
// Execution = new ExecutionData(),
// PostJobExecution = null,
// },
// });
// _taskManager.Setup(x => x.Load(It.Is<Pipelines.TaskStep>(t => t.DisplayName == "task2")))
// .Returns(new Definition()
// {
// Data = new DefinitionData()
// {
// PreJobExecution = new ExecutionData(),
// Execution = new ExecutionData(),
// PostJobExecution = new ExecutionData(),
// },
// });
// _taskManager.Setup(x => x.Load(It.Is<Pipelines.TaskStep>(t => t.DisplayName == "task3")))
// .Returns(new Definition()
// {
// Data = new DefinitionData()
// {
// PreJobExecution = new ExecutionData(),
// Execution = null,
// PostJobExecution = new ExecutionData(),
// },
// });
// _taskManager.Setup(x => x.Load(It.Is<Pipelines.TaskStep>(t => t.DisplayName == "task4")))
// .Returns(new Definition()
// {
// Data = new DefinitionData()
// {
// PreJobExecution = new ExecutionData(),
// Execution = null,
// PostJobExecution = null,
// },
// });
// _taskManager.Setup(x => x.Load(It.Is<Pipelines.TaskStep>(t => t.DisplayName == "task5")))
// .Returns(new Definition()
// {
// Data = new DefinitionData()
// {
// PreJobExecution = null,
// Execution = null,
// PostJobExecution = new ExecutionData(),
// },
// });
// _taskManager.Setup(x => x.Load(It.Is<Pipelines.TaskStep>(t => t.DisplayName == "task6")))
// .Returns(new Definition()
// {
// Data = new DefinitionData()
// {
// PreJobExecution = new ExecutionData(),
// Execution = new ExecutionData(),
// PostJobExecution = null,
// },
// });
// _taskManager.Setup(x => x.Load(It.Is<Pipelines.TaskStep>(t => t.DisplayName == "task7")))
// .Returns(new Definition()
// {
// Data = new DefinitionData()
// {
// PreJobExecution = null,
// Execution = new ExecutionData(),
// PostJobExecution = new ExecutionData(),
// },
// });
// hc.SetSingleton(_taskManager.Object);
// hc.SetSingleton(_config.Object);
// hc.SetSingleton(_jobServerQueue.Object);
// hc.SetSingleton(_proxy.Object);
// hc.SetSingleton(_cert.Object);
// hc.SetSingleton(_express.Object);
// hc.SetSingleton(_containerProvider.Object);
// hc.SetSingleton(_logPlugin.Object);
// hc.EnqueueInstance<IPagingLogger>(_logger.Object); // jobcontext logger
// hc.EnqueueInstance<IPagingLogger>(_logger.Object); // init step logger
// hc.EnqueueInstance<IPagingLogger>(_logger.Object); // step 1
// hc.EnqueueInstance<IPagingLogger>(_logger.Object);
// hc.EnqueueInstance<IPagingLogger>(_logger.Object);
// hc.EnqueueInstance<IPagingLogger>(_logger.Object);
// hc.EnqueueInstance<IPagingLogger>(_logger.Object);
// hc.EnqueueInstance<IPagingLogger>(_logger.Object);
// hc.EnqueueInstance<IPagingLogger>(_logger.Object);
// hc.EnqueueInstance<IPagingLogger>(_logger.Object);
// hc.EnqueueInstance<IPagingLogger>(_logger.Object);
// hc.EnqueueInstance<IPagingLogger>(_logger.Object);
// hc.EnqueueInstance<IPagingLogger>(_logger.Object);
// hc.EnqueueInstance<IPagingLogger>(_logger.Object); // step 12
// hc.EnqueueInstance<ITaskRunner>(step1);
// hc.EnqueueInstance<ITaskRunner>(step2);
// hc.EnqueueInstance<ITaskRunner>(step3);
// hc.EnqueueInstance<ITaskRunner>(step4);
// hc.EnqueueInstance<ITaskRunner>(step5);
// hc.EnqueueInstance<ITaskRunner>(step6);
// hc.EnqueueInstance<ITaskRunner>(step7);
// hc.EnqueueInstance<ITaskRunner>(step8);
// hc.EnqueueInstance<ITaskRunner>(step9);
// hc.EnqueueInstance<ITaskRunner>(step10);
// hc.EnqueueInstance<ITaskRunner>(step11);
// hc.EnqueueInstance<ITaskRunner>(step12);
// _jobEc.Initialize(hc);
// _jobEc.InitializeJob(_message, _tokenSource.Token);
// return hc;
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Worker")]
// public async Task JobExtensioBuildStepsList()
// {
// using (TestHostContext hc = CreateTestContext())
// {
// TestJobExtension testExtension = new TestJobExtension();
// testExtension.Initialize(hc);
// List<IStep> result = await testExtension.InitializeJob(_jobEc, _message);
// var trace = hc.GetTrace();
// trace.Info(string.Join(", ", result.Select(x => x.DisplayName)));
// Assert.Equal(12, result.Count);
// Assert.Equal("task2", result[0].DisplayName);
// Assert.Equal("task3", result[1].DisplayName);
// Assert.Equal("task4", result[2].DisplayName);
// Assert.Equal("task6", result[3].DisplayName);
// Assert.Equal("task1", result[4].DisplayName);
// Assert.Equal("task2", result[5].DisplayName);
// Assert.Equal("task6", result[6].DisplayName);
// Assert.Equal("task7", result[7].DisplayName);
// Assert.Equal("task7", result[8].DisplayName);
// Assert.Equal("task5", result[9].DisplayName);
// Assert.Equal("task3", result[10].DisplayName);
// Assert.Equal("task2", result[11].DisplayName);
// }
// }
// // [Fact]
// // [Trait("Level", "L0")]
// // [Trait("Category", "Worker")]
// // public async Task JobExtensionIntraTaskState()
// // {
// // using (TestHostContext hc = CreateTestContext())
// // {
// // TestJobExtension testExtension = new TestJobExtension();
// // testExtension.Initialize(hc);
// // List<IStep> result = await testExtension.InitializeJob(_jobEc, _message);
// // var trace = hc.GetTrace();
// // trace.Info(string.Join(", ", result.Select(x => x.DisplayName)));
// // Assert.Equal(12, result.Count);
// // result[0].ExecutionContext.TaskVariables.Set("state1", "value1", false);
// // Assert.Equal("value1", result[5].ExecutionContext.TaskVariables.Get("state1"));
// // Assert.Equal("value1", result[11].ExecutionContext.TaskVariables.Get("state1"));
// // Assert.Null(result[4].ExecutionContext.TaskVariables.Get("state1"));
// // Assert.Null(result[1].ExecutionContext.TaskVariables.Get("state1"));
// // Assert.Null(result[2].ExecutionContext.TaskVariables.Get("state1"));
// // Assert.Null(result[10].ExecutionContext.TaskVariables.Get("state1"));
// // Assert.Null(result[6].ExecutionContext.TaskVariables.Get("state1"));
// // Assert.Null(result[7].ExecutionContext.TaskVariables.Get("state1"));
// // }
// // }
// #if OS_WINDOWS
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Worker")]
// public async Task JobExtensionManagementScriptStep()
// {
// using (TestHostContext hc = CreateTestContext())
// {
// hc.EnqueueInstance<IPagingLogger>(_logger.Object);
// hc.EnqueueInstance<IPagingLogger>(_logger.Object);
// Environment.SetEnvironmentVariable("VSTS_AGENT_INIT_INTERNAL_TEMP_HACK", "C:\\init.ps1");
// Environment.SetEnvironmentVariable("VSTS_AGENT_CLEANUP_INTERNAL_TEMP_HACK", "C:\\clenup.ps1");
// try
// {
// TestJobExtension testExtension = new TestJobExtension();
// testExtension.Initialize(hc);
// List<IStep> result = await testExtension.InitializeJob(_jobEc, _message);
// var trace = hc.GetTrace();
// trace.Info(string.Join(", ", result.Select(x => x.DisplayName)));
// Assert.Equal(14, result.Count);
// Assert.True(result[0] is ManagementScriptStep);
// Assert.True(result[13] is ManagementScriptStep);
// Assert.Equal(result[0].DisplayName, "Agent Initialization");
// Assert.Equal(result[13].DisplayName, "Agent Cleanup");
// }
// finally
// {
// Environment.SetEnvironmentVariable("VSTS_AGENT_INIT_INTERNAL_TEMP_HACK", "");
// Environment.SetEnvironmentVariable("VSTS_AGENT_CLEANUP_INTERNAL_TEMP_HACK", "");
// }
// }
// }
// #endif
// }
// }

View File

@@ -0,0 +1,223 @@
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Worker;
using Moq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xunit;
using System.Threading;
using System.Collections.ObjectModel;
using Pipelines = GitHub.DistributedTask.Pipelines;
namespace GitHub.Runner.Common.Tests.Worker
{
public sealed class JobRunnerL0
{
private IExecutionContext _jobEc;
private JobRunner _jobRunner;
private List<IStep> _initResult = new List<IStep>();
private Pipelines.AgentJobRequestMessage _message;
private CancellationTokenSource _tokenSource;
private Mock<IJobServer> _jobServer;
private Mock<IJobServerQueue> _jobServerQueue;
private Mock<IRunnerWebProxy> _proxyConfig;
private Mock<IRunnerCertificateManager> _cert;
private Mock<IConfigurationStore> _config;
private Mock<IExtensionManager> _extensions;
private Mock<IStepsRunner> _stepRunner;
private Mock<IJobExtension> _jobExtension;
private Mock<IPagingLogger> _logger;
private Mock<ITempDirectoryManager> _temp;
private Mock<IDiagnosticLogManager> _diagnosticLogManager;
private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
{
var hc = new TestHostContext(this, testName);
_jobEc = new Runner.Worker.ExecutionContext();
_config = new Mock<IConfigurationStore>();
_extensions = new Mock<IExtensionManager>();
_jobExtension = new Mock<IJobExtension>();
_jobServer = new Mock<IJobServer>();
_jobServerQueue = new Mock<IJobServerQueue>();
_proxyConfig = new Mock<IRunnerWebProxy>();
_cert = new Mock<IRunnerCertificateManager>();
_stepRunner = new Mock<IStepsRunner>();
_logger = new Mock<IPagingLogger>();
_temp = new Mock<ITempDirectoryManager>();
_diagnosticLogManager = new Mock<IDiagnosticLogManager>();
if (_tokenSource != null)
{
_tokenSource.Dispose();
_tokenSource = null;
}
_tokenSource = new CancellationTokenSource();
var expressionManager = new ExpressionManager();
expressionManager.Initialize(hc);
hc.SetSingleton<IExpressionManager>(expressionManager);
_jobRunner = new JobRunner();
_jobRunner.Initialize(hc);
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference();
TimelineReference timeline = new Timeline(Guid.NewGuid());
JobEnvironment environment = new JobEnvironment();
environment.Variables[Constants.Variables.System.Culture] = "en-US";
environment.SystemConnection = new ServiceEndpoint()
{
Name = WellKnownServiceEndpointNames.SystemVssConnection,
Url = new Uri("https://test.visualstudio.com"),
Authorization = new EndpointAuthorization()
{
Scheme = "Test",
}
};
environment.SystemConnection.Authorization.Parameters["AccessToken"] = "token";
List<TaskInstance> tasks = new List<TaskInstance>();
Guid JobId = Guid.NewGuid();
_message = Pipelines.AgentJobRequestMessageUtil.Convert(new AgentJobRequestMessage(plan, timeline, JobId, testName, testName, environment, tasks));
_message.Resources.Repositories.Add(new Pipelines.RepositoryResource()
{
Alias = Pipelines.PipelineConstants.SelfAlias,
Id = "github",
Version = "sha1"
});
_message.ContextData.Add("github", new Pipelines.ContextData.DictionaryContextData());
_initResult.Clear();
_jobExtension.Setup(x => x.InitializeJob(It.IsAny<IExecutionContext>(), It.IsAny<Pipelines.AgentJobRequestMessage>())).
Returns(Task.FromResult(_initResult));
_proxyConfig.Setup(x => x.ProxyAddress)
.Returns(string.Empty);
var settings = new RunnerSettings
{
AgentId = 1,
AgentName = "agent1",
ServerUrl = "https://test.visualstudio.com",
WorkFolder = "_work",
};
_config.Setup(x => x.GetSettings())
.Returns(settings);
_logger.Setup(x => x.Setup(It.IsAny<Guid>(), It.IsAny<Guid>()));
hc.SetSingleton(_config.Object);
hc.SetSingleton(_jobServer.Object);
hc.SetSingleton(_jobServerQueue.Object);
hc.SetSingleton(_proxyConfig.Object);
hc.SetSingleton(_cert.Object);
hc.SetSingleton(_stepRunner.Object);
hc.SetSingleton(_extensions.Object);
hc.SetSingleton(_temp.Object);
hc.SetSingleton(_diagnosticLogManager.Object);
hc.EnqueueInstance<IExecutionContext>(_jobEc);
hc.EnqueueInstance<IPagingLogger>(_logger.Object);
hc.EnqueueInstance<IJobExtension>(_jobExtension.Object);
return hc;
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public async Task JobExtensionInitializeFailure()
{
using (TestHostContext hc = CreateTestContext())
{
_jobExtension.Setup(x => x.InitializeJob(It.IsAny<IExecutionContext>(), It.IsAny<Pipelines.AgentJobRequestMessage>()))
.Throws(new Exception());
await _jobRunner.RunAsync(_message, _tokenSource.Token);
Assert.Equal(TaskResult.Failed, _jobEc.Result);
_stepRunner.Verify(x => x.RunAsync(It.IsAny<IExecutionContext>()), Times.Never);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public async Task JobExtensionInitializeCancelled()
{
using (TestHostContext hc = CreateTestContext())
{
_jobExtension.Setup(x => x.InitializeJob(It.IsAny<IExecutionContext>(), It.IsAny<Pipelines.AgentJobRequestMessage>()))
.Throws(new OperationCanceledException());
_tokenSource.Cancel();
await _jobRunner.RunAsync(_message, _tokenSource.Token);
Assert.Equal(TaskResult.Canceled, _jobEc.Result);
_stepRunner.Verify(x => x.RunAsync(It.IsAny<IExecutionContext>()), Times.Never);
}
}
// TODO: Move these tests over to JobExtensionL0.cs
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Worker")]
// public async Task UploadDiganosticLogIfEnvironmentVariableSet()
// {
// using (TestHostContext hc = CreateTestContext())
// {
// _message.Variables[Constants.Variables.Actions.RunnerDebug] = "true";
// await _jobRunner.RunAsync(_message, _tokenSource.Token);
// _diagnosticLogManager.Verify(x =>
// x.UploadDiagnosticLogsAsync(
// It.IsAny<IExecutionContext>(),
// It.IsAny<Pipelines.AgentJobRequestMessage>(),
// It.IsAny<DateTime>()),
// Times.Once);
// }
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Worker")]
// public async Task DontUploadDiagnosticLogIfEnvironmentVariableFalse()
// {
// using (TestHostContext hc = CreateTestContext())
// {
// _message.Variables[Constants.Variables.Actions.RunnerDebug] = "false";
// await _jobRunner.RunAsync(_message, _tokenSource.Token);
// _diagnosticLogManager.Verify(x =>
// x.UploadDiagnosticLogsAsync(
// It.IsAny<IExecutionContext>(),
// It.IsAny<Pipelines.AgentJobRequestMessage>(),
// It.IsAny<DateTime>()),
// Times.Never);
// }
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Worker")]
// public async Task DontUploadDiagnosticLogIfEnvironmentVariableMissing()
// {
// using (TestHostContext hc = CreateTestContext())
// {
// await _jobRunner.RunAsync(_message, _tokenSource.Token);
// _diagnosticLogManager.Verify(x =>
// x.UploadDiagnosticLogsAsync(
// It.IsAny<IExecutionContext>(),
// It.IsAny<Pipelines.AgentJobRequestMessage>(),
// It.IsAny<DateTime>()),
// Times.Never);
// }
// }
}
}

View File

@@ -0,0 +1,728 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using GitHub.Runner.Sdk;
using GitHub.Runner.Worker;
using GitHub.Runner.Worker.Handlers;
using Moq;
using Xunit;
using DTWebApi = GitHub.DistributedTask.WebApi;
namespace GitHub.Runner.Common.Tests.Worker
{
public sealed class OutputManagerL0
{
private Mock<IExecutionContext> _executionContext;
private Mock<IActionCommandManager> _commandManager;
private Variables _variables;
private OnMatcherChanged _onMatcherChanged;
private List<Tuple<DTWebApi.Issue, string>> _issues;
private List<string> _messages;
private List<string> _commands;
private OutputManager _outputManager;
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void AddMatcher_Clobber()
{
var matchers = new IssueMatchersConfig
{
Matchers =
{
new IssueMatcherConfig
{
Owner = "my-matcher-1",
Patterns = new[]
{
new IssuePatternConfig
{
Pattern = "ERROR: (.+)",
Message = 1,
},
},
},
new IssueMatcherConfig
{
Owner = "my-matcher-2",
Patterns = new[]
{
new IssuePatternConfig
{
Pattern = "NOT GOOD: (.+)",
Message = 1,
},
},
},
},
};
using (Setup(matchers: matchers))
using (_outputManager)
{
Process("ERROR: message 1");
Process("NOT GOOD: message 2");
Add(new IssueMatcherConfig
{
Owner = "my-matcher-1",
Patterns = new[]
{
new IssuePatternConfig
{
Pattern = "ERROR: (.+) END MESSAGE",
Message = 1,
},
},
});
Process("ERROR: message 3 END MESSAGE");
Process("ERROR: message 4");
Process("NOT GOOD: message 5");
Assert.Equal(4, _issues.Count);
Assert.Equal("message 1", _issues[0].Item1.Message);
Assert.Equal("message 2", _issues[1].Item1.Message);
Assert.Equal("message 3", _issues[2].Item1.Message);
Assert.Equal("message 5", _issues[3].Item1.Message);
Assert.Equal(0, _commands.Count);
Assert.Equal(1, _messages.Count);
Assert.Equal("ERROR: message 4", _messages[0]);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void AddMatcher_Prepend()
{
var matchers = new IssueMatchersConfig
{
Matchers =
{
new IssueMatcherConfig
{
Owner = "my-matcher-1",
Patterns = new[]
{
new IssuePatternConfig
{
Pattern = "ERROR: (.+)",
Message = 1,
},
},
},
new IssueMatcherConfig
{
Owner = "my-matcher-2",
Patterns = new[]
{
new IssuePatternConfig
{
Pattern = "NOT GOOD: (.+)",
Message = 1,
},
},
},
},
};
using (Setup(matchers: matchers))
using (_outputManager)
{
Process("ERROR: message 1");
Process("NOT GOOD: message 2");
Add(new IssueMatcherConfig
{
Owner = "new-matcher",
Patterns = new[]
{
new IssuePatternConfig
{
Pattern = "ERROR: (.+) END MESSAGE",
Message = 1,
},
},
});
Process("ERROR: message 3 END MESSAGE");
Process("ERROR: message 4");
Process("NOT GOOD: message 5");
Assert.Equal(5, _issues.Count);
Assert.Equal("message 1", _issues[0].Item1.Message);
Assert.Equal("message 2", _issues[1].Item1.Message);
Assert.Equal("message 3", _issues[2].Item1.Message);
Assert.Equal("message 4", _issues[3].Item1.Message);
Assert.Equal("message 5", _issues[4].Item1.Message);
Assert.Equal(0, _commands.Count);
Assert.Equal(0, _messages.Count);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Code()
{
var matchers = new IssueMatchersConfig
{
Matchers =
{
new IssueMatcherConfig
{
Owner = "my-matcher-1",
Patterns = new[]
{
new IssuePatternConfig
{
Pattern = @"(.*): (.+)",
Code = 1,
Message = 2,
},
},
},
},
};
using (Setup(matchers: matchers))
using (_outputManager)
{
Process("BAD: real bad");
Process(": not working");
Assert.Equal(2, _issues.Count);
Assert.Equal("real bad", _issues[0].Item1.Message);
Assert.Equal("BAD", _issues[0].Item1.Data["code"]);
Assert.Equal("not working", _issues[1].Item1.Message);
Assert.False(_issues[1].Item1.Data.ContainsKey("code"));
Assert.Equal(0, _commands.Count);
Assert.Equal(0, _messages.Count);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void DoesNotResetMatchingMatcher()
{
var matchers = new IssueMatchersConfig
{
Matchers =
{
new IssueMatcherConfig
{
Owner = "my-matcher-1",
Patterns = new[]
{
new IssuePatternConfig
{
Pattern = "Start: .+",
},
new IssuePatternConfig
{
Pattern = "Error: (.+)",
Message = 1,
Loop = true,
},
},
},
},
};
using (Setup(matchers: matchers))
using (_outputManager)
{
Process("Start: hello");
Process("Error: it broke");
Process("Error: oh no");
Process("Error: not good");
Process("regular message 1");
Process("Start: hello again");
Process("Error: it broke again");
Process("Error: real bad");
Process("regular message 2");
Assert.Equal(5, _issues.Count);
Assert.Equal("it broke", _issues[0].Item1.Message);
Assert.Equal("oh no", _issues[1].Item1.Message);
Assert.Equal("not good", _issues[2].Item1.Message);
Assert.Equal("it broke again", _issues[3].Item1.Message);
Assert.Equal("real bad", _issues[4].Item1.Message);
Assert.Equal(0, _commands.Count);
Assert.Equal(4, _messages.Count);
Assert.Equal("Start: hello", _messages[0]);
Assert.Equal("regular message 1", _messages[1]);
Assert.Equal("Start: hello again", _messages[2]);
Assert.Equal("regular message 2", _messages[3]);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void InitialMatchers()
{
var matchers = new IssueMatchersConfig
{
Matchers =
{
new IssueMatcherConfig
{
Owner = "my-matcher-1",
Patterns = new[]
{
new IssuePatternConfig
{
Pattern = "ERROR: (.+)",
Message = 1,
},
},
},
new IssueMatcherConfig
{
Owner = "my-matcher-2",
Patterns = new[]
{
new IssuePatternConfig
{
Pattern = "NOT GOOD: (.+)",
Message = 1,
},
},
},
},
};
using (Setup(matchers: matchers))
using (_outputManager)
{
Process("ERROR: it is broken");
Process("NOT GOOD: that did not work");
Assert.Equal(2, _issues.Count);
Assert.Equal("it is broken", _issues[0].Item1.Message);
Assert.Equal("that did not work", _issues[1].Item1.Message);
Assert.Equal(0, _commands.Count);
Assert.Equal(0, _messages.Count);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void LineColumn()
{
var matchers = new IssueMatchersConfig
{
Matchers =
{
new IssueMatcherConfig
{
Owner = "my-matcher-1",
Patterns = new[]
{
new IssuePatternConfig
{
Pattern = @"\((.+),(.+)\): (.+)",
Line = 1,
Column = 2,
Message = 3,
},
},
},
},
};
using (Setup(matchers: matchers))
using (_outputManager)
{
Process("(12,34): real bad");
Process("(12,thirty-four): it is broken");
Process("(twelve,34): not working");
Assert.Equal(3, _issues.Count);
Assert.Equal("real bad", _issues[0].Item1.Message);
Assert.Equal("12", _issues[0].Item1.Data["line"]);
Assert.Equal("34", _issues[0].Item1.Data["col"]);
Assert.Equal("it is broken", _issues[1].Item1.Message);
Assert.Equal("12", _issues[1].Item1.Data["line"]);
Assert.False(_issues[1].Item1.Data.ContainsKey("col"));
Assert.Equal("not working", _issues[2].Item1.Message);
Assert.False(_issues[2].Item1.Data.ContainsKey("line"));
Assert.Equal("34", _issues[2].Item1.Data["col"]);
Assert.Equal(0, _commands.Count);
Assert.Equal(2, _messages.Count);
Assert.Equal("##[debug]Unable to parse column number 'thirty-four'", _messages[0]);
Assert.Equal("##[debug]Unable to parse line number 'twelve'", _messages[1]);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void ProcessCommand()
{
using (Setup())
using (_outputManager)
{
Add(new IssueMatcherConfig
{
Owner = "my-matcher",
Patterns = new[]
{
new IssuePatternConfig
{
Pattern = "ERROR: (.+)",
Message = 1,
},
},
});
Process("this line is an ERROR: it is broken");
Process("##[some-command]this line is a command even though it contains ERROR: not working");
Process("this line is a command too ##[some-command]even though it contains ERROR: not working again");
Process("##[not-command]this line is an ERROR: it is broken again");
Assert.Equal(2, _issues.Count);
Assert.Equal("it is broken", _issues[0].Item1.Message);
Assert.Equal("it is broken again", _issues[1].Item1.Message);
Assert.Equal(2, _commands.Count);
Assert.Equal("##[some-command]this line is a command even though it contains ERROR: not working", _commands[0]);
Assert.Equal("this line is a command too ##[some-command]even though it contains ERROR: not working again", _commands[1]);
Assert.Equal(0, _messages.Count);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void RemoveColorCodes()
{
using (Setup())
using (_outputManager)
{
Add(new IssueMatcherConfig
{
Owner = "my-matcher",
Patterns = new[]
{
new IssuePatternConfig
{
Pattern = "^the error: (.+)$",
Message = 1,
},
},
});
Process("the error: \033[31mred, \033[1;31mbright red, \033[mreset");
Assert.Equal(1, _issues.Count);
Assert.Equal("red, bright red, reset", _issues[0].Item1.Message);
Assert.Equal("the error: red, bright red, reset", _issues[0].Item2);
Assert.Equal(0, _commands.Count);
Assert.Equal(0, _messages.Count);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void RemoveMatcher()
{
var matchers = new IssueMatchersConfig
{
Matchers =
{
new IssueMatcherConfig
{
Owner = "my-matcher-1",
Patterns = new[]
{
new IssuePatternConfig
{
Pattern = "ERROR: (.+)",
Message = 1,
},
},
},
new IssueMatcherConfig
{
Owner = "my-matcher-2",
Patterns = new[]
{
new IssuePatternConfig
{
Pattern = "NOT GOOD: (.+)",
Message = 1,
},
},
},
},
};
using (Setup(matchers: matchers))
using (_outputManager)
{
Process("ERROR: message 1");
Process("NOT GOOD: message 2");
Remove("my-matcher-1");
Process("ERROR: message 3");
Process("NOT GOOD: message 4");
Assert.Equal(3, _issues.Count);
Assert.Equal("message 1", _issues[0].Item1.Message);
Assert.Equal("message 2", _issues[1].Item1.Message);
Assert.Equal("message 4", _issues[2].Item1.Message);
Assert.Equal(0, _commands.Count);
Assert.Equal(1, _messages.Count);
Assert.Equal("ERROR: message 3", _messages[0]);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void ResetsOtherMatchers()
{
var matchers = new IssueMatchersConfig
{
Matchers =
{
new IssueMatcherConfig
{
Owner = "my-matcher-1",
Patterns = new[]
{
new IssuePatternConfig
{
Pattern = "Matches both line 1: .+",
},
new IssuePatternConfig
{
Pattern = "Matches 1 only line 2: (.+)",
Message = 1,
},
},
},
new IssueMatcherConfig
{
Owner = "my-matcher-2",
Patterns = new[]
{
new IssuePatternConfig
{
Pattern = "Matches both line 1: (.+)",
},
new IssuePatternConfig
{
Pattern = "(.+)",
Message = 1,
},
},
},
},
};
using (Setup(matchers: matchers))
using (_outputManager)
{
Process("Matches both line 1: hello");
Process("Matches 1 only line 2: it broke");
Process("regular message 1");
Process("regular message 2");
Process("Matches both line 1: hello again");
Process("oh no, another error");
Assert.Equal(2, _issues.Count);
Assert.Equal("it broke", _issues[0].Item1.Message);
Assert.Equal("oh no, another error", _issues[1].Item1.Message);
Assert.Equal(0, _commands.Count);
Assert.Equal(4, _messages.Count);
Assert.Equal("Matches both line 1: hello", _messages[0]);
Assert.Equal("regular message 1", _messages[1]);
Assert.Equal("regular message 2", _messages[2]);
Assert.Equal("Matches both line 1: hello again", _messages[3]);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Severity()
{
var matchers = new IssueMatchersConfig
{
Matchers =
{
new IssueMatcherConfig
{
Owner = "my-matcher-1",
Patterns = new[]
{
new IssuePatternConfig
{
Pattern = "(.*): (.+)",
Severity = 1,
Message = 2,
},
},
},
new IssueMatcherConfig
{
Owner = "my-matcher-2",
Patterns = new[]
{
new IssuePatternConfig
{
Pattern = "ERROR! (.+)",
Message = 1,
},
},
},
},
};
using (Setup(matchers: matchers))
using (_outputManager)
{
Process("ERRor: real bad");
Process("WARNing: not great");
Process("info: hey");
Process(": not working");
Process("ERROR! uh oh");
Assert.Equal(4, _issues.Count);
Assert.Equal("real bad", _issues[0].Item1.Message);
Assert.Equal(DTWebApi.IssueType.Error, _issues[0].Item1.Type);
Assert.Equal("not great", _issues[1].Item1.Message);
Assert.Equal(DTWebApi.IssueType.Warning, _issues[1].Item1.Type);
Assert.Equal("not working", _issues[2].Item1.Message);
Assert.Equal(DTWebApi.IssueType.Error, _issues[2].Item1.Type);
Assert.Equal("uh oh", _issues[3].Item1.Message);
Assert.Equal(DTWebApi.IssueType.Error, _issues[3].Item1.Type);
Assert.Equal(0, _commands.Count);
Assert.Equal(2, _messages.Count);
Assert.StartsWith("##[debug]Skipped", _messages[0]);
Assert.Contains("'info'", _messages[0]);
Assert.Equal("info: hey", _messages[1]);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Timeout()
{
Environment.SetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_ISSUE_MATCHER_TIMEOUT", "0:0:0.01");
var matchers = new IssueMatchersConfig
{
Matchers =
{
new IssueMatcherConfig
{
Owner = "email",
Patterns = new[]
{
new IssuePatternConfig
{
Pattern = @"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$",
Message = 0,
},
},
},
new IssueMatcherConfig
{
Owner = "err",
Patterns = new[]
{
new IssuePatternConfig
{
Pattern = @"ERR: (.+)",
Message = 1,
},
},
},
},
};
using (Setup(matchers: matchers))
using (_outputManager)
{
Process("john.doe@contoso.com");
Process("t@t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.c%20");
Process("jane.doe@contoso.com");
Process("ERR: this error");
Assert.Equal(3, _issues.Count);
Assert.Equal("john.doe@contoso.com", _issues[0].Item1.Message);
Assert.Contains("Removing issue matcher 'email'", _issues[1].Item1.Message);
Assert.Equal("this error", _issues[2].Item1.Message);
Assert.Equal(0, _commands.Count);
Assert.Equal(2, _messages.Where(x => x.StartsWith("##[debug]Timeout processing issue matcher")).Count());
Assert.Equal(1, _messages.Where(x => x.Equals("t@t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.t.c%20")).Count());
Assert.Equal(1, _messages.Where(x => x.StartsWith("jane.doe@contoso.com")).Count());
}
}
// todo: roots file against fromPath
// todo: roots file against system.defaultWorkingDirectory
// todo: matches repository
// todo: checks file exists
private TestHostContext Setup(
[CallerMemberName] string name = "",
IssueMatchersConfig matchers = null)
{
matchers?.Validate();
_onMatcherChanged = null;
_issues = new List<Tuple<DTWebApi.Issue, string>>();
_messages = new List<string>();
_commands = new List<string>();
var hostContext = new TestHostContext(this, name);
_variables = new Variables(hostContext, new Dictionary<string, DTWebApi.VariableValue>());
_executionContext = new Mock<IExecutionContext>();
_executionContext.Setup(x => x.WriteDebug)
.Returns(true);
_executionContext.Setup(x => x.Variables)
.Returns(_variables);
_executionContext.Setup(x => x.GetMatchers())
.Returns(matchers?.Matchers ?? new List<IssueMatcherConfig>());
_executionContext.Setup(x => x.Add(It.IsAny<OnMatcherChanged>()))
.Callback((OnMatcherChanged handler) =>
{
_onMatcherChanged = handler;
});
_executionContext.Setup(x => x.AddIssue(It.IsAny<DTWebApi.Issue>(), It.IsAny<string>()))
.Callback((DTWebApi.Issue issue, string logMessage) =>
{
_issues.Add(new Tuple<DTWebApi.Issue, string>(issue, logMessage));
});
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
.Callback((string tag, string message) =>
{
_messages.Add($"{tag}{message}");
hostContext.GetTrace().Info($"{tag}{message}");
});
_commandManager = new Mock<IActionCommandManager>();
_commandManager.Setup(x => x.TryProcessCommand(It.IsAny<IExecutionContext>(), It.IsAny<string>()))
.Returns((IExecutionContext executionContext, string line) =>
{
if (line.IndexOf("##[some-command]") >= 0)
{
_commands.Add(line);
return true;
}
return false;
});
_outputManager = new OutputManager(_executionContext.Object, _commandManager.Object);
return hostContext;
}
private void Add(IssueMatcherConfig matcher)
{
var matchers = new IssueMatchersConfig
{
Matchers =
{
matcher,
},
};
matchers.Validate();
_onMatcherChanged(null, new MatcherChangedEventArgs(matcher));
}
private void Remove(string owner)
{
var matcher = new IssueMatcherConfig { Owner = owner };
_onMatcherChanged(null, new MatcherChangedEventArgs(matcher));
}
private void Process(string line)
{
_outputManager.OnDataReceived(null, new ProcessDataReceivedEventArgs(line));
}
}
}

View File

@@ -0,0 +1,229 @@
using Pipelines = GitHub.DistributedTask.Pipelines;
using GitHub.Runner.Worker;
using Moq;
using System.IO;
using System.Runtime.CompilerServices;
using Xunit;
using System;
namespace GitHub.Runner.Common.Tests.Worker
{
public sealed class PipelineDirectoryManagerL0
{
private PipelineDirectoryManager _pipelineDirectoryManager;
private Mock<IExecutionContext> _ec;
private Pipelines.WorkspaceOptions _workspaceOptions;
private TrackingConfig _existingConfig;
private TrackingConfig _newConfig;
private string _trackingFile;
private Mock<ITrackingManager> _trackingManager;
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void CreatesPipelineDirectories()
{
// Arrange.
using (TestHostContext hc = Setup())
{
_trackingManager.Setup(x => x.LoadIfExists(_ec.Object, _trackingFile)).Returns(default(TrackingConfig));
_trackingManager.Setup(x => x.Create(_ec.Object, _trackingFile)).Returns(new TrackingConfig(_ec.Object));
// Act.
_newConfig = _pipelineDirectoryManager.PrepareDirectory(_ec.Object, _workspaceOptions);
// Assert.
_trackingManager.Verify(x => x.Create(_ec.Object, _trackingFile));
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Work), _newConfig.WorkspaceDirectory)));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void DeletesResourceDirectoryWhenCleanIsResources()
{
// Arrange.
using (TestHostContext hc = Setup())
{
_existingConfig = new TrackingConfig(_ec.Object);
_trackingManager.Setup(x => x.LoadIfExists(_ec.Object, _trackingFile)).Returns(_existingConfig);
_workspaceOptions.Clean = Pipelines.PipelineConstants.WorkspaceCleanOptions.Resources;
string workspaceDirectory = Path.Combine(hc.GetDirectory(WellKnownDirectory.Work), _existingConfig.WorkspaceDirectory);
string sourceFile = Path.Combine(workspaceDirectory, "some subdirectory", "some source file");
Directory.CreateDirectory(Path.GetDirectoryName(sourceFile));
File.WriteAllText(path: sourceFile, contents: "some source contents");
// Act.
_pipelineDirectoryManager.PrepareDirectory(_ec.Object, _workspaceOptions);
// Assert.
Assert.True(Directory.Exists(workspaceDirectory));
Assert.Equal(0, Directory.GetFileSystemEntries(workspaceDirectory, "*", SearchOption.AllDirectories).Length);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void DeletesNonResourceDirectoryWhenCleanIsOutputs()
{
// Arrange.
using (TestHostContext hc = Setup())
{
_existingConfig = new TrackingConfig(_ec.Object);
_trackingManager.Setup(x => x.LoadIfExists(_ec.Object, _trackingFile)).Returns(_existingConfig);
_workspaceOptions.Clean = Pipelines.PipelineConstants.WorkspaceCleanOptions.Outputs;
string nonResourceDirectory = Path.Combine(hc.GetDirectory(WellKnownDirectory.Work), _existingConfig.PipelineDirectory, "somedir");
string sourceFile = Path.Combine(nonResourceDirectory, "some subdirectory", "some source file");
Directory.CreateDirectory(Path.GetDirectoryName(sourceFile));
File.WriteAllText(path: sourceFile, contents: "some source contents");
// Act.
_pipelineDirectoryManager.PrepareDirectory(_ec.Object, _workspaceOptions);
// Assert.
Assert.False(Directory.Exists(nonResourceDirectory));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void RecreatesPipelinesDirectoryWhenCleanIsAll()
{
// Arrange.
using (TestHostContext hc = Setup())
{
_existingConfig = new TrackingConfig(_ec.Object);
_trackingManager.Setup(x => x.LoadIfExists(_ec.Object, _trackingFile)).Returns(_existingConfig);
_workspaceOptions.Clean = Pipelines.PipelineConstants.WorkspaceCleanOptions.All;
string pipelinesDirectory = Path.Combine(hc.GetDirectory(WellKnownDirectory.Work), _existingConfig.PipelineDirectory);
string looseFile = Path.Combine(pipelinesDirectory, "some loose directory", "some loose file");
Directory.CreateDirectory(Path.GetDirectoryName(looseFile));
File.WriteAllText(path: looseFile, contents: "some loose file contents");
// Act.
_pipelineDirectoryManager.PrepareDirectory(_ec.Object, _workspaceOptions);
// Assert.
Assert.Equal(1, Directory.GetFileSystemEntries(pipelinesDirectory, "*", SearchOption.AllDirectories).Length);
Assert.True(Directory.Exists(Path.Combine(hc.GetDirectory(WellKnownDirectory.Work), _existingConfig.WorkspaceDirectory)));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void UpdatesExistingConfig()
{
// Arrange.
using (TestHostContext hc = Setup())
{
_existingConfig = new TrackingConfig(_ec.Object);
_trackingManager.Setup(x => x.LoadIfExists(_ec.Object, _trackingFile)).Returns(_existingConfig);
// Act.
_pipelineDirectoryManager.PrepareDirectory(_ec.Object, _workspaceOptions);
// Assert.
_trackingManager.Verify(x => x.LoadIfExists(_ec.Object, _trackingFile));
_trackingManager.Verify(x => x.Update(_ec.Object, _existingConfig, _trackingFile));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void UpdatesRepositoryDirectoryWorkspaceRepo()
{
// Arrange.
using (TestHostContext hc = Setup())
{
_existingConfig = new TrackingConfig(_ec.Object);
_trackingManager.Setup(x => x.LoadIfExists(_ec.Object, _trackingFile)).Returns(_existingConfig);
// Act.
_pipelineDirectoryManager.UpdateRepositoryDirectory(_ec.Object, "actions/runner", Path.Combine(hc.GetDirectory(WellKnownDirectory.Work), _existingConfig.PipelineDirectory, "my_new_path"), true);
// Assert.
_trackingManager.Verify(x => x.LoadIfExists(_ec.Object, _trackingFile));
_trackingManager.Verify(x => x.Update(_ec.Object, _existingConfig, _trackingFile));
_ec.Verify(x => x.SetGitHubContext("workspace", Path.Combine(hc.GetDirectory(WellKnownDirectory.Work), _existingConfig.PipelineDirectory, "my_new_path")));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void UpdatesRepositoryDirectoryNoneWorkspaceRepo()
{
// Arrange.
using (TestHostContext hc = Setup())
{
_existingConfig = new TrackingConfig(_ec.Object);
_trackingManager.Setup(x => x.LoadIfExists(_ec.Object, _trackingFile)).Returns(_existingConfig);
// Act.
_pipelineDirectoryManager.UpdateRepositoryDirectory(_ec.Object, "actions/notrunner", Path.Combine(hc.GetDirectory(WellKnownDirectory.Work), _existingConfig.PipelineDirectory, "notrunner"), false);
// Assert.
_trackingManager.Verify(x => x.LoadIfExists(_ec.Object, _trackingFile));
_trackingManager.Verify(x => x.Update(_ec.Object, _existingConfig, _trackingFile));
_ec.Verify(x => x.SetGitHubContext("workspace", It.IsAny<string>()), Times.Never);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void UpdatesRepositoryDirectoryThrowOnInvalidPath()
{
// Arrange.
using (TestHostContext hc = Setup())
{
_existingConfig = new TrackingConfig(_ec.Object);
_trackingManager.Setup(x => x.LoadIfExists(_ec.Object, _trackingFile)).Returns(_existingConfig);
// Act.
Assert.ThrowsAny<ArgumentException>(()=> _pipelineDirectoryManager.UpdateRepositoryDirectory(_ec.Object, "actions/notrunner", Path.Combine(hc.GetDirectory(WellKnownDirectory.Work), "not_under_pipeline_directory"), false));
}
}
private TestHostContext Setup(
[CallerMemberName] string name = "")
{
// Setup the host context.
TestHostContext hc = new TestHostContext(this, name);
// Setup the execution context.
_ec = new Mock<IExecutionContext>();
GitHubContext githubContext = new GitHubContext();
_ec.Setup(x => x.GetGitHubContext("repository")).Returns("actions/runner");
// Store the expected tracking file path.
_trackingFile = Path.Combine(
hc.GetDirectory(WellKnownDirectory.Work),
Constants.Pipeline.Path.PipelineMappingDirectory,
"actions/runner",
Constants.Pipeline.Path.TrackingConfigFile);
_workspaceOptions = new Pipelines.WorkspaceOptions();
// Setup the tracking manager.
_trackingManager = new Mock<ITrackingManager>();
hc.SetSingleton<ITrackingManager>(_trackingManager.Object);
// Setup the build directory manager.
_pipelineDirectoryManager = new PipelineDirectoryManager();
_pipelineDirectoryManager.Initialize(hc);
return hc;
}
}
}

View File

@@ -0,0 +1,448 @@
// using GitHub.DistributedTask.WebApi;
// using GitHub.Runner.Worker;
// using Moq;
// using System;
// using System.Collections.Generic;
// using System.Globalization;
// using System.Linq;
// using System.Runtime.CompilerServices;
// using System.Threading.Tasks;
// using Xunit;
// using GitHub.DistributedTask.Expressions2;
// using GitHub.DistributedTask.Pipelines.ContextData;
// namespace GitHub.Runner.Common.Tests.Worker
// {
// public sealed class StepsRunnerL0
// {
// private Mock<IExecutionContext> _ec;
// private StepsRunner _stepsRunner;
// private Variables _variables;
// private Dictionary<string, PipelineContextData> _contexts;
// private TestHostContext CreateTestContext([CallerMemberName] String testName = "")
// {
// var hc = new TestHostContext(this, testName);
// var expressionManager = new ExpressionManager();
// expressionManager.Initialize(hc);
// hc.SetSingleton<IExpressionManager>(expressionManager);
// Dictionary<string, VariableValue> variablesToCopy = new Dictionary<string, VariableValue>();
// variablesToCopy.Add(Constants.Variables.Agent.RetainDefaultEncoding, new VariableValue("true", false));
// _variables = new Variables(
// hostContext: hc,
// copy: variablesToCopy);
// _ec = new Mock<IExecutionContext>();
// _ec.SetupAllProperties();
// _ec.Setup(x => x.Variables).Returns(_variables);
// _contexts = new Dictionary<string, PipelineContextData>();
// _contexts["github"] = new DictionaryContextData();
// _contexts["runner"] = new DictionaryContextData();
// _contexts["actions"] = new DictionaryContextData();
// _ec.Setup(x => x.ExpressionValues).Returns(_contexts);
// var _stepContext = new StepsContext();
// _ec.Setup(x => x.StepsContext).Returns(_stepContext);
// _stepsRunner = new StepsRunner();
// _stepsRunner.Initialize(hc);
// return hc;
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Worker")]
// public async Task RunNormalStepsAllStepPass()
// {
// using (TestHostContext hc = CreateTestContext())
// {
// // Arrange.
// var variableSets = new[]
// {
// new[] { CreateStep(TaskResult.Succeeded, ExpressionManager.Succeeded), CreateStep(TaskResult.Succeeded, ExpressionManager.Succeeded) },
// new[] { CreateStep(TaskResult.Succeeded, ExpressionManager.Succeeded), CreateStep(TaskResult.Succeeded, ExpressionManager.SucceededOrFailed) },
// new[] { CreateStep(TaskResult.Succeeded, ExpressionManager.Succeeded), CreateStep(TaskResult.Succeeded, ExpressionManager.Always) }
// };
// foreach (var variableSet in variableSets)
// {
// _ec.Object.Result = null;
// // Act.
// await _stepsRunner.RunAsync(
// jobContext: _ec.Object,
// steps: variableSet.Select(x => x.Object).ToList());
// // Assert.
// Assert.Equal(TaskResult.Succeeded, _ec.Object.Result ?? TaskResult.Succeeded);
// Assert.Equal(2, variableSet.Length);
// variableSet[0].Verify(x => x.RunAsync());
// variableSet[1].Verify(x => x.RunAsync());
// }
// }
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Worker")]
// public async Task RunNormalStepsContinueOnError()
// {
// using (TestHostContext hc = CreateTestContext())
// {
// // Arrange.
// var variableSets = new[]
// {
// new[] { CreateStep(TaskResult.Failed, ExpressionManager.Succeeded, true), CreateStep(TaskResult.Succeeded, ExpressionManager.Succeeded) },
// new[] { CreateStep(TaskResult.Failed, ExpressionManager.Succeeded, true), CreateStep(TaskResult.Succeeded, ExpressionManager.SucceededOrFailed) },
// new[] { CreateStep(TaskResult.Failed, ExpressionManager.Succeeded, true), CreateStep(TaskResult.Succeeded, ExpressionManager.Always) },
// new[] { CreateStep(TaskResult.Failed, ExpressionManager.Succeeded, true), CreateStep(TaskResult.Failed, ExpressionManager.Succeeded, true) },
// new[] { CreateStep(TaskResult.Failed, ExpressionManager.Succeeded, true), CreateStep(TaskResult.Failed, ExpressionManager.SucceededOrFailed, true) },
// new[] { CreateStep(TaskResult.Failed, ExpressionManager.Succeeded, true), CreateStep(TaskResult.Failed, ExpressionManager.Always, true) }
// };
// foreach (var variableSet in variableSets)
// {
// _ec.Object.Result = null;
// // Act.
// await _stepsRunner.RunAsync(
// jobContext: _ec.Object,
// steps: variableSet.Select(x => x.Object).ToList());
// // Assert.
// Assert.Equal(TaskResult.SucceededWithIssues, _ec.Object.Result);
// Assert.Equal(2, variableSet.Length);
// variableSet[0].Verify(x => x.RunAsync());
// variableSet[1].Verify(x => x.RunAsync());
// }
// }
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Worker")]
// public async Task RunsAfterFailureBasedOnCondition()
// {
// using (TestHostContext hc = CreateTestContext())
// {
// // Arrange.
// var variableSets = new[]
// {
// new
// {
// Steps = new[] { CreateStep(TaskResult.Failed, ExpressionManager.Succeeded), CreateStep(TaskResult.Succeeded, ExpressionManager.Succeeded) },
// Expected = false,
// },
// new
// {
// Steps = new[] { CreateStep(TaskResult.Failed, ExpressionManager.Succeeded), CreateStep(TaskResult.Succeeded, ExpressionManager.SucceededOrFailed) },
// Expected = true,
// },
// };
// foreach (var variableSet in variableSets)
// {
// _ec.Object.Result = null;
// // Act.
// await _stepsRunner.RunAsync(
// jobContext: _ec.Object,
// steps: variableSet.Steps.Select(x => x.Object).ToList());
// // Assert.
// Assert.Equal(TaskResult.Failed, _ec.Object.Result ?? TaskResult.Succeeded);
// Assert.Equal(2, variableSet.Steps.Length);
// variableSet.Steps[0].Verify(x => x.RunAsync());
// variableSet.Steps[1].Verify(x => x.RunAsync(), variableSet.Expected ? Times.Once() : Times.Never());
// }
// }
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Worker")]
// public async Task RunsAlwaysSteps()
// {
// using (TestHostContext hc = CreateTestContext())
// {
// // Arrange.
// var variableSets = new[]
// {
// new
// {
// Steps = new[] { CreateStep(TaskResult.Succeeded, ExpressionManager.Succeeded), CreateStep(TaskResult.Succeeded, ExpressionManager.Always) },
// Expected = TaskResult.Succeeded,
// },
// new
// {
// Steps = new[] { CreateStep(TaskResult.Failed, ExpressionManager.Succeeded), CreateStep(TaskResult.Succeeded, ExpressionManager.Always) },
// Expected = TaskResult.Failed,
// },
// new
// {
// Steps = new[] { CreateStep(TaskResult.Failed, ExpressionManager.Succeeded), CreateStep(TaskResult.Succeeded, ExpressionManager.Always) },
// Expected = TaskResult.Failed,
// },
// new
// {
// Steps = new[] { CreateStep(TaskResult.Succeeded, ExpressionManager.Succeeded), CreateStep(TaskResult.Failed, ExpressionManager.Always) },
// Expected = TaskResult.Failed,
// },
// new
// {
// Steps = new[] { CreateStep(TaskResult.Succeeded, ExpressionManager.Succeeded), CreateStep(TaskResult.Failed, ExpressionManager.Always, true) },
// Expected = TaskResult.SucceededWithIssues,
// },
// };
// foreach (var variableSet in variableSets)
// {
// _ec.Object.Result = null;
// // Act.
// await _stepsRunner.RunAsync(
// jobContext: _ec.Object,
// steps: variableSet.Steps.Select(x => x.Object).ToList());
// // Assert.
// Assert.Equal(variableSet.Expected, _ec.Object.Result ?? TaskResult.Succeeded);
// Assert.Equal(2, variableSet.Steps.Length);
// variableSet.Steps[0].Verify(x => x.RunAsync());
// variableSet.Steps[1].Verify(x => x.RunAsync());
// }
// }
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Worker")]
// public async Task SetsJobResultCorrectly()
// {
// using (TestHostContext hc = CreateTestContext())
// {
// // Arrange.
// var variableSets = new[]
// {
// new
// {
// Steps = new[] { CreateStep(TaskResult.Failed, ExpressionManager.Succeeded), CreateStep(TaskResult.Succeeded, ExpressionManager.Succeeded) },
// Expected = TaskResult.Failed
// },
// new
// {
// Steps = new[] { CreateStep(TaskResult.Failed, ExpressionManager.Succeeded), CreateStep(TaskResult.Succeeded, ExpressionManager.SucceededOrFailed) },
// Expected = TaskResult.Failed
// },
// new
// {
// Steps = new[] { CreateStep(TaskResult.Failed, ExpressionManager.Succeeded), CreateStep(TaskResult.Succeeded, ExpressionManager.Always) },
// Expected = TaskResult.Failed
// },
// new
// {
// Steps = new[] { CreateStep(TaskResult.Failed, ExpressionManager.Succeeded, continueOnError: true), CreateStep(TaskResult.Failed, ExpressionManager.Succeeded) },
// Expected = TaskResult.Failed
// },
// new
// {
// Steps = new[] { CreateStep(TaskResult.Failed, ExpressionManager.Succeeded, continueOnError: true), CreateStep(TaskResult.Succeeded, ExpressionManager.Succeeded) },
// Expected = TaskResult.SucceededWithIssues
// },
// new
// {
// Steps = new[] { CreateStep(TaskResult.Failed, ExpressionManager.Succeeded, continueOnError: true), CreateStep(TaskResult.Failed, ExpressionManager.Succeeded, continueOnError: true) },
// Expected = TaskResult.SucceededWithIssues
// },
// new
// {
// Steps = new[] { CreateStep(TaskResult.Succeeded, ExpressionManager.SucceededOrFailed) },
// Expected = TaskResult.Succeeded
// },
// new
// {
// Steps = new[] { CreateStep(TaskResult.Succeeded, ExpressionManager.Succeeded), CreateStep(TaskResult.Failed, ExpressionManager.Succeeded) },
// Expected = TaskResult.Failed
// },
// new
// {
// Steps = new[] { CreateStep(TaskResult.Succeeded, ExpressionManager.Succeeded), CreateStep(TaskResult.SucceededWithIssues, ExpressionManager.Succeeded) },
// Expected = TaskResult.SucceededWithIssues
// },
// new
// {
// Steps = new[] { CreateStep(TaskResult.SucceededWithIssues, ExpressionManager.Succeeded), CreateStep(TaskResult.Succeeded, ExpressionManager.Succeeded) },
// Expected = TaskResult.SucceededWithIssues
// },
// new
// {
// Steps = new[] { CreateStep(TaskResult.SucceededWithIssues, ExpressionManager.Succeeded), CreateStep(TaskResult.Failed, ExpressionManager.Succeeded) },
// Expected = TaskResult.Failed
// },
// // Abandoned
// // Canceled
// // Failed
// // Skipped
// // Succeeded
// // SucceededWithIssues
// };
// foreach (var variableSet in variableSets)
// {
// _ec.Object.Result = null;
// // Act.
// await _stepsRunner.RunAsync(
// jobContext: _ec.Object,
// steps: variableSet.Steps.Select(x => x.Object).ToList());
// // Assert.
// Assert.True(
// variableSet.Expected == (_ec.Object.Result ?? TaskResult.Succeeded),
// $"Expected '{variableSet.Expected}'. Actual '{_ec.Object.Result}'. Steps: {FormatSteps(variableSet.Steps)}");
// }
// }
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Worker")]
// public async Task SkipsAfterFailureOnlyBaseOnCondition()
// {
// using (TestHostContext hc = CreateTestContext())
// {
// // Arrange.
// var variableSets = new[]
// {
// new
// {
// Step = new[] { CreateStep(TaskResult.Failed, ExpressionManager.Succeeded), CreateStep(TaskResult.Succeeded, ExpressionManager.Succeeded) },
// Expected = false
// },
// new
// {
// Step = new[] { CreateStep(TaskResult.Failed, ExpressionManager.Succeeded), CreateStep(TaskResult.Succeeded, ExpressionManager.SucceededOrFailed) },
// Expected = true
// },
// new
// {
// Step = new[] { CreateStep(TaskResult.Failed, ExpressionManager.Succeeded), CreateStep(TaskResult.Succeeded, ExpressionManager.Always) },
// Expected = true
// }
// };
// foreach (var variableSet in variableSets)
// {
// _ec.Object.Result = null;
// // Act.
// await _stepsRunner.RunAsync(
// jobContext: _ec.Object,
// steps: variableSet.Step.Select(x => x.Object).ToList());
// // Assert.
// Assert.Equal(2, variableSet.Step.Length);
// variableSet.Step[0].Verify(x => x.RunAsync());
// variableSet.Step[1].Verify(x => x.RunAsync(), variableSet.Expected ? Times.Once() : Times.Never());
// }
// }
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Worker")]
// public async Task AlwaysMeansAlways()
// {
// using (TestHostContext hc = CreateTestContext())
// {
// // Arrange.
// var variableSets = new[]
// {
// new[] { CreateStep(TaskResult.Succeeded, ExpressionManager.Succeeded), CreateStep(TaskResult.Succeeded, ExpressionManager.Always) },
// new[] { CreateStep(TaskResult.SucceededWithIssues, ExpressionManager.Succeeded), CreateStep(TaskResult.Succeeded, ExpressionManager.Always) },
// new[] { CreateStep(TaskResult.Failed, ExpressionManager.Succeeded), CreateStep(TaskResult.Succeeded, ExpressionManager.Always) },
// new[] { CreateStep(TaskResult.Canceled, ExpressionManager.Succeeded), CreateStep(TaskResult.Succeeded, ExpressionManager.Always) }
// };
// foreach (var variableSet in variableSets)
// {
// _ec.Object.Result = null;
// // Act.
// await _stepsRunner.RunAsync(
// jobContext: _ec.Object,
// steps: variableSet.Select(x => x.Object).ToList());
// // Assert.
// Assert.Equal(2, variableSet.Length);
// variableSet[0].Verify(x => x.RunAsync());
// variableSet[1].Verify(x => x.RunAsync(), Times.Once());
// }
// }
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Worker")]
// public async Task TreatsConditionErrorAsFailure()
// {
// using (TestHostContext hc = CreateTestContext())
// {
// var expressionManager = new Mock<IExpressionManager>();
// expressionManager.Object.Initialize(hc);
// hc.SetSingleton<IExpressionManager>(expressionManager.Object);
// expressionManager.Setup(x => x.Evaluate(It.IsAny<IExecutionContext>(), It.IsAny<IExpressionNode>(), It.IsAny<bool>())).Throws(new Exception());
// // Arrange.
// var variableSets = new[]
// {
// new[] { CreateStep(TaskResult.Succeeded, ExpressionManager.Succeeded) },
// new[] { CreateStep(TaskResult.Succeeded, ExpressionManager.Succeeded) },
// };
// foreach (var variableSet in variableSets)
// {
// _ec.Object.Result = null;
// // Act.
// await _stepsRunner.RunAsync(
// jobContext: _ec.Object,
// steps: variableSet.Select(x => x.Object).ToList());
// // Assert.
// Assert.Equal(TaskResult.Failed, _ec.Object.Result ?? TaskResult.Succeeded);
// }
// }
// }
// private Mock<IStep> CreateStep(TaskResult result, IExpressionNode condition, Boolean continueOnError = false)
// {
// // Setup the step.
// var step = new Mock<IStep>();
// step.Setup(x => x.Condition).Returns(condition);
// step.Setup(x => x.ContinueOnError).Returns(continueOnError);
// step.Setup(x => x.Enabled).Returns(true);
// step.Setup(x => x.RunAsync()).Returns(Task.CompletedTask);
// // Setup the step execution context.
// var stepContext = new Mock<IExecutionContext>();
// stepContext.SetupAllProperties();
// stepContext.Setup(x => x.Variables).Returns(_variables);
// stepContext.Setup(x => x.ExpressionValues).Returns(_contexts);
// stepContext.Setup(x => x.Complete(It.IsAny<TaskResult?>(), It.IsAny<string>(), It.IsAny<string>()))
// .Callback((TaskResult? r, string currentOperation, string resultCode) =>
// {
// if (r != null)
// {
// stepContext.Object.Result = r;
// }
// });
// stepContext.Object.Result = result;
// step.Setup(x => x.ExecutionContext).Returns(stepContext.Object);
// return step;
// }
// private string FormatSteps(IEnumerable<Mock<IStep>> steps)
// {
// return String.Join(
// " ; ",
// steps.Select(x => String.Format(
// CultureInfo.InvariantCulture,
// "Returns={0},Condition=[{1}],ContinueOnError={2},Enabled={3}",
// x.Object.ExecutionContext.Result,
// x.Object.Condition,
// x.Object.ContinueOnError,
// x.Object.Enabled)));
// }
// }
// }

View File

@@ -0,0 +1,181 @@
// using System;
// using System.Collections.Generic;
// using System.Runtime.CompilerServices;
// using GitHub.DistributedTask.WebApi;
// using GitHub.Runner.Worker;
// using Moq;
// using Xunit;
// namespace GitHub.Runner.Common.Tests.Worker
// {
// public sealed class TaskCommandExtensionL0
// {
// private TestHostContext _hc;
// private Mock<IExecutionContext> _ec;
// private ServiceEndpoint _endpoint;
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Worker")]
// public void SetEndpointAuthParameter()
// {
// SetupMocks();
// TaskCommandExtension commandExtension = new TaskCommandExtension();
// commandExtension.Initialize(_hc);
// var cmd = new Command("task", "setEndpoint");
// cmd.Data = "blah";
// cmd.Properties.Add("field", "authParameter");
// cmd.Properties.Add("id", Guid.Empty.ToString());
// cmd.Properties.Add("key", "test");
// commandExtension.ProcessCommand(_ec.Object, cmd);
// Assert.Equal(_endpoint.Authorization.Parameters["test"], "blah");
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Worker")]
// public void SetEndpointDataParameter()
// {
// SetupMocks();
// TaskCommandExtension commandExtension = new TaskCommandExtension();
// var cmd = new Command("task", "setEndpoint");
// cmd.Data = "blah";
// cmd.Properties.Add("field", "dataParameter");
// cmd.Properties.Add("id", Guid.Empty.ToString());
// cmd.Properties.Add("key", "test");
// commandExtension.ProcessCommand(_ec.Object, cmd);
// Assert.Equal(_endpoint.Data["test"], "blah");
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Worker")]
// public void SetEndpointUrlParameter()
// {
// SetupMocks();
// TaskCommandExtension commandExtension = new TaskCommandExtension();
// var cmd = new Command("task", "setEndpoint");
// cmd.Data = "http://blah/";
// cmd.Properties.Add("field", "url");
// cmd.Properties.Add("id", Guid.Empty.ToString());
// commandExtension.ProcessCommand(_ec.Object, cmd);
// Assert.Equal(_endpoint.Url.ToString(), cmd.Data);
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Worker")]
// public void SetEndpointWithoutValue()
// {
// SetupMocks();
// TaskCommandExtension commandExtension = new TaskCommandExtension();
// var cmd = new Command("task", "setEndpoint");
// Assert.Throws<Exception>(() => commandExtension.ProcessCommand(_ec.Object, cmd));
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Worker")]
// public void SetEndpointWithoutEndpointField()
// {
// SetupMocks();
// TaskCommandExtension commandExtension = new TaskCommandExtension();
// var cmd = new Command("task", "setEndpoint");
// Assert.Throws<Exception>(() => commandExtension.ProcessCommand(_ec.Object, cmd));
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Worker")]
// public void SetEndpointInvalidEndpointField()
// {
// SetupMocks();
// TaskCommandExtension commandExtension = new TaskCommandExtension();
// var cmd = new Command("task", "setEndpoint");
// cmd.Properties.Add("field", "blah");
// Assert.Throws<Exception>(() => commandExtension.ProcessCommand(_ec.Object, cmd));
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Worker")]
// public void SetEndpointWithoutEndpointId()
// {
// SetupMocks();
// TaskCommandExtension commandExtension = new TaskCommandExtension();
// var cmd = new Command("task", "setEndpoint");
// cmd.Properties.Add("field", "url");
// Assert.Throws<Exception>(() => commandExtension.ProcessCommand(_ec.Object, cmd));
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Worker")]
// public void SetEndpointInvalidEndpointId()
// {
// SetupMocks();
// TaskCommandExtension commandExtension = new TaskCommandExtension();
// var cmd = new Command("task", "setEndpoint");
// cmd.Properties.Add("field", "url");
// cmd.Properties.Add("id", "blah");
// Assert.Throws<Exception>(() => commandExtension.ProcessCommand(_ec.Object, cmd));
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Worker")]
// public void SetEndpointIdWithoutEndpointKey()
// {
// SetupMocks();
// TaskCommandExtension commandExtension = new TaskCommandExtension();
// var cmd = new Command("task", "setEndpoint");
// cmd.Properties.Add("field", "authParameter");
// cmd.Properties.Add("id", Guid.Empty.ToString());
// Assert.Throws<Exception>(() => commandExtension.ProcessCommand(_ec.Object, cmd));
// }
// [Fact]
// [Trait("Level", "L0")]
// [Trait("Category", "Worker")]
// public void SetEndpointUrlWithInvalidValue()
// {
// SetupMocks();
// TaskCommandExtension commandExtension = new TaskCommandExtension();
// var cmd = new Command("task", "setEndpoint");
// cmd.Data = "blah";
// cmd.Properties.Add("field", "url");
// cmd.Properties.Add("id", Guid.Empty.ToString());
// Assert.Throws<Exception>(() => commandExtension.ProcessCommand(_ec.Object, cmd));
// }
// private void SetupMocks([CallerMemberName] string name = "")
// {
// _hc = new TestHostContext(this, name);
// _ec = new Mock<IExecutionContext>();
// _endpoint = new ServiceEndpoint()
// {
// Id = Guid.Empty,
// Url = new Uri("https://test.com"),
// Authorization = new EndpointAuthorization()
// {
// Scheme = "Test",
// }
// };
// _ec.Setup(x => x.Endpoints).Returns(new List<ServiceEndpoint> { _endpoint });
// }
// }
// }

View File

@@ -0,0 +1,129 @@
using GitHub.Runner.Worker;
using Moq;
using System;
using System.IO;
using System.Runtime.CompilerServices;
using Xunit;
namespace GitHub.Runner.Common.Tests.Worker
{
public sealed class TrackingManagerL0
{
private Mock<IExecutionContext> _ec;
private TrackingManager _trackingManager;
private string _workFolder;
public TestHostContext Setup([CallerMemberName] string name = "")
{
// Setup the host context.
TestHostContext hc = new TestHostContext(this, name);
// Create a random work path.
_workFolder = hc.GetDirectory(WellKnownDirectory.Work);
// Setup the execution context.
_ec = new Mock<IExecutionContext>();
GitHubContext githubContext = new GitHubContext();
_ec.Setup(x => x.GetGitHubContext("repository")).Returns("actions/runner");
// Setup the tracking manager.
_trackingManager = new TrackingManager();
_trackingManager.Initialize(hc);
return hc;
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void CreatesTrackingConfig()
{
using (TestHostContext hc = Setup())
{
// Arrange.
string trackingFile = Path.Combine(_workFolder, "trackingconfig.json");
DateTimeOffset testStartOn = DateTimeOffset.Now;
// Act.
_trackingManager.Create(_ec.Object, trackingFile);
// Assert.
TrackingConfig config = _trackingManager.LoadIfExists(_ec.Object, trackingFile);
Assert.Equal("runner", config.PipelineDirectory);
Assert.Equal($"runner{Path.DirectorySeparatorChar}runner", config.WorkspaceDirectory);
Assert.Equal("actions/runner", config.RepositoryName);
Assert.Equal(1, config.Repositories.Count);
Assert.Equal($"runner{Path.DirectorySeparatorChar}runner", config.Repositories["actions/runner"].RepositoryPath);
// Manipulate the expected seconds due to loss of granularity when the
// date-time-offset is serialized in a friendly format.
Assert.True(testStartOn.AddSeconds(-1) <= config.LastRunOn);
Assert.True(DateTimeOffset.Now.AddSeconds(1) >= config.LastRunOn);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void LoadsTrackingConfig()
{
using (TestHostContext hc = Setup())
{
// Arrange.
Directory.CreateDirectory(_workFolder);
string filePath = Path.Combine(_workFolder, "trackingconfig.json");
_trackingManager.Create(_ec.Object, filePath);
// Act.
TrackingConfig config = _trackingManager.LoadIfExists(_ec.Object, filePath);
// Assert.
Assert.NotNull(config);
Assert.Equal("actions/runner", config.RepositoryName);
Assert.Equal("runner", config.PipelineDirectory);
Assert.Equal($"runner{Path.DirectorySeparatorChar}runner", config.WorkspaceDirectory);
Assert.Equal(1, config.Repositories.Count);
Assert.Equal($"runner{Path.DirectorySeparatorChar}runner", config.Repositories["actions/runner"].RepositoryPath);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void LoadsTrackingConfig_NotExists()
{
using (TestHostContext hc = Setup())
{
// Act.
TrackingConfig config = _trackingManager.LoadIfExists(
_ec.Object,
Path.Combine(_workFolder, "foo.json"));
// Assert.
Assert.Null(config);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void UpdatesTrackingConfigJobRunProperties()
{
using (TestHostContext hc = Setup())
{
// Arrange.
TrackingConfig config = new TrackingConfig() { RepositoryName = "actions/runner" };
string trackingFile = Path.Combine(_workFolder, "trackingconfig.json");
// Act.
_trackingManager.Update(_ec.Object, config, trackingFile);
// Assert.
config = _trackingManager.LoadIfExists(_ec.Object, trackingFile);
Assert.NotNull(config);
Assert.Equal("actions/runner", config.RepositoryName);
}
}
}
}

View File

@@ -0,0 +1,193 @@
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using GitHub.Runner.Worker;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Xunit;
namespace GitHub.Runner.Common.Tests.Worker
{
public sealed class VariablesL0
{
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Constructor_AppliesMaskHints()
{
using (TestHostContext hc = new TestHostContext(this))
{
// Arrange.
var copy = new Dictionary<string, VariableValue>
{
{ "MySecretName", new VariableValue("My secret value", true) },
{ "MyPublicVariable", "My public value" },
};
var variables = new Variables(hc, copy);
// Assert.
Assert.Equal(2, variables.AllVariables.Count());
Assert.Equal("My public value", variables.Get("MyPublicVariable"));
Assert.Equal("My secret value", variables.Get("MySecretName"));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Constructor_HandlesNullValue()
{
using (TestHostContext hc = new TestHostContext(this))
{
// Arrange.
var copy = new Dictionary<string, VariableValue>
{
{ "variable1", new VariableValue(null, false) },
{ "variable2", "some variable 2 value" },
};
// Act.
var variables = new Variables(hc, copy);
// Assert.
Assert.Equal(string.Empty, variables.Get("variable1"));
Assert.Equal("some variable 2 value", variables.Get("variable2"));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Constructor_SetsNullAsEmpty()
{
using (TestHostContext hc = new TestHostContext(this))
{
// Arrange.
var copy = new Dictionary<string, VariableValue>
{
{ "variable1", new VariableValue(null, false) },
};
// Act.
var variables = new Variables(hc, copy);
// Assert.
Assert.Equal(string.Empty, variables.Get("variable1"));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Constructor_SetsOrdinalIgnoreCaseComparer()
{
using (TestHostContext hc = new TestHostContext(this))
{
// Arrange.
CultureInfo currentCulture = CultureInfo.CurrentCulture;
CultureInfo currentUICulture = CultureInfo.CurrentUICulture;
try
{
CultureInfo.CurrentCulture = new CultureInfo("tr-TR");
CultureInfo.CurrentUICulture = new CultureInfo("tr-TR");
var copy = new Dictionary<string, VariableValue>
{
{ "i", "foo" },
{ "I", "foo" },
};
// Act.
var variables = new Variables(hc, copy);
// Assert.
Assert.Equal(1, variables.AllVariables.Count());
}
finally
{
// Cleanup.
CultureInfo.CurrentCulture = currentCulture;
CultureInfo.CurrentUICulture = currentUICulture;
}
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Constructor_SkipVariableWithEmptyName()
{
using (TestHostContext hc = new TestHostContext(this))
{
// Arrange.
var copy = new Dictionary<string, VariableValue>
{
{ "", "" },
{ " ", "" },
{ "MyPublicVariable", "My public value" },
};
var variables = new Variables(hc, copy);
// Assert.
Assert.Equal(1, variables.AllVariables.Count());
Assert.Equal("MyPublicVariable", variables.AllVariables.Single().Name);
Assert.Equal("My public value", variables.AllVariables.Single().Value);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void Get_ReturnsNullIfNotFound()
{
using (TestHostContext hc = new TestHostContext(this))
{
// Arrange.
var variables = new Variables(hc, new Dictionary<string, VariableValue>());
// Act.
string actual = variables.Get("no such");
// Assert.
Assert.Equal(null, actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void GetBoolean_DoesNotThrowWhenNull()
{
using (TestHostContext hc = new TestHostContext(this))
{
// Arrange.
var variables = new Variables(hc, new Dictionary<string, VariableValue>());
// Act.
bool? actual = variables.GetBoolean("no such");
// Assert.
Assert.Null(actual);
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void GetEnum_DoesNotThrowWhenNull()
{
using (TestHostContext hc = new TestHostContext(this))
{
// Arrange.
var variables = new Variables(hc, new Dictionary<string, VariableValue>());
// Act.
System.IO.FileShare? actual = variables.GetEnum<System.IO.FileShare>("no such");
// Assert.
Assert.Null(actual);
}
}
}
}

View File

@@ -0,0 +1,308 @@
using GitHub.DistributedTask.WebApi;
using GitHub.DistributedTask.ObjectTemplating.Tokens;
using GitHub.Runner.Worker;
using Moq;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
using GitHub.Services.WebApi;
using Pipelines = GitHub.DistributedTask.Pipelines;
namespace GitHub.Runner.Common.Tests.Worker
{
public sealed class WorkerL0
{
private Mock<IProcessChannel> _processChannel;
private Mock<IJobRunner> _jobRunner;
private Mock<IRunnerWebProxy> _proxy;
private Mock<IRunnerCertificateManager> _cert;
public WorkerL0()
{
_processChannel = new Mock<IProcessChannel>();
_jobRunner = new Mock<IJobRunner>();
_proxy = new Mock<IRunnerWebProxy>();
_cert = new Mock<IRunnerCertificateManager>();
}
private Pipelines.AgentJobRequestMessage CreateJobRequestMessage(string jobName)
{
TaskOrchestrationPlanReference plan = new TaskOrchestrationPlanReference() { PlanId = Guid.NewGuid() };
TimelineReference timeline = null;
Dictionary<string, VariableValue> variables = new Dictionary<string, VariableValue>(StringComparer.OrdinalIgnoreCase);
variables[Constants.Variables.System.Culture] = "en-US";
Pipelines.JobResources resources = new Pipelines.JobResources();
var serviceEndpoint = new ServiceEndpoint();
serviceEndpoint.Authorization = new EndpointAuthorization();
serviceEndpoint.Authorization.Parameters.Add("nullValue", null);
resources.Endpoints.Add(serviceEndpoint);
List<Pipelines.JobStep> tasks = new List<Pipelines.JobStep>();
tasks.Add(new Pipelines.TaskStep()
{
Id = Guid.NewGuid(),
Reference = new Pipelines.TaskStepDefinitionReference()
{
Id = Guid.NewGuid(),
Name = "TestTask",
Version = "1.0.0"
}
});
Guid JobId = Guid.NewGuid();
var sidecarContainers = new MappingToken(null, null, null)
{
{
new StringToken(null, null, null, "nginx"),
new MappingToken(null, null, null)
{
{
new StringToken(null, null, null, "image"),
new StringToken(null, null, null, "nginx")
},
}
},
};
var context = new Pipelines.ContextData.DictionaryContextData
{
{
"github",
new Pipelines.ContextData.DictionaryContextData()
},
};
var jobRequest = new Pipelines.AgentJobRequestMessage(plan, timeline, JobId, jobName, jobName, new StringToken(null, null, null, "ubuntu"), sidecarContainers, null, variables, new List<MaskHint>(), resources, context, null, tasks, null);
return jobRequest;
}
private JobCancelMessage CreateJobCancelMessage(Guid jobId)
{
return new JobCancelMessage(jobId, TimeSpan.FromSeconds(0));
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public async void DispatchRunNewJob()
{
//Arrange
using (var hc = new TestHostContext(this))
using (var tokenSource = new CancellationTokenSource())
{
var worker = new GitHub.Runner.Worker.Worker();
hc.EnqueueInstance<IProcessChannel>(_processChannel.Object);
hc.EnqueueInstance<IJobRunner>(_jobRunner.Object);
hc.SetSingleton<IRunnerWebProxy>(_proxy.Object);
hc.SetSingleton<IRunnerCertificateManager>(_cert.Object);
worker.Initialize(hc);
var jobMessage = CreateJobRequestMessage("job1");
var arWorkerMessages = new WorkerMessage[]
{
new WorkerMessage
{
Body = JsonUtility.ToString(jobMessage),
MessageType = MessageType.NewJobRequest
}
};
var workerMessages = new Queue<WorkerMessage>(arWorkerMessages);
_processChannel
.Setup(x => x.ReceiveAsync(It.IsAny<CancellationToken>()))
.Returns(async () =>
{
// Return the job message.
if (workerMessages.Count > 0)
{
return workerMessages.Dequeue();
}
// Wait for the text to run
await Task.Delay(-1, tokenSource.Token);
return default(WorkerMessage);
});
_jobRunner.Setup(x => x.RunAsync(It.IsAny<Pipelines.AgentJobRequestMessage>(), It.IsAny<CancellationToken>()))
.Returns(Task.FromResult<TaskResult>(TaskResult.Succeeded));
//Act
await worker.RunAsync(pipeIn: "1", pipeOut: "2");
//Assert
_processChannel.Verify(x => x.StartClient("1", "2"), Times.Once());
_jobRunner.Verify(x => x.RunAsync(
It.Is<Pipelines.AgentJobRequestMessage>(y => IsMessageIdentical(y, jobMessage)), It.IsAny<CancellationToken>()));
tokenSource.Cancel();
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public async void DispatchCancellation()
{
//Arrange
using (var hc = new TestHostContext(this))
{
var worker = new GitHub.Runner.Worker.Worker();
hc.EnqueueInstance<IProcessChannel>(_processChannel.Object);
hc.EnqueueInstance<IJobRunner>(_jobRunner.Object);
hc.SetSingleton<IRunnerWebProxy>(_proxy.Object);
hc.SetSingleton<IRunnerCertificateManager>(_cert.Object);
worker.Initialize(hc);
var jobMessage = CreateJobRequestMessage("job1");
var cancelMessage = CreateJobCancelMessage(jobMessage.JobId);
var arWorkerMessages = new WorkerMessage[]
{
new WorkerMessage
{
Body = JsonUtility.ToString(jobMessage),
MessageType = MessageType.NewJobRequest
},
new WorkerMessage
{
Body = JsonUtility.ToString(cancelMessage),
MessageType = MessageType.CancelRequest
}
};
var workerMessages = new Queue<WorkerMessage>(arWorkerMessages);
_processChannel.Setup(x => x.ReceiveAsync(It.IsAny<CancellationToken>()))
.Returns(() => Task.FromResult(workerMessages.Dequeue()));
_jobRunner.Setup(x => x.RunAsync(It.IsAny<Pipelines.AgentJobRequestMessage>(), It.IsAny<CancellationToken>()))
.Returns(
async (Pipelines.AgentJobRequestMessage jm, CancellationToken ct) =>
{
await Task.Delay(-1, ct);
return TaskResult.Canceled;
});
//Act
await Assert.ThrowsAsync<TaskCanceledException>(
async () => await worker.RunAsync("1", "2"));
//Assert
_processChannel.Verify(x => x.StartClient("1", "2"), Times.Once());
_jobRunner.Verify(x => x.RunAsync(
It.Is<Pipelines.AgentJobRequestMessage>(y => IsMessageIdentical(y, jobMessage)), It.IsAny<CancellationToken>()));
}
}
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Worker")]
public void VerifyJobRequestMessagePiiDataIsScrubbed()
{
// Arrange
Pipelines.AgentJobRequestMessage message = CreateJobRequestMessage("jobwithpiidata");
// Populate PII variables
foreach (string piiVariable in Variables.PiiVariables)
{
message.Variables.Add(piiVariable, "MyPiiVariable");
}
foreach (string piiVariableSuffix in Variables.PiiArtifactVariableSuffixes)
{
message.Variables.Add($"{Variables.PiiArtifactVariablePrefix}.MyArtifact.{piiVariableSuffix}", "MyPiiVariable");
}
// Populate the repository PII data
Pipelines.RepositoryResource repository = new Pipelines.RepositoryResource();
repository.Properties.Set(
Pipelines.RepositoryPropertyNames.VersionInfo,
new Pipelines.VersionInfo()
{
Author = "MyAuthor",
Message = "MyMessage"
});
message.Resources.Repositories.Add(repository);
// Act
Pipelines.AgentJobRequestMessage scrubbedMessage = WorkerUtilities.ScrubPiiData(message);
// Assert
foreach (string piiVariable in Variables.PiiVariables)
{
scrubbedMessage.Variables.TryGetValue(piiVariable, out VariableValue value);
Assert.Equal("[PII]", value.Value);
}
foreach (string piiVariableSuffix in Variables.PiiArtifactVariableSuffixes)
{
scrubbedMessage.Variables.TryGetValue($"{Variables.PiiArtifactVariablePrefix}.MyArtifact.{piiVariableSuffix}", out VariableValue value);
Assert.Equal("[PII]", value.Value);
}
Pipelines.RepositoryResource scrubbedRepo = scrubbedMessage.Resources.Repositories[0];
Pipelines.VersionInfo scrubbedInfo = scrubbedRepo.Properties.Get<Pipelines.VersionInfo>(Pipelines.RepositoryPropertyNames.VersionInfo);
Assert.Equal("[PII]", scrubbedInfo.Author);
}
private bool IsMessageIdentical(Pipelines.AgentJobRequestMessage source, Pipelines.AgentJobRequestMessage target)
{
if (source == null && target == null)
{
return true;
}
if (source != null && target == null)
{
return false;
}
if (source == null && target != null)
{
return false;
}
if (JsonUtility.ToString(source.JobContainer) != JsonUtility.ToString(target.JobContainer))
{
return false;
}
if (source.JobDisplayName != target.JobDisplayName)
{
return false;
}
if (source.JobId != target.JobId)
{
return false;
}
if (source.JobName != target.JobName)
{
return false;
}
if (source.MaskHints.Count != target.MaskHints.Count)
{
return false;
}
if (source.MessageType != target.MessageType)
{
return false;
}
if (source.Plan.PlanId != target.Plan.PlanId)
{
return false;
}
if (source.RequestId != target.RequestId)
{
return false;
}
if (source.Resources.Endpoints.Count != target.Resources.Endpoints.Count)
{
return false;
}
if (source.Steps.Count != target.Steps.Count)
{
return false;
}
if (source.Variables.Count != target.Variables.Count)
{
return false;
}
return true;
}
}
}

3
src/Test/Properties.cs Normal file
View File

@@ -0,0 +1,3 @@
using Xunit;
[assembly: CollectionBehavior(DisableTestParallelization = true)]

70
src/Test/Test.csproj Normal file
View File

@@ -0,0 +1,70 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm;rhel.6-x64;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AssetTargetFallback>portable-net45+win8</AssetTargetFallback>
<NoWarn>NU1701;NU1603;NU1603;xUnit2013;</NoWarn>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Sdk\Sdk.csproj" />
<ProjectReference Include="..\Runner.Listener\Runner.Listener.csproj" />
<ProjectReference Include="..\Runner.Common\Runner.Common.csproj" />
<ProjectReference Include="..\Runner.Worker\Runner.Worker.csproj" />
<ProjectReference Include="..\Runner.Plugins\Runner.Plugins.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="System.Buffers" Version="4.3.0" />
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.4.0" />
<PackageReference Include="System.Threading.ThreadPool" Version="4.3.0" />
<PackageReference Include="Moq" Version="4.11.0" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(PackageRuntime)' == 'win-x64'">
<DefineConstants>OS_WINDOWS;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(PackageRuntime)' == 'win-x86'">
<DefineConstants>OS_WINDOWS;X86;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'win-x64'">
<DefineConstants>OS_WINDOWS;X64;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'win-x86'">
<DefineConstants>OS_WINDOWS;X86;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">
<DefineConstants>OS_OSX;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true' AND '$(Configuration)' == 'Debug'">
<DefineConstants>OS_OSX;DEBUG;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(PackageRuntime)' == 'linux-x64'">
<DefineConstants>OS_LINUX;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(PackageRuntime)' == 'rhel.6-x64'">
<DefineConstants>OS_LINUX;OS_RHEL6;X64;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(PackageRuntime)' == 'linux-arm'">
<DefineConstants>OS_LINUX;ARM;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'linux-x64'">
<DefineConstants>OS_LINUX;X64;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'rhel.6-x64'">
<DefineConstants>OS_LINUX;OS_RHEL6;X64;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true' AND '$(Configuration)' == 'Debug' AND '$(PackageRuntime)' == 'linux-arm'">
<DefineConstants>OS_LINUX;ARM;DEBUG;TRACE</DefineConstants>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,25 @@
name: 'Hello World'
description: 'Greet the world and record the time'
author: 'Test Corporation'
inputs:
greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true
default: 'Hello'
entryPoint: # id of input
description: 'optional docker entrypoint overwrite.'
required: false
outputs:
time: # id of output
description: 'The time we did the greeting'
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
color: 'green' # optional, decorates the entry in the GitHub Marketplace
runs:
using: 'docker'
image: 'Dockerfile'
args:
- 'bzz'
entrypoint: 'main.sh'
env:
Token: foo
Url: bar

View File

@@ -0,0 +1,25 @@
name: 'Hello World'
description: 'Greet the world and record the time'
author: 'Test Corporation'
inputs:
greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true
default: 'Hello'
entryPoint: # id of input
description: 'optional docker entrypoint overwrite.'
required: false
outputs:
time: # id of output
description: 'The time we did the greeting'
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
color: 'green' # optional, decorates the entry in the GitHub Marketplace
runs:
using: 'docker'
image: 'Dockerfile'
args:
- '${{ inputs.greeting }}'
entrypoint: 'main.sh'
env:
Token: foo
Url: '${{ inputs.entryPoint }}'

View File

@@ -0,0 +1,27 @@
name: 'Hello World'
description: 'Greet the world and record the time'
author: 'Test Corporation'
inputs:
greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true
default: 'Hello'
entryPoint: # id of input
description: 'optional docker entrypoint overwrite.'
required: false
outputs:
time: # id of output
description: 'The time we did the greeting'
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
color: 'green' # optional, decorates the entry in the GitHub Marketplace
runs:
using: 'docker'
image: 'Dockerfile'
args:
- 'bzz'
entrypoint: 'main.sh'
env:
Token: foo
Url: bar
post-entrypoint: 'cleanup.sh'
post-if: 'failure()'

View File

@@ -0,0 +1,19 @@
name: 'Hello World'
description: 'Greet the world and record the time'
author: 'Test Corporation'
inputs:
greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true
default: 'Hello'
entryPoint: # id of input
description: 'optional docker entrypoint overwrite.'
required: false
outputs:
time: # id of output
description: 'The time we did the greeting'
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
color: 'green' # optional, decorates the entry in the GitHub Marketplace
runs:
using: 'docker'
image: 'Dockerfile'

View File

@@ -0,0 +1,25 @@
name: 'Hello World'
description: 'Greet the world and record the time'
author: 'Test Corporation'
inputs:
greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true
default: 'Hello'
entryPoint: # id of input
description: 'optional docker entrypoint overwrite.'
required: false
outputs:
time: # id of output
description: 'The time we did the greeting'
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
color: 'green' # optional, decorates the entry in the GitHub Marketplace
runs:
using: 'docker'
image: 'images/Dockerfile'
args:
- '${{ inputs.greeting }}'
entrypoint: 'main.sh'
env:
Token: foo
Url: bar

View File

@@ -0,0 +1,25 @@
name: 'Hello World'
description: 'Greet the world and record the time'
author: 'Test Corporation'
inputs:
greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true
default: 'Hello'
entryPoint: # id of input
description: 'optional docker entrypoint overwrite.'
required: false
outputs:
time: # id of output
description: 'The time we did the greeting'
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
color: 'green' # optional, decorates the entry in the GitHub Marketplace
runs:
using: 'docker'
image: 'docker://ubuntu:18.04'
args:
- 'bzz'
entrypoint: 'main.sh'
env:
Token: foo
Url: bar

View File

@@ -0,0 +1,20 @@
name: 'Hello World'
description: 'Greet the world and record the time'
author: 'Test Corporation'
inputs:
greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true
default: 'Hello'
deprecationMessage: 'This property has been deprecated'
entryPoint: # id of input
description: 'optional docker entrypoint overwrite.'
required: false
outputs:
time: # id of output
description: 'The time we did the greeting'
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
color: 'green' # optional, decorates the entry in the GitHub Marketplace
runs:
using: 'node12'
main: 'main.js'

View File

@@ -0,0 +1,22 @@
name: 'Hello World'
description: 'Greet the world and record the time'
author: 'Test Corporation'
inputs:
greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true
default: 'Hello'
deprecationMessage: 'This property has been deprecated'
entryPoint: # id of input
description: 'optional docker entrypoint overwrite.'
required: false
outputs:
time: # id of output
description: 'The time we did the greeting'
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
color: 'green' # optional, decorates the entry in the GitHub Marketplace
runs:
using: 'node12'
main: 'main.js'
post: 'cleanup.js'
post-if: 'cancelled()'

View File

@@ -0,0 +1,19 @@
name: 'Hello World'
description: 'Greet the world and record the time'
author: 'Test Corporation'
inputs:
greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true
default: 'Hello'
entryPoint: # id of input
description: 'optional docker entrypoint overwrite.'
required: false
outputs:
time: # id of output
description: 'The time we did the greeting'
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
color: 'green' # optional, decorates the entry in the GitHub Marketplace
runs:
using: 'node12'
main: 'scripts/main.js'

View File

@@ -0,0 +1,18 @@
name: 'Hello World'
description: 'Greet the world and record the time'
author: 'Test Corporation'
inputs:
greeting: # id of input
description: 'The greeting we choose - will print ""{greeting}, World!"" on stdout'
required: true
default: 'Hello'
entryPoint: # id of input
description: 'optional docker entrypoint overwrite.'
required: false
outputs:
time: # id of output
description: 'The time we did the greeting'
icon: 'hello.svg' # vector art to display in the GitHub Marketplace
color: 'green' # optional, decorates the entry in the GitHub Marketplace
runs:
plugin: 'someplugin'