mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-11-02 05:16:47 +08:00
fix: handle workflow commands like GitHub's runner (#1333)
Commands the runner applies itself, like the `::set-output::` older actions still print, showed up raw in the job log. The reporter also re-parsed every line with one job-wide stop-commands and echo state, while act keeps one command handler per step, stage, composite child and hook, as GitHub does. A `::stop-commands::` left active in one of them kept the reporter suppressed for the others, so their `::add-mask::` values were never registered, and post steps logged command lines twice. - act's command handler decides what reaches the log, as GitHub's runner does: applied commands only under `ACTIONS_STEP_DEBUG` or `::echo::on`, `::stop-commands::` and its resume line shown with long tokens masked - Masks go straight to the reporter, which no longer parses command lines - One parser follows GitHub's rules: leading whitespace, `##[...]` anywhere in a line, registered commands only - Service logs honour only `::add-mask::` --------- Co-authored-by: bircni <bircni@icloud.com> Reviewed-on: https://gitea.com/gitea/runner/pulls/1333 Reviewed-by: bircni <bircni@icloud.com> Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
@@ -5,12 +5,13 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
"gitea.com/gitea/runner/act/common/logging"
|
||||
"gitea.com/gitea/runner/act/common/logging/logtest"
|
||||
|
||||
"gitea.dev/actionslib/pkg/model"
|
||||
@@ -18,9 +19,56 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseActionCommand(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name, line, resumeCommand, command string
|
||||
properties map[string]string
|
||||
data string
|
||||
legacy, ok bool
|
||||
}{
|
||||
{"plain", "plain output", "", "", nil, "", false, false},
|
||||
{"unknown v2", "::unknown::v", "", "", nil, "", false, false},
|
||||
{"missing v2 terminator", "::set-output name=x", "", "", nil, "", false, false},
|
||||
{"empty command", "::::", "", "", nil, "", false, false},
|
||||
{"text before v2", "debug ::set-output name=x::v", "", "", nil, "", false, false},
|
||||
{"missing legacy terminator", "##[debug", "", "", nil, "", false, false},
|
||||
{"tab is not a name separator", "::set-output\tname=x::v", "", "", nil, "", false, false},
|
||||
{"v2 leading unicode whitespace and line endings", " \t\u2003::set-output name=tab::v\r\n\r\n", "", "set-output", map[string]string{"name": "tab"}, "v", false, true},
|
||||
{"legacy prefix and casing", "prefix ##[SET-OUTPUT NAME=mid;]v\r\n", "", "set-output", map[string]string{"name": "mid"}, "v", true, true},
|
||||
{"only first legacy prefix", "##[unknown]##[set-output name=x]v", "", "", nil, "", false, false},
|
||||
{"v2 delimiter boundaries", "::set-output name=a:b,other=c:d:: a::b ", "", "set-output", map[string]string{"name": "a:b", "other": "c:d"}, " a::b ", false, true},
|
||||
{"legacy first closing bracket", "##[set-output name=x]v]tail", "", "set-output", map[string]string{"name": "x"}, "v]tail", true, true},
|
||||
{"v2 semicolons are literal", "::set-output name=x;y=z::v", "", "set-output", map[string]string{"name": "x;y=z"}, "v", false, true},
|
||||
{"legacy commas are literal", "##[set-output name=x,y=z]v", "", "set-output", map[string]string{"name": "x,y=z"}, "v", true, true},
|
||||
{"values keep their leading equals", "::set-output name==x=y,=key=v,empty=,=x,spaced= =x::v", "", "set-output", map[string]string{"name": "=x=y", "key": "v", "spaced": " =x"}, "v", false, true},
|
||||
{"v2 trims whole list only", "::set-output \tfile=x, line=2,name = value \t::v", "", "set-output", map[string]string{"file": "x", " line": "2", "name ": " value"}, "v", false, true},
|
||||
{"legacy preserves list whitespace", "##[set-output name=q ]v", "", "set-output", map[string]string{" name": "q "}, "v", true, true},
|
||||
{"case insensitive duplicate keys", "::SET-OUTPUT NAME=first,Name=last::v", "", "set-output", map[string]string{"name": "last"}, "v", false, true},
|
||||
{"keys stay escaped", "::set-output na%3Ame=x::v", "", "set-output", map[string]string{"na%3ame": "x"}, "v", false, true},
|
||||
{"v2 decode table", "::set-output name=%0D%0A%3A%2C%25%253A%252C%250A%2525%3a%2c%0d%0a%3B%5D::%0D%0A%25%3A%2C%3B%5D%250A", "", "set-output", map[string]string{"name": "\r\n:,%%3A%2C%0A%25%3a%2c%0d%0a%3B%5D"}, "\r\n%%3A%2C%3B%5D%0A", false, true},
|
||||
{"legacy decode table", "##[set-output name=%3B%0D%0A%5D%25%253B%255D%250A%2525%3b%5d%0d%0a%3A%2C]v%5Dw%3Bx", "", "set-output", map[string]string{"name": ";\r\n]%%3B%5D%0A%25%3b%5d%0d%0a%3A%2C"}, "v]w;x", true, true},
|
||||
{"unknown v2 falls back to legacy", "::unknown::prefix ##[set-output name=x]v", "", "set-output", map[string]string{"name": "x"}, "v", true, true},
|
||||
{"unterminated v2 falls back to legacy", "::debug ##[set-output name=x]v", "", "set-output", map[string]string{"name": "x"}, "v", true, true},
|
||||
{"v2 wins over legacy", "::debug::##[set-output name=x]v", "", "debug", map[string]string{}, "##[set-output name=x]v", false, true},
|
||||
{"internal line breaks are not commands", "::debug::hidden\rplain\r\n", "", "", nil, "", false, false},
|
||||
{"v2 resume token", "::ReSuMe-ToKeN::", "RESUME-TOKEN", "resume-token", map[string]string{}, "", false, true},
|
||||
{"legacy resume token", "prefix ##[RESUME-TOKEN]v", "resume-token", "resume-token", map[string]string{}, "v", true, true},
|
||||
{"different resume token", "::other-token::", "resume-token", "", nil, "", false, false},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
command, properties, data, legacy, ok := parseActionCommand(test.line, test.resumeCommand)
|
||||
assert.Equal(t, test.command, command)
|
||||
assert.Equal(t, test.properties, properties)
|
||||
assert.Equal(t, test.data, data)
|
||||
assert.Equal(t, test.legacy, legacy)
|
||||
assert.Equal(t, test.ok, ok)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// unsecureRC opts into ::set-env:: and ::add-path::, which are refused without it.
|
||||
func unsecureRC() *RunContext {
|
||||
return &RunContext{Env: map[string]string{allowUnsecureCommandsVar: "true"}}
|
||||
return &RunContext{Config: &Config{}, Env: map[string]string{allowUnsecureCommandsVar: "true"}}
|
||||
}
|
||||
|
||||
func TestSetEnv(t *testing.T) {
|
||||
@@ -37,212 +85,37 @@ func TestSetEnv(t *testing.T) {
|
||||
a.NotContains(rc.Env, "NODE_OPTIONS")
|
||||
a.NotContains(rc.Env, "node_options")
|
||||
entries := hook.AllRecords()
|
||||
require.Len(t, entries, 3)
|
||||
a.Equal("##[error]Can't update NODE_OPTIONS environment variable using ::set-env:: command.", entries[1].Message)
|
||||
a.Equal("##[error]Can't store NODE_OPTIONS output parameter using '$GITHUB_ENV' command.", entries[2].Message)
|
||||
}
|
||||
|
||||
func TestStopCommandsKeepsSuppressedLinesInLog(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
ctx := t.Context()
|
||||
rc := unsecureRC()
|
||||
handler := rc.commandHandler(ctx)
|
||||
|
||||
// Stop command processing until the matching end token is seen.
|
||||
a.True(handler("::stop-commands::my-end-token\n"))
|
||||
|
||||
// A command-shaped line while stopped must not be executed (env unchanged),
|
||||
// but must still return true so it reaches the raw_output log handler and is
|
||||
// not dropped from the step log.
|
||||
a.True(handler("::set-env name=x::valz\n"))
|
||||
a.NotContains(rc.Env, "x")
|
||||
|
||||
// The matching end token resumes command processing.
|
||||
a.True(handler("::my-end-token::\n"))
|
||||
|
||||
// Commands are processed again after resuming.
|
||||
a.True(handler("::set-env name=y::valy\n"))
|
||||
a.Equal("valy", rc.Env["y"])
|
||||
require.Len(t, entries, 2)
|
||||
a.Equal("##[error]Can't update NODE_OPTIONS environment variable using ::set-env:: command.", entries[0].Message)
|
||||
a.Equal("##[error]Can't store NODE_OPTIONS output parameter using '$GITHUB_ENV' command.", entries[1].Message)
|
||||
}
|
||||
|
||||
func TestSetOutput(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
ctx := t.Context()
|
||||
rc := new(RunContext)
|
||||
rc.StepResults = make(map[string]*model.StepResult)
|
||||
handler := rc.commandHandler(ctx)
|
||||
rc := &RunContext{Config: &Config{}, CurrentStep: "my-step", StepResults: map[string]*model.StepResult{"my-step": {Outputs: map[string]string{}}}}
|
||||
handler := rc.commandHandler(t.Context())
|
||||
|
||||
rc.CurrentStep = "my-step"
|
||||
rc.StepResults[rc.CurrentStep] = &model.StepResult{
|
||||
Outputs: make(map[string]string),
|
||||
}
|
||||
handler("::set-output name=x::valz\n")
|
||||
a.Equal("valz", rc.StepResults["my-step"].Outputs["x"])
|
||||
|
||||
handler("::set-output name=x::percent2%25\n")
|
||||
a.Equal("percent2%", rc.StepResults["my-step"].Outputs["x"])
|
||||
|
||||
handler("::set-output name=x::percent2%25%0Atest\n")
|
||||
a.Equal("percent2%\ntest", rc.StepResults["my-step"].Outputs["x"])
|
||||
|
||||
handler("::set-output name=x::percent2%25%0Atest another3%25test\n")
|
||||
a.Equal("percent2%\ntest another3%test", rc.StepResults["my-step"].Outputs["x"])
|
||||
|
||||
handler("::set-output name=x%3A::percent2%25%0Atest\n")
|
||||
a.Equal("percent2%\ntest", rc.StepResults["my-step"].Outputs["x:"])
|
||||
|
||||
handler("::set-output name=x%3A%2C%0A%25%0D%3A::percent2%25%0Atest\n")
|
||||
a.Equal("percent2%\ntest", rc.StepResults["my-step"].Outputs["x:,\n%\r:"])
|
||||
handler("::set-output name=symbol::std::vector")
|
||||
a.Equal("std::vector", rc.StepResults["my-step"].Outputs["symbol"])
|
||||
handler("::set-output name=a=b::value\n")
|
||||
a.Equal("value", rc.StepResults["my-step"].Outputs["a=b"])
|
||||
handler("##[set-output name=legacy%3B%5D]value%3B%5D")
|
||||
a.Equal("value;]", rc.StepResults["my-step"].Outputs["legacy;]"])
|
||||
handler("##[set-output name=bracket]value]tail")
|
||||
a.Equal("value]tail", rc.StepResults["my-step"].Outputs["bracket"])
|
||||
handler("::set-output name=modern%3B%5D::value%3B%5D\n")
|
||||
a.Equal("value%3B%5D", rc.StepResults["my-step"].Outputs["modern%3B%5D"])
|
||||
handler("::set-output NAME=modern%3A::v%0D%0A%25%3B%5D%250A\n")
|
||||
handler("##[set-output name=legacy%3B%5D]v%0D%0A%25%3B%5D%250A\n")
|
||||
assert.Equal(t, map[string]string{"modern:": "v\r\n%%3B%5D%0A", "legacy;]": "v\r\n%;]%0A"}, rc.StepResults["my-step"].Outputs)
|
||||
}
|
||||
|
||||
func TestAddpath(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
ctx := t.Context()
|
||||
rc := unsecureRC()
|
||||
handler := rc.commandHandler(ctx)
|
||||
|
||||
handler("::add-path::/zoo\n")
|
||||
a.Equal("/zoo", rc.ExtraPath[0])
|
||||
|
||||
handler("::add-path::/boo\n")
|
||||
a.Equal("/boo", rc.ExtraPath[0])
|
||||
}
|
||||
|
||||
func TestStopCommands(t *testing.T) {
|
||||
logger, hook := logtest.NewLogger()
|
||||
|
||||
a := assert.New(t)
|
||||
ctx := common.WithLogger(t.Context(), logger)
|
||||
rc := unsecureRC()
|
||||
handler := rc.commandHandler(ctx)
|
||||
|
||||
handler("::set-env name=x::valz\n")
|
||||
a.Equal("valz", rc.Env["x"])
|
||||
handler("::stop-commands::MY-END-TOKEN\n")
|
||||
handler("::set-env name=x::abcd\n")
|
||||
a.Equal("valz", rc.Env["x"])
|
||||
handler("::my-end-token::\n")
|
||||
handler("::set-env name=x::abcd\n")
|
||||
a.Equal("abcd", rc.Env["x"])
|
||||
|
||||
messages := make([]string, 0)
|
||||
for _, entry := range hook.AllRecords() {
|
||||
messages = append(messages, entry.Message)
|
||||
for _, prefix := range []string{"::add-path::", "##[add-path]"} {
|
||||
t.Run(prefix, func(t *testing.T) {
|
||||
rc := unsecureRC()
|
||||
handler := rc.commandHandler(t.Context())
|
||||
handler(prefix + "/zoo\n")
|
||||
handler(prefix + "/boo\n")
|
||||
assert.Equal(t, []string{"/boo", "/zoo"}, rc.ExtraPath)
|
||||
handler(prefix + "/zoo\n")
|
||||
assert.Equal(t, []string{"/zoo", "/boo"}, rc.ExtraPath)
|
||||
})
|
||||
}
|
||||
|
||||
a.Contains(messages, "::set-env name=x::abcd\n")
|
||||
}
|
||||
|
||||
// The end token is arbitrary, so one that happens to name a real command must still resume
|
||||
// rather than being swallowed by that command's case.
|
||||
func TestStopCommandsResumesOnCommandNamedToken(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
rc := unsecureRC()
|
||||
handler := rc.commandHandler(t.Context())
|
||||
|
||||
handler("::stop-commands::add-mask\n")
|
||||
handler("::set-env name=x::suppressed\n")
|
||||
a.NotContains(rc.Env, "x")
|
||||
|
||||
handler("::add-mask::\n")
|
||||
handler("::set-env name=x::resumed\n")
|
||||
a.Equal("resumed", rc.Env["x"])
|
||||
}
|
||||
|
||||
func TestAddpathADO(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
ctx := t.Context()
|
||||
rc := unsecureRC()
|
||||
handler := rc.commandHandler(ctx)
|
||||
|
||||
handler("##[add-path]/zoo\n")
|
||||
a.Equal("/zoo", rc.ExtraPath[0])
|
||||
|
||||
handler("##[add-path]/boo\n")
|
||||
a.Equal("/boo", rc.ExtraPath[0])
|
||||
}
|
||||
|
||||
func TestAddmask(t *testing.T) {
|
||||
logger, hook := logtest.NewLogger()
|
||||
|
||||
a := assert.New(t)
|
||||
ctx := t.Context()
|
||||
loggerCtx := common.WithLogger(ctx, logger)
|
||||
|
||||
rc := new(RunContext)
|
||||
handler := rc.commandHandler(loggerCtx)
|
||||
handler("::ADD-MASK::my::secret")
|
||||
|
||||
a.Equal("***", hook.LastRecord().Message)
|
||||
a.Equal([]string{"my::secret"}, rc.Masks)
|
||||
}
|
||||
|
||||
// based on https://stackoverflow.com/a/10476304
|
||||
func captureOutput(t *testing.T, f func()) string {
|
||||
old := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
f()
|
||||
|
||||
outC := make(chan string)
|
||||
|
||||
go func() {
|
||||
var buf bytes.Buffer
|
||||
_, err := io.Copy(&buf, r)
|
||||
if err != nil {
|
||||
a := assert.New(t)
|
||||
a.Fail("io.Copy failed")
|
||||
}
|
||||
outC <- buf.String()
|
||||
}()
|
||||
|
||||
w.Close()
|
||||
os.Stdout = old
|
||||
out := <-outC
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func TestAddmaskUsemask(t *testing.T) {
|
||||
rc := new(RunContext)
|
||||
rc.StepResults = make(map[string]*model.StepResult)
|
||||
rc.CurrentStep = "my-step"
|
||||
rc.StepResults[rc.CurrentStep] = &model.StepResult{
|
||||
Outputs: make(map[string]string),
|
||||
}
|
||||
|
||||
a := assert.New(t)
|
||||
|
||||
config := &Config{
|
||||
Secrets: map[string]string{},
|
||||
InsecureSecrets: false,
|
||||
}
|
||||
|
||||
re := captureOutput(t, func() {
|
||||
ctx := t.Context()
|
||||
ctx = WithJobLogger(ctx, "0", "testjob", config, &rc.Masks, map[string]any{})
|
||||
|
||||
handler := rc.commandHandler(ctx)
|
||||
handler("::add-mask::secret\n")
|
||||
handler("::set-output:: token=secret\n")
|
||||
})
|
||||
|
||||
a.Equal("[testjob] ***\n[testjob] ::set-output:: = token=***\n", re)
|
||||
}
|
||||
|
||||
func TestSaveState(t *testing.T) {
|
||||
rc := &RunContext{
|
||||
Config: &Config{},
|
||||
CurrentStep: "step",
|
||||
StepResults: map[string]*model.StepResult{},
|
||||
}
|
||||
@@ -278,12 +151,12 @@ func TestUnsecureCommands(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := assert.New(t)
|
||||
rc := &RunContext{Env: tt.jobEnv}
|
||||
rc := &RunContext{Config: &Config{}, Env: tt.jobEnv}
|
||||
rc.setCurrentStepEnv(tt.stepEnv)
|
||||
handler := rc.commandHandler(t.Context())
|
||||
|
||||
handler("::set-env name=x::valz\n")
|
||||
handler("::add-path::/opt/bin\n")
|
||||
a.False(handler("::set-env name=x::valz\n"))
|
||||
a.False(handler("::add-path::/opt/bin\n"))
|
||||
|
||||
if !tt.optedIn {
|
||||
a.Empty(rc.Env["x"])
|
||||
@@ -299,3 +172,109 @@ func TestUnsecureCommands(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type maskRecorder struct {
|
||||
logging.Handler
|
||||
masks []string
|
||||
}
|
||||
|
||||
func (m *maskRecorder) AddMask(mask string) { m.masks = append(m.masks, mask) }
|
||||
|
||||
func TestCommandLogWriter(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
debug bool
|
||||
steps [][]string
|
||||
log []string
|
||||
masks []string
|
||||
}{
|
||||
{
|
||||
name: "applied commands are hidden unless echoed",
|
||||
steps: [][]string{{
|
||||
"::set-env name=x::y", "::add-path::/bin", "::set-output name=a::b", "##[save-state name=a]b", "::add-matcher::m.json", "::remove-matcher owner=m::",
|
||||
"::echo::On%0A",
|
||||
"::set-env name=x::y", "::add-path::/bin", "::set-output name=a::b", "##[save-state name=a]b", "::add-matcher::m.json", "::remove-matcher owner=m::",
|
||||
"::ADD-MASK::my::secret", "::echo::bogus", "::echo::off", "::add-mask ::empty-properties", "::unknown::v", "plain",
|
||||
}},
|
||||
log: []string{
|
||||
"::set-env name=x::y", "::add-path::/bin", "::set-output name=a::b", "##[save-state name=a]b", "::add-matcher::m.json", "::remove-matcher owner=m::",
|
||||
"::add-mask::***", "::echo::bogus", "::echo::off", "::unknown::v", "plain",
|
||||
},
|
||||
masks: []string{"my::secret", "empty-properties"},
|
||||
},
|
||||
{
|
||||
name: "step debug shows debug lines and echoes commands until echo is off",
|
||||
debug: true,
|
||||
steps: [][]string{{"::debug::first%0Asecond", "::set-output name=a::b", "::echo::off", "::debug::again", "::set-output name=a::b"}},
|
||||
log: []string{"::debug::first%0Asecond", "::set-output name=a::b", "::echo::off", "::debug::again"},
|
||||
},
|
||||
{
|
||||
name: "debug lines are hidden without step debug and an invalid echo changes nothing",
|
||||
steps: [][]string{{"::echo::bogus", "::set-output name=a::b", "::echo::on", "::debug::d"}},
|
||||
},
|
||||
{
|
||||
name: "annotations fold their location once whether echoed or not",
|
||||
steps: [][]string{{
|
||||
"::echo::on",
|
||||
"::error file=main.go,line=12,col=5,title=vet::undefined: x",
|
||||
"::warning File=a%3Ab.go,Line=1,column=2,title=100%252C::50%25%0Anext",
|
||||
"::warning file=x,line=1,col=2,column=3::m",
|
||||
"::notice line=9,col=4,title=title::m",
|
||||
"::error file=x,col=2::m",
|
||||
"::error file=x,line=1::m",
|
||||
"::notice::std::vector",
|
||||
"##[error file=x%3By]m%5D",
|
||||
}},
|
||||
log: []string{
|
||||
"::error::main.go:12:5: vet: undefined: x", "::warning::a:b.go:1:2: 100%252C: 50%25%0Anext", "::warning::x:1:2: m",
|
||||
"::notice::title: m", "::error::x: m", "::error::x:1: m", "::notice::std::vector", "::error::x;y: m]",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "stop-commands keeps lines verbatim until resumed",
|
||||
steps: [][]string{{"::stop-commands::resume-token", "::add-mask::kept", "::set-output name=a::b", "::RESUME-TOKEN::", "::add-mask::masked"}},
|
||||
log: []string{"::stop-commands::resume-token", "::add-mask::kept", "::set-output name=a::b", "::RESUME-TOKEN::"},
|
||||
masks: []string{"resume-token", "masked"},
|
||||
},
|
||||
{
|
||||
name: "a short stop token naming a command stays unmasked and resumes in any case",
|
||||
steps: [][]string{{"::stop-commands::NOTICE", "::notice::", "::add-mask::masked"}},
|
||||
log: []string{"::stop-commands::NOTICE", "::notice::"},
|
||||
masks: []string{"masked"},
|
||||
},
|
||||
{
|
||||
name: "state does not carry over to the next step",
|
||||
steps: [][]string{{"::echo::on", "::stop-commands::token77"}, {"::set-output name=a::b", "::add-mask::masked"}},
|
||||
log: []string{"::stop-commands::token77"},
|
||||
masks: []string{"token77", "masked"},
|
||||
},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
logger, hook := logtest.NewLogger()
|
||||
recorder := &maskRecorder{}
|
||||
ctx := common.WithLoggerHandler(common.WithLogger(t.Context(), logger), recorder)
|
||||
rc := &RunContext{
|
||||
Config: &Config{},
|
||||
Parent: &RunContext{Config: &Config{Secrets: map[string]string{"ACTIONS_STEP_DEBUG": strconv.FormatBool(tt.debug)}}},
|
||||
Env: map[string]string{allowUnsecureCommandsVar: "true"},
|
||||
CurrentStep: "step",
|
||||
StepResults: map[string]*model.StepResult{"step": {Outputs: map[string]string{}}},
|
||||
}
|
||||
for _, lines := range tt.steps {
|
||||
writer := rc.commandLogWriter(ctx)
|
||||
for _, line := range lines {
|
||||
_, err := io.WriteString(writer, line+"\n")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
var log []string
|
||||
for _, record := range hook.AllRecords() {
|
||||
log = append(log, strings.TrimSuffix(record.Message, "\n"))
|
||||
}
|
||||
assert.Equal(t, tt.log, log)
|
||||
assert.Equal(t, tt.masks, recorder.masks)
|
||||
assert.Equal(t, tt.masks, rc.Masks)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user