-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathsnap.go
More file actions
203 lines (165 loc) · 4.07 KB
/
Copy pathsnap.go
File metadata and controls
203 lines (165 loc) · 4.07 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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
package pgsnap
import (
"database/sql"
"errors"
"fmt"
"log"
"net"
"os"
"testing"
"time"
)
type Snap struct {
t testing.TB
addr string
msgchan chan string
done chan struct{}
l net.Listener
isDebug bool
proxy *proxy // will be fill if using proxy
server *server // will be fill if using fake server
finishFuncs []func() error
}
type Config struct {
// TestTimeout Default 5s
TestTimeout time.Duration
// Force to create proxy and connect to real postgres server
ForceWrite bool
// Debug if true it will print more verbose
Debug bool
}
// NewDB will create *sql.DB to be used in the test
func NewDB(t testing.TB, url string) (*sql.DB, *Snap) {
snap := NewSnap(t, url)
db, err := sql.Open("postgres", snap.Addr())
if err != nil {
t.Fatal(err)
}
return db, snap
}
// NewDBWithConfig will create *sql.DB to be used in the test
// but it will ignore the snapshot file
func NewDBWithConfig(t testing.TB, url string, cfg Config) (*sql.DB, *Snap) {
snap := NewSnapWithConfig(t, url, cfg)
db, err := sql.Open("postgres", snap.Addr())
if err != nil {
t.Fatal(err)
}
return db, snap
}
// NewSnap will create snap
func NewSnap(t testing.TB, postgreURL string) *Snap {
t.Helper()
return NewSnapWithConfig(t, postgreURL, Config{
ForceWrite: os.Getenv("PGSNAP_FORCE_WRITE") == "true",
Debug: os.Getenv("PGSNAP_DEBUG") == "true",
TestTimeout: 5 * time.Second,
})
}
// Deprecated
// NewSnapWithForceWrite function
func NewSnapWithForceWrite(t testing.TB, url string, forceWrite bool) *Snap {
return NewSnapWithConfig(t, url, Config{
ForceWrite: forceWrite,
Debug: os.Getenv("PGSNAP_DEBUG") == "true",
TestTimeout: 5 * time.Second,
})
}
// Make it private first, because we still design the api first
func NewSnapWithConfig(t testing.TB, url string, cfg Config) *Snap {
t.Helper()
cfg = setDefaultValue(cfg)
s := &Snap{
t: t,
msgchan: make(chan string, 100),
done: make(chan struct{}, 1),
isDebug: cfg.Debug,
}
s.setFailAfter(cfg.TestTimeout)
s.listen()
script := newScript(t)
if cfg.ForceWrite {
s.runProxy(t, url, script, cfg)
return s
}
pgxScript, err := script.Read()
if s.shouldRunProxy(err) {
s.runProxy(t, url, script, cfg)
return s
}
if err != nil {
s.t.Fatalf("can't open file \"%s\": %v", script.getFilename(), err)
}
s.server = newServer(s.l, s.done, s.t, s.isDebug)
s.server.Run(pgxScript)
return s
}
func (s *Snap) runProxy(t testing.TB, url string, script *script, cfg Config) {
t.Helper()
s.proxy = newProxy(t, url, script, s.l, cfg.Debug)
s.proxy.run()
}
// setFaileAfter will call (*testing.T).Fatalf after timeout
func (s *Snap) setFailAfter(timeout time.Duration) {
start := time.Now()
go func() {
select {
case <-time.After(timeout):
log.Printf("pgsnap timeout after %v, start at %v, end at %v", timeout, start, time.Now())
s.t.Errorf("pgsnap timeout after %v", timeout)
s.t.FailNow()
case <-s.done:
}
}()
}
func (s *Snap) Finish() {
// ignore the error
_ = s.l.Close()
if s.proxy != nil {
s.proxy.finish()
s.done <- struct{}{}
}
if s.server != nil {
s.server.Wait()
}
for _, f := range s.finishFuncs {
err := f()
if err != nil {
s.t.Error(err)
}
}
}
// AddFinishFunc will add function that will be called when
// Finish() is called. It used by docker to remove container
func (s *Snap) AddFinishFunc(f func() error) {
s.finishFuncs = append(s.finishFuncs, f)
}
// Addr will return proxy / fake postgres address in form of
// postgres://user:password@127.0.0.1:15432/postgres
func (s *Snap) Addr() string {
return s.addr
}
func (s *Snap) listen() net.Listener {
var err error
s.l, err = net.Listen("tcp", "127.0.0.1:")
if err != nil {
s.t.Fatal("can't open port: " + err.Error())
}
s.addr = fmt.Sprintf("postgres://user@%s/?sslmode=disable", s.l.Addr())
return s.l
}
func (s *Snap) shouldRunProxy(err error) bool {
if os.IsNotExist(err) {
return true
}
if errors.Is(EmptyScript, err) {
return true
}
return false
}
func setDefaultValue(cfg Config) Config {
if cfg.TestTimeout == 0 {
cfg.TestTimeout = 5 * time.Second
}
return cfg
}