mirror of
https://github.com/actions/runner.git
synced 2025-12-13 00:36:29 +00:00
Add support for Step Summary (#1642)
* First prototype of step summary environment variable * Fix file contention issue * Try to simplify cleaning up file references * use step id as md file name, queue file attachment * separate logic into attachment summary func * Fix indentation * Add (experimental) feature flag support * reorganize summary upload determination logic * file i/o exception handling + pr feedback * Revert changes for now to reintroduce them later * Add skeleton SetStepSummaryCommand * Update step summary feature flag name * Port ShouldUploadAttachment from previous iteration * Port QueueStepSummaryUpload from previous iteration * Improve exception handling when uploading attachment * Add some minor logging improvements * Refuse to upload files larger than 128k * Implement secrets scrubbing * Add TODO comment to remove debugging temp files * Add first tests * Add test for secret masking * Add some naming/style fixes suggested in feedback * inline check for feature flag * Inline method for style consistency * Make sure that scrubbed file doesn't exist before creating it * Rename SetStepSummaryCommand to CreateStepSummaryCommand * Fix error handling messages * Fix file command name when registering extension * Remove unnecessary file deletion Co-authored-by: Rob Herley <robherley@github.com>
This commit is contained in:
241
src/Test/L0/Worker/CreateStepSummaryCommandL0.cs
Normal file
241
src/Test/L0/Worker/CreateStepSummaryCommandL0.cs
Normal file
@@ -0,0 +1,241 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Runtime.CompilerServices;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Runner.Worker;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
using DTWebApi = GitHub.DistributedTask.WebApi;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests.Worker
|
||||
{
|
||||
public sealed class CreateStepSummaryCommandL0
|
||||
{
|
||||
private Mock<IExecutionContext> _executionContext;
|
||||
private List<Tuple<DTWebApi.Issue, string>> _issues;
|
||||
private Variables _variables;
|
||||
private string _rootDirectory;
|
||||
private CreateStepSummaryCommand _createStepCommand;
|
||||
private ITraceWriter _trace;
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void CreateStepSummaryCommand_FeatureDisabled()
|
||||
{
|
||||
using (var hostContext = Setup(featureFlagState: "false"))
|
||||
{
|
||||
var stepSummaryFile = Path.Combine(_rootDirectory, "feature-off");
|
||||
|
||||
_executionContext.Verify(e => e.QueueAttachFile(ChecksAttachmentType.StepSummary, It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||
|
||||
Assert.Equal(0, _issues.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void CreateStepSummaryCommand_FileNull()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
_createStepCommand.ProcessCommand(_executionContext.Object, null, null);
|
||||
|
||||
_executionContext.Verify(e => e.QueueAttachFile(ChecksAttachmentType.StepSummary, It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||
Assert.Equal(0, _issues.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void CreateStepSummaryCommand_DirectoryNotFound()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stepSummaryFile = Path.Combine(_rootDirectory, "directory-not-found", "env");
|
||||
|
||||
_createStepCommand.ProcessCommand(_executionContext.Object, stepSummaryFile, null);
|
||||
|
||||
_executionContext.Verify(e => e.QueueAttachFile(ChecksAttachmentType.StepSummary, It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||
Assert.Equal(0, _issues.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void CreateStepSummaryCommand_FileNotFound()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stepSummaryFile = Path.Combine(_rootDirectory, "file-not-found");
|
||||
|
||||
_createStepCommand.ProcessCommand(_executionContext.Object, stepSummaryFile, null);
|
||||
|
||||
_executionContext.Verify(e => e.QueueAttachFile(ChecksAttachmentType.StepSummary, It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||
Assert.Equal(0, _issues.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void CreateStepSummaryCommand_EmptyFile()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stepSummaryFile = Path.Combine(_rootDirectory, "empty-file");
|
||||
File.Create(stepSummaryFile).Dispose();
|
||||
|
||||
_createStepCommand.ProcessCommand(_executionContext.Object, stepSummaryFile, null);
|
||||
|
||||
_executionContext.Verify(e => e.QueueAttachFile(ChecksAttachmentType.StepSummary, It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||
Assert.Equal(0, _issues.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void CreateStepSummaryCommand_LargeFile()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stepSummaryFile = Path.Combine(_rootDirectory, "empty-file");
|
||||
File.WriteAllBytes(stepSummaryFile, new byte[128 * 1024 + 1]);
|
||||
|
||||
_createStepCommand.ProcessCommand(_executionContext.Object, stepSummaryFile, null);
|
||||
|
||||
_executionContext.Verify(e => e.QueueAttachFile(ChecksAttachmentType.StepSummary, It.IsAny<string>(), It.IsAny<string>()), Times.Never());
|
||||
Assert.Equal(1, _issues.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void CreateStepSummaryCommand_Simple()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
var stepSummaryFile = Path.Combine(_rootDirectory, "simple");
|
||||
var content = new List<string>
|
||||
{
|
||||
"# This is some markdown content",
|
||||
"",
|
||||
"## This is more markdown content",
|
||||
};
|
||||
WriteContent(stepSummaryFile, content);
|
||||
|
||||
_createStepCommand.ProcessCommand(_executionContext.Object, stepSummaryFile, null);
|
||||
|
||||
_executionContext.Verify(e => e.QueueAttachFile(ChecksAttachmentType.StepSummary, _executionContext.Object.Id.ToString(), stepSummaryFile + "-scrubbed"), Times.Once());
|
||||
Assert.Equal(0, _issues.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void CreateStepSummaryCommand_ScrubSecrets()
|
||||
{
|
||||
using (var hostContext = Setup())
|
||||
{
|
||||
// configure secretmasker to actually mask secrets
|
||||
hostContext.SecretMasker.AddRegex("Password=.*");
|
||||
hostContext.SecretMasker.AddRegex("ghs_.*");
|
||||
|
||||
var stepSummaryFile = Path.Combine(_rootDirectory, "simple");
|
||||
var scrubbedFile = stepSummaryFile + "-scrubbed";
|
||||
var content = new List<string>
|
||||
{
|
||||
"# Password=ThisIsMySecretPassword!",
|
||||
"",
|
||||
"# GITHUB_TOKEN ghs_verysecuretoken",
|
||||
};
|
||||
WriteContent(stepSummaryFile, content);
|
||||
|
||||
_createStepCommand.ProcessCommand(_executionContext.Object, stepSummaryFile, null);
|
||||
|
||||
var scrubbedFileContents = File.ReadAllText(scrubbedFile);
|
||||
Assert.DoesNotContain("ThisIsMySecretPassword!", scrubbedFileContents);
|
||||
Assert.DoesNotContain("ghs_verysecuretoken", scrubbedFileContents);
|
||||
|
||||
_executionContext.Verify(e => e.QueueAttachFile(ChecksAttachmentType.StepSummary, _executionContext.Object.Id.ToString(), scrubbedFile), Times.Once());
|
||||
Assert.Equal(0, _issues.Count);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteContent(
|
||||
string path,
|
||||
List<string> 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 = "", string featureFlagState = "true")
|
||||
{
|
||||
_issues = new List<Tuple<DTWebApi.Issue, string>>();
|
||||
|
||||
var hostContext = new TestHostContext(this, name);
|
||||
|
||||
// Trace
|
||||
_trace = hostContext.GetTrace();
|
||||
|
||||
_variables = new Variables(hostContext, new Dictionary<string, VariableValue>
|
||||
{
|
||||
{ "MySecretName", new VariableValue("My secret value", true) },
|
||||
{ "DistributedTask.UploadStepSummary", featureFlagState },
|
||||
});
|
||||
|
||||
// Directory for test data
|
||||
var workDirectory = hostContext.GetDirectory(WellKnownDirectory.Work);
|
||||
ArgUtil.NotNullOrEmpty(workDirectory, nameof(workDirectory));
|
||||
Directory.CreateDirectory(workDirectory);
|
||||
_rootDirectory = Path.Combine(workDirectory, nameof(CreateStepSummaryCommandL0));
|
||||
Directory.CreateDirectory(_rootDirectory);
|
||||
|
||||
// Execution context
|
||||
_executionContext = new Mock<IExecutionContext>();
|
||||
_executionContext.Setup(x => x.Global)
|
||||
.Returns(new GlobalContext
|
||||
{
|
||||
EnvironmentVariables = new Dictionary<string, string>(VarUtil.EnvironmentVariableKeyComparer),
|
||||
WriteDebug = true,
|
||||
Variables = _variables,
|
||||
});
|
||||
_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));
|
||||
var message = !string.IsNullOrEmpty(logMessage) ? logMessage : issue.Message;
|
||||
_trace.Info($"Issue '{issue.Type}': {message}");
|
||||
});
|
||||
_executionContext.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Callback((string tag, string message) =>
|
||||
{
|
||||
_trace.Info($"{tag}{message}");
|
||||
});
|
||||
|
||||
//CreateStepSummaryCommand
|
||||
_createStepCommand = new CreateStepSummaryCommand();
|
||||
_createStepCommand.Initialize(hostContext);
|
||||
|
||||
return hostContext;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user