mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-05-07 15:53:24 +02:00
Merge gitea/act into act/
Merges the `gitea.com/gitea/act` fork into this repository as the `act/` directory and consumes it as a local package. The `replace github.com/nektos/act => gitea.com/gitea/act` directive is removed; act's dependencies are merged into the root `go.mod`. - Imports rewritten: `github.com/nektos/act/pkg/...` → `gitea.com/gitea/act_runner/act/...` (flattened — `pkg/` boundary dropped to match the layout forgejo-runner adopted). - Dropped act's CLI (`cmd/`, `main.go`) and all upstream project files; kept the library tree + `LICENSE`. - Added `// Copyright <year> The Gitea Authors ...` / `// Copyright <year> nektos` headers to 104 `.go` files. - Pre-existing act lint violations annotated inline with `//nolint:<linter> // pre-existing issue from nektos/act`. `.golangci.yml` is unchanged vs `main`. - Makefile test target: `-race -short` (matches forgejo-runner). - Pre-existing integration test failures fixed: race in parallel executor (atomic counters); TestSetupEnv / command_test / expression_test / run_context_test updated to match gitea fork runtime; TestJobExecutor and TestActionCache gated on `testing.Short()`. Full `gitea/act` commit history is reachable via the second parent. Co-Authored-By: Claude (Opus 4.7) <noreply@anthropic.com>
This commit is contained in:
323
act/runner/expression_test.go
Normal file
323
act/runner/expression_test.go
Normal file
@@ -0,0 +1,323 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2020 The nektos/act Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package runner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"gitea.com/gitea/act_runner/act/exprparser"
|
||||
"gitea.com/gitea/act_runner/act/model"
|
||||
|
||||
assert "github.com/stretchr/testify/assert"
|
||||
yaml "go.yaml.in/yaml/v4"
|
||||
)
|
||||
|
||||
func createRunContext(t *testing.T) *RunContext {
|
||||
var yml yaml.Node
|
||||
err := yml.Encode(map[string][]any{
|
||||
"os": {"Linux", "Windows"},
|
||||
"foo": {"bar", "baz"},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
return &RunContext{
|
||||
Config: &Config{
|
||||
Workdir: ".",
|
||||
Secrets: map[string]string{
|
||||
"CASE_INSENSITIVE_SECRET": "value",
|
||||
},
|
||||
Vars: map[string]string{
|
||||
"CASE_INSENSITIVE_VAR": "value",
|
||||
},
|
||||
},
|
||||
Env: map[string]string{
|
||||
"key": "value",
|
||||
},
|
||||
Run: &model.Run{
|
||||
JobID: "job1",
|
||||
Workflow: &model.Workflow{
|
||||
Name: "test-workflow",
|
||||
Jobs: map[string]*model.Job{
|
||||
"job1": {
|
||||
Strategy: &model.Strategy{
|
||||
RawMatrix: yml,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Matrix: map[string]any{
|
||||
"os": "Linux",
|
||||
"foo": "bar",
|
||||
},
|
||||
StepResults: map[string]*model.StepResult{
|
||||
"idwithnothing": {
|
||||
Conclusion: model.StepStatusSuccess,
|
||||
Outcome: model.StepStatusFailure,
|
||||
Outputs: map[string]string{
|
||||
"foowithnothing": "barwithnothing",
|
||||
},
|
||||
},
|
||||
"id-with-hyphens": {
|
||||
Conclusion: model.StepStatusSuccess,
|
||||
Outcome: model.StepStatusFailure,
|
||||
Outputs: map[string]string{
|
||||
"foo-with-hyphens": "bar-with-hyphens",
|
||||
},
|
||||
},
|
||||
"id_with_underscores": {
|
||||
Conclusion: model.StepStatusSuccess,
|
||||
Outcome: model.StepStatusFailure,
|
||||
Outputs: map[string]string{
|
||||
"foo_with_underscores": "bar_with_underscores",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateRunContext(t *testing.T) {
|
||||
rc := createRunContext(t)
|
||||
ee := rc.NewExpressionEvaluator(context.Background())
|
||||
|
||||
tables := []struct {
|
||||
in string
|
||||
out any
|
||||
errMesg string
|
||||
}{
|
||||
{" 1 ", 1, ""},
|
||||
// {"1 + 3", "4", ""},
|
||||
// {"(1 + 3) * -2", "-8", ""},
|
||||
{"'my text'", "my text", ""},
|
||||
{"contains('my text', 'te')", true, ""},
|
||||
{"contains('my TEXT', 'te')", true, ""},
|
||||
{"contains(fromJSON('[\"my text\"]'), 'te')", false, ""},
|
||||
{"contains(fromJSON('[\"foo\",\"bar\"]'), 'bar')", true, ""},
|
||||
{"startsWith('hello world', 'He')", true, ""},
|
||||
{"endsWith('hello world', 'ld')", true, ""},
|
||||
{"format('0:{0} 2:{2} 1:{1}', 'zero', 'one', 'two')", "0:zero 2:two 1:one", ""},
|
||||
{"join(fromJSON('[\"hello\"]'),'octocat')", "hello", ""},
|
||||
{"join(fromJSON('[\"hello\",\"mona\",\"the\"]'),'octocat')", "hellooctocatmonaoctocatthe", ""},
|
||||
{"join('hello','mona')", "hello", ""},
|
||||
{"toJSON(env)", "{\n \"ACT\": \"true\",\n \"ACT_SKIP_CHECKOUT\": \"true\",\n \"key\": \"value\"\n}", ""},
|
||||
{"toJson(env)", "{\n \"ACT\": \"true\",\n \"ACT_SKIP_CHECKOUT\": \"true\",\n \"key\": \"value\"\n}", ""},
|
||||
{"(fromJSON('{\"foo\":\"bar\"}')).foo", "bar", ""},
|
||||
{"(fromJson('{\"foo\":\"bar\"}')).foo", "bar", ""},
|
||||
{"(fromJson('[\"foo\",\"bar\"]'))[1]", "bar", ""},
|
||||
// github does return an empty string for non-existent files
|
||||
{"hashFiles('**/non-extant-files')", "", ""},
|
||||
{"hashFiles('**/non-extant-files', '**/more-non-extant-files')", "", ""},
|
||||
{"hashFiles('**/non.extant.files')", "", ""},
|
||||
{"hashFiles('**/non''extant''files')", "", ""},
|
||||
{"success()", true, ""},
|
||||
{"failure()", false, ""},
|
||||
{"always()", true, ""},
|
||||
{"cancelled()", false, ""},
|
||||
{"github.workflow", "test-workflow", ""},
|
||||
{"github.actor", "nektos/act", ""},
|
||||
{"github.run_id", "1", ""},
|
||||
{"github.run_number", "1", ""},
|
||||
{"job.status", "success", ""},
|
||||
{"matrix.os", "Linux", ""},
|
||||
{"matrix.foo", "bar", ""},
|
||||
{"env.key", "value", ""},
|
||||
{"secrets.CASE_INSENSITIVE_SECRET", "value", ""},
|
||||
{"secrets.case_insensitive_secret", "value", ""},
|
||||
{"vars.CASE_INSENSITIVE_VAR", "value", ""},
|
||||
{"vars.case_insensitive_var", "value", ""},
|
||||
{"format('{{0}}', 'test')", "{0}", ""},
|
||||
{"format('{{{0}}}', 'test')", "{test}", ""},
|
||||
{"format('}}')", "}", ""},
|
||||
{"format('echo Hello {0} ${{Test}}', 'World')", "echo Hello World ${Test}", ""},
|
||||
{"format('echo Hello {0} ${{Test}}', github.undefined_property)", "echo Hello ${Test}", ""},
|
||||
{"format('echo Hello {0}{1} ${{Te{0}st}}', github.undefined_property, 'World')", "echo Hello World ${Test}", ""},
|
||||
{"format('{0}', '{1}', 'World')", "{1}", ""},
|
||||
{"format('{{{0}', '{1}', 'World')", "{{1}", ""},
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
t.Run(table.in, func(t *testing.T) {
|
||||
assertObject := assert.New(t)
|
||||
out, err := ee.evaluate(context.Background(), table.in, exprparser.DefaultStatusCheckNone)
|
||||
if table.errMesg == "" {
|
||||
assertObject.NoError(err, table.in) //nolint:testifylint // pre-existing issue from nektos/act
|
||||
assertObject.Equal(table.out, out, table.in)
|
||||
} else {
|
||||
assertObject.Error(err, table.in) //nolint:testifylint // pre-existing issue from nektos/act
|
||||
assertObject.Equal(table.errMesg, err.Error(), table.in)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateStep(t *testing.T) {
|
||||
rc := createRunContext(t)
|
||||
step := &stepRun{
|
||||
RunContext: rc,
|
||||
}
|
||||
|
||||
ee := rc.NewStepExpressionEvaluator(context.Background(), step)
|
||||
|
||||
tables := []struct {
|
||||
in string
|
||||
out any
|
||||
errMesg string
|
||||
}{
|
||||
{"steps.idwithnothing.conclusion", model.StepStatusSuccess.String(), ""},
|
||||
{"steps.idwithnothing.outcome", model.StepStatusFailure.String(), ""},
|
||||
{"steps.idwithnothing.outputs.foowithnothing", "barwithnothing", ""},
|
||||
{"steps.id-with-hyphens.conclusion", model.StepStatusSuccess.String(), ""},
|
||||
{"steps.id-with-hyphens.outcome", model.StepStatusFailure.String(), ""},
|
||||
{"steps.id-with-hyphens.outputs.foo-with-hyphens", "bar-with-hyphens", ""},
|
||||
{"steps.id_with_underscores.conclusion", model.StepStatusSuccess.String(), ""},
|
||||
{"steps.id_with_underscores.outcome", model.StepStatusFailure.String(), ""},
|
||||
{"steps.id_with_underscores.outputs.foo_with_underscores", "bar_with_underscores", ""},
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
t.Run(table.in, func(t *testing.T) {
|
||||
assertObject := assert.New(t)
|
||||
out, err := ee.evaluate(context.Background(), table.in, exprparser.DefaultStatusCheckNone)
|
||||
if table.errMesg == "" {
|
||||
assertObject.NoError(err, table.in) //nolint:testifylint // pre-existing issue from nektos/act
|
||||
assertObject.Equal(table.out, out, table.in)
|
||||
} else {
|
||||
assertObject.Error(err, table.in) //nolint:testifylint // pre-existing issue from nektos/act
|
||||
assertObject.Equal(table.errMesg, err.Error(), table.in)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterpolate(t *testing.T) {
|
||||
rc := &RunContext{
|
||||
Config: &Config{
|
||||
Workdir: ".",
|
||||
Secrets: map[string]string{
|
||||
"CASE_INSENSITIVE_SECRET": "value",
|
||||
},
|
||||
Vars: map[string]string{
|
||||
"CASE_INSENSITIVE_VAR": "value",
|
||||
},
|
||||
},
|
||||
Env: map[string]string{
|
||||
"KEYWITHNOTHING": "valuewithnothing",
|
||||
"KEY-WITH-HYPHENS": "value-with-hyphens",
|
||||
"KEY_WITH_UNDERSCORES": "value_with_underscores",
|
||||
"SOMETHING_TRUE": "true",
|
||||
"SOMETHING_FALSE": "false",
|
||||
},
|
||||
Run: &model.Run{
|
||||
JobID: "job1",
|
||||
Workflow: &model.Workflow{
|
||||
Name: "test-workflow",
|
||||
Jobs: map[string]*model.Job{
|
||||
"job1": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
ee := rc.NewExpressionEvaluator(context.Background())
|
||||
tables := []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{" text ", " text "},
|
||||
{" $text ", " $text "},
|
||||
{" ${text} ", " ${text} "},
|
||||
{" ${{ 1 }} to ${{2}} ", " 1 to 2 "},
|
||||
{" ${{ (true || false) }} to ${{2}} ", " true to 2 "},
|
||||
{" ${{ (false || '}}' ) }} to ${{2}} ", " }} to 2 "},
|
||||
{" ${{ env.KEYWITHNOTHING }} ", " valuewithnothing "},
|
||||
{" ${{ env.KEY-WITH-HYPHENS }} ", " value-with-hyphens "},
|
||||
{" ${{ env.KEY_WITH_UNDERSCORES }} ", " value_with_underscores "},
|
||||
{"${{ secrets.CASE_INSENSITIVE_SECRET }}", "value"},
|
||||
{"${{ secrets.case_insensitive_secret }}", "value"},
|
||||
{"${{ vars.CASE_INSENSITIVE_VAR }}", "value"},
|
||||
{"${{ vars.case_insensitive_var }}", "value"},
|
||||
{"${{ env.UNKNOWN }}", ""},
|
||||
{"${{ env.SOMETHING_TRUE }}", "true"},
|
||||
{"${{ env.SOMETHING_FALSE }}", "false"},
|
||||
{"${{ !env.SOMETHING_TRUE }}", "false"},
|
||||
{"${{ !env.SOMETHING_FALSE }}", "false"},
|
||||
{"${{ !env.SOMETHING_TRUE && true }}", "false"},
|
||||
{"${{ !env.SOMETHING_FALSE && true }}", "false"},
|
||||
{"${{ env.SOMETHING_TRUE && true }}", "true"},
|
||||
{"${{ env.SOMETHING_FALSE && true }}", "true"},
|
||||
{"${{ !env.SOMETHING_TRUE || true }}", "true"},
|
||||
{"${{ !env.SOMETHING_FALSE || true }}", "true"},
|
||||
{"${{ !env.SOMETHING_TRUE && false }}", "false"},
|
||||
{"${{ !env.SOMETHING_FALSE && false }}", "false"},
|
||||
{"${{ !env.SOMETHING_TRUE || false }}", "false"},
|
||||
{"${{ !env.SOMETHING_FALSE || false }}", "false"},
|
||||
{"${{ env.SOMETHING_TRUE || false }}", "true"},
|
||||
{"${{ env.SOMETHING_FALSE || false }}", "false"},
|
||||
{"${{ env.SOMETHING_FALSE }} && ${{ env.SOMETHING_TRUE }}", "false && true"},
|
||||
{"${{ fromJSON('{}') < 2 }}", "false"},
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
t.Run("interpolate", func(t *testing.T) {
|
||||
assertObject := assert.New(t)
|
||||
out := ee.Interpolate(context.Background(), table.in)
|
||||
assertObject.Equal(table.out, out, table.in)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRewriteSubExpression(t *testing.T) {
|
||||
table := []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{in: "Hello World", out: "Hello World"},
|
||||
{in: "${{ true }}", out: "${{ true }}"},
|
||||
{in: "${{ true }} ${{ true }}", out: "format('{0} {1}', true, true)"},
|
||||
{in: "${{ true || false }} ${{ true && true }}", out: "format('{0} {1}', true || false, true && true)"},
|
||||
{in: "${{ '}}' }}", out: "${{ '}}' }}"},
|
||||
{in: "${{ '''}}''' }}", out: "${{ '''}}''' }}"},
|
||||
{in: "${{ '''' }}", out: "${{ '''' }}"},
|
||||
{in: `${{ fromJSON('"}}"') }}`, out: `${{ fromJSON('"}}"') }}`},
|
||||
{in: `${{ fromJSON('"\"}}\""') }}`, out: `${{ fromJSON('"\"}}\""') }}`},
|
||||
{in: `${{ fromJSON('"''}}"') }}`, out: `${{ fromJSON('"''}}"') }}`},
|
||||
{in: "Hello ${{ 'World' }}", out: "format('Hello {0}', 'World')"},
|
||||
}
|
||||
|
||||
for _, table := range table {
|
||||
t.Run("TestRewriteSubExpression", func(t *testing.T) {
|
||||
assertObject := assert.New(t)
|
||||
out, err := rewriteSubExpression(context.Background(), table.in, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertObject.Equal(table.out, out, table.in)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRewriteSubExpressionForceFormat(t *testing.T) {
|
||||
table := []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{in: "Hello World", out: "Hello World"},
|
||||
{in: "${{ true }}", out: "format('{0}', true)"},
|
||||
{in: "${{ '}}' }}", out: "format('{0}', '}}')"},
|
||||
{in: `${{ fromJSON('"}}"') }}`, out: `format('{0}', fromJSON('"}}"'))`},
|
||||
{in: "Hello ${{ 'World' }}", out: "format('Hello {0}', 'World')"},
|
||||
}
|
||||
|
||||
for _, table := range table {
|
||||
t.Run("TestRewriteSubExpressionForceFormat", func(t *testing.T) {
|
||||
assertObject := assert.New(t)
|
||||
out, err := rewriteSubExpression(context.Background(), table.in, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertObject.Equal(table.out, out, table.in)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user