mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-07-07 14:09:25 +00:00
test: Enhance Coverage + CI (#1055)
Reviewed-on: https://gitea.com/gitea/runner/pulls/1055 Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: bircni <bircni@icloud.com>
This commit is contained in:
63
act/model/action_test.go
Normal file
63
act/model/action_test.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReadActionDefaultsAndCaseInsensitiveUsing(t *testing.T) {
|
||||
action, err := ReadAction(strings.NewReader(`
|
||||
name: example
|
||||
runs:
|
||||
using: NoDe24
|
||||
main: dist/index.js
|
||||
`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if action.Runs.Using != ActionRunsUsingNode24 {
|
||||
t.Fatalf("using = %q, want %q", action.Runs.Using, ActionRunsUsingNode24)
|
||||
}
|
||||
if action.Runs.PreIf != "always()" {
|
||||
t.Fatalf("pre-if = %q, want always()", action.Runs.PreIf)
|
||||
}
|
||||
if action.Runs.PostIf != "always()" {
|
||||
t.Fatalf("post-if = %q, want always()", action.Runs.PostIf)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadActionPreservesExplicitConditions(t *testing.T) {
|
||||
action, err := ReadAction(strings.NewReader(`
|
||||
runs:
|
||||
using: composite
|
||||
pre-if: success()
|
||||
post-if: failure()
|
||||
steps:
|
||||
- run: echo hello
|
||||
`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if action.Runs.PreIf != "success()" || action.Runs.PostIf != "failure()" {
|
||||
t.Fatalf("conditions = %q/%q, want explicit values", action.Runs.PreIf, action.Runs.PostIf)
|
||||
}
|
||||
if !action.Runs.Using.IsComposite() || action.Runs.Using.IsDocker() || action.Runs.Using.IsNode() {
|
||||
t.Fatalf("unexpected using predicates for %q", action.Runs.Using)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadActionRejectsUnknownUsing(t *testing.T) {
|
||||
_, err := ReadAction(strings.NewReader(`
|
||||
runs:
|
||||
using: node99
|
||||
`))
|
||||
if err == nil {
|
||||
t.Fatal("expected unknown runs.using to fail")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "node99") {
|
||||
t.Fatalf("error = %q, want invalid value", err)
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,12 @@ package model
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type WorkflowPlanTest struct {
|
||||
@@ -65,3 +67,133 @@ func TestWorkflow(t *testing.T) {
|
||||
assert.NoError(t, err) //nolint:testifylint // pre-existing issue from nektos/act
|
||||
assert.NotNil(t, result)
|
||||
}
|
||||
|
||||
func TestNewSingleWorkflowPlannerAndPlanMethods(t *testing.T) {
|
||||
planner, err := NewSingleWorkflowPlanner("ci.yml", strings.NewReader(`
|
||||
name: CI
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
build:
|
||||
name: Build project
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: make build
|
||||
test:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: make test
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, []string{"pull_request", "push"}, planner.GetEvents())
|
||||
|
||||
eventPlan, err := planner.PlanEvent("push")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, eventPlan.Stages, 2)
|
||||
assert.Equal(t, []string{"build"}, eventPlan.Stages[0].GetJobIDs())
|
||||
assert.Equal(t, []string{"test"}, eventPlan.Stages[1].GetJobIDs())
|
||||
assert.Equal(t, len("Build project"), eventPlan.MaxRunNameLen())
|
||||
assert.Equal(t, "Build project", eventPlan.Stages[0].Runs[0].String())
|
||||
assert.Equal(t, "build", eventPlan.Stages[0].Runs[0].JobID)
|
||||
assert.NotNil(t, eventPlan.Stages[0].Runs[0].Job())
|
||||
|
||||
jobPlan, err := planner.PlanJob("test")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, jobPlan.Stages, 2)
|
||||
assert.Equal(t, []string{"build"}, jobPlan.Stages[0].GetJobIDs())
|
||||
assert.Equal(t, []string{"test"}, jobPlan.Stages[1].GetJobIDs())
|
||||
|
||||
allPlan, err := planner.PlanAll()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, allPlan.Stages, 2)
|
||||
assert.Equal(t, []string{"build"}, allPlan.Stages[0].GetJobIDs())
|
||||
assert.Equal(t, []string{"test"}, allPlan.Stages[1].GetJobIDs())
|
||||
}
|
||||
|
||||
func TestCombineWorkflowPlannerMergesWorkflowStages(t *testing.T) {
|
||||
first := mustReadWorkflow(t, `
|
||||
name: First
|
||||
on: push
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: make build
|
||||
`)
|
||||
second := mustReadWorkflow(t, `
|
||||
name: Second
|
||||
on: push
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: make lint
|
||||
test:
|
||||
needs: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: make test
|
||||
`)
|
||||
|
||||
planner := CombineWorkflowPlanner(first, second)
|
||||
plan, err := planner.PlanEvent("push")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, plan.Stages, 2)
|
||||
assert.ElementsMatch(t, []string{"build", "lint"}, plan.Stages[0].GetJobIDs())
|
||||
assert.Equal(t, []string{"test"}, plan.Stages[1].GetJobIDs())
|
||||
|
||||
empty, err := planner.PlanEvent("schedule")
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, empty.Stages)
|
||||
}
|
||||
|
||||
func TestPlannerErrorsForMissingAndCyclicJobs(t *testing.T) {
|
||||
workflow := mustReadWorkflow(t, `
|
||||
name: Cyclic
|
||||
on: push
|
||||
jobs:
|
||||
a:
|
||||
needs: b
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo a
|
||||
b:
|
||||
needs: a
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo b
|
||||
`)
|
||||
planner := CombineWorkflowPlanner(workflow)
|
||||
|
||||
plan, err := planner.PlanJob("missing")
|
||||
require.Error(t, err)
|
||||
assert.Empty(t, plan.Stages)
|
||||
assert.Contains(t, err.Error(), "Could not find any stages")
|
||||
|
||||
plan, err = planner.PlanEvent("push")
|
||||
require.Error(t, err)
|
||||
assert.Empty(t, plan.Stages)
|
||||
assert.Contains(t, err.Error(), "unable to build dependency graph")
|
||||
}
|
||||
|
||||
func TestNewSingleWorkflowPlannerErrors(t *testing.T) {
|
||||
_, err := NewSingleWorkflowPlanner("empty.yml", strings.NewReader(""))
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "file is empty")
|
||||
|
||||
_, err = NewSingleWorkflowPlanner("invalid.yml", strings.NewReader("jobs: ["))
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "workflow is not valid")
|
||||
}
|
||||
|
||||
func mustReadWorkflow(t *testing.T, content string) *Workflow {
|
||||
t.Helper()
|
||||
|
||||
workflow, err := ReadWorkflow(strings.NewReader(content))
|
||||
require.NoError(t, err)
|
||||
if workflow.Name == "" {
|
||||
workflow.Name = "workflow"
|
||||
}
|
||||
return workflow
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -58,6 +59,190 @@ func TestJobNeedsResult(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobSetContinueOnErrorFirmFailureWins(t *testing.T) {
|
||||
job := &Job{}
|
||||
job.SetContinueOnError(true)
|
||||
assert.True(t, job.ContinueOnError)
|
||||
|
||||
job.SetContinueOnError(false)
|
||||
assert.False(t, job.ContinueOnError)
|
||||
|
||||
job.SetContinueOnError(true)
|
||||
assert.False(t, job.ContinueOnError, "a later tolerated failure must not hide an earlier firm failure")
|
||||
}
|
||||
|
||||
func TestStepStatusText(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
status stepStatus
|
||||
text string
|
||||
}{
|
||||
{StepStatusSuccess, "success"},
|
||||
{StepStatusFailure, "failure"},
|
||||
{StepStatusSkipped, "skipped"},
|
||||
} {
|
||||
t.Run(tc.text, func(t *testing.T) {
|
||||
got, err := tc.status.MarshalText()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.text, string(got))
|
||||
|
||||
var parsed stepStatus
|
||||
require.NoError(t, parsed.UnmarshalText(got))
|
||||
assert.Equal(t, tc.status, parsed)
|
||||
assert.Equal(t, tc.text, parsed.String())
|
||||
})
|
||||
}
|
||||
|
||||
var parsed stepStatus
|
||||
require.Error(t, parsed.UnmarshalText([]byte("cancelled")))
|
||||
assert.Empty(t, stepStatus(99).String())
|
||||
}
|
||||
|
||||
func TestWorkflowCallConfig(t *testing.T) {
|
||||
workflow, err := ReadWorkflow(strings.NewReader(`
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
name:
|
||||
required: true
|
||||
type: string
|
||||
outputs:
|
||||
digest:
|
||||
value: ${{ jobs.build.outputs.digest }}
|
||||
jobs: {}
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
|
||||
config := workflow.WorkflowCallConfig()
|
||||
require.NotNil(t, config)
|
||||
require.Contains(t, config.Inputs, "name")
|
||||
assert.True(t, config.Inputs["name"].Required)
|
||||
assert.Equal(t, "string", config.Inputs["name"].Type)
|
||||
assert.Equal(t, "${{ jobs.build.outputs.digest }}", config.Outputs["digest"].Value)
|
||||
|
||||
listWorkflow, err := ReadWorkflow(strings.NewReader("on: [workflow_call]\njobs: {}\n"))
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, listWorkflow.WorkflowCallConfig())
|
||||
assert.Empty(t, listWorkflow.WorkflowCallConfig().Inputs)
|
||||
}
|
||||
|
||||
func TestJobSecretsAndEnvironment(t *testing.T) {
|
||||
inheritJob := readJob(t, `
|
||||
secrets: inherit
|
||||
env:
|
||||
A: one
|
||||
B: two
|
||||
`)
|
||||
assert.True(t, inheritJob.InheritSecrets())
|
||||
assert.Nil(t, inheritJob.Secrets())
|
||||
assert.Equal(t, map[string]string{"A": "one", "B": "two"}, inheritJob.Environment())
|
||||
|
||||
mappingJob := readJob(t, `
|
||||
secrets:
|
||||
TOKEN: ${{ secrets.TOKEN }}
|
||||
`)
|
||||
assert.False(t, mappingJob.InheritSecrets())
|
||||
assert.Equal(t, map[string]string{"TOKEN": "${{ secrets.TOKEN }}"}, mappingJob.Secrets())
|
||||
}
|
||||
|
||||
func TestJobTypeAndString(t *testing.T) {
|
||||
tests := []struct {
|
||||
job Job
|
||||
want JobType
|
||||
wantErr bool
|
||||
}{
|
||||
{job: Job{}, want: JobTypeDefault},
|
||||
{job: Job{Uses: "./.github/workflows/reuse.yml"}, want: JobTypeReusableWorkflowLocal},
|
||||
{job: Job{Uses: "owner/repo/.github/workflows/reuse.yaml@v1"}, want: JobTypeReusableWorkflowRemote},
|
||||
{job: Job{Uses: "owner/repo/.github/workflows/reuse.yaml"}, want: JobTypeInvalid, wantErr: true},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(fmt.Sprintf("%s/%s", tc.job.Uses, tc.want), func(t *testing.T) {
|
||||
got, err := tc.job.Type()
|
||||
if tc.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
|
||||
assert.Equal(t, "default", JobTypeDefault.String())
|
||||
assert.Equal(t, "local-reusable-workflow", JobTypeReusableWorkflowLocal.String())
|
||||
assert.Equal(t, "remote-reusable-workflow", JobTypeReusableWorkflowRemote.String())
|
||||
assert.Equal(t, "unknown", JobType(99).String())
|
||||
}
|
||||
|
||||
func TestStepStringEnvironmentEnvAndType(t *testing.T) {
|
||||
step := readStep(t, `
|
||||
id: example
|
||||
env:
|
||||
DIRECT: value
|
||||
with:
|
||||
mixed-key: input
|
||||
`)
|
||||
assert.Equal(t, "example", step.String())
|
||||
assert.Equal(t, map[string]string{"DIRECT": "value"}, step.Environment())
|
||||
assert.Equal(t, map[string]string{"DIRECT": "value", "INPUT_MIXED-KEY": "input"}, step.GetEnv())
|
||||
|
||||
for _, tc := range []struct {
|
||||
step Step
|
||||
want StepType
|
||||
}{
|
||||
{step: Step{}, want: StepTypeInvalid},
|
||||
{step: Step{Run: "echo hi"}, want: StepTypeRun},
|
||||
{step: Step{Run: "echo hi", Uses: "actions/checkout@v4"}, want: StepTypeInvalid},
|
||||
{step: Step{Uses: "docker://alpine:latest"}, want: StepTypeUsesDockerURL},
|
||||
{step: Step{Uses: "./.github/workflows/reuse.yml"}, want: StepTypeReusableWorkflowLocal},
|
||||
{step: Step{Uses: "owner/repo/.github/workflows/reuse.yml@v1"}, want: StepTypeReusableWorkflowRemote},
|
||||
{step: Step{Uses: "./actions/local"}, want: StepTypeUsesActionLocal},
|
||||
{step: Step{Uses: "actions/checkout@v4"}, want: StepTypeUsesActionRemote},
|
||||
} {
|
||||
t.Run(tc.want.String(), func(t *testing.T) {
|
||||
assert.Equal(t, tc.want, tc.step.Type())
|
||||
})
|
||||
}
|
||||
|
||||
assert.Equal(t, "invalid", StepTypeInvalid.String())
|
||||
assert.Equal(t, "run", StepTypeRun.String())
|
||||
assert.Equal(t, "local-action", StepTypeUsesActionLocal.String())
|
||||
assert.Equal(t, "remote-action", StepTypeUsesActionRemote.String())
|
||||
assert.Equal(t, "docker", StepTypeUsesDockerURL.String())
|
||||
assert.Equal(t, "local-reusable-workflow", StepTypeReusableWorkflowLocal.String())
|
||||
assert.Equal(t, "remote-reusable-workflow", StepTypeReusableWorkflowRemote.String())
|
||||
assert.Equal(t, "unknown", StepType(99).String())
|
||||
assert.NotEmpty(t, (&Step{Uses: "actions/checkout@v4"}).UsesHash())
|
||||
}
|
||||
|
||||
func TestWorkflowGetJobAndIDs(t *testing.T) {
|
||||
workflow := &Workflow{Jobs: map[string]*Job{"build": {}}}
|
||||
assert.Equal(t, []string{"build"}, workflow.GetJobIDs())
|
||||
|
||||
job := workflow.GetJob("build")
|
||||
require.NotNil(t, job)
|
||||
assert.Equal(t, "build", job.Name)
|
||||
assert.Equal(t, "success()", job.If.Value)
|
||||
assert.Nil(t, workflow.GetJob("missing"))
|
||||
}
|
||||
|
||||
func TestRawConcurrencyYaml(t *testing.T) {
|
||||
var expr RawConcurrency
|
||||
require.NoError(t, yaml.Unmarshal([]byte("group-${{ github.ref }}"), &expr))
|
||||
assert.Equal(t, "group-${{ github.ref }}", expr.RawExpression)
|
||||
marshaled, err := expr.MarshalYAML()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "group-${{ github.ref }}", marshaled)
|
||||
|
||||
var object RawConcurrency
|
||||
require.NoError(t, yaml.Unmarshal([]byte("group: ci\ncancel-in-progress: true\n"), &object))
|
||||
assert.Equal(t, "ci", object.Group)
|
||||
assert.Equal(t, "true", object.CancelInProgress)
|
||||
marshaled, err = object.MarshalYAML()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, (*objectConcurrency)(&object), marshaled)
|
||||
}
|
||||
|
||||
func TestReadWorkflow_ScheduleEvent(t *testing.T) {
|
||||
yaml := `
|
||||
name: local-action-docker-url
|
||||
@@ -952,3 +1137,19 @@ func TestJobMatrixValidation(t *testing.T) {
|
||||
assert.Nil(t, matrix, "matrix with nested map should return nil")
|
||||
})
|
||||
}
|
||||
|
||||
func readJob(t *testing.T, content string) *Job {
|
||||
t.Helper()
|
||||
|
||||
var job Job
|
||||
require.NoError(t, yaml.Unmarshal([]byte(content), &job))
|
||||
return &job
|
||||
}
|
||||
|
||||
func readStep(t *testing.T, content string) *Step {
|
||||
t.Helper()
|
||||
|
||||
var step Step
|
||||
require.NoError(t, yaml.Unmarshal([]byte(content), &step))
|
||||
return &step
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user