using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Runtime.CompilerServices; using GitHub.Runner.Common.Util; using GitHub.Runner.Sdk; using GitHub.Runner.Worker; using GitHub.Runner.Worker.Container; using GitHub.Runner.Worker.Handlers; using Moq; using Xunit; using DTWebApi = GitHub.DistributedTask.WebApi; namespace GitHub.Runner.Common.Tests.Worker { public sealed class SetEnvFileCommandL0 { private Mock _executionContext; private List> _issues; private string _rootDirectory; private SetEnvFileCommand _setEnvFileCommand; private ITraceWriter _trace; [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void SetEnvFileCommand_DirectoryNotFound() { using (var hostContext = Setup()) { var envFile = Path.Combine(_rootDirectory, "directory-not-found", "env"); _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null); Assert.Equal(0, _issues.Count); Assert.Equal(0, _executionContext.Object.Global.EnvironmentVariables.Count); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void SetEnvFileCommand_NotFound() { using (var hostContext = Setup()) { var envFile = Path.Combine(_rootDirectory, "file-not-found"); _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null); Assert.Equal(0, _issues.Count); Assert.Equal(0, _executionContext.Object.Global.EnvironmentVariables.Count); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void SetEnvFileCommand_EmptyFile() { using (var hostContext = Setup()) { var envFile = Path.Combine(_rootDirectory, "empty-file"); var content = new List(); WriteContent(envFile, content); _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null); Assert.Equal(0, _issues.Count); Assert.Equal(0, _executionContext.Object.Global.EnvironmentVariables.Count); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void SetEnvFileCommand_Simple() { using (var hostContext = Setup()) { var envFile = Path.Combine(_rootDirectory, "simple"); var content = new List { "MY_ENV=MY VALUE", }; WriteContent(envFile, content); _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null); Assert.Equal(0, _issues.Count); Assert.Equal(1, _executionContext.Object.Global.EnvironmentVariables.Count); Assert.Equal("MY VALUE", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void SetEnvFileCommand_Simple_SkipEmptyLines() { using (var hostContext = Setup()) { var envFile = Path.Combine(_rootDirectory, "simple"); var content = new List { string.Empty, "MY_ENV=my value", string.Empty, "MY_ENV_2=my second value", string.Empty, }; WriteContent(envFile, content); _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null); Assert.Equal(0, _issues.Count); Assert.Equal(2, _executionContext.Object.Global.EnvironmentVariables.Count); Assert.Equal("my value", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]); Assert.Equal("my second value", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_2"]); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void SetEnvFileCommand_Simple_EmptyValue() { using (var hostContext = Setup()) { var envFile = Path.Combine(_rootDirectory, "simple-empty-value"); var content = new List { "MY_ENV=", }; WriteContent(envFile, content); _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null); Assert.Equal(0, _issues.Count); Assert.Equal(1, _executionContext.Object.Global.EnvironmentVariables.Count); Assert.Equal(string.Empty, _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void SetEnvFileCommand_Simple_MultipleValues() { using (var hostContext = Setup()) { var envFile = Path.Combine(_rootDirectory, "simple"); var content = new List { "MY_ENV=my value", "MY_ENV_2=", "MY_ENV_3=my third value", }; WriteContent(envFile, content); _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null); Assert.Equal(0, _issues.Count); Assert.Equal(3, _executionContext.Object.Global.EnvironmentVariables.Count); Assert.Equal("my value", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]); Assert.Equal(string.Empty, _executionContext.Object.Global.EnvironmentVariables["MY_ENV_2"]); Assert.Equal("my third value", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_3"]); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void SetEnvFileCommand_Simple_SpecialCharacters() { using (var hostContext = Setup()) { var envFile = Path.Combine(_rootDirectory, "simple"); var content = new List { "MY_ENV==abc", "MY_ENV_2=def=ghi", "MY_ENV_3=jkl=", }; WriteContent(envFile, content); _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null); Assert.Equal(0, _issues.Count); Assert.Equal(3, _executionContext.Object.Global.EnvironmentVariables.Count); Assert.Equal("=abc", _executionContext.Object.Global.EnvironmentVariables["MY_ENV"]); Assert.Equal("def=ghi", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_2"]); Assert.Equal("jkl=", _executionContext.Object.Global.EnvironmentVariables["MY_ENV_3"]); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void SetEnvFileCommand_BlockListItemsFiltered() { using (var hostContext = Setup()) { var stateFile = Path.Combine(_rootDirectory, "simple"); var content = new List { "NODE_OPTIONS=asdf", }; WriteContent(stateFile, content); _setEnvFileCommand.ProcessCommand(_executionContext.Object, stateFile, null); Assert.Equal(1, _issues.Count); Assert.Equal(0, _executionContext.Object.Global.EnvironmentVariables.Count); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void SetEnvFileCommand_BlockListItemsFiltered_Heredoc() { using (var hostContext = Setup()) { var stateFile = Path.Combine(_rootDirectory, "simple"); var content = new List { "NODE_OPTIONS< { "MY_ENV< { "MY_ENV< { string.Empty, "MY_ENV< { "MY_ENV<<=EOF", "hello", "one", "=EOF", "MY_ENV_2<< { "MY_ENV<(() => _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null)); Assert.Contains("Matching delimiter not found", ex.Message); } } [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void SetEnvFileCommand_Heredoc_MissingNewLineMultipleLinesEnv() { using (var hostContext = Setup()) { var envFile = Path.Combine(_rootDirectory, "heredoc"); var content = new List { "MY_ENV<(() => _setEnvFileCommand.ProcessCommand(_executionContext.Object, envFile, null)); Assert.Contains("EOF marker missing new line", ex.Message); } } #if OS_WINDOWS [Fact] [Trait("Level", "L0")] [Trait("Category", "Worker")] public void SetEnvFileCommand_Heredoc_PreservesNewline() { using (var hostContext = Setup()) { var newline = "\n"; var envFile = Path.Combine(_rootDirectory, "heredoc"); var content = new List { "MY_ENV< content, string newline = null) { if (string.IsNullOrEmpty(newline)) { newline = Environment.NewLine; } var encoding = new UTF8Encoding(true); // Emit BOM var contentStr = string.Join(newline, content); File.WriteAllText(path, contentStr, encoding); } private TestHostContext Setup([CallerMemberName] string name = "") { _issues = new List>(); var hostContext = new TestHostContext(this, name); // Trace _trace = hostContext.GetTrace(); // Directory for test data var workDirectory = hostContext.GetDirectory(WellKnownDirectory.Work); ArgUtil.NotNullOrEmpty(workDirectory, nameof(workDirectory)); Directory.CreateDirectory(workDirectory); _rootDirectory = Path.Combine(workDirectory, nameof(SetEnvFileCommandL0)); Directory.CreateDirectory(_rootDirectory); // Execution context _executionContext = new Mock(); _executionContext.Setup(x => x.Global) .Returns(new GlobalContext { EnvironmentVariables = new Dictionary(VarUtil.EnvironmentVariableKeyComparer), WriteDebug = true, }); _executionContext.Setup(x => x.AddIssue(It.IsAny(), It.IsAny())) .Callback((DTWebApi.Issue issue, ExecutionContextLogOptions logOptions) => { var resolvedMessage = issue.Message; if (logOptions.WriteToLog && !string.IsNullOrEmpty(logOptions.LogMessageOverride)) { resolvedMessage = logOptions.LogMessageOverride; } _issues.Add(new(issue, resolvedMessage)); _trace.Info($"Issue '{issue.Type}': {resolvedMessage}"); }); _executionContext.Setup(x => x.Write(It.IsAny(), It.IsAny())) .Callback((string tag, string message) => { _trace.Info($"{tag}{message}"); }); // SetEnvFileCommand _setEnvFileCommand = new SetEnvFileCommand(); _setEnvFileCommand.Initialize(hostContext); return hostContext; } } }