-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy patherror_test.go
More file actions
127 lines (105 loc) · 3.5 KB
/
error_test.go
File metadata and controls
127 lines (105 loc) · 3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package flow_test
import (
"context"
"fmt"
"strings"
"testing"
"time"
flow "github.com/Azure/go-workflow"
"github.com/benbjohnson/clock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestErrCycleDependency(t *testing.T) {
w := new(flow.Workflow).Add(
flow.Step(succeededStep).DependsOn(succeededStep),
)
var errCycle flow.ErrCycleDependency
if assert.ErrorAs(t, w.Do(context.Background()), &errCycle) {
assert.ErrorContains(t, errCycle, "Succeeded depends on [\n\t\tSucceeded\n\t]")
}
}
// serialStep is a controllable step: signals when started, waits to be released.
type serialStep struct {
name string
started chan struct{}
release chan struct{}
err error
}
func newSerialStep(name string, err error) *serialStep {
return &serialStep{
name: name,
started: make(chan struct{}, 1),
release: make(chan struct{}),
err: err,
}
}
func (s *serialStep) Do(_ context.Context) error {
s.started <- struct{}{}
<-s.release
return s.err
}
func (s *serialStep) String() string { return s.name }
func TestErrWorkflowOutputOrdering(t *testing.T) {
// Serial chain: stepC -> stepA -> stepB
// Names chosen so alphabetical order != execution order.
mockClock := clock.NewMock()
stepC := newSerialStep("C-first", fmt.Errorf("C failed"))
stepA := newSerialStep("A-second", fmt.Errorf("A failed"))
stepB := newSerialStep("B-third", fmt.Errorf("B failed"))
w := &flow.Workflow{Option: flow.WorkflowOption{Clock: mockClock}}
w.Add(
flow.Step(stepC),
flow.Step(stepA).DependsOn(stepC).When(flow.Always),
flow.Step(stepB).DependsOn(stepA).When(flow.Always),
)
done := make(chan error, 1)
go func() { done <- w.Do(context.Background()) }()
<-stepC.started
mockClock.Add(time.Second)
close(stepC.release)
<-stepA.started
mockClock.Add(time.Second)
close(stepA.release)
<-stepB.started
mockClock.Add(time.Second)
close(stepB.release)
err := <-done
require.Error(t, err)
var errW flow.ErrWorkflow
require.ErrorAs(t, err, &errW)
output := errW.Error()
posC := strings.Index(output, "C-first")
posA := strings.Index(output, "A-second")
posB := strings.Index(output, "B-third")
require.GreaterOrEqual(t, posC, 0, "C-first not found in error output")
require.GreaterOrEqual(t, posA, 0, "A-second not found in error output")
require.GreaterOrEqual(t, posB, 0, "B-third not found in error output")
assert.Greater(t, posA, posC, "A-second should appear after C-first")
assert.Greater(t, posB, posA, "B-third should appear after A-second")
}
func TestErrWorkflowTieBreakByName(t *testing.T) {
// Two parallel steps fail at the same clock tick → alphabetical order.
mockClock := clock.NewMock()
stepZ := newSerialStep("Z-step", fmt.Errorf("Z failed"))
stepA := newSerialStep("A-step", fmt.Errorf("A failed"))
w := &flow.Workflow{Option: flow.WorkflowOption{Clock: mockClock}}
w.Add(flow.Step(stepZ), flow.Step(stepA))
done := make(chan error, 1)
go func() { done <- w.Do(context.Background()) }()
<-stepZ.started
<-stepA.started
mockClock.Add(time.Second) // both get same FinishedAt
close(stepZ.release)
close(stepA.release)
err := <-done
require.Error(t, err)
var errW flow.ErrWorkflow
require.ErrorAs(t, err, &errW)
output := errW.Error()
posA := strings.Index(output, "A-step")
posZ := strings.Index(output, "Z-step")
require.GreaterOrEqual(t, posA, 0, "A-step not found in error output")
require.GreaterOrEqual(t, posZ, 0, "Z-step not found in error output")
assert.Less(t, posA, posZ, "A-step should appear before Z-step (tie-break by name)")
}