From 6ddcf67cf58f27314ef85c1917b936a28e25c72a Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 22 Jun 2024 20:18:41 +0200 Subject: [PATCH 01/19] init mock backend --- cli/exec/exec.go | 2 + cmd/agent/main.go | 2 + pipeline/backend/mock/mock.go | 199 +++++++++++++++++++++++++++++ pipeline/backend/mock/mock_test.go | 81 ++++++++++++ 4 files changed, 284 insertions(+) create mode 100644 pipeline/backend/mock/mock.go create mode 100644 pipeline/backend/mock/mock_test.go diff --git a/cli/exec/exec.go b/cli/exec/exec.go index 68db9aabef..740fe9098d 100644 --- a/cli/exec/exec.go +++ b/cli/exec/exec.go @@ -34,6 +34,7 @@ import ( "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/docker" "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/kubernetes" "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/local" + "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/mock" backendTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml" "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/compiler" @@ -228,6 +229,7 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error kubernetes.New(), docker.New(), local.New(), + mock.New(), } backendEngine, err := backend.FindBackend(backendCtx, backends, c.String("backend-engine")) if err != nil { diff --git a/cmd/agent/main.go b/cmd/agent/main.go index 062fc07292..8aad05e2ed 100644 --- a/cmd/agent/main.go +++ b/cmd/agent/main.go @@ -19,6 +19,7 @@ import ( "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/docker" "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/kubernetes" "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/local" + "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/mock" backendTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" ) @@ -27,5 +28,6 @@ func main() { kubernetes.New(), docker.New(), local.New(), + mock.New(), }) } diff --git a/pipeline/backend/mock/mock.go b/pipeline/backend/mock/mock.go new file mode 100644 index 0000000000..842c87d3a9 --- /dev/null +++ b/pipeline/backend/mock/mock.go @@ -0,0 +1,199 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mock + +import ( + "context" + "fmt" + "io" + "strings" + "sync" + "time" + + "github.com/rs/zerolog/log" + "github.com/urfave/cli/v2" + + backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" +) + +type mock struct { + kv sync.Map +} + +const ( + // Step names to control mock behavior. + StepStartFail = "step_start_fail" + StepExecError = "step_exec_error" + EnvKeyStepSleep = "SLEEP" + EnvKeyStepType = "EXPECT_TYPE" + + // Internal const. + stepStateStarted = "started" + stepStateDone = "done" +) + +// New returns a new Docker Backend. +func New() backend.Backend { + return &mock{ + kv: sync.Map{}, + } +} + +func (e *mock) Name() string { + return "mock" +} + +func (e *mock) IsAvailable(_ context.Context) bool { + return true +} + +func (e *mock) Flags() []cli.Flag { + return nil +} + +// Load new client for Docker Backend using environment variables. +func (e *mock) Load(_ context.Context) (*backend.BackendInfo, error) { + return &backend.BackendInfo{ + Platform: "mock", + }, nil +} + +func (e *mock) SetupWorkflow(_ context.Context, _ *backend.Config, taskUUID string) error { + log.Trace().Str("taskUUID", taskUUID).Msg("create workflow environment") + e.kv.Store("task_"+taskUUID, "setup") + return nil +} + +func (e *mock) StartStep(_ context.Context, step *backend.Step, taskUUID string) error { + log.Trace().Str("taskUUID", taskUUID).Msgf("start step %s", step.Name) + + _, exist := e.kv.Load("task_" + taskUUID) + if !exist { + return fmt.Errorf("expect env of workflow %s to exist but found none to destroy", taskUUID) + } + + if step.Name == StepStartFail { + return fmt.Errorf("expected fail to start step") + } + + expectStepType, testStepType := step.Environment[EnvKeyStepType] + if testStepType && string(step.Type) != expectStepType { + return fmt.Errorf("expected step type '%s' but got '%s'", expectStepType, step.Type) + } + + e.kv.Store(fmt.Sprintf("task_%s_step_%s", taskUUID, step.UUID), stepStateStarted) + return nil +} + +func (e *mock) WaitStep(_ context.Context, step *backend.Step, taskUUID string) (*backend.State, error) { + log.Trace().Str("taskUUID", taskUUID).Msgf("wait for step %s", step.Name) + + _, exist := e.kv.Load("task_" + taskUUID) + if !exist { + err := fmt.Errorf("expect env of workflow %s to exist but found none to destroy", taskUUID) + return &backend.State{Error: err}, err + } + + // check state + stepState, stepExist := e.kv.Load(fmt.Sprintf("task_%s_step_%s", taskUUID, step.UUID)) + if !stepExist { + err := fmt.Errorf("WaitStep expect step '%s' (%s) to be created but found none", step.Name, step.UUID) + return &backend.State{Error: err}, err + } + if stepState != stepStateStarted { + err := fmt.Errorf("WaitStep expect step '%s' (%s) to be '%s' but it is: %s", step.Name, step.UUID, stepStateStarted, stepState) + return &backend.State{Error: err}, err + } + + // extend wait time logic + if sleep, sleepExist := step.Environment[EnvKeyStepSleep]; sleepExist { + toSleep, err := time.ParseDuration(sleep) + if err != nil { + err = fmt.Errorf("WaitStep fail to parse sleep duration: %w", err) + return &backend.State{Error: err}, err + } + time.Sleep(toSleep) + } else { + time.Sleep(time.Nanosecond) + } + + e.kv.Store(fmt.Sprintf("task_%s_step_%s", taskUUID, step.UUID), stepStateDone) + + if step.Name == StepExecError { + return &backend.State{ + ExitCode: 1, + Exited: true, + OOMKilled: false, + }, nil + } + + return &backend.State{ + ExitCode: 0, + Exited: true, + OOMKilled: false, + }, nil +} + +func (e *mock) TailStep(_ context.Context, step *backend.Step, taskUUID string) (io.ReadCloser, error) { + log.Trace().Str("taskUUID", taskUUID).Msgf("tail logs of step %s", step.Name) + + _, exist := e.kv.Load("task_" + taskUUID) + if !exist { + return nil, fmt.Errorf("expect env of workflow %s to exist but found none to destroy", taskUUID) + } + + // check state + stepState, stepExist := e.kv.Load(fmt.Sprintf("task_%s_step_%s", taskUUID, step.UUID)) + if !stepExist { + return nil, fmt.Errorf("WaitStep expect step '%s' (%s) to be created but found none", step.Name, step.UUID) + } + if stepState != stepStateStarted { + return nil, fmt.Errorf("WaitStep expect step '%s' (%s) to be '%s' but it is: %s", step.Name, step.UUID, stepStateStarted, stepState) + } + + return io.NopCloser(strings.NewReader(strings.Join(step.Commands, "\n"))), nil +} + +func (e *mock) DestroyStep(_ context.Context, step *backend.Step, taskUUID string) error { + log.Trace().Str("taskUUID", taskUUID).Msgf("stop step %s", step.Name) + + _, exist := e.kv.Load("task_" + taskUUID) + if !exist { + return fmt.Errorf("expect env of workflow %s to exist but found none to destroy", taskUUID) + } + + // check state + stepState, stepExist := e.kv.Load(fmt.Sprintf("task_%s_step_%s", taskUUID, step.UUID)) + if !stepExist { + return fmt.Errorf("WaitStep expect step '%s' (%s) to be created but found none", step.Name, step.UUID) + } + if stepState != stepStateDone { + return fmt.Errorf("WaitStep expect step '%s' (%s) to be '%s' but it is: %s", step.Name, step.UUID, stepStateDone, stepState) + } + + e.kv.Delete(fmt.Sprintf("task_%s_step_%s", taskUUID, step.UUID)) + return nil +} + +func (e *mock) DestroyWorkflow(_ context.Context, _ *backend.Config, taskUUID string) error { + log.Trace().Str("taskUUID", taskUUID).Msgf("delete workflow environment") + + _, exist := e.kv.Load("task_" + taskUUID) + if !exist { + return fmt.Errorf("expect env of workflow %s to exist but found none to destroy", taskUUID) + } + e.kv.Delete("task_" + taskUUID) + return nil +} diff --git a/pipeline/backend/mock/mock_test.go b/pipeline/backend/mock/mock_test.go new file mode 100644 index 0000000000..d92c9134c4 --- /dev/null +++ b/pipeline/backend/mock/mock_test.go @@ -0,0 +1,81 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mock_test + +import ( + "context" + "io" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/mock" + "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" +) + +func TestSmalPipelineMockRun(t *testing.T) { + mockEngine := mock.New() + ctx := context.Background() + + assert.True(t, mockEngine.IsAvailable(ctx)) + assert.EqualValues(t, "mock", mockEngine.Name()) + _, err := mockEngine.Load(ctx) + assert.NoError(t, err) + + t.Run("expect fail of step func with non setup workflow", func(t *testing.T) { + step := &types.Step{Name: "step1", UUID: "SID_1"} + nonExistWorkflowID := "WID_NONE" + + err := mockEngine.StartStep(ctx, step, nonExistWorkflowID) + assert.Error(t, err) + + _, err = mockEngine.TailStep(ctx, step, nonExistWorkflowID) + assert.Error(t, err) + + _, err = mockEngine.WaitStep(ctx, step, nonExistWorkflowID) + assert.Error(t, err) + + err = mockEngine.DestroyStep(ctx, step, nonExistWorkflowID) + assert.Error(t, err) + }) + + t.Run("success", func(t *testing.T) { + step := &types.Step{ + Name: "step1", + UUID: "SID_1", + Environment: map[string]string{}, + Commands: []string{"echo ja", "echo nein"}, + } + workflowUUID := "WID_1" + + assert.NoError(t, mockEngine.SetupWorkflow(ctx, nil, workflowUUID)) + + assert.NoError(t, mockEngine.StartStep(ctx, step, workflowUUID)) + + reader, err := mockEngine.TailStep(ctx, step, workflowUUID) + assert.NoError(t, err) + log, err := io.ReadAll(reader) + assert.NoError(t, err) + assert.EqualValues(t, strings.Join(step.Commands, "\n"), string(log)) + + state, err := mockEngine.WaitStep(ctx, step, workflowUUID) + assert.NoError(t, err) + assert.EqualValues(t, 0, state.ExitCode) + assert.NoError(t, state.Error) + + assert.NoError(t, mockEngine.DestroyWorkflow(ctx, nil, workflowUUID)) + }) +} From 1f210334323001c80cde7baa0938dbbf51f4a57c Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 22 Jun 2024 20:32:04 +0200 Subject: [PATCH 02/19] extend --- pipeline/backend/mock/mock_test.go | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/pipeline/backend/mock/mock_test.go b/pipeline/backend/mock/mock_test.go index d92c9134c4..cc30c04324 100644 --- a/pipeline/backend/mock/mock_test.go +++ b/pipeline/backend/mock/mock_test.go @@ -52,7 +52,7 @@ func TestSmalPipelineMockRun(t *testing.T) { assert.Error(t, err) }) - t.Run("success", func(t *testing.T) { + t.Run("step exec successfully", func(t *testing.T) { step := &types.Step{ Name: "step1", UUID: "SID_1", @@ -73,8 +73,36 @@ func TestSmalPipelineMockRun(t *testing.T) { state, err := mockEngine.WaitStep(ctx, step, workflowUUID) assert.NoError(t, err) + assert.NoError(t, state.Error) assert.EqualValues(t, 0, state.ExitCode) + + assert.NoError(t, mockEngine.DestroyStep(ctx, step, workflowUUID)) + + assert.NoError(t, mockEngine.DestroyWorkflow(ctx, nil, workflowUUID)) + }) + + t.Run("step exec fail", func(t *testing.T) { + step := &types.Step{ + Name: mock.StepExecError, + UUID: "SID_2", + Type: types.StepTypePlugin, + Environment: map[string]string{mock.EnvKeyStepType: "plugin"}, + } + workflowUUID := "WID_1" + + assert.NoError(t, mockEngine.SetupWorkflow(ctx, nil, workflowUUID)) + + assert.NoError(t, mockEngine.StartStep(ctx, step, workflowUUID)) + + _, err := mockEngine.TailStep(ctx, step, workflowUUID) + assert.NoError(t, err) + + state, err := mockEngine.WaitStep(ctx, step, workflowUUID) + assert.NoError(t, err) assert.NoError(t, state.Error) + assert.EqualValues(t, 1, state.ExitCode) + + assert.NoError(t, mockEngine.DestroyStep(ctx, step, workflowUUID)) assert.NoError(t, mockEngine.DestroyWorkflow(ctx, nil, workflowUUID)) }) From 353ab685af5f82d67388a74267932c59efe41099 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 22 Jun 2024 21:04:09 +0200 Subject: [PATCH 03/19] emulate services in mock backend --- pipeline/backend/mock/mock.go | 23 ++++++++++++++++++----- pipeline/backend/mock/mock_test.go | 3 +-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/pipeline/backend/mock/mock.go b/pipeline/backend/mock/mock.go index 842c87d3a9..63cc95e266 100644 --- a/pipeline/backend/mock/mock.go +++ b/pipeline/backend/mock/mock.go @@ -40,8 +40,9 @@ const ( EnvKeyStepType = "EXPECT_TYPE" // Internal const. - stepStateStarted = "started" - stepStateDone = "done" + stepStateStarted = "started" + stepStateDone = "done" + testServiceTimeout = 1 * time.Second ) // New returns a new Docker Backend. @@ -97,7 +98,7 @@ func (e *mock) StartStep(_ context.Context, step *backend.Step, taskUUID string) return nil } -func (e *mock) WaitStep(_ context.Context, step *backend.Step, taskUUID string) (*backend.State, error) { +func (e *mock) WaitStep(ctx context.Context, step *backend.Step, taskUUID string) (*backend.State, error) { log.Trace().Str("taskUUID", taskUUID).Msgf("wait for step %s", step.Name) _, exist := e.kv.Load("task_" + taskUUID) @@ -126,7 +127,17 @@ func (e *mock) WaitStep(_ context.Context, step *backend.Step, taskUUID string) } time.Sleep(toSleep) } else { - time.Sleep(time.Nanosecond) + if step.Type == backend.StepTypeService { + select { + case <-time.NewTimer(testServiceTimeout).C: + err := fmt.Errorf("WaitStep fail due to timeout of service after 1 second") + return &backend.State{Error: err}, err + case <-ctx.Done(): + // context for service closed ... we can move forward + } + } else { + time.Sleep(time.Nanosecond) + } } e.kv.Store(fmt.Sprintf("task_%s_step_%s", taskUUID, step.UUID), stepStateDone) @@ -163,7 +174,9 @@ func (e *mock) TailStep(_ context.Context, step *backend.Step, taskUUID string) return nil, fmt.Errorf("WaitStep expect step '%s' (%s) to be '%s' but it is: %s", step.Name, step.UUID, stepStateStarted, stepState) } - return io.NopCloser(strings.NewReader(strings.Join(step.Commands, "\n"))), nil + return io.NopCloser(strings.NewReader( + fmt.Sprintf("StepName: %s\nStepType: %s\nStepUUID: %sStepCommands:\n\n%s\n", step.Name, step.Type, step.UUID, strings.Join(step.Commands, "\n")), + )), nil } func (e *mock) DestroyStep(_ context.Context, step *backend.Step, taskUUID string) error { diff --git a/pipeline/backend/mock/mock_test.go b/pipeline/backend/mock/mock_test.go index cc30c04324..eb8157ba6b 100644 --- a/pipeline/backend/mock/mock_test.go +++ b/pipeline/backend/mock/mock_test.go @@ -17,7 +17,6 @@ package mock_test import ( "context" "io" - "strings" "testing" "github.com/stretchr/testify/assert" @@ -69,7 +68,7 @@ func TestSmalPipelineMockRun(t *testing.T) { assert.NoError(t, err) log, err := io.ReadAll(reader) assert.NoError(t, err) - assert.EqualValues(t, strings.Join(step.Commands, "\n"), string(log)) + assert.EqualValues(t, "StepName: step1\nStepType: \nStepUUID: SID_1StepCommands:\n\necho ja\necho nein\n", string(log)) state, err := mockEngine.WaitStep(ctx, step, workflowUUID) assert.NoError(t, err) From f2c38fcc316f8ef84289c660cad263029cf58e86 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 22 Jun 2024 21:07:28 +0200 Subject: [PATCH 04/19] check against dubble start of steps --- pipeline/backend/mock/mock.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pipeline/backend/mock/mock.go b/pipeline/backend/mock/mock.go index 63cc95e266..c0c9862989 100644 --- a/pipeline/backend/mock/mock.go +++ b/pipeline/backend/mock/mock.go @@ -80,10 +80,16 @@ func (e *mock) SetupWorkflow(_ context.Context, _ *backend.Config, taskUUID stri func (e *mock) StartStep(_ context.Context, step *backend.Step, taskUUID string) error { log.Trace().Str("taskUUID", taskUUID).Msgf("start step %s", step.Name) + // internal state checks _, exist := e.kv.Load("task_" + taskUUID) if !exist { return fmt.Errorf("expect env of workflow %s to exist but found none to destroy", taskUUID) } + stepState, stepExist := e.kv.Load(fmt.Sprintf("task_%s_step_%s", taskUUID, step.UUID)) + if stepExist { + // Detect issues like https://github.com/woodpecker-ci/woodpecker/issues/3494 + return fmt.Errorf("StartStep detect already started step '%s' (%s) in state: %s", step.Name, step.UUID, stepState) + } if step.Name == StepStartFail { return fmt.Errorf("expected fail to start step") From 2b7ee2221fafe0c66aa5c32d986b1ba399227199 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 22 Jun 2024 21:12:44 +0200 Subject: [PATCH 05/19] test the mock more --- pipeline/backend/mock/mock_test.go | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/pipeline/backend/mock/mock_test.go b/pipeline/backend/mock/mock_test.go index eb8157ba6b..907f6f3e89 100644 --- a/pipeline/backend/mock/mock_test.go +++ b/pipeline/backend/mock/mock_test.go @@ -80,7 +80,7 @@ func TestSmalPipelineMockRun(t *testing.T) { assert.NoError(t, mockEngine.DestroyWorkflow(ctx, nil, workflowUUID)) }) - t.Run("step exec fail", func(t *testing.T) { + t.Run("step exec error", func(t *testing.T) { step := &types.Step{ Name: mock.StepExecError, UUID: "SID_2", @@ -105,4 +105,30 @@ func TestSmalPipelineMockRun(t *testing.T) { assert.NoError(t, mockEngine.DestroyWorkflow(ctx, nil, workflowUUID)) }) + + t.Run("step start fail", func(t *testing.T) { + step := &types.Step{ + Name: mock.StepStartFail, + UUID: "SID_2", + Type: types.StepTypeService, + Environment: map[string]string{mock.EnvKeyStepType: "service"}, + } + workflowUUID := "WID_1" + + assert.NoError(t, mockEngine.SetupWorkflow(ctx, nil, workflowUUID)) + + assert.Error(t, mockEngine.StartStep(ctx, step, workflowUUID)) + + _, err := mockEngine.TailStep(ctx, step, workflowUUID) + assert.Error(t, err) + + state, err := mockEngine.WaitStep(ctx, step, workflowUUID) + assert.Error(t, err) + assert.Error(t, state.Error) + assert.EqualValues(t, 0, state.ExitCode) + + assert.Error(t, mockEngine.DestroyStep(ctx, step, workflowUUID)) + + assert.NoError(t, mockEngine.DestroyWorkflow(ctx, nil, workflowUUID)) + }) } From 5e24efa4c087fe13b54eb0315ea70d48bcd72a8f Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 22 Jun 2024 21:26:02 +0200 Subject: [PATCH 06/19] docu mock backend --- docs/docs/92-development/09-testing.md | 75 ++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 docs/docs/92-development/09-testing.md diff --git a/docs/docs/92-development/09-testing.md b/docs/docs/92-development/09-testing.md new file mode 100644 index 0000000000..5fda2fcfbc --- /dev/null +++ b/docs/docs/92-development/09-testing.md @@ -0,0 +1,75 @@ +# Testing + +## Backend + +### Unit Tests + +TODO + +### Integration Tests + +#### Pipeline Engine + +The pipeline engine has a special backend called **`mock`** witch does not exec but emulate how a typical backend should behave. + +an example pipeline confilg would be: + +```yaml +when: + event: manual + +steps: + - name: echo + image: dummy + commands: echo ja + environment: + SLEEP: "1s" + +services: + echo: + image: dummy + commands: echo ja +``` + +witch could be executed via `woodpecker-cli --log-level trace exec --backend-engine mock example.yaml`: + +```none +... +9:18PM DBG pipeline/pipeline.go:94 > executing 2 stages, in order of: CLI=exec +9:18PM DBG pipeline/pipeline.go:104 > stage CLI=exec StagePos=0 Steps=echo +9:18PM DBG pipeline/pipeline.go:104 > stage CLI=exec StagePos=1 Steps=echo +9:18PM TRC pipeline/backend/mock/mock.go:75 > create workflow environment taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM DBG pipeline/pipeline.go:176 > prepare CLI=exec step=echo +9:18PM DBG pipeline/pipeline.go:203 > executing CLI=exec step=echo +9:18PM TRC pipeline/backend/mock/mock.go:81 > start step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM TRC pipeline/backend/mock/mock.go:167 > tail logs of step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM DBG pipeline/pipeline.go:209 > complete CLI=exec step=echo +[echo:L0:0s] StepName: echo +[echo:L1:0s] StepType: service +[echo:L2:0s] StepUUID: 01J10P578JQE6E25VV1A2DNQN9StepCommands: +[echo:L3:0s] +[echo:L4:0s] echo ja +[echo:L5:0s] 9:18PM DBG pipeline/pipeline.go:176 > prepare CLI=exec step=echo +9:18PM DBG pipeline/pipeline.go:203 > executing CLI=exec step=echo +9:18PM TRC pipeline/backend/mock/mock.go:81 > start step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM TRC pipeline/backend/mock/mock.go:167 > tail logs of step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +[echo:L0:0s] StepName: echo +[echo:L1:0s] StepType: commands +[echo:L2:0s] StepUUID: 01J10P578JQE6E25VV1DFSXX1YStepCommands: +[echo:L3:0s] +[echo:L4:0s] echo ja +[echo:L5:0s] 9:18PM TRC pipeline/backend/mock/mock.go:108 > wait for step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM TRC pipeline/backend/mock/mock.go:189 > stop step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM DBG pipeline/pipeline.go:209 > complete CLI=exec step=echo +9:18PM TRC pipeline/backend/mock/mock.go:210 > delete workflow environment taskUUID=01J10P578JQE6E25VV1EQF0745 +``` + +You can control the step behavior via its name: + +- If you name it `step_start_fail` the engine will simulate a step start who fail (e.g. happens when the container image can not be pulled). +- If you name it `step_exec_error` the engine will simulate a command who executes with status code **1**. + +There are also environment variables to alter things: + +- `SLEEP` witch will simulate a given time duration as command execution time. +- `EXPECT_TYPE` witch let the backend error if set and the step is not the expected step-type. From 3e02a08170a02aeb0ffd1ab942994ade04bbe8dc Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 22 Jun 2024 21:35:32 +0200 Subject: [PATCH 07/19] call it dummy --- cli/exec/exec.go | 4 +- cmd/agent/main.go | 4 +- docs/docs/92-development/09-testing.md | 25 +++---- .../backend/{mock/mock.go => dummy/dummy.go} | 34 +++++----- .../mock_test.go => dummy/dummy_test.go} | 66 +++++++++---------- 5 files changed, 67 insertions(+), 66 deletions(-) rename pipeline/backend/{mock/mock.go => dummy/dummy.go} (86%) rename pipeline/backend/{mock/mock_test.go => dummy/dummy_test.go} (53%) diff --git a/cli/exec/exec.go b/cli/exec/exec.go index 740fe9098d..7b5659c152 100644 --- a/cli/exec/exec.go +++ b/cli/exec/exec.go @@ -32,9 +32,9 @@ import ( "go.woodpecker-ci.org/woodpecker/v2/pipeline" "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend" "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/docker" + "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/dummy" "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/kubernetes" "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/local" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/mock" backendTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml" "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/compiler" @@ -229,7 +229,7 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error kubernetes.New(), docker.New(), local.New(), - mock.New(), + dummy.New(), } backendEngine, err := backend.FindBackend(backendCtx, backends, c.String("backend-engine")) if err != nil { diff --git a/cmd/agent/main.go b/cmd/agent/main.go index 8aad05e2ed..8c4aa3f140 100644 --- a/cmd/agent/main.go +++ b/cmd/agent/main.go @@ -17,9 +17,9 @@ package main import ( "go.woodpecker-ci.org/woodpecker/v2/cmd/agent/core" "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/docker" + "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/dummy" "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/kubernetes" "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/local" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/mock" backendTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" ) @@ -28,6 +28,6 @@ func main() { kubernetes.New(), docker.New(), local.New(), - mock.New(), + dummy.New(), }) } diff --git a/docs/docs/92-development/09-testing.md b/docs/docs/92-development/09-testing.md index 5fda2fcfbc..b1ff62c8a2 100644 --- a/docs/docs/92-development/09-testing.md +++ b/docs/docs/92-development/09-testing.md @@ -4,15 +4,16 @@ ### Unit Tests -TODO +[We use default golang unit tests.](https://go.dev/doc/tutorial/add-a-test) +With [`"github.com/stretchr/testify/assert"`](https://pkg.go.dev/github.com/stretchr/testify@v1.9.0/assert) to simplify the test code. ### Integration Tests #### Pipeline Engine -The pipeline engine has a special backend called **`mock`** witch does not exec but emulate how a typical backend should behave. +The pipeline engine has a special backend called **`dummy`** witch does not exec but emulate how a typical backend should behave. -an example pipeline confilg would be: +An example pipeline config would be: ```yaml when: @@ -31,18 +32,18 @@ services: commands: echo ja ``` -witch could be executed via `woodpecker-cli --log-level trace exec --backend-engine mock example.yaml`: +witch could be executed via `woodpecker-cli --log-level trace exec --backend-engine dummy example.yaml`: ```none ... 9:18PM DBG pipeline/pipeline.go:94 > executing 2 stages, in order of: CLI=exec 9:18PM DBG pipeline/pipeline.go:104 > stage CLI=exec StagePos=0 Steps=echo 9:18PM DBG pipeline/pipeline.go:104 > stage CLI=exec StagePos=1 Steps=echo -9:18PM TRC pipeline/backend/mock/mock.go:75 > create workflow environment taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM TRC pipeline/backend/dummy/dummy.go:75 > create workflow environment taskUUID=01J10P578JQE6E25VV1EQF0745 9:18PM DBG pipeline/pipeline.go:176 > prepare CLI=exec step=echo 9:18PM DBG pipeline/pipeline.go:203 > executing CLI=exec step=echo -9:18PM TRC pipeline/backend/mock/mock.go:81 > start step echo taskUUID=01J10P578JQE6E25VV1EQF0745 -9:18PM TRC pipeline/backend/mock/mock.go:167 > tail logs of step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM TRC pipeline/backend/dummy/dummy.go:81 > start step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM TRC pipeline/backend/dummy/dummy.go:167 > tail logs of step echo taskUUID=01J10P578JQE6E25VV1EQF0745 9:18PM DBG pipeline/pipeline.go:209 > complete CLI=exec step=echo [echo:L0:0s] StepName: echo [echo:L1:0s] StepType: service @@ -51,17 +52,17 @@ witch could be executed via `woodpecker-cli --log-level trace exec --backend-eng [echo:L4:0s] echo ja [echo:L5:0s] 9:18PM DBG pipeline/pipeline.go:176 > prepare CLI=exec step=echo 9:18PM DBG pipeline/pipeline.go:203 > executing CLI=exec step=echo -9:18PM TRC pipeline/backend/mock/mock.go:81 > start step echo taskUUID=01J10P578JQE6E25VV1EQF0745 -9:18PM TRC pipeline/backend/mock/mock.go:167 > tail logs of step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM TRC pipeline/backend/dummy/dummy.go:81 > start step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM TRC pipeline/backend/dummy/dummy.go:167 > tail logs of step echo taskUUID=01J10P578JQE6E25VV1EQF0745 [echo:L0:0s] StepName: echo [echo:L1:0s] StepType: commands [echo:L2:0s] StepUUID: 01J10P578JQE6E25VV1DFSXX1YStepCommands: [echo:L3:0s] [echo:L4:0s] echo ja -[echo:L5:0s] 9:18PM TRC pipeline/backend/mock/mock.go:108 > wait for step echo taskUUID=01J10P578JQE6E25VV1EQF0745 -9:18PM TRC pipeline/backend/mock/mock.go:189 > stop step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +[echo:L5:0s] 9:18PM TRC pipeline/backend/dummy/dummy.go:108 > wait for step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM TRC pipeline/backend/dummy/dummy.go:189 > stop step echo taskUUID=01J10P578JQE6E25VV1EQF0745 9:18PM DBG pipeline/pipeline.go:209 > complete CLI=exec step=echo -9:18PM TRC pipeline/backend/mock/mock.go:210 > delete workflow environment taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM TRC pipeline/backend/dummy/dummy.go:210 > delete workflow environment taskUUID=01J10P578JQE6E25VV1EQF0745 ``` You can control the step behavior via its name: diff --git a/pipeline/backend/mock/mock.go b/pipeline/backend/dummy/dummy.go similarity index 86% rename from pipeline/backend/mock/mock.go rename to pipeline/backend/dummy/dummy.go index c0c9862989..15e1a85221 100644 --- a/pipeline/backend/mock/mock.go +++ b/pipeline/backend/dummy/dummy.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package mock +package dummy import ( "context" @@ -28,12 +28,12 @@ import ( backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" ) -type mock struct { +type dummy struct { kv sync.Map } const ( - // Step names to control mock behavior. + // Step names to control step behavior of dummy backend. StepStartFail = "step_start_fail" StepExecError = "step_exec_error" EnvKeyStepSleep = "SLEEP" @@ -45,39 +45,39 @@ const ( testServiceTimeout = 1 * time.Second ) -// New returns a new Docker Backend. +// New returns a dummy backend. func New() backend.Backend { - return &mock{ + return &dummy{ kv: sync.Map{}, } } -func (e *mock) Name() string { - return "mock" +func (e *dummy) Name() string { + return "dummy" } -func (e *mock) IsAvailable(_ context.Context) bool { +func (e *dummy) IsAvailable(_ context.Context) bool { return true } -func (e *mock) Flags() []cli.Flag { +func (e *dummy) Flags() []cli.Flag { return nil } // Load new client for Docker Backend using environment variables. -func (e *mock) Load(_ context.Context) (*backend.BackendInfo, error) { +func (e *dummy) Load(_ context.Context) (*backend.BackendInfo, error) { return &backend.BackendInfo{ - Platform: "mock", + Platform: "dummy", }, nil } -func (e *mock) SetupWorkflow(_ context.Context, _ *backend.Config, taskUUID string) error { +func (e *dummy) SetupWorkflow(_ context.Context, _ *backend.Config, taskUUID string) error { log.Trace().Str("taskUUID", taskUUID).Msg("create workflow environment") e.kv.Store("task_"+taskUUID, "setup") return nil } -func (e *mock) StartStep(_ context.Context, step *backend.Step, taskUUID string) error { +func (e *dummy) StartStep(_ context.Context, step *backend.Step, taskUUID string) error { log.Trace().Str("taskUUID", taskUUID).Msgf("start step %s", step.Name) // internal state checks @@ -104,7 +104,7 @@ func (e *mock) StartStep(_ context.Context, step *backend.Step, taskUUID string) return nil } -func (e *mock) WaitStep(ctx context.Context, step *backend.Step, taskUUID string) (*backend.State, error) { +func (e *dummy) WaitStep(ctx context.Context, step *backend.Step, taskUUID string) (*backend.State, error) { log.Trace().Str("taskUUID", taskUUID).Msgf("wait for step %s", step.Name) _, exist := e.kv.Load("task_" + taskUUID) @@ -163,7 +163,7 @@ func (e *mock) WaitStep(ctx context.Context, step *backend.Step, taskUUID string }, nil } -func (e *mock) TailStep(_ context.Context, step *backend.Step, taskUUID string) (io.ReadCloser, error) { +func (e *dummy) TailStep(_ context.Context, step *backend.Step, taskUUID string) (io.ReadCloser, error) { log.Trace().Str("taskUUID", taskUUID).Msgf("tail logs of step %s", step.Name) _, exist := e.kv.Load("task_" + taskUUID) @@ -185,7 +185,7 @@ func (e *mock) TailStep(_ context.Context, step *backend.Step, taskUUID string) )), nil } -func (e *mock) DestroyStep(_ context.Context, step *backend.Step, taskUUID string) error { +func (e *dummy) DestroyStep(_ context.Context, step *backend.Step, taskUUID string) error { log.Trace().Str("taskUUID", taskUUID).Msgf("stop step %s", step.Name) _, exist := e.kv.Load("task_" + taskUUID) @@ -206,7 +206,7 @@ func (e *mock) DestroyStep(_ context.Context, step *backend.Step, taskUUID strin return nil } -func (e *mock) DestroyWorkflow(_ context.Context, _ *backend.Config, taskUUID string) error { +func (e *dummy) DestroyWorkflow(_ context.Context, _ *backend.Config, taskUUID string) error { log.Trace().Str("taskUUID", taskUUID).Msgf("delete workflow environment") _, exist := e.kv.Load("task_" + taskUUID) diff --git a/pipeline/backend/mock/mock_test.go b/pipeline/backend/dummy/dummy_test.go similarity index 53% rename from pipeline/backend/mock/mock_test.go rename to pipeline/backend/dummy/dummy_test.go index 907f6f3e89..f5b939699d 100644 --- a/pipeline/backend/mock/mock_test.go +++ b/pipeline/backend/dummy/dummy_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package mock_test +package dummy_test import ( "context" @@ -21,33 +21,33 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/mock" + "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/dummy" "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" ) -func TestSmalPipelineMockRun(t *testing.T) { - mockEngine := mock.New() +func TestSmalPipelineDummyRun(t *testing.T) { + dummyEngine := dummy.New() ctx := context.Background() - assert.True(t, mockEngine.IsAvailable(ctx)) - assert.EqualValues(t, "mock", mockEngine.Name()) - _, err := mockEngine.Load(ctx) + assert.True(t, dummyEngine.IsAvailable(ctx)) + assert.EqualValues(t, "dummy", dummyEngine.Name()) + _, err := dummyEngine.Load(ctx) assert.NoError(t, err) t.Run("expect fail of step func with non setup workflow", func(t *testing.T) { step := &types.Step{Name: "step1", UUID: "SID_1"} nonExistWorkflowID := "WID_NONE" - err := mockEngine.StartStep(ctx, step, nonExistWorkflowID) + err := dummyEngine.StartStep(ctx, step, nonExistWorkflowID) assert.Error(t, err) - _, err = mockEngine.TailStep(ctx, step, nonExistWorkflowID) + _, err = dummyEngine.TailStep(ctx, step, nonExistWorkflowID) assert.Error(t, err) - _, err = mockEngine.WaitStep(ctx, step, nonExistWorkflowID) + _, err = dummyEngine.WaitStep(ctx, step, nonExistWorkflowID) assert.Error(t, err) - err = mockEngine.DestroyStep(ctx, step, nonExistWorkflowID) + err = dummyEngine.DestroyStep(ctx, step, nonExistWorkflowID) assert.Error(t, err) }) @@ -60,75 +60,75 @@ func TestSmalPipelineMockRun(t *testing.T) { } workflowUUID := "WID_1" - assert.NoError(t, mockEngine.SetupWorkflow(ctx, nil, workflowUUID)) + assert.NoError(t, dummyEngine.SetupWorkflow(ctx, nil, workflowUUID)) - assert.NoError(t, mockEngine.StartStep(ctx, step, workflowUUID)) + assert.NoError(t, dummyEngine.StartStep(ctx, step, workflowUUID)) - reader, err := mockEngine.TailStep(ctx, step, workflowUUID) + reader, err := dummyEngine.TailStep(ctx, step, workflowUUID) assert.NoError(t, err) log, err := io.ReadAll(reader) assert.NoError(t, err) assert.EqualValues(t, "StepName: step1\nStepType: \nStepUUID: SID_1StepCommands:\n\necho ja\necho nein\n", string(log)) - state, err := mockEngine.WaitStep(ctx, step, workflowUUID) + state, err := dummyEngine.WaitStep(ctx, step, workflowUUID) assert.NoError(t, err) assert.NoError(t, state.Error) assert.EqualValues(t, 0, state.ExitCode) - assert.NoError(t, mockEngine.DestroyStep(ctx, step, workflowUUID)) + assert.NoError(t, dummyEngine.DestroyStep(ctx, step, workflowUUID)) - assert.NoError(t, mockEngine.DestroyWorkflow(ctx, nil, workflowUUID)) + assert.NoError(t, dummyEngine.DestroyWorkflow(ctx, nil, workflowUUID)) }) t.Run("step exec error", func(t *testing.T) { step := &types.Step{ - Name: mock.StepExecError, + Name: dummy.StepExecError, UUID: "SID_2", Type: types.StepTypePlugin, - Environment: map[string]string{mock.EnvKeyStepType: "plugin"}, + Environment: map[string]string{dummy.EnvKeyStepType: "plugin"}, } workflowUUID := "WID_1" - assert.NoError(t, mockEngine.SetupWorkflow(ctx, nil, workflowUUID)) + assert.NoError(t, dummyEngine.SetupWorkflow(ctx, nil, workflowUUID)) - assert.NoError(t, mockEngine.StartStep(ctx, step, workflowUUID)) + assert.NoError(t, dummyEngine.StartStep(ctx, step, workflowUUID)) - _, err := mockEngine.TailStep(ctx, step, workflowUUID) + _, err := dummyEngine.TailStep(ctx, step, workflowUUID) assert.NoError(t, err) - state, err := mockEngine.WaitStep(ctx, step, workflowUUID) + state, err := dummyEngine.WaitStep(ctx, step, workflowUUID) assert.NoError(t, err) assert.NoError(t, state.Error) assert.EqualValues(t, 1, state.ExitCode) - assert.NoError(t, mockEngine.DestroyStep(ctx, step, workflowUUID)) + assert.NoError(t, dummyEngine.DestroyStep(ctx, step, workflowUUID)) - assert.NoError(t, mockEngine.DestroyWorkflow(ctx, nil, workflowUUID)) + assert.NoError(t, dummyEngine.DestroyWorkflow(ctx, nil, workflowUUID)) }) t.Run("step start fail", func(t *testing.T) { step := &types.Step{ - Name: mock.StepStartFail, + Name: dummy.StepStartFail, UUID: "SID_2", Type: types.StepTypeService, - Environment: map[string]string{mock.EnvKeyStepType: "service"}, + Environment: map[string]string{dummy.EnvKeyStepType: "service"}, } workflowUUID := "WID_1" - assert.NoError(t, mockEngine.SetupWorkflow(ctx, nil, workflowUUID)) + assert.NoError(t, dummyEngine.SetupWorkflow(ctx, nil, workflowUUID)) - assert.Error(t, mockEngine.StartStep(ctx, step, workflowUUID)) + assert.Error(t, dummyEngine.StartStep(ctx, step, workflowUUID)) - _, err := mockEngine.TailStep(ctx, step, workflowUUID) + _, err := dummyEngine.TailStep(ctx, step, workflowUUID) assert.Error(t, err) - state, err := mockEngine.WaitStep(ctx, step, workflowUUID) + state, err := dummyEngine.WaitStep(ctx, step, workflowUUID) assert.Error(t, err) assert.Error(t, state.Error) assert.EqualValues(t, 0, state.ExitCode) - assert.Error(t, mockEngine.DestroyStep(ctx, step, workflowUUID)) + assert.Error(t, dummyEngine.DestroyStep(ctx, step, workflowUUID)) - assert.NoError(t, mockEngine.DestroyWorkflow(ctx, nil, workflowUUID)) + assert.NoError(t, dummyEngine.DestroyWorkflow(ctx, nil, workflowUUID)) }) } From 6972c1c0bb86a2e829580bd2731061a58481f649 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 22 Jun 2024 21:43:58 +0200 Subject: [PATCH 08/19] nit --- docs/docs/92-development/09-testing.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/docs/92-development/09-testing.md b/docs/docs/92-development/09-testing.md index b1ff62c8a2..2f80753a7b 100644 --- a/docs/docs/92-development/09-testing.md +++ b/docs/docs/92-development/09-testing.md @@ -35,7 +35,6 @@ services: witch could be executed via `woodpecker-cli --log-level trace exec --backend-engine dummy example.yaml`: ```none -... 9:18PM DBG pipeline/pipeline.go:94 > executing 2 stages, in order of: CLI=exec 9:18PM DBG pipeline/pipeline.go:104 > stage CLI=exec StagePos=0 Steps=echo 9:18PM DBG pipeline/pipeline.go:104 > stage CLI=exec StagePos=1 Steps=echo @@ -48,7 +47,7 @@ witch could be executed via `woodpecker-cli --log-level trace exec --backend-eng [echo:L0:0s] StepName: echo [echo:L1:0s] StepType: service [echo:L2:0s] StepUUID: 01J10P578JQE6E25VV1A2DNQN9StepCommands: -[echo:L3:0s] +[echo:L3:0s] [echo:L4:0s] echo ja [echo:L5:0s] 9:18PM DBG pipeline/pipeline.go:176 > prepare CLI=exec step=echo 9:18PM DBG pipeline/pipeline.go:203 > executing CLI=exec step=echo @@ -57,7 +56,7 @@ witch could be executed via `woodpecker-cli --log-level trace exec --backend-eng [echo:L0:0s] StepName: echo [echo:L1:0s] StepType: commands [echo:L2:0s] StepUUID: 01J10P578JQE6E25VV1DFSXX1YStepCommands: -[echo:L3:0s] +[echo:L3:0s] [echo:L4:0s] echo ja [echo:L5:0s] 9:18PM TRC pipeline/backend/dummy/dummy.go:108 > wait for step echo taskUUID=01J10P578JQE6E25VV1EQF0745 9:18PM TRC pipeline/backend/dummy/dummy.go:189 > stop step echo taskUUID=01J10P578JQE6E25VV1EQF0745 From b5351fb8daea858641df2fca9694caa775df542d Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 22 Jun 2024 21:55:48 +0200 Subject: [PATCH 09/19] better dummy log output --- docs/docs/92-development/09-testing.md | 24 ++++++++++++++---------- pipeline/backend/dummy/dummy.go | 15 ++++++++++++--- pipeline/backend/dummy/dummy_test.go | 11 ++++++++++- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/docs/docs/92-development/09-testing.md b/docs/docs/92-development/09-testing.md index 2f80753a7b..2e00dbcd73 100644 --- a/docs/docs/92-development/09-testing.md +++ b/docs/docs/92-development/09-testing.md @@ -46,22 +46,26 @@ witch could be executed via `woodpecker-cli --log-level trace exec --backend-eng 9:18PM DBG pipeline/pipeline.go:209 > complete CLI=exec step=echo [echo:L0:0s] StepName: echo [echo:L1:0s] StepType: service -[echo:L2:0s] StepUUID: 01J10P578JQE6E25VV1A2DNQN9StepCommands: -[echo:L3:0s] -[echo:L4:0s] echo ja -[echo:L5:0s] 9:18PM DBG pipeline/pipeline.go:176 > prepare CLI=exec step=echo +[echo:L2:0s] StepUUID: 01J10P578JQE6E25VV1A2DNQN9 +[echo:L3:0s] StepCommands: +[echo:L4:0s] ------------------ +[echo:L5:0s] echo ja +[echo:L6:0s] ------------------ +[echo:L7:0s] 9:18PM DBG pipeline/pipeline.go:176 > prepare CLI=exec step=echo 9:18PM DBG pipeline/pipeline.go:203 > executing CLI=exec step=echo 9:18PM TRC pipeline/backend/dummy/dummy.go:81 > start step echo taskUUID=01J10P578JQE6E25VV1EQF0745 9:18PM TRC pipeline/backend/dummy/dummy.go:167 > tail logs of step echo taskUUID=01J10P578JQE6E25VV1EQF0745 [echo:L0:0s] StepName: echo [echo:L1:0s] StepType: commands -[echo:L2:0s] StepUUID: 01J10P578JQE6E25VV1DFSXX1YStepCommands: -[echo:L3:0s] -[echo:L4:0s] echo ja -[echo:L5:0s] 9:18PM TRC pipeline/backend/dummy/dummy.go:108 > wait for step echo taskUUID=01J10P578JQE6E25VV1EQF0745 -9:18PM TRC pipeline/backend/dummy/dummy.go:189 > stop step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +[echo:L2:0s] StepUUID: 01J10P578JQE6E25VV1DFSXX1Y +[echo:L3:0s] StepCommands: +[echo:L4:0s] ------------------ +[echo:L5:0s] echo ja +[echo:L6:0s] ------------------ +[echo:L7:0s] 9:18PM TRC pipeline/backend/dummy/dummy.go:108 > wait for step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM TRC pipeline/backend/dummy/dummy.go:187 > stop step echo taskUUID=01J10P578JQE6E25VV1EQF0745 9:18PM DBG pipeline/pipeline.go:209 > complete CLI=exec step=echo -9:18PM TRC pipeline/backend/dummy/dummy.go:210 > delete workflow environment taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM TRC pipeline/backend/dummy/dummy.go:208 > delete workflow environment taskUUID=01J10P578JQE6E25VV1EQF0745 ``` You can control the step behavior via its name: diff --git a/pipeline/backend/dummy/dummy.go b/pipeline/backend/dummy/dummy.go index 15e1a85221..6ca3280b26 100644 --- a/pipeline/backend/dummy/dummy.go +++ b/pipeline/backend/dummy/dummy.go @@ -180,9 +180,7 @@ func (e *dummy) TailStep(_ context.Context, step *backend.Step, taskUUID string) return nil, fmt.Errorf("WaitStep expect step '%s' (%s) to be '%s' but it is: %s", step.Name, step.UUID, stepStateStarted, stepState) } - return io.NopCloser(strings.NewReader( - fmt.Sprintf("StepName: %s\nStepType: %s\nStepUUID: %sStepCommands:\n\n%s\n", step.Name, step.Type, step.UUID, strings.Join(step.Commands, "\n")), - )), nil + return io.NopCloser(strings.NewReader(dummyExecStepOutput(step))), nil } func (e *dummy) DestroyStep(_ context.Context, step *backend.Step, taskUUID string) error { @@ -216,3 +214,14 @@ func (e *dummy) DestroyWorkflow(_ context.Context, _ *backend.Config, taskUUID s e.kv.Delete("task_" + taskUUID) return nil } + +func dummyExecStepOutput(step *backend.Step) string { + return fmt.Sprintf(`StepName: %s +StepType: %s +StepUUID: %s +StepCommands: +------------------ +%s +------------------ +`, step.Name, step.Type, step.UUID, strings.Join(step.Commands, "\n")) +} diff --git a/pipeline/backend/dummy/dummy_test.go b/pipeline/backend/dummy/dummy_test.go index f5b939699d..e5429afe78 100644 --- a/pipeline/backend/dummy/dummy_test.go +++ b/pipeline/backend/dummy/dummy_test.go @@ -55,6 +55,7 @@ func TestSmalPipelineDummyRun(t *testing.T) { step := &types.Step{ Name: "step1", UUID: "SID_1", + Type: types.StepTypeCommands, Environment: map[string]string{}, Commands: []string{"echo ja", "echo nein"}, } @@ -68,7 +69,15 @@ func TestSmalPipelineDummyRun(t *testing.T) { assert.NoError(t, err) log, err := io.ReadAll(reader) assert.NoError(t, err) - assert.EqualValues(t, "StepName: step1\nStepType: \nStepUUID: SID_1StepCommands:\n\necho ja\necho nein\n", string(log)) + assert.EqualValues(t, `StepName: step1 +StepType: commands +StepUUID: SID_1 +StepCommands: +------------------ +echo ja +echo nein +------------------ +`, string(log)) state, err := dummyEngine.WaitStep(ctx, step, workflowUUID) assert.NoError(t, err) From 3373780572e8d82aa7884d77042d35c35485831d Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 22 Jun 2024 22:08:56 +0200 Subject: [PATCH 10/19] prettier --- docs/docs/92-development/09-testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/92-development/09-testing.md b/docs/docs/92-development/09-testing.md index 2e00dbcd73..795310f6fd 100644 --- a/docs/docs/92-development/09-testing.md +++ b/docs/docs/92-development/09-testing.md @@ -24,7 +24,7 @@ steps: image: dummy commands: echo ja environment: - SLEEP: "1s" + SLEEP: '1s' services: echo: From e09de765f10ce61bb922b40b3031ea17ddb7bbc8 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Mon, 24 Jun 2024 01:34:55 +0200 Subject: [PATCH 11/19] exclude it in release binary --- pipeline/backend/dummy/dummy.go | 3 ++ pipeline/backend/dummy/dummy_noop.go | 78 ++++++++++++++++++++++++++++ pipeline/backend/dummy/dummy_test.go | 3 ++ 3 files changed, 84 insertions(+) create mode 100644 pipeline/backend/dummy/dummy_noop.go diff --git a/pipeline/backend/dummy/dummy.go b/pipeline/backend/dummy/dummy.go index 6ca3280b26..af89928117 100644 --- a/pipeline/backend/dummy/dummy.go +++ b/pipeline/backend/dummy/dummy.go @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build integration +// +build integration + package dummy import ( diff --git a/pipeline/backend/dummy/dummy_noop.go b/pipeline/backend/dummy/dummy_noop.go new file mode 100644 index 0000000000..9febe568b5 --- /dev/null +++ b/pipeline/backend/dummy/dummy_noop.go @@ -0,0 +1,78 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !integration +// +build !integration + +package dummy + +import ( + "context" + "errors" + "io" + + "github.com/urfave/cli/v2" + + "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" +) + +type noop struct{} + +var ErrOnCompileExcluded = errors.New("the dummy backend engine was excluded on compile time") + +// New returns a dummy backend. +func New() types.Backend { + return &noop{} +} + +func (e *noop) Name() string { + return "dummy" +} + +func (e *noop) IsAvailable(_ context.Context) bool { + return false +} + +func (e *noop) Flags() []cli.Flag { + return nil +} + +// Load new client for Docker Backend using environment variables. +func (e *noop) Load(_ context.Context) (*types.BackendInfo, error) { + return nil, ErrOnCompileExcluded +} + +func (e *noop) SetupWorkflow(_ context.Context, _ *types.Config, taskUUID string) error { + return ErrOnCompileExcluded +} + +func (e *noop) StartStep(_ context.Context, step *types.Step, taskUUID string) error { + return ErrOnCompileExcluded +} + +func (e *noop) WaitStep(ctx context.Context, step *types.Step, taskUUID string) (*types.State, error) { + return nil, ErrOnCompileExcluded +} + +func (e *noop) TailStep(_ context.Context, step *types.Step, taskUUID string) (io.ReadCloser, error) { + return nil, ErrOnCompileExcluded +} + +func (e *noop) DestroyStep(_ context.Context, step *types.Step, taskUUID string) error { + return ErrOnCompileExcluded +} + +func (e *noop) DestroyWorkflow(_ context.Context, _ *types.Config, taskUUID string) error { + return ErrOnCompileExcluded +} diff --git a/pipeline/backend/dummy/dummy_test.go b/pipeline/backend/dummy/dummy_test.go index e5429afe78..d33c1ebada 100644 --- a/pipeline/backend/dummy/dummy_test.go +++ b/pipeline/backend/dummy/dummy_test.go @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build integration +// +build integration + package dummy_test import ( From 455ca10004d6fa3d52fd565eb20096ad504088d3 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Mon, 24 Jun 2024 01:47:52 +0200 Subject: [PATCH 12/19] pass tags flag on compile --- Makefile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index d5471b0b45..764f4b08b1 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,7 @@ else endif endif +TAGS ?= LDFLAGS := -X go.woodpecker-ci.org/woodpecker/v2/version.Version=${VERSION} STATIC_BUILD ?= true ifeq ($(STATIC_BUILD),true) @@ -193,13 +194,13 @@ build-ui: ## Build UI (cd web/; pnpm install --frozen-lockfile; pnpm build) build-server: build-ui generate-swagger ## Build server - CGO_ENABLED=${CGO_ENABLED} GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags '${LDFLAGS}' -o dist/woodpecker-server${BIN_SUFFIX} go.woodpecker-ci.org/woodpecker/v2/cmd/server + CGO_ENABLED=${CGO_ENABLED} GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -tags '$(TAGS)' -ldflags '${LDFLAGS}' -o dist/woodpecker-server${BIN_SUFFIX} go.woodpecker-ci.org/woodpecker/v2/cmd/server build-agent: ## Build agent - CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags '${LDFLAGS}' -o dist/woodpecker-agent${BIN_SUFFIX} go.woodpecker-ci.org/woodpecker/v2/cmd/agent + CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -tags '$(TAGS)' -ldflags '${LDFLAGS}' -o dist/woodpecker-agent${BIN_SUFFIX} go.woodpecker-ci.org/woodpecker/v2/cmd/agent build-cli: ## Build cli - CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags '${LDFLAGS}' -o dist/woodpecker-cli${BIN_SUFFIX} go.woodpecker-ci.org/woodpecker/v2/cmd/cli + CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -tags '$(TAGS)' -ldflags '${LDFLAGS}' -o dist/woodpecker-cli${BIN_SUFFIX} go.woodpecker-ci.org/woodpecker/v2/cmd/cli build-tarball: ## Build tar archive mkdir -p dist && tar chzvf dist/woodpecker-src.tar.gz \ From 0f5d2f11620282b841df5d563836aaf0dad4e6d9 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Mon, 24 Jun 2024 04:44:21 +0200 Subject: [PATCH 13/19] jup --- pipeline/backend/dummy/dummy_noop.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pipeline/backend/dummy/dummy_noop.go b/pipeline/backend/dummy/dummy_noop.go index 9febe568b5..8e0434a59c 100644 --- a/pipeline/backend/dummy/dummy_noop.go +++ b/pipeline/backend/dummy/dummy_noop.go @@ -40,7 +40,7 @@ func (e *noop) Name() string { return "dummy" } -func (e *noop) IsAvailable(_ context.Context) bool { +func (e *noop) IsAvailable(context.Context) bool { return false } @@ -49,30 +49,30 @@ func (e *noop) Flags() []cli.Flag { } // Load new client for Docker Backend using environment variables. -func (e *noop) Load(_ context.Context) (*types.BackendInfo, error) { +func (e *noop) Load(context.Context) (*types.BackendInfo, error) { return nil, ErrOnCompileExcluded } -func (e *noop) SetupWorkflow(_ context.Context, _ *types.Config, taskUUID string) error { +func (e *noop) SetupWorkflow(context.Context, *types.Config, string) error { return ErrOnCompileExcluded } -func (e *noop) StartStep(_ context.Context, step *types.Step, taskUUID string) error { +func (e *noop) StartStep(context.Context, *types.Step, string) error { return ErrOnCompileExcluded } -func (e *noop) WaitStep(ctx context.Context, step *types.Step, taskUUID string) (*types.State, error) { +func (e *noop) WaitStep(context.Context, *types.Step, string) (*types.State, error) { return nil, ErrOnCompileExcluded } -func (e *noop) TailStep(_ context.Context, step *types.Step, taskUUID string) (io.ReadCloser, error) { +func (e *noop) TailStep(context.Context, *types.Step, string) (io.ReadCloser, error) { return nil, ErrOnCompileExcluded } -func (e *noop) DestroyStep(_ context.Context, step *types.Step, taskUUID string) error { +func (e *noop) DestroyStep(context.Context, *types.Step, string) error { return ErrOnCompileExcluded } -func (e *noop) DestroyWorkflow(_ context.Context, _ *types.Config, taskUUID string) error { +func (e *noop) DestroyWorkflow(context.Context, *types.Config, string) error { return ErrOnCompileExcluded } From f71109d1b772c84a452f85d76362284f98aeb363 Mon Sep 17 00:00:00 2001 From: "m.huber" Date: Mon, 24 Jun 2024 15:33:01 +0200 Subject: [PATCH 14/19] also dont compie mockerly generated into release --- pipeline/backend/dummy/dummy.go | 4 ++-- pipeline/backend/dummy/dummy_noop.go | 4 ++-- pipeline/backend/dummy/dummy_test.go | 4 ++-- server/forge/forge.go | 2 +- server/forge/mocks/forge.go | 3 +++ 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/pipeline/backend/dummy/dummy.go b/pipeline/backend/dummy/dummy.go index af89928117..5745483582 100644 --- a/pipeline/backend/dummy/dummy.go +++ b/pipeline/backend/dummy/dummy.go @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build integration -// +build integration +//go:build integration || test +// +build integration test package dummy diff --git a/pipeline/backend/dummy/dummy_noop.go b/pipeline/backend/dummy/dummy_noop.go index 8e0434a59c..fd530e0432 100644 --- a/pipeline/backend/dummy/dummy_noop.go +++ b/pipeline/backend/dummy/dummy_noop.go @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build !integration -// +build !integration +//go:build !(integration || test) +// +build !integration,!test package dummy diff --git a/pipeline/backend/dummy/dummy_test.go b/pipeline/backend/dummy/dummy_test.go index d33c1ebada..df6bcba8e3 100644 --- a/pipeline/backend/dummy/dummy_test.go +++ b/pipeline/backend/dummy/dummy_test.go @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build integration -// +build integration +//go:build integration || test +// +build integration test package dummy_test diff --git a/server/forge/forge.go b/server/forge/forge.go index bb5db187d6..4169e60d05 100644 --- a/server/forge/forge.go +++ b/server/forge/forge.go @@ -15,7 +15,7 @@ package forge -//go:generate mockery --name Forge --output mocks --case underscore +//go:generate mockery --name Forge --output mocks --case underscore --note "+build test" import ( "context" diff --git a/server/forge/mocks/forge.go b/server/forge/mocks/forge.go index a926ca16a0..3deb6b960d 100644 --- a/server/forge/mocks/forge.go +++ b/server/forge/mocks/forge.go @@ -1,5 +1,8 @@ // Code generated by mockery. DO NOT EDIT. +//go:build test +// +build test + package mocks import ( From 7efddcb7a84455e21247645a1842d4395113d942 Mon Sep 17 00:00:00 2001 From: "m.huber" Date: Mon, 24 Jun 2024 15:41:22 +0200 Subject: [PATCH 15/19] all mockerly only in test --- Makefile | 14 +++++++------- pipeline/rpc/mocks/peer.go | 3 +++ pipeline/rpc/peer.go | 2 +- server/services/manager.go | 2 +- server/services/mocks/manager.go | 3 +++ server/store/mocks/store.go | 3 +++ server/store/store.go | 2 +- woodpecker-go/woodpecker/interface.go | 2 +- woodpecker-go/woodpecker/mocks/client.go | 3 +++ 9 files changed, 23 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 3568a04d24..b1f7a4f020 100644 --- a/Makefile +++ b/Makefile @@ -163,20 +163,20 @@ lint-ui: ui-dependencies ## Lint UI code (cd web/; pnpm lint --quiet) test-agent: ## Test agent code - go test -race -cover -coverprofile agent-coverage.out -timeout 30s go.woodpecker-ci.org/woodpecker/v2/cmd/agent go.woodpecker-ci.org/woodpecker/v2/agent/... + go test -race -cover -coverprofile agent-coverage.out -timeout 30s -tags 'test $(TAGS)' go.woodpecker-ci.org/woodpecker/v2/cmd/agent go.woodpecker-ci.org/woodpecker/v2/agent/... test-server: ## Test server code - go test -race -cover -coverprofile server-coverage.out -timeout 30s go.woodpecker-ci.org/woodpecker/v2/cmd/server $(shell go list go.woodpecker-ci.org/woodpecker/v2/server/... | grep -v '/store') + go test -race -cover -coverprofile server-coverage.out -timeout 30s -tags 'test $(TAGS)' go.woodpecker-ci.org/woodpecker/v2/cmd/server $(shell go list go.woodpecker-ci.org/woodpecker/v2/server/... | grep -v '/store') test-cli: ## Test cli code - go test -race -cover -coverprofile cli-coverage.out -timeout 30s go.woodpecker-ci.org/woodpecker/v2/cmd/cli go.woodpecker-ci.org/woodpecker/v2/cli/... + go test -race -cover -coverprofile cli-coverage.out -timeout 30s -tags 'test $(TAGS)' go.woodpecker-ci.org/woodpecker/v2/cmd/cli go.woodpecker-ci.org/woodpecker/v2/cli/... test-server-datastore: ## Test server datastore - go test -timeout 120s -run TestMigrate go.woodpecker-ci.org/woodpecker/v2/server/store/... - go test -race -timeout 30s -skip TestMigrate go.woodpecker-ci.org/woodpecker/v2/server/store/... + go test -timeout 120s -tags 'test $(TAGS)' -run TestMigrate go.woodpecker-ci.org/woodpecker/v2/server/store/... + go test -race -timeout 30s -tags 'test $(TAGS)' -skip TestMigrate go.woodpecker-ci.org/woodpecker/v2/server/store/... test-server-datastore-coverage: ## Test server datastore with coverage report - go test -race -cover -coverprofile datastore-coverage.out -timeout 180s go.woodpecker-ci.org/woodpecker/v2/server/store/... + go test -race -cover -coverprofile datastore-coverage.out -timeout 180s -tags 'test $(TAGS)' go.woodpecker-ci.org/woodpecker/v2/server/store/... test-ui: ui-dependencies ## Test UI code (cd web/; pnpm run lint) @@ -185,7 +185,7 @@ test-ui: ui-dependencies ## Test UI code (cd web/; pnpm run test) test-lib: ## Test lib code - go test -race -cover -coverprofile coverage.out -timeout 30s $(shell go list ./... | grep -v '/cmd\|/agent\|/cli\|/server') + go test -race -cover -coverprofile coverage.out -timeout 30s -tags 'test $(TAGS)' $(shell go list ./... | grep -v '/cmd\|/agent\|/cli\|/server') .PHONY: test test: test-agent test-server test-server-datastore test-cli test-lib ## Run all tests diff --git a/pipeline/rpc/mocks/peer.go b/pipeline/rpc/mocks/peer.go index 3409ca35d5..3279e2b3a3 100644 --- a/pipeline/rpc/mocks/peer.go +++ b/pipeline/rpc/mocks/peer.go @@ -1,5 +1,8 @@ // Code generated by mockery. DO NOT EDIT. +//go:build test +// +build test + package mocks import ( diff --git a/pipeline/rpc/peer.go b/pipeline/rpc/peer.go index 8025080b55..d8d82a1630 100644 --- a/pipeline/rpc/peer.go +++ b/pipeline/rpc/peer.go @@ -50,7 +50,7 @@ type ( } ) -//go:generate mockery --name Peer --output mocks --case underscore +//go:generate mockery --name Peer --output mocks --case underscore --note "+build test" // Peer defines a peer-to-peer connection. type Peer interface { diff --git a/server/services/manager.go b/server/services/manager.go index 079222a02d..5526937f86 100644 --- a/server/services/manager.go +++ b/server/services/manager.go @@ -30,7 +30,7 @@ import ( "go.woodpecker-ci.org/woodpecker/v2/server/store" ) -//go:generate mockery --name Manager --output mocks --case underscore +//go:generate mockery --name Manager --output mocks --case underscore --note "+build test" const forgeCacheTTL = 10 * time.Minute diff --git a/server/services/mocks/manager.go b/server/services/mocks/manager.go index 1d64c2af38..b71d8ea4d5 100644 --- a/server/services/mocks/manager.go +++ b/server/services/mocks/manager.go @@ -1,5 +1,8 @@ // Code generated by mockery. DO NOT EDIT. +//go:build test +// +build test + package mocks import ( diff --git a/server/store/mocks/store.go b/server/store/mocks/store.go index ee1a825d08..bfafcc6d41 100644 --- a/server/store/mocks/store.go +++ b/server/store/mocks/store.go @@ -1,5 +1,8 @@ // Code generated by mockery. DO NOT EDIT. +//go:build test +// +build test + package mocks import ( diff --git a/server/store/store.go b/server/store/store.go index 2a007e487c..ecf33ad64a 100644 --- a/server/store/store.go +++ b/server/store/store.go @@ -14,7 +14,7 @@ package store -//go:generate mockery --name Store --output mocks --case underscore +//go:generate mockery --name Store --output mocks --case underscore --note "+build test" import ( "go.woodpecker-ci.org/woodpecker/v2/server/model" diff --git a/woodpecker-go/woodpecker/interface.go b/woodpecker-go/woodpecker/interface.go index 6adc29d18c..0d54af18f0 100644 --- a/woodpecker-go/woodpecker/interface.go +++ b/woodpecker-go/woodpecker/interface.go @@ -18,7 +18,7 @@ import ( "net/http" ) -//go:generate mockery --name Client --output mocks --case underscore +//go:generate mockery --name Client --output mocks --case underscore --note "+build test" // Client is used to communicate with a Woodpecker server. type Client interface { diff --git a/woodpecker-go/woodpecker/mocks/client.go b/woodpecker-go/woodpecker/mocks/client.go index 7f9571c564..fcbd7f9579 100644 --- a/woodpecker-go/woodpecker/mocks/client.go +++ b/woodpecker-go/woodpecker/mocks/client.go @@ -1,5 +1,8 @@ // Code generated by mockery. DO NOT EDIT. +//go:build test +// +build test + package mocks import ( From a9cd8764b651ec9ecd941fe33665d90f27b696d0 Mon Sep 17 00:00:00 2001 From: "m.huber" Date: Mon, 24 Jun 2024 19:15:40 +0200 Subject: [PATCH 16/19] fix lint --- .golangci.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.golangci.yaml b/.golangci.yaml index 391282e2c4..6631206798 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -184,3 +184,6 @@ issues: run: timeout: 15m + build-tags: + - test + - integration From 9f041a489b5ca56d866cf54050a52de660709d4b Mon Sep 17 00:00:00 2001 From: "m.huber" Date: Mon, 24 Jun 2024 22:51:17 +0200 Subject: [PATCH 17/19] clean --- cli/exec/exec.go | 2 - cmd/agent/main.go | 2 - pipeline/backend/dummy/dummy.go | 230 --------------------------- pipeline/backend/dummy/dummy_noop.go | 78 --------- pipeline/backend/dummy/dummy_test.go | 146 ----------------- 5 files changed, 458 deletions(-) delete mode 100644 pipeline/backend/dummy/dummy.go delete mode 100644 pipeline/backend/dummy/dummy_noop.go delete mode 100644 pipeline/backend/dummy/dummy_test.go diff --git a/cli/exec/exec.go b/cli/exec/exec.go index 7b5659c152..68db9aabef 100644 --- a/cli/exec/exec.go +++ b/cli/exec/exec.go @@ -32,7 +32,6 @@ import ( "go.woodpecker-ci.org/woodpecker/v2/pipeline" "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend" "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/docker" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/dummy" "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/kubernetes" "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/local" backendTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" @@ -229,7 +228,6 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error kubernetes.New(), docker.New(), local.New(), - dummy.New(), } backendEngine, err := backend.FindBackend(backendCtx, backends, c.String("backend-engine")) if err != nil { diff --git a/cmd/agent/main.go b/cmd/agent/main.go index 8c4aa3f140..062fc07292 100644 --- a/cmd/agent/main.go +++ b/cmd/agent/main.go @@ -17,7 +17,6 @@ package main import ( "go.woodpecker-ci.org/woodpecker/v2/cmd/agent/core" "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/docker" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/dummy" "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/kubernetes" "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/local" backendTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" @@ -28,6 +27,5 @@ func main() { kubernetes.New(), docker.New(), local.New(), - dummy.New(), }) } diff --git a/pipeline/backend/dummy/dummy.go b/pipeline/backend/dummy/dummy.go deleted file mode 100644 index 5745483582..0000000000 --- a/pipeline/backend/dummy/dummy.go +++ /dev/null @@ -1,230 +0,0 @@ -// Copyright 2024 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build integration || test -// +build integration test - -package dummy - -import ( - "context" - "fmt" - "io" - "strings" - "sync" - "time" - - "github.com/rs/zerolog/log" - "github.com/urfave/cli/v2" - - backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" -) - -type dummy struct { - kv sync.Map -} - -const ( - // Step names to control step behavior of dummy backend. - StepStartFail = "step_start_fail" - StepExecError = "step_exec_error" - EnvKeyStepSleep = "SLEEP" - EnvKeyStepType = "EXPECT_TYPE" - - // Internal const. - stepStateStarted = "started" - stepStateDone = "done" - testServiceTimeout = 1 * time.Second -) - -// New returns a dummy backend. -func New() backend.Backend { - return &dummy{ - kv: sync.Map{}, - } -} - -func (e *dummy) Name() string { - return "dummy" -} - -func (e *dummy) IsAvailable(_ context.Context) bool { - return true -} - -func (e *dummy) Flags() []cli.Flag { - return nil -} - -// Load new client for Docker Backend using environment variables. -func (e *dummy) Load(_ context.Context) (*backend.BackendInfo, error) { - return &backend.BackendInfo{ - Platform: "dummy", - }, nil -} - -func (e *dummy) SetupWorkflow(_ context.Context, _ *backend.Config, taskUUID string) error { - log.Trace().Str("taskUUID", taskUUID).Msg("create workflow environment") - e.kv.Store("task_"+taskUUID, "setup") - return nil -} - -func (e *dummy) StartStep(_ context.Context, step *backend.Step, taskUUID string) error { - log.Trace().Str("taskUUID", taskUUID).Msgf("start step %s", step.Name) - - // internal state checks - _, exist := e.kv.Load("task_" + taskUUID) - if !exist { - return fmt.Errorf("expect env of workflow %s to exist but found none to destroy", taskUUID) - } - stepState, stepExist := e.kv.Load(fmt.Sprintf("task_%s_step_%s", taskUUID, step.UUID)) - if stepExist { - // Detect issues like https://github.com/woodpecker-ci/woodpecker/issues/3494 - return fmt.Errorf("StartStep detect already started step '%s' (%s) in state: %s", step.Name, step.UUID, stepState) - } - - if step.Name == StepStartFail { - return fmt.Errorf("expected fail to start step") - } - - expectStepType, testStepType := step.Environment[EnvKeyStepType] - if testStepType && string(step.Type) != expectStepType { - return fmt.Errorf("expected step type '%s' but got '%s'", expectStepType, step.Type) - } - - e.kv.Store(fmt.Sprintf("task_%s_step_%s", taskUUID, step.UUID), stepStateStarted) - return nil -} - -func (e *dummy) WaitStep(ctx context.Context, step *backend.Step, taskUUID string) (*backend.State, error) { - log.Trace().Str("taskUUID", taskUUID).Msgf("wait for step %s", step.Name) - - _, exist := e.kv.Load("task_" + taskUUID) - if !exist { - err := fmt.Errorf("expect env of workflow %s to exist but found none to destroy", taskUUID) - return &backend.State{Error: err}, err - } - - // check state - stepState, stepExist := e.kv.Load(fmt.Sprintf("task_%s_step_%s", taskUUID, step.UUID)) - if !stepExist { - err := fmt.Errorf("WaitStep expect step '%s' (%s) to be created but found none", step.Name, step.UUID) - return &backend.State{Error: err}, err - } - if stepState != stepStateStarted { - err := fmt.Errorf("WaitStep expect step '%s' (%s) to be '%s' but it is: %s", step.Name, step.UUID, stepStateStarted, stepState) - return &backend.State{Error: err}, err - } - - // extend wait time logic - if sleep, sleepExist := step.Environment[EnvKeyStepSleep]; sleepExist { - toSleep, err := time.ParseDuration(sleep) - if err != nil { - err = fmt.Errorf("WaitStep fail to parse sleep duration: %w", err) - return &backend.State{Error: err}, err - } - time.Sleep(toSleep) - } else { - if step.Type == backend.StepTypeService { - select { - case <-time.NewTimer(testServiceTimeout).C: - err := fmt.Errorf("WaitStep fail due to timeout of service after 1 second") - return &backend.State{Error: err}, err - case <-ctx.Done(): - // context for service closed ... we can move forward - } - } else { - time.Sleep(time.Nanosecond) - } - } - - e.kv.Store(fmt.Sprintf("task_%s_step_%s", taskUUID, step.UUID), stepStateDone) - - if step.Name == StepExecError { - return &backend.State{ - ExitCode: 1, - Exited: true, - OOMKilled: false, - }, nil - } - - return &backend.State{ - ExitCode: 0, - Exited: true, - OOMKilled: false, - }, nil -} - -func (e *dummy) TailStep(_ context.Context, step *backend.Step, taskUUID string) (io.ReadCloser, error) { - log.Trace().Str("taskUUID", taskUUID).Msgf("tail logs of step %s", step.Name) - - _, exist := e.kv.Load("task_" + taskUUID) - if !exist { - return nil, fmt.Errorf("expect env of workflow %s to exist but found none to destroy", taskUUID) - } - - // check state - stepState, stepExist := e.kv.Load(fmt.Sprintf("task_%s_step_%s", taskUUID, step.UUID)) - if !stepExist { - return nil, fmt.Errorf("WaitStep expect step '%s' (%s) to be created but found none", step.Name, step.UUID) - } - if stepState != stepStateStarted { - return nil, fmt.Errorf("WaitStep expect step '%s' (%s) to be '%s' but it is: %s", step.Name, step.UUID, stepStateStarted, stepState) - } - - return io.NopCloser(strings.NewReader(dummyExecStepOutput(step))), nil -} - -func (e *dummy) DestroyStep(_ context.Context, step *backend.Step, taskUUID string) error { - log.Trace().Str("taskUUID", taskUUID).Msgf("stop step %s", step.Name) - - _, exist := e.kv.Load("task_" + taskUUID) - if !exist { - return fmt.Errorf("expect env of workflow %s to exist but found none to destroy", taskUUID) - } - - // check state - stepState, stepExist := e.kv.Load(fmt.Sprintf("task_%s_step_%s", taskUUID, step.UUID)) - if !stepExist { - return fmt.Errorf("WaitStep expect step '%s' (%s) to be created but found none", step.Name, step.UUID) - } - if stepState != stepStateDone { - return fmt.Errorf("WaitStep expect step '%s' (%s) to be '%s' but it is: %s", step.Name, step.UUID, stepStateDone, stepState) - } - - e.kv.Delete(fmt.Sprintf("task_%s_step_%s", taskUUID, step.UUID)) - return nil -} - -func (e *dummy) DestroyWorkflow(_ context.Context, _ *backend.Config, taskUUID string) error { - log.Trace().Str("taskUUID", taskUUID).Msgf("delete workflow environment") - - _, exist := e.kv.Load("task_" + taskUUID) - if !exist { - return fmt.Errorf("expect env of workflow %s to exist but found none to destroy", taskUUID) - } - e.kv.Delete("task_" + taskUUID) - return nil -} - -func dummyExecStepOutput(step *backend.Step) string { - return fmt.Sprintf(`StepName: %s -StepType: %s -StepUUID: %s -StepCommands: ------------------- -%s ------------------- -`, step.Name, step.Type, step.UUID, strings.Join(step.Commands, "\n")) -} diff --git a/pipeline/backend/dummy/dummy_noop.go b/pipeline/backend/dummy/dummy_noop.go deleted file mode 100644 index fd530e0432..0000000000 --- a/pipeline/backend/dummy/dummy_noop.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2024 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build !(integration || test) -// +build !integration,!test - -package dummy - -import ( - "context" - "errors" - "io" - - "github.com/urfave/cli/v2" - - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" -) - -type noop struct{} - -var ErrOnCompileExcluded = errors.New("the dummy backend engine was excluded on compile time") - -// New returns a dummy backend. -func New() types.Backend { - return &noop{} -} - -func (e *noop) Name() string { - return "dummy" -} - -func (e *noop) IsAvailable(context.Context) bool { - return false -} - -func (e *noop) Flags() []cli.Flag { - return nil -} - -// Load new client for Docker Backend using environment variables. -func (e *noop) Load(context.Context) (*types.BackendInfo, error) { - return nil, ErrOnCompileExcluded -} - -func (e *noop) SetupWorkflow(context.Context, *types.Config, string) error { - return ErrOnCompileExcluded -} - -func (e *noop) StartStep(context.Context, *types.Step, string) error { - return ErrOnCompileExcluded -} - -func (e *noop) WaitStep(context.Context, *types.Step, string) (*types.State, error) { - return nil, ErrOnCompileExcluded -} - -func (e *noop) TailStep(context.Context, *types.Step, string) (io.ReadCloser, error) { - return nil, ErrOnCompileExcluded -} - -func (e *noop) DestroyStep(context.Context, *types.Step, string) error { - return ErrOnCompileExcluded -} - -func (e *noop) DestroyWorkflow(context.Context, *types.Config, string) error { - return ErrOnCompileExcluded -} diff --git a/pipeline/backend/dummy/dummy_test.go b/pipeline/backend/dummy/dummy_test.go deleted file mode 100644 index df6bcba8e3..0000000000 --- a/pipeline/backend/dummy/dummy_test.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2024 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build integration || test -// +build integration test - -package dummy_test - -import ( - "context" - "io" - "testing" - - "github.com/stretchr/testify/assert" - - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/dummy" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" -) - -func TestSmalPipelineDummyRun(t *testing.T) { - dummyEngine := dummy.New() - ctx := context.Background() - - assert.True(t, dummyEngine.IsAvailable(ctx)) - assert.EqualValues(t, "dummy", dummyEngine.Name()) - _, err := dummyEngine.Load(ctx) - assert.NoError(t, err) - - t.Run("expect fail of step func with non setup workflow", func(t *testing.T) { - step := &types.Step{Name: "step1", UUID: "SID_1"} - nonExistWorkflowID := "WID_NONE" - - err := dummyEngine.StartStep(ctx, step, nonExistWorkflowID) - assert.Error(t, err) - - _, err = dummyEngine.TailStep(ctx, step, nonExistWorkflowID) - assert.Error(t, err) - - _, err = dummyEngine.WaitStep(ctx, step, nonExistWorkflowID) - assert.Error(t, err) - - err = dummyEngine.DestroyStep(ctx, step, nonExistWorkflowID) - assert.Error(t, err) - }) - - t.Run("step exec successfully", func(t *testing.T) { - step := &types.Step{ - Name: "step1", - UUID: "SID_1", - Type: types.StepTypeCommands, - Environment: map[string]string{}, - Commands: []string{"echo ja", "echo nein"}, - } - workflowUUID := "WID_1" - - assert.NoError(t, dummyEngine.SetupWorkflow(ctx, nil, workflowUUID)) - - assert.NoError(t, dummyEngine.StartStep(ctx, step, workflowUUID)) - - reader, err := dummyEngine.TailStep(ctx, step, workflowUUID) - assert.NoError(t, err) - log, err := io.ReadAll(reader) - assert.NoError(t, err) - assert.EqualValues(t, `StepName: step1 -StepType: commands -StepUUID: SID_1 -StepCommands: ------------------- -echo ja -echo nein ------------------- -`, string(log)) - - state, err := dummyEngine.WaitStep(ctx, step, workflowUUID) - assert.NoError(t, err) - assert.NoError(t, state.Error) - assert.EqualValues(t, 0, state.ExitCode) - - assert.NoError(t, dummyEngine.DestroyStep(ctx, step, workflowUUID)) - - assert.NoError(t, dummyEngine.DestroyWorkflow(ctx, nil, workflowUUID)) - }) - - t.Run("step exec error", func(t *testing.T) { - step := &types.Step{ - Name: dummy.StepExecError, - UUID: "SID_2", - Type: types.StepTypePlugin, - Environment: map[string]string{dummy.EnvKeyStepType: "plugin"}, - } - workflowUUID := "WID_1" - - assert.NoError(t, dummyEngine.SetupWorkflow(ctx, nil, workflowUUID)) - - assert.NoError(t, dummyEngine.StartStep(ctx, step, workflowUUID)) - - _, err := dummyEngine.TailStep(ctx, step, workflowUUID) - assert.NoError(t, err) - - state, err := dummyEngine.WaitStep(ctx, step, workflowUUID) - assert.NoError(t, err) - assert.NoError(t, state.Error) - assert.EqualValues(t, 1, state.ExitCode) - - assert.NoError(t, dummyEngine.DestroyStep(ctx, step, workflowUUID)) - - assert.NoError(t, dummyEngine.DestroyWorkflow(ctx, nil, workflowUUID)) - }) - - t.Run("step start fail", func(t *testing.T) { - step := &types.Step{ - Name: dummy.StepStartFail, - UUID: "SID_2", - Type: types.StepTypeService, - Environment: map[string]string{dummy.EnvKeyStepType: "service"}, - } - workflowUUID := "WID_1" - - assert.NoError(t, dummyEngine.SetupWorkflow(ctx, nil, workflowUUID)) - - assert.Error(t, dummyEngine.StartStep(ctx, step, workflowUUID)) - - _, err := dummyEngine.TailStep(ctx, step, workflowUUID) - assert.Error(t, err) - - state, err := dummyEngine.WaitStep(ctx, step, workflowUUID) - assert.Error(t, err) - assert.Error(t, state.Error) - assert.EqualValues(t, 0, state.ExitCode) - - assert.Error(t, dummyEngine.DestroyStep(ctx, step, workflowUUID)) - - assert.NoError(t, dummyEngine.DestroyWorkflow(ctx, nil, workflowUUID)) - }) -} From a3c0a02db1c22b1d81fb21ad439fff4138f0d424 Mon Sep 17 00:00:00 2001 From: "m.huber" Date: Mon, 24 Jun 2024 22:52:46 +0200 Subject: [PATCH 18/19] clean --- docs/docs/92-development/09-testing.md | 79 -------------------------- 1 file changed, 79 deletions(-) delete mode 100644 docs/docs/92-development/09-testing.md diff --git a/docs/docs/92-development/09-testing.md b/docs/docs/92-development/09-testing.md deleted file mode 100644 index 795310f6fd..0000000000 --- a/docs/docs/92-development/09-testing.md +++ /dev/null @@ -1,79 +0,0 @@ -# Testing - -## Backend - -### Unit Tests - -[We use default golang unit tests.](https://go.dev/doc/tutorial/add-a-test) -With [`"github.com/stretchr/testify/assert"`](https://pkg.go.dev/github.com/stretchr/testify@v1.9.0/assert) to simplify the test code. - -### Integration Tests - -#### Pipeline Engine - -The pipeline engine has a special backend called **`dummy`** witch does not exec but emulate how a typical backend should behave. - -An example pipeline config would be: - -```yaml -when: - event: manual - -steps: - - name: echo - image: dummy - commands: echo ja - environment: - SLEEP: '1s' - -services: - echo: - image: dummy - commands: echo ja -``` - -witch could be executed via `woodpecker-cli --log-level trace exec --backend-engine dummy example.yaml`: - -```none -9:18PM DBG pipeline/pipeline.go:94 > executing 2 stages, in order of: CLI=exec -9:18PM DBG pipeline/pipeline.go:104 > stage CLI=exec StagePos=0 Steps=echo -9:18PM DBG pipeline/pipeline.go:104 > stage CLI=exec StagePos=1 Steps=echo -9:18PM TRC pipeline/backend/dummy/dummy.go:75 > create workflow environment taskUUID=01J10P578JQE6E25VV1EQF0745 -9:18PM DBG pipeline/pipeline.go:176 > prepare CLI=exec step=echo -9:18PM DBG pipeline/pipeline.go:203 > executing CLI=exec step=echo -9:18PM TRC pipeline/backend/dummy/dummy.go:81 > start step echo taskUUID=01J10P578JQE6E25VV1EQF0745 -9:18PM TRC pipeline/backend/dummy/dummy.go:167 > tail logs of step echo taskUUID=01J10P578JQE6E25VV1EQF0745 -9:18PM DBG pipeline/pipeline.go:209 > complete CLI=exec step=echo -[echo:L0:0s] StepName: echo -[echo:L1:0s] StepType: service -[echo:L2:0s] StepUUID: 01J10P578JQE6E25VV1A2DNQN9 -[echo:L3:0s] StepCommands: -[echo:L4:0s] ------------------ -[echo:L5:0s] echo ja -[echo:L6:0s] ------------------ -[echo:L7:0s] 9:18PM DBG pipeline/pipeline.go:176 > prepare CLI=exec step=echo -9:18PM DBG pipeline/pipeline.go:203 > executing CLI=exec step=echo -9:18PM TRC pipeline/backend/dummy/dummy.go:81 > start step echo taskUUID=01J10P578JQE6E25VV1EQF0745 -9:18PM TRC pipeline/backend/dummy/dummy.go:167 > tail logs of step echo taskUUID=01J10P578JQE6E25VV1EQF0745 -[echo:L0:0s] StepName: echo -[echo:L1:0s] StepType: commands -[echo:L2:0s] StepUUID: 01J10P578JQE6E25VV1DFSXX1Y -[echo:L3:0s] StepCommands: -[echo:L4:0s] ------------------ -[echo:L5:0s] echo ja -[echo:L6:0s] ------------------ -[echo:L7:0s] 9:18PM TRC pipeline/backend/dummy/dummy.go:108 > wait for step echo taskUUID=01J10P578JQE6E25VV1EQF0745 -9:18PM TRC pipeline/backend/dummy/dummy.go:187 > stop step echo taskUUID=01J10P578JQE6E25VV1EQF0745 -9:18PM DBG pipeline/pipeline.go:209 > complete CLI=exec step=echo -9:18PM TRC pipeline/backend/dummy/dummy.go:208 > delete workflow environment taskUUID=01J10P578JQE6E25VV1EQF0745 -``` - -You can control the step behavior via its name: - -- If you name it `step_start_fail` the engine will simulate a step start who fail (e.g. happens when the container image can not be pulled). -- If you name it `step_exec_error` the engine will simulate a command who executes with status code **1**. - -There are also environment variables to alter things: - -- `SLEEP` witch will simulate a given time duration as command execution time. -- `EXPECT_TYPE` witch let the backend error if set and the step is not the expected step-type. From c6bc534fd8be28d0e61a6d1a84e5b48e138a0148 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Mon, 24 Jun 2024 22:56:24 +0200 Subject: [PATCH 19/19] Update .golangci.yaml --- .golangci.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.golangci.yaml b/.golangci.yaml index 6631206798..aaa9d14f08 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -186,4 +186,3 @@ run: timeout: 15m build-tags: - test - - integration