mirror of
https://github.com/actions/runner.git
synced 2025-12-18 16:26:58 +00:00
GitHub Actions Runner
This commit is contained in:
127
src/Test/L0/CommandLineParserL0.cs
Normal file
127
src/Test/L0/CommandLineParserL0.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/Test/L0/ConstantGenerationL0.cs
Normal file
29
src/Test/L0/ConstantGenerationL0.cs
Normal 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}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/Test/L0/Container/ContainerInfoL0.cs
Normal file
38
src/Test/L0/Container/ContainerInfoL0.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
130
src/Test/L0/Container/DockerUtilL0.cs
Normal file
130
src/Test/L0/Container/DockerUtilL0.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
58
src/Test/L0/DotnetsdkDownloadScriptL0.cs
Normal file
58
src/Test/L0/DotnetsdkDownloadScriptL0.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
62
src/Test/L0/ExtensionManagerL0.cs
Normal file
62
src/Test/L0/ExtensionManagerL0.cs
Normal 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}'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
84
src/Test/L0/HostContextL0.cs
Normal file
84
src/Test/L0/HostContextL0.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
574
src/Test/L0/Listener/AgentL0.cs
Normal file
574
src/Test/L0/Listener/AgentL0.cs
Normal 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());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
784
src/Test/L0/Listener/CommandSettingsL0.cs
Normal file
784
src/Test/L0/Listener/CommandSettingsL0.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/Test/L0/Listener/Configuration/AgentCredentialL0.cs
Normal file
26
src/Test/L0/Listener/Configuration/AgentCredentialL0.cs
Normal 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
220
src/Test/L0/Listener/Configuration/ConfigurationManagerL0.cs
Normal file
220
src/Test/L0/Listener/Configuration/ConfigurationManagerL0.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
228
src/Test/L0/Listener/Configuration/PromptManagerTestsL0.cs
Normal file
228
src/Test/L0/Listener/Configuration/PromptManagerTestsL0.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
502
src/Test/L0/Listener/JobDispatcherL0.cs
Normal file
502
src/Test/L0/Listener/JobDispatcherL0.cs
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
214
src/Test/L0/Listener/MessageListenerL0.cs
Normal file
214
src/Test/L0/Listener/MessageListenerL0.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
131
src/Test/L0/PagingLoggerL0.cs
Normal file
131
src/Test/L0/PagingLoggerL0.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
70
src/Test/L0/ProcessExtensionL0.cs
Normal file
70
src/Test/L0/ProcessExtensionL0.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
350
src/Test/L0/ProcessInvokerL0.cs
Normal file
350
src/Test/L0/ProcessInvokerL0.cs
Normal 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
|
||||
}
|
||||
}
|
||||
116
src/Test/L0/ProxyConfigL0.cs
Normal file
116
src/Test/L0/ProxyConfigL0.cs
Normal 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)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
115
src/Test/L0/ServiceInterfacesL0.cs
Normal file
115
src/Test/L0/ServiceInterfacesL0.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
373
src/Test/L0/TestHostContext.cs
Normal file
373
src/Test/L0/TestHostContext.cs
Normal 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
39
src/Test/L0/TestUtil.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
148
src/Test/L0/Util/ArgUtilL0.cs
Normal file
148
src/Test/L0/Util/ArgUtilL0.cs
Normal 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");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
957
src/Test/L0/Util/IOUtilL0.cs
Normal file
957
src/Test/L0/Util/IOUtilL0.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
190
src/Test/L0/Util/StringUtilL0.cs
Normal file
190
src/Test/L0/Util/StringUtilL0.cs
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
206
src/Test/L0/Util/TaskResultUtilL0.cs
Normal file
206
src/Test/L0/Util/TaskResultUtilL0.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
65
src/Test/L0/Util/UrlUtilL0.cs
Normal file
65
src/Test/L0/Util/UrlUtilL0.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
56
src/Test/L0/Util/VssUtilL0.cs
Normal file
56
src/Test/L0/Util/VssUtilL0.cs
Normal 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", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
74
src/Test/L0/Util/WhichUtilL0.cs
Normal file
74
src/Test/L0/Util/WhichUtilL0.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
211
src/Test/L0/Worker/ActionCommandL0.cs
Normal file
211
src/Test/L0/Worker/ActionCommandL0.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
150
src/Test/L0/Worker/ActionCommandManagerL0.cs
Normal file
150
src/Test/L0/Worker/ActionCommandManagerL0.cs
Normal 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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1759
src/Test/L0/Worker/ActionManagerL0.cs
Normal file
1759
src/Test/L0/Worker/ActionManagerL0.cs
Normal file
File diff suppressed because it is too large
Load Diff
497
src/Test/L0/Worker/ActionManifestManagerL0.cs
Normal file
497
src/Test/L0/Worker/ActionManifestManagerL0.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
359
src/Test/L0/Worker/ActionRunnerL0.cs
Normal file
359
src/Test/L0/Worker/ActionRunnerL0.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
259
src/Test/L0/Worker/ExecutionContextL0.cs
Normal file
259
src/Test/L0/Worker/ExecutionContextL0.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
186
src/Test/L0/Worker/ExpressionManagerL0.cs
Normal file
186
src/Test/L0/Worker/ExpressionManagerL0.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
795
src/Test/L0/Worker/IssueMatcherL0.cs
Normal file
795
src/Test/L0/Worker/IssueMatcherL0.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
386
src/Test/L0/Worker/JobExtensionL0.cs
Normal file
386
src/Test/L0/Worker/JobExtensionL0.cs
Normal 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
|
||||
// }
|
||||
// }
|
||||
223
src/Test/L0/Worker/JobRunnerL0.cs
Normal file
223
src/Test/L0/Worker/JobRunnerL0.cs
Normal 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);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
728
src/Test/L0/Worker/OutputManagerL0.cs
Normal file
728
src/Test/L0/Worker/OutputManagerL0.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
229
src/Test/L0/Worker/PipelineDirectoryManagerL0.cs
Normal file
229
src/Test/L0/Worker/PipelineDirectoryManagerL0.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
448
src/Test/L0/Worker/StepsRunnerL0.cs
Normal file
448
src/Test/L0/Worker/StepsRunnerL0.cs
Normal 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)));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
181
src/Test/L0/Worker/TaskCommandExtensionL0.cs
Normal file
181
src/Test/L0/Worker/TaskCommandExtensionL0.cs
Normal 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 });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
129
src/Test/L0/Worker/TrackingManagerL0.cs
Normal file
129
src/Test/L0/Worker/TrackingManagerL0.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
193
src/Test/L0/Worker/VariablesL0.cs
Normal file
193
src/Test/L0/Worker/VariablesL0.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
308
src/Test/L0/Worker/WorkerL0.cs
Normal file
308
src/Test/L0/Worker/WorkerL0.cs
Normal 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
3
src/Test/Properties.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
using Xunit;
|
||||
|
||||
[assembly: CollectionBehavior(DisableTestParallelization = true)]
|
||||
70
src/Test/Test.csproj
Normal file
70
src/Test/Test.csproj
Normal 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>
|
||||
25
src/Test/TestData/dockerfileaction.yml
Normal file
25
src/Test/TestData/dockerfileaction.yml
Normal 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
|
||||
25
src/Test/TestData/dockerfileaction_arg_env_expression.yml
Normal file
25
src/Test/TestData/dockerfileaction_arg_env_expression.yml
Normal 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 }}'
|
||||
27
src/Test/TestData/dockerfileaction_cleanup.yml
Normal file
27
src/Test/TestData/dockerfileaction_cleanup.yml
Normal 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()'
|
||||
@@ -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'
|
||||
25
src/Test/TestData/dockerfilerelativeaction.yml
Normal file
25
src/Test/TestData/dockerfilerelativeaction.yml
Normal 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
|
||||
25
src/Test/TestData/dockerhubaction.yml
Normal file
25
src/Test/TestData/dockerhubaction.yml
Normal 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
|
||||
20
src/Test/TestData/nodeaction.yml
Normal file
20
src/Test/TestData/nodeaction.yml
Normal 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'
|
||||
22
src/Test/TestData/nodeaction_cleanup.yml
Normal file
22
src/Test/TestData/nodeaction_cleanup.yml
Normal 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()'
|
||||
19
src/Test/TestData/noderelativeaction.yml
Normal file
19
src/Test/TestData/noderelativeaction.yml
Normal 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'
|
||||
18
src/Test/TestData/pluginaction.yml
Normal file
18
src/Test/TestData/pluginaction.yml
Normal 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'
|
||||
Reference in New Issue
Block a user