-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathstory.go
130 lines (114 loc) · 3.31 KB
/
story.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
package pg_stories
import (
"fmt"
"github.com/jackc/pgx/pgproto3"
"testing"
)
// Step is the interface of every step in a Story
type Step interface {
Step()
}
// Command is a type of Step that wraps pgproto3.FrontendMessage
type Command struct {
pgproto3.FrontendMessage
}
// Step is here just to identify Command as a Step implementation
func (c *Command) Step() {}
// Response is a type of Step that wraps pgproto3.BackendMessage
type Response struct {
pgproto3.BackendMessage
}
// Step is here just to identify Command as a Step implementation
func (r *Response) Step() {}
// Compare checks if the value of the provided msg equals to the underlying BackendMessage.
// In some cases it performs deep compare.
func (r *Response) Compare(msg pgproto3.BackendMessage) error {
expectedRaw := r.BackendMessage.Encode([]byte{})
actualRaw := msg.Encode([]byte{})
if len(expectedRaw) == 0 {
return fmt.Errorf("invalid message expected")
}
if len(actualRaw) == 0 {
return fmt.Errorf("invalid message received")
}
if expectedRaw[0] != actualRaw[0] {
return fmt.Errorf("wrong type of message. expected: %T. got %T", r.BackendMessage, msg)
}
switch r.BackendMessage.(type) {
case *pgproto3.ErrorResponse:
expectedCode := r.BackendMessage.(*pgproto3.ErrorResponse).Code
actualCode := msg.(*pgproto3.ErrorResponse).Code
if expectedCode != "" && expectedCode != actualCode {
return fmt.Errorf("expected error response with code: %s. got %s", expectedCode, actualCode)
}
}
return nil
}
// Story holds a sequence of Step and uses pgproto3.Frontend to communicate with the tested backend.
type Story struct {
// Frontend is the component that communicates with the tested backend
Frontend *pgproto3.Frontend
// Steps is a sequence of Step that defines a story
Steps []Step
// Filter is a function that tells the runner which types of responses it should verify
Filter func(pgproto3.BackendMessage) bool
}
// Run is running the Steps and fails the provided t on error. Because it uses go routines
// and waits infinitely for expected responses, it also listens the provided chan for kill signals.
// Most common use of c is a timeout error that should be generated by the caller of Run.
func (s *Story) Run(t *testing.T, c <-chan interface{}) (err error) {
success := make(chan bool)
errors := make(chan error)
responseBuffer := make(chan pgproto3.BackendMessage, 100)
go func() {
for {
b, err := s.Frontend.Receive()
if err != nil {
errors <- err
return
}
if s.Filter == nil || s.Filter(b) {
responseBuffer <- b
}
}
}()
go func() {
for _, step := range s.Steps {
var e error
switch step.(type) {
case *Command:
if len(responseBuffer) > 0 {
e = fmt.Errorf("backend messages exist in buffer")
break
}
msg := step.(*Command).FrontendMessage
t.Logf("==>> %#v\n", msg)
e = s.Frontend.Send(msg)
case *Response:
msg := <-responseBuffer
t.Logf("<<== %#v\n", msg)
e = step.(*Response).Compare(msg)
}
if e != nil {
errors <- e
return
}
}
if len(responseBuffer) > 0 {
errors <- fmt.Errorf("expected missing step for: %#v", <-responseBuffer)
return
}
success <- true
}()
select {
case e := <-errors:
err = e
break
case <-success:
break
case v := <-c:
err = fmt.Errorf("received stop signal %#v", v)
break
}
return
}