-
Notifications
You must be signed in to change notification settings - Fork 1
/
executor.go
134 lines (113 loc) · 3.62 KB
/
executor.go
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
128
129
130
131
132
133
134
package testkit
import (
"context"
"sync"
"github.com/dogmatiq/dogma"
"github.com/dogmatiq/testkit/engine"
)
// CommandExecutor is an implementation of [dogma.CommandExecutor] that executes
// commands within the context of a Test.
//
// Each instance is bound to a particular Test. Use Test.CommandExecutor() to
// obtain an instance.
type CommandExecutor struct {
m sync.RWMutex
next engine.CommandExecutor
interceptor CommandExecutorInterceptor
}
var _ dogma.CommandExecutor = (*CommandExecutor)(nil)
// ExecuteCommand executes the command message m.
//
// It panics unless it is called during an Action, such as when calling
// Test.Prepare() or Test.Expect().
func (e *CommandExecutor) ExecuteCommand(
ctx context.Context,
m dogma.Command,
_ ...dogma.ExecuteCommandOption,
) error {
e.m.RLock()
defer e.m.RUnlock()
if e.next.Engine == nil {
panic("ExecuteCommand(): cannot be called outside of a test")
}
if e.interceptor != nil {
return e.interceptor(ctx, m, e.next)
}
return e.next.ExecuteCommand(ctx, m)
}
// Bind sets the engine and options used to execute commands.
//
// It is intended for use within Action implementations that support executing
// commands outside of a Dogma handler, such as Call().
//
// It must be called before ExecuteCommand(), otherwise ExecuteCommand() panics.
//
// It must be accompanied by a call to Unbind() upon completion of the Action.
func (e *CommandExecutor) Bind(eng *engine.Engine, options []engine.OperationOption) {
e.m.Lock()
defer e.m.Unlock()
e.next.Engine = eng
e.next.Options = options
}
// Unbind removes the engine and options configured by a prior call to Bind().
//
// Calls to ExecuteCommand() on an unbound executor will cause a panic.
func (e *CommandExecutor) Unbind() {
e.m.Lock()
defer e.m.Unlock()
e.next.Engine = nil
e.next.Options = nil
}
// Intercept installs an interceptor function that is invoked whenever
// ExecuteCommand() is called.
//
// If fn is nil the interceptor is removed.
//
// It returns the previous interceptor, if any.
func (e *CommandExecutor) Intercept(fn CommandExecutorInterceptor) CommandExecutorInterceptor {
e.m.Lock()
defer e.m.Unlock()
prev := e.interceptor
e.interceptor = fn
return prev
}
// CommandExecutorInterceptor is used by the InterceptCommandExecutor() option
// to specify custom behavior for the dogma.CommandExecutor returned by
// Test.CommandExecutor().
//
// m is the command being executed.
//
// e can be used to execute the command as it would be executed without this
// interceptor installed.
type CommandExecutorInterceptor func(
ctx context.Context,
m dogma.Command,
e dogma.CommandExecutor,
) error
// InterceptCommandExecutor returns an option that causes fn to be called
// whenever a command is executed via the dogma.CommandExecutor returned by
// Test.CommandExecutor().
//
// Intercepting calls to the command executor allows the user to simulate
// failures (or any other behavior) in the command executor.
func InterceptCommandExecutor(fn CommandExecutorInterceptor) interface {
TestOption
CallOption
} {
if fn == nil {
panic("InterceptCommandExecutor(<nil>): function must not be nil")
}
return interceptCommandExecutorOption{fn}
}
// interceptCommandExecutorOption is an implementation of both TestOption and
// CallOption that allows the InterceptCommandExecutor() option to be used with
// both Test.Begin() and Call().
type interceptCommandExecutorOption struct {
fn CommandExecutorInterceptor
}
func (o interceptCommandExecutorOption) applyTestOption(t *Test) {
t.executor.Intercept(o.fn)
}
func (o interceptCommandExecutorOption) applyCallOption(a *callAction) {
a.onExecute = o.fn
}