diff --git a/act/runner/command.go b/act/runner/command.go index 159953c5..5516b7d7 100644 --- a/act/runner/command.go +++ b/act/runner/command.go @@ -118,7 +118,11 @@ func (rc *RunContext) commandHandler(ctx context.Context) common.LineHandler { rawLogger.Infof("%s", formatAnnotation(command, kvPairs, arg)) return false case "group", "endgroup": - return true + if strings.HasPrefix(line, "::"+command+"::") || strings.HasPrefix(line, "##["+command+"]") { + return true + } + rawLogger.Infof("::%s::%s", command, EscapeCommandData(arg)) + return false } return echoed } diff --git a/act/runner/command_test.go b/act/runner/command_test.go index 3a55d707..c86662ce 100644 --- a/act/runner/command_test.go +++ b/act/runner/command_test.go @@ -240,10 +240,23 @@ func TestCommandLogWriter(t *testing.T) { "::notice::title: m", "::error::x: m", "::error::x:1: m", "::notice::std::vector", "::error::x;y: m]", }, }, + { + name: "groups normalize unsupported prefixes and preserve recognized prefixes", + steps: [][]string{{ + "::group::a%3Bb%5Dc%250A", "::endgroup::", "##[group]a%3Bb%5Dc%250A", "##[endgroup]", + "::GrOuP::a%25b%0Ac", "::EnDgRoUp::", " \t::group::indented", " \t::endgroup::", + "prefix ##[GrOuP]a%3Bb%5Dc%253B%0A", "prefix ##[EnDgRoUp]", + }}, + log: []string{ + "::group::a%3Bb%5Dc%250A", "::endgroup::", "##[group]a%3Bb%5Dc%250A", "##[endgroup]", + "::group::a%25b%0Ac", "::endgroup::", "::group::indented", "::endgroup::", + "::group::a;b]c%253B%0A", "::endgroup::", + }, + }, { 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::"}, + steps: [][]string{{"::stop-commands::resume-token", "::add-mask::kept", "::set-output name=a::b", " \t::GrOuP::kept%3B", "prefix ##[EnDgRoUp]", "::RESUME-TOKEN::", "::add-mask::masked"}}, + log: []string{"::stop-commands::resume-token", "::add-mask::kept", "::set-output name=a::b", " \t::GrOuP::kept%3B", "prefix ##[EnDgRoUp]", "::RESUME-TOKEN::"}, masks: []string{"resume-token", "masked"}, }, { diff --git a/act/runner/expression.go b/act/runner/expression.go index 86a972a6..ea0d7583 100644 --- a/act/runner/expression.go +++ b/act/runner/expression.go @@ -136,7 +136,7 @@ func getHashFilesFunction(ctx context.Context, rc *RunContext) func(v []reflect. followSymlink := false for i, p := range v { - s := p.String() + s := exprparser.CoerceToString(p) if i == 0 && strings.HasPrefix(s, "--") { if strings.EqualFold(s, "--follow-symbolic-links") { followSymlink = true diff --git a/act/runner/expression_test.go b/act/runner/expression_test.go index b30bc14c..9a8cbf6e 100644 --- a/act/runner/expression_test.go +++ b/act/runner/expression_test.go @@ -178,7 +178,7 @@ func (c *hashFilesContainer) ReplaceLogWriter(stdout, stderr io.Writer) (io.Writ } func TestGetHashFilesFunction(t *testing.T) { - const failureMessage = "hashFiles('dir''file, *.sum') failed. Fail to hash files under directory '/workspace'" + const failureMessage = "hashFiles('dir''file, *.sum, 123, true, ') failed. Fail to hash files under directory '/workspace'" for _, test := range []struct { name string output string @@ -192,7 +192,7 @@ func TestGetHashFilesFunction(t *testing.T) { {name: "success", output: "patterns __OUTPUT__wrong__OUTPUT__ echoed\n__OUTPUT__digest__OUTPUT__", hash: "digest"}, {name: "no matches", output: "__OUTPUT____OUTPUT__"}, {name: "script failure", output: "Cannot find: node in PATH\n__OUTPUT__digest__OUTPUT__", execErr: container.ExitCodeError(1), err: failureMessage}, - {name: "timeout", execErr: context.DeadlineExceeded, err: "hashFiles('dir''file, *.sum') couldn't finish within 120 seconds"}, + {name: "timeout", execErr: context.DeadlineExceeded, err: "hashFiles('dir''file, *.sum, 123, true, ') couldn't finish within 120 seconds"}, {name: "job deadline", execErr: context.DeadlineExceeded, expired: true, err: context.DeadlineExceeded.Error()}, {name: "node missing", execErr: fmt.Errorf("wrapped: %w", container.ExitCodeError(127)), warning: true}, {name: "node not executable", execErr: container.ExitCodeError(126), warning: true}, @@ -205,7 +205,7 @@ func TestGetHashFilesFunction(t *testing.T) { jobContainer := &hashFilesContainer{stdout: stdout, stderr: stderr, copyErr: test.copyErr} jobContainer.execute = func(command []string, env map[string]string, user, workdir string) common.Executor { assert.Equal(t, []string{"node", "/var/run/act/workflow/hashfiles/index.js"}, command) - assert.Equal(t, map[string]string{"patterns": "dir'file\n*.sum", "followSymbolicLinks": "true"}, env) + assert.Equal(t, map[string]string{"patterns": "dir'file\n*.sum\n123\ntrue\n", "followSymbolicLinks": "true"}, env) assert.Empty(t, user) assert.Empty(t, workdir) return func(_ctx context.Context) error { @@ -223,7 +223,7 @@ func TestGetHashFilesFunction(t *testing.T) { result, err := getHashFilesFunction(ctx, &RunContext{ Config: &Config{Workdir: "/workspace"}, JobContainer: jobContainer, - })([]reflect.Value{reflect.ValueOf("--follow-symbolic-links"), reflect.ValueOf("dir'file"), reflect.ValueOf("*.sum")}) + })([]reflect.Value{reflect.ValueOf("--follow-symbolic-links"), reflect.ValueOf("dir'file"), reflect.ValueOf("*.sum"), reflect.ValueOf(float64(123)), reflect.ValueOf(true), {}}) if test.err == "" { require.NoError(t, err) } else {