mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-08-23 05:26:17 +00:00
test: speed up tests (#1181)
Parallelize isolated workflow tests, consolidate redundant fixtures, and replace fixed waits with deterministic synchronization. Keep the readable curl service probe and use `getent` for hostname resolution. Measured on the same machine with `make test`: 1. Wall time: 170.50s to 136.12s, down 34.38s or 20.2%. 1. `act/runner`: 138.417s to 124.291s, down 14.126s or 10.2%. 1. `act/runner` coverage: unchanged at 85.2%. 1. `TestDockerExecAbort`: 2.514s to 0.012s package time. Stability checks: 1. Cancellation and deadline tests: 100 race-enabled repetitions. 1. Host runner suite: 10 race-enabled repetitions. 1. Changed Docker fixtures: 3 consecutive repetitions. Full race suite, Go and Windows lint, source checks, and security scan pass. Assisted-by: Codex:GPT-5 Reviewed-on: https://gitea.com/gitea/runner/pulls/1181 Reviewed-by: bircni <bircni@icloud.com> Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
@@ -16,7 +16,6 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
|
||||
@@ -148,12 +147,17 @@ func (m *mockDockerClient) NetworkRemove(ctx context.Context, id string, opts mo
|
||||
return args.Get(0).(mobyclient.NetworkRemoveResult), args.Error(1)
|
||||
}
|
||||
|
||||
type endlessReader struct {
|
||||
io.Reader
|
||||
type interruptReader struct {
|
||||
started chan struct{}
|
||||
interrupted chan struct{}
|
||||
stopped chan struct{}
|
||||
}
|
||||
|
||||
func (r endlessReader) Read(_ []byte) (n int, err error) {
|
||||
return 1, nil
|
||||
func (r *interruptReader) Read(_ []byte) (int, error) {
|
||||
close(r.started)
|
||||
<-r.interrupted
|
||||
close(r.stopped)
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
type mockConn struct {
|
||||
@@ -173,15 +177,18 @@ func (m *mockConn) Close() (err error) {
|
||||
func TestDockerExecAbort(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
reader := &interruptReader{started: make(chan struct{}), interrupted: make(chan struct{}), stopped: make(chan struct{})}
|
||||
conn := &mockConn{}
|
||||
conn.On("Write", mock.AnythingOfType("[]uint8")).Return(1, nil)
|
||||
conn.On("Write", []byte{3}).
|
||||
Run(func(mock.Arguments) { close(reader.interrupted) }).
|
||||
Return(1, nil)
|
||||
|
||||
client := &mockDockerClient{}
|
||||
client.On("ExecCreate", ctx, "123", mock.AnythingOfType("client.ExecCreateOptions")).Return(mobyclient.ExecCreateResult{ID: "id"}, nil)
|
||||
client.On("ExecAttach", ctx, "id", mock.AnythingOfType("client.ExecAttachOptions")).Return(mobyclient.ExecAttachResult{
|
||||
HijackedResponse: mobyclient.HijackedResponse{
|
||||
Conn: conn,
|
||||
Reader: bufio.NewReader(endlessReader{}),
|
||||
Reader: bufio.NewReader(reader),
|
||||
},
|
||||
}, nil)
|
||||
|
||||
@@ -199,11 +206,11 @@ func TestDockerExecAbort(t *testing.T) {
|
||||
channel <- cr.exec([]string{""}, map[string]string{}, "user", "workdir")(ctx)
|
||||
}()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
<-reader.started
|
||||
cancel()
|
||||
|
||||
err := <-channel
|
||||
<-reader.stopped
|
||||
assert.ErrorIs(t, err, context.Canceled) //nolint:testifylint // pre-existing issue from nektos/act
|
||||
|
||||
conn.AssertExpectations(t)
|
||||
|
||||
@@ -107,17 +107,14 @@ func TestMainStepsExecutorMarksFailedOnTimeoutBetweenSteps(t *testing.T) {
|
||||
"job1": createJob(t, `runs-on: ubuntu-latest`, ""),
|
||||
})
|
||||
|
||||
// A short deadline that we let elapse between steps, so no step records the error itself.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
|
||||
defer cancel()
|
||||
ctx := newControllableDeadlineContext(context.Background())
|
||||
|
||||
var ran []string
|
||||
var laterStepCtxErr error
|
||||
steps := []common.Executor{
|
||||
func(c context.Context) error {
|
||||
func(context.Context) error {
|
||||
ran = append(ran, "step1")
|
||||
// Block until the job deadline elapses, then return cleanly: the interrupt lands in the loop's between-steps check, not inside a step.
|
||||
<-c.Done()
|
||||
ctx.expire()
|
||||
return nil
|
||||
},
|
||||
func(c context.Context) error {
|
||||
|
||||
@@ -528,18 +528,39 @@ func TestNewJobExecutor(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type controllableDeadlineContext struct {
|
||||
context.Context
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func newControllableDeadlineContext(parent context.Context) *controllableDeadlineContext {
|
||||
return &controllableDeadlineContext{Context: parent, done: make(chan struct{})}
|
||||
}
|
||||
|
||||
func (ctx *controllableDeadlineContext) Done() <-chan struct{} {
|
||||
return ctx.done
|
||||
}
|
||||
|
||||
func (ctx *controllableDeadlineContext) Err() error {
|
||||
select {
|
||||
case <-ctx.done:
|
||||
return context.DeadlineExceeded
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *controllableDeadlineContext) expire() {
|
||||
close(ctx.done)
|
||||
}
|
||||
|
||||
// TestNewJobExecutorRunsPostStepsAfterTimeout guards the timeout-minutes cleanup
|
||||
// path: when a job exceeds its timeout the job context is DeadlineExceeded, but
|
||||
// the post steps (cleanup hooks like actions/checkout post and cache save) must
|
||||
// still run against a fresh, non-expired context, and the job must still be
|
||||
// reported as failed.
|
||||
func TestNewJobExecutorRunsPostStepsAfterTimeout(t *testing.T) {
|
||||
ctx := common.WithJobErrorContainer(context.Background())
|
||||
// The timeout is generous so the main step (which blocks on ctx.Done below) is
|
||||
// always reached before the deadline fires; otherwise the pipeline would
|
||||
// short-circuit before the step runs and the job error would never be set.
|
||||
ctx, cancel := context.WithTimeout(ctx, 200*time.Millisecond)
|
||||
defer cancel()
|
||||
ctx := newControllableDeadlineContext(common.WithJobErrorContainer(context.Background()))
|
||||
|
||||
jim := &jobInfoMock{}
|
||||
sfm := &stepFactoryMock{}
|
||||
@@ -570,11 +591,9 @@ func TestNewJobExecutorRunsPostStepsAfterTimeout(t *testing.T) {
|
||||
sm := &stepMock{}
|
||||
sfm.On("newStep", stepModel, rc).Return(sm, nil)
|
||||
sm.On("pre").Return(func(ctx context.Context) error { return nil })
|
||||
// The main step runs past the job timeout: it blocks until the job context is
|
||||
// done, mirroring a step that overruns timeout-minutes.
|
||||
sm.On("main").Return(func(ctx context.Context) error {
|
||||
<-ctx.Done()
|
||||
return ctx.Err()
|
||||
sm.On("main").Return(func(stepCtx context.Context) error {
|
||||
ctx.expire()
|
||||
return stepCtx.Err()
|
||||
})
|
||||
|
||||
var postRan bool
|
||||
|
||||
+29
-48
@@ -14,6 +14,7 @@ import (
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -171,6 +172,23 @@ func TestGraphEvent(t *testing.T) {
|
||||
assert.NoError(t, err) //nolint:testifylint // pre-existing issue from nektos/act
|
||||
assert.NotNil(t, plan)
|
||||
assert.Empty(t, plan.Stages)
|
||||
|
||||
for _, workflowPath := range []string{
|
||||
"testdata/workflow_dispatch_no_inputs_mapping/workflow_dispatch.yml",
|
||||
"testdata/workflow_dispatch-scalar/workflow_dispatch.yml",
|
||||
} {
|
||||
planner, err := model.NewWorkflowPlanner(workflowPath, true)
|
||||
if !assert.NoError(t, err, workflowPath) { //nolint:testifylint // pre-existing issue from nektos/act
|
||||
continue
|
||||
}
|
||||
plan, err := planner.PlanEvent("workflow_dispatch")
|
||||
if !assert.NoError(t, err, workflowPath) || !assert.NotNil(t, plan, workflowPath) { //nolint:testifylint // pre-existing issue from nektos/act
|
||||
continue
|
||||
}
|
||||
if assert.Len(t, plan.Stages, 1, workflowPath) {
|
||||
assert.Len(t, plan.Stages[0].Runs, 1, workflowPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// these two build the same action Dockerfiles into one image tag, so they cannot overlap
|
||||
@@ -276,7 +294,6 @@ func TestRunEvent(t *testing.T) {
|
||||
{workdir, "fail", "push", "exit with `FAILURE`: 1", platforms, secrets},
|
||||
{workdir, "checkout", "push", "", platforms, secrets},
|
||||
{workdir, "job-container", "push", "", platforms, secrets},
|
||||
{workdir, "job-container-non-root", "push", "", platforms, secrets},
|
||||
{workdir, "job-container-invalid-credentials", "push", "failed to handle credentials: failed to interpolate container.credentials.password", platforms, secrets},
|
||||
{workdir, "container-hostname", "push", "", platforms, secrets},
|
||||
{workdir, "matrix", "push", "", platforms, secrets},
|
||||
@@ -286,17 +303,14 @@ func TestRunEvent(t *testing.T) {
|
||||
{workdir, "defaults-run", "push", "", platforms, secrets},
|
||||
{workdir, "composite-fail-with-output", "push", "", platforms, secrets},
|
||||
{workdir, "issue-597", "push", "", platforms, secrets},
|
||||
{workdir, "issue-598", "push", "", platforms, secrets},
|
||||
{workdir, "if-env-act", "push", "", platforms, secrets},
|
||||
{workdir, "env-and-path", "push", "", platforms, secrets},
|
||||
{workdir, "environment-files", "push", "", platforms, secrets},
|
||||
{workdir, "GITHUB_STATE", "push", "", platforms, secrets},
|
||||
{workdir, "environment-files-parser-bug", "push", "", platforms, secrets},
|
||||
{workdir, "non-existent-action", "push", "Job 'nopanic' failed", platforms, secrets},
|
||||
{workdir, "outputs", "push", "", platforms, secrets},
|
||||
{workdir, "networking", "push", "", platforms, secrets},
|
||||
{workdir, "steps-context/conclusion", "push", "", platforms, secrets},
|
||||
{workdir, "steps-context/outcome", "push", "", platforms, secrets},
|
||||
{workdir, "steps-context", "push", "", platforms, secrets},
|
||||
{workdir, "job-status-check", "push", "job 'fail' failed", platforms, secrets},
|
||||
{workdir, "if-expressions", "push", "Job 'mytest' failed", platforms, secrets},
|
||||
{workdir, "actions-environment-and-context-tests", "push", "", platforms, secrets},
|
||||
@@ -306,8 +320,6 @@ func TestRunEvent(t *testing.T) {
|
||||
{workdir, "ensure-post-steps", "push", "Job 'second-post-step-should-fail' failed", platforms, secrets},
|
||||
{workdir, "workflow_call_inputs", "workflow_call", "", platforms, secrets},
|
||||
{workdir, "workflow_dispatch", "workflow_dispatch", "", platforms, secrets},
|
||||
{workdir, "workflow_dispatch_no_inputs_mapping", "workflow_dispatch", "", platforms, secrets},
|
||||
{workdir, "workflow_dispatch-scalar", "workflow_dispatch", "", platforms, secrets},
|
||||
{workdir, "workflow_dispatch-scalar-composite-action", "workflow_dispatch", "", platforms, secrets},
|
||||
{workdir, "job-needs-context-contains-result", "push", "", platforms, secrets},
|
||||
{workdir, "container-volumes", "push", "", platforms, secrets},
|
||||
@@ -323,14 +335,17 @@ func TestRunEvent(t *testing.T) {
|
||||
{workdir, "services-empty-image", "push", "", platforms, secrets},
|
||||
}
|
||||
|
||||
var sharedImageMu sync.Mutex
|
||||
for _, table := range tables {
|
||||
t.Run(table.workflowPath, func(t *testing.T) {
|
||||
if table.workflowPath == "container-volumes" {
|
||||
// host /proc bind mounts are Linux-Docker-only
|
||||
requireLinuxDocker(t)
|
||||
}
|
||||
if !slices.Contains(sharedImageWorkflows, table.workflowPath) {
|
||||
t.Parallel()
|
||||
t.Parallel()
|
||||
if slices.Contains(sharedImageWorkflows, table.workflowPath) {
|
||||
sharedImageMu.Lock()
|
||||
defer sharedImageMu.Unlock()
|
||||
}
|
||||
|
||||
config := &Config{
|
||||
@@ -386,20 +401,17 @@ func TestRunEventHostEnvironment(t *testing.T) {
|
||||
{workdir, "evalmatrix-merge-map", "push", "", platforms, secrets},
|
||||
{workdir, "evalmatrix-merge-array", "push", "", platforms, secrets},
|
||||
|
||||
{workdir, "fail", "push", "exit with `FAILURE`: 1", platforms, secrets},
|
||||
{workdir, "checkout", "push", "", platforms, secrets},
|
||||
{workdir, "matrix", "push", "", platforms, secrets},
|
||||
{workdir, "commands", "push", "", platforms, secrets},
|
||||
{workdir, "defaults-run", "push", "", platforms, secrets},
|
||||
{workdir, "composite-fail-with-output", "push", "", platforms, secrets},
|
||||
{workdir, "issue-597", "push", "", platforms, secrets},
|
||||
{workdir, "issue-598", "push", "", platforms, secrets},
|
||||
{workdir, "if-env-act", "push", "", platforms, secrets},
|
||||
{workdir, "env-and-path", "push", "", platforms, secrets},
|
||||
{workdir, "non-existent-action", "push", "Job 'nopanic' failed", platforms, secrets},
|
||||
{workdir, "outputs", "push", "", platforms, secrets},
|
||||
{workdir, "steps-context/conclusion", "push", "", platforms, secrets},
|
||||
{workdir, "steps-context/outcome", "push", "", platforms, secrets},
|
||||
{workdir, "steps-context", "push", "", platforms, secrets},
|
||||
{workdir, "job-status-check", "push", "job 'fail' failed", platforms, secrets},
|
||||
{workdir, "if-expressions", "push", "Job 'mytest' failed", platforms, secrets},
|
||||
{workdir, "evalenv", "push", "", platforms, secrets},
|
||||
@@ -432,6 +444,7 @@ func TestRunEventHostEnvironment(t *testing.T) {
|
||||
}...)
|
||||
}
|
||||
|
||||
hostPlanSlots := make(chan struct{}, 2)
|
||||
for _, table := range tables {
|
||||
t.Run(table.workflowPath, func(t *testing.T) {
|
||||
switch table.workflowPath {
|
||||
@@ -440,6 +453,9 @@ func TestRunEventHostEnvironment(t *testing.T) {
|
||||
case "nix-prepend-path":
|
||||
requireHostTools(t, "nix")
|
||||
}
|
||||
t.Parallel()
|
||||
hostPlanSlots <- struct{}{}
|
||||
defer func() { <-hostPlanSlots }()
|
||||
table.runTest(ctx, t, &Config{})
|
||||
})
|
||||
}
|
||||
@@ -544,41 +560,6 @@ func TestRunEventSecrets(t *testing.T) {
|
||||
tjfi.runTest(context.Background(), t, &Config{Secrets: secrets, Env: env})
|
||||
}
|
||||
|
||||
func TestRunWithService(t *testing.T) {
|
||||
requireDocker(t)
|
||||
|
||||
log.SetLevel(log.DebugLevel)
|
||||
ctx := context.Background()
|
||||
|
||||
platforms := map[string]string{
|
||||
"ubuntu-latest": "node:24-bookworm-slim",
|
||||
}
|
||||
|
||||
workflowPath := "services"
|
||||
eventName := "push"
|
||||
|
||||
workdir, err := filepath.Abs("testdata")
|
||||
assert.NoError(t, err, workflowPath) //nolint:testifylint // pre-existing issue from nektos/act
|
||||
|
||||
runnerConfig := &Config{
|
||||
Workdir: workdir,
|
||||
EventName: eventName,
|
||||
PlatformPicker: mapPlatformPicker(platforms),
|
||||
ContainerMaxLifetime: time.Hour, // otherwise the job container is `sleep 0` and exits at once
|
||||
}
|
||||
runner, err := New(runnerConfig)
|
||||
assert.NoError(t, err, workflowPath) //nolint:testifylint // pre-existing issue from nektos/act
|
||||
|
||||
planner, err := model.NewWorkflowPlanner("testdata/"+workflowPath, true)
|
||||
assert.NoError(t, err, workflowPath) //nolint:testifylint // pre-existing issue from nektos/act
|
||||
|
||||
plan, err := planner.PlanEvent(eventName)
|
||||
assert.NoError(t, err, workflowPath) //nolint:testifylint // pre-existing issue from nektos/act
|
||||
|
||||
err = runner.NewPlanExecutor(plan)(ctx)
|
||||
assert.NoError(t, err, workflowPath)
|
||||
}
|
||||
|
||||
func TestRunEventPullRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
requireDocker(t)
|
||||
|
||||
+4
-1
@@ -1,4 +1,4 @@
|
||||
name: issue-597
|
||||
name: issues-597-598
|
||||
on: push
|
||||
|
||||
|
||||
@@ -13,6 +13,9 @@ jobs:
|
||||
- name: My first true step
|
||||
if: ${{endsWith('Hello world', 'ld')}}
|
||||
run: echo "Renst the Octocat"
|
||||
- name: My second true step
|
||||
if: "!endsWith('Hello world', 'od')"
|
||||
run: echo "Renst the Octocat"
|
||||
- name: My second false step
|
||||
if: "endsWith('Should not evaluate', 'o2')"
|
||||
run: exit 1
|
||||
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
name: issue-598
|
||||
on: push
|
||||
|
||||
|
||||
jobs:
|
||||
my_first_job:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: My first false step
|
||||
if: "endsWith('Hello world', 'o1')"
|
||||
run: exit 1
|
||||
- name: My first true step
|
||||
if: "!endsWith('Hello world', 'od')"
|
||||
run: echo "Renst the Octocat"
|
||||
- name: My second false step
|
||||
if: "endsWith('Hello world', 'o2')"
|
||||
run: exit 1
|
||||
- name: My third false step
|
||||
if: "endsWith('Hello world', 'o2')"
|
||||
run: exit 1
|
||||
@@ -1,11 +0,0 @@
|
||||
name: job-container
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: node:24-bookworm-slim
|
||||
options: --user 1000
|
||||
steps:
|
||||
- run: echo PASS
|
||||
Vendored
+2
-2
@@ -12,13 +12,13 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-18.04, macos-latest]
|
||||
node: [4, 6, 8, 10]
|
||||
node: [4, 10]
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: [8.x, 10.x, 12.x, 13.x]
|
||||
node: [8.x, 13.x]
|
||||
steps:
|
||||
- run: echo ${NODE_VERSION} | grep ${{ matrix.node }}
|
||||
env:
|
||||
|
||||
+2
-8
@@ -4,11 +4,5 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install tools
|
||||
run: |
|
||||
apt update
|
||||
apt install -y iputils-ping
|
||||
- name: Run hostname test
|
||||
run: |
|
||||
hostname -f
|
||||
ping -c 4 $(hostname -f)
|
||||
- name: Resolve the container hostname
|
||||
run: getent hosts "$(hostname -f)"
|
||||
|
||||
Vendored
+4
-19
@@ -12,32 +12,17 @@ jobs:
|
||||
- id: set_2
|
||||
run: |
|
||||
echo "::set-output name=var_3::$(echo var3)"
|
||||
- id: set_3
|
||||
run: |
|
||||
echo "::set-output name=var_4::$(echo var4)"
|
||||
outputs:
|
||||
variable_1: ${{ steps.set_1.outputs.var_1 }}
|
||||
variable_2: ${{ steps.set_1.outputs.var_2 }}
|
||||
variable_3: ${{ steps.set_2.outputs.var_3 }}
|
||||
variable_4: ${{ steps.set_3.outputs.var_4 }}
|
||||
|
||||
build:
|
||||
needs: build_output
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check set_1 var1
|
||||
- name: Check outputs
|
||||
run: |
|
||||
echo "${{ needs.build_output.outputs.variable_1 }}"
|
||||
echo "${{ needs.build_output.outputs.variable_1 }}" | grep 'var1' || exit 1
|
||||
- name: Check set_1 var2
|
||||
run: |
|
||||
echo "${{ needs.build_output.outputs.variable_2 }}"
|
||||
echo "${{ needs.build_output.outputs.variable_2 }}" | grep 'var2' || exit 1
|
||||
- name: Check set_2 var3
|
||||
run: |
|
||||
echo "${{ needs.build_output.outputs.variable_3 }}"
|
||||
echo "${{ needs.build_output.outputs.variable_3 }}" | grep 'var3' || exit 1
|
||||
- name: Check set_3 var4
|
||||
run: |
|
||||
echo "${{ needs.build_output.outputs.variable_4 }}"
|
||||
echo "${{ needs.build_output.outputs.variable_4 }}" | grep 'var4' || exit 1
|
||||
test "${{ needs.build_output.outputs.variable_1 }}" = var1
|
||||
test "${{ needs.build_output.outputs.variable_2 }}" = var2
|
||||
test "${{ needs.build_output.outputs.variable_3 }}" = var3
|
||||
|
||||
+1
-5
@@ -10,14 +10,10 @@ jobs:
|
||||
ports:
|
||||
- 80
|
||||
steps:
|
||||
- name: Echo the Postgres service ID / Network / Ports
|
||||
run: |
|
||||
echo "id: ${{ job.services.postgres.id }}"
|
||||
echo "network: ${{ job.services.postgres.network }}"
|
||||
echo "ports: ${{ job.services.postgres.ports }}"
|
||||
- name: The job context describes the started containers
|
||||
run: |
|
||||
test -n "${{ job.container.id }}"
|
||||
test -n "${{ job.services.postgres.id }}"
|
||||
test -n "${{ job.services.postgres.ports }}"
|
||||
test -n "${{ job.services.postgres.ports['80'] }}"
|
||||
test "${{ job.services.postgres.network }}" = "${{ job.container.network }}"
|
||||
|
||||
+9
-12
@@ -4,6 +4,9 @@ env:
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ env.MY_SHELL }}
|
||||
steps:
|
||||
- shell: ${{ env.MY_SHELL }}
|
||||
run: |
|
||||
@@ -12,6 +15,12 @@ jobs:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
- run: |
|
||||
if [[ -n "$BASH" ]]; then
|
||||
echo "I'm $BASH!"
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
check-container:
|
||||
runs-on: ubuntu-latest
|
||||
container: node:24-bookworm-slim
|
||||
@@ -23,15 +32,3 @@ jobs:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
check-job-default:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ env.MY_SHELL }}
|
||||
steps:
|
||||
- run: |
|
||||
if [[ -n "$BASH" ]]; then
|
||||
echo "I'm $BASH!"
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
+3
-6
@@ -3,16 +3,13 @@ env:
|
||||
MY_SHELL: pwsh
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- shell: ${{ env.MY_SHELL }}
|
||||
run: |
|
||||
$PSVersionTable
|
||||
check-job-default:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ env.MY_SHELL }}
|
||||
steps:
|
||||
- shell: ${{ env.MY_SHELL }}
|
||||
run: |
|
||||
$PSVersionTable
|
||||
- run: |
|
||||
$PSVersionTable
|
||||
|
||||
+9
-12
@@ -4,6 +4,9 @@ env:
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ env.MY_SHELL }}
|
||||
steps:
|
||||
- shell: ${{ env.MY_SHELL }}
|
||||
run: |
|
||||
@@ -12,6 +15,12 @@ jobs:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
- run: |
|
||||
if [ -z ${BASH+x} ]; then
|
||||
echo "I'm sh!"
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
check-container:
|
||||
runs-on: ubuntu-latest
|
||||
container: alpine:latest
|
||||
@@ -23,15 +32,3 @@ jobs:
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
check-job-default:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ env.MY_SHELL }}
|
||||
steps:
|
||||
- run: |
|
||||
if [ -z ${BASH+x} ]; then
|
||||
echo "I'm sh!"
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
name: conclusion
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- id: first
|
||||
run: exit 0
|
||||
- id: second
|
||||
continue-on-error: true
|
||||
run: exit 1
|
||||
- run: echo '${{ steps.first.conclusion }}' | grep 'success'
|
||||
- run: echo '${{ steps.second.conclusion }}' | grep 'success'
|
||||
@@ -1,14 +0,0 @@
|
||||
name: outcome
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- id: first
|
||||
run: exit 0
|
||||
- id: second
|
||||
continue-on-error: true
|
||||
run: exit 1
|
||||
- run: echo '${{ steps.first.outcome }}' | grep 'success'
|
||||
- run: echo '${{ steps.second.outcome }}' | grep 'failure'
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
name: steps context
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- id: first
|
||||
run: exit 0
|
||||
- id: second
|
||||
continue-on-error: true
|
||||
run: exit 1
|
||||
- run: |
|
||||
test '${{ steps.first.conclusion }}' = success
|
||||
test '${{ steps.second.conclusion }}' = success
|
||||
test '${{ steps.first.outcome }}' = success
|
||||
test '${{ steps.second.outcome }}' = failure
|
||||
Reference in New Issue
Block a user