1
2
3
4
5
6
7
8
9
10
11
12
13 package kiviktest
14
15 import (
16 "context"
17 "errors"
18 "flag"
19 "fmt"
20 "net/http"
21 "net/url"
22 "os"
23 "strings"
24 "sync"
25 "sync/atomic"
26 "testing"
27
28 "github.com/go-kivik/kivik/v4"
29 _ "github.com/go-kivik/kivik/v4/kiviktest/client"
30 _ "github.com/go-kivik/kivik/v4/kiviktest/db"
31 "github.com/go-kivik/kivik/v4/kiviktest/kt"
32 )
33
34
35 const (
36 SuiteAuto = "auto"
37 SuitePouchLocal = "pouch"
38 SuitePouchRemote = "pouchRemote"
39 SuiteCouch22 = "couch22"
40 SuiteCouch23 = "couch23"
41 SuiteCouch30 = "couch30"
42 SuiteCouch31 = "couch31"
43 SuiteCouch32 = "couch32"
44 SuiteCouch33 = "couch33"
45 SuiteKivikServer = "kivikServer"
46 SuiteKivikMemory = "kivikMemory"
47 SuiteKivikSQLite = "kivikSQLite"
48 SuiteKivikFS = "kivikFilesystem"
49 )
50
51
52 var AllSuites = []string{
53 SuitePouchLocal,
54 SuitePouchRemote,
55 SuiteCouch22,
56 SuiteCouch30,
57 SuiteCouch31,
58 SuiteCouch32,
59 SuiteCouch33,
60 SuiteKivikMemory,
61 SuiteKivikFS,
62 SuiteKivikSQLite,
63 SuiteKivikServer,
64 }
65
66 var driverMap = map[string]string{
67 SuitePouchLocal: "pouch",
68 SuitePouchRemote: "pouch",
69 SuiteCouch22: "couch",
70 SuiteCouch23: "couch",
71 SuiteCouch30: "couch",
72 SuiteCouch31: "couch",
73 SuiteCouch32: "couch",
74 SuiteCouch33: "couch",
75 SuiteKivikServer: "couch",
76 SuiteKivikMemory: "memory",
77 SuiteKivikSQLite: "sqlite",
78 SuiteKivikFS: "fs",
79 }
80
81
82 func ListTests() {
83 fmt.Printf("Available test suites:\n\tauto\n")
84 for _, suite := range AllSuites {
85 fmt.Printf("\t%s\n", suite)
86 }
87 }
88
89
90 type Options struct {
91 Driver string
92 DSN string
93 Verbose bool
94 RW bool
95 Match string
96 Suites []string
97 Cleanup bool
98 }
99
100
101
102 func CleanupTests(driver, dsn string, verbose bool) error {
103 client, err := kivik.New(driver, dsn)
104 if err != nil {
105 return err
106 }
107 count, err := doCleanup(client, verbose)
108 if verbose {
109 fmt.Printf("Deleted %d test databases\n", count)
110 }
111 return err
112 }
113
114 func doCleanup(client *kivik.Client, verbose bool) (int, error) {
115 ctx, cancel := context.WithCancel(context.Background())
116 defer cancel()
117 const chanCap = 3
118 errCh := make(chan error, chanCap)
119 var count int32
120 var wg sync.WaitGroup
121
122 wg.Add(1)
123 go func() {
124 defer wg.Done()
125 c, err := cleanupDatabases(ctx, client, verbose)
126 if err != nil {
127 cancel()
128 }
129 atomic.AddInt32(&count, int32(c))
130 errCh <- err
131 }()
132
133 wg.Add(1)
134 go func() {
135 defer wg.Done()
136 c, err := cleanupUsers(ctx, client, verbose)
137 if err != nil {
138 cancel()
139 }
140 atomic.AddInt32(&count, int32(c))
141 errCh <- err
142 }()
143
144 wg.Add(1)
145 go func() {
146 defer wg.Done()
147 c, err := cleanupReplications(ctx, client, verbose)
148 if err != nil {
149 cancel()
150 }
151 atomic.AddInt32(&count, int32(c))
152 errCh <- err
153 }()
154
155 wg.Wait()
156 err := <-errCh
157 for len(errCh) > 0 {
158 <-errCh
159 }
160 return int(count), err
161 }
162
163 func cleanupDatabases(ctx context.Context, client *kivik.Client, verbose bool) (int, error) {
164 if verbose {
165 fmt.Printf("Cleaning up stale databases\n")
166 }
167 allDBs, err := client.AllDBs(ctx)
168 if err != nil {
169 return 0, err
170 }
171 var count int
172 for _, dbName := range allDBs {
173
174
175 if strings.HasPrefix(dbName, kt.TestDBPrefix) {
176 if verbose {
177 fmt.Printf("\t--- Deleting %s\n", dbName)
178 }
179 if e := client.DestroyDB(ctx, dbName); e != nil && kivik.HTTPStatus(e) != http.StatusNotFound {
180 return count, e
181 }
182 count++
183 }
184 }
185 replicator := client.DB("_replicator")
186 if e := replicator.Err(); e != nil {
187 if kivik.HTTPStatus(e) != http.StatusNotFound && kivik.HTTPStatus(e) != http.StatusNotImplemented {
188 return count, e
189 }
190 return count, nil
191 }
192 docs := replicator.AllDocs(context.Background(), kivik.IncludeDocs())
193 if err := docs.Err(); err != nil {
194 if kivik.HTTPStatus(err) == http.StatusNotImplemented || kivik.HTTPStatus(err) == http.StatusNotFound {
195 return count, nil
196 }
197 return count, err
198 }
199 var replDoc struct {
200 Rev string `json:"_rev"`
201 }
202 for docs.Next() {
203 id, _ := docs.ID()
204 if strings.HasPrefix(id, "kivik$") {
205 if err := docs.ScanDoc(&replDoc); err != nil {
206 return count, err
207 }
208 if _, err := replicator.Delete(context.Background(), id, replDoc.Rev); err != nil {
209 return count, err
210 }
211 count++
212 }
213 }
214 return count, nil
215 }
216
217 func cleanupUsers(ctx context.Context, client *kivik.Client, verbose bool) (int, error) {
218 if verbose {
219 fmt.Printf("Cleaning up stale users\n")
220 }
221 db := client.DB("_users")
222 if err := db.Err(); err != nil {
223 switch kivik.HTTPStatus(err) {
224 case http.StatusNotFound, http.StatusNotImplemented:
225 return 0, nil
226 }
227 return 0, err
228 }
229 users := db.AllDocs(ctx, kivik.IncludeDocs())
230 if err := users.Err(); err != nil {
231 switch kivik.HTTPStatus(err) {
232 case http.StatusNotFound, http.StatusNotImplemented:
233 return 0, nil
234 }
235 return 0, err
236 }
237 var count int
238 for users.Next() {
239 id, _ := users.ID()
240 if strings.HasPrefix(id, "org.couchdb.user:kivik$") {
241 if verbose {
242 fmt.Printf("\t--- Deleting user %s\n", id)
243 }
244 var doc struct {
245 Rev string `json:"_rev"`
246 }
247 if err := users.ScanDoc(&doc); err != nil {
248 return count, err
249 }
250 if _, err := db.Delete(ctx, id, doc.Rev); err != nil {
251 return count, err
252 }
253 count++
254 }
255 }
256 return count, users.Err()
257 }
258
259 func cleanupReplications(ctx context.Context, client *kivik.Client, verbose bool) (int, error) {
260 if verbose {
261 fmt.Printf("Cleaning up stale replications\n")
262 }
263 db := client.DB("_replicator")
264 if err := db.Err(); err != nil {
265 switch kivik.HTTPStatus(err) {
266 case http.StatusNotFound, http.StatusNotImplemented:
267 return 0, nil
268 }
269 return 0, err
270 }
271 reps := db.AllDocs(ctx, kivik.IncludeDocs())
272 if err := reps.Err(); err != nil {
273 switch kivik.HTTPStatus(err) {
274 case http.StatusNotFound, http.StatusNotImplemented:
275 return 0, nil
276 }
277 return 0, err
278 }
279 var count int
280 for reps.Next() {
281 var doc struct {
282 Rev string `json:"_rev"`
283 Source string `json:"source"`
284 Target string `json:"target"`
285 }
286 if err := reps.ScanDoc(&doc); err != nil {
287 return count, err
288 }
289 id, _ := reps.ID()
290 if strings.HasPrefix(id, "kivik$") ||
291 strings.HasPrefix(doc.Source, "kivik$") ||
292 strings.HasPrefix(doc.Target, "kivik$") {
293 if verbose {
294 fmt.Printf("\t--- Deleting replication %s\n", id)
295 }
296 if _, err := db.Delete(ctx, id, doc.Rev); err != nil {
297 return count, err
298 }
299 count++
300 }
301 }
302 return count, reps.Err()
303 }
304
305
306 func RunTests(opts Options) {
307 if opts.Cleanup {
308 err := CleanupTests(opts.Driver, opts.DSN, opts.Verbose)
309 if err != nil {
310 fmt.Printf("Cleanup failed: %s\n", err)
311 os.Exit(1)
312 }
313 os.Exit(0)
314 }
315 _ = flag.Set("test.run", opts.Match)
316 if opts.Verbose {
317 _ = flag.Set("test.v", "true")
318 }
319 tests := []testing.InternalTest{
320 {
321 Name: "MainTest",
322 F: func(t *testing.T) {
323 Test(t, opts.Driver, opts.DSN, opts.Suites, opts.RW)
324 },
325 },
326 }
327
328 mainStart(tests)
329 }
330
331
332
333 func Test(t *testing.T, driver, dsn string, testSuites []string, rw bool) {
334 clients, err := ConnectClients(t, driver, dsn, nil)
335 if err != nil {
336 t.Fatalf("Failed to connect to %s (%s driver): %s\n", dsn, driver, err)
337 }
338 clients.RW = rw
339 tests := make(map[string]struct{})
340 for _, test := range testSuites {
341 tests[test] = struct{}{}
342 }
343 if _, ok := tests[SuiteAuto]; ok {
344 t.Log("Detecting target service compatibility...")
345 suites, err := detectCompatibility(clients.Admin)
346 if err != nil {
347 t.Fatalf("Unable to determine server suite compatibility: %s\n", err)
348 }
349 tests = make(map[string]struct{})
350 for _, suite := range suites {
351 tests[suite] = struct{}{}
352 }
353 }
354 testSuites = make([]string, 0, len(tests))
355 for test := range tests {
356 testSuites = append(testSuites, test)
357 }
358 t.Logf("Running the following test suites: %s\n", strings.Join(testSuites, ", "))
359 for _, suite := range testSuites {
360 RunTestsInternal(clients, suite)
361 }
362 }
363
364
365 func RunTestsInternal(ctx *kt.Context, suite string) {
366 conf, ok := suites[suite]
367 if !ok {
368 ctx.Skipf("No configuration found for suite '%s'", suite)
369 }
370 ctx.Config = conf
371
372 ctx.Run("PreCleanup", func(ctx *kt.Context) {
373 ctx.RunAdmin(func(ctx *kt.Context) {
374 count, err := doCleanup(ctx.Admin, true)
375 if count > 0 {
376 ctx.Logf("Pre-cleanup removed %d databases from previous test runs", count)
377 }
378 if err != nil {
379 ctx.Fatalf("Pre-cleanup failed: %s", err)
380 }
381 })
382 })
383 kt.RunSubtests(ctx)
384 }
385
386 func detectCompatibility(client *kivik.Client) ([]string, error) {
387 info, err := client.Version(context.Background())
388 if err != nil {
389 return nil, err
390 }
391 switch info.Vendor {
392 case "PouchDB":
393 return []string{SuitePouchLocal}, nil
394 case "Kivik Memory Adaptor":
395 return []string{SuiteKivikMemory}, nil
396 }
397 return []string{}, errors.New("Unable to automatically determine the proper test suite")
398 }
399
400
401 func ConnectClients(t *testing.T, driverName, dsn string, opts kivik.Option) (*kt.Context, error) {
402 t.Helper()
403 var noAuthDSN string
404 if parsed, err := url.Parse(dsn); err == nil {
405 if parsed.User == nil {
406 return nil, errors.New("DSN does not contain authentication credentials")
407 }
408 parsed.User = nil
409 noAuthDSN = parsed.String()
410 }
411 clients := &kt.Context{
412 T: t,
413 }
414 t.Logf("Connecting to %s ...\n", dsn)
415 if client, err := kivik.New(driverName, dsn, opts); err == nil {
416 clients.Admin = client
417 } else {
418 return nil, err
419 }
420
421 t.Logf("Connecting to %s ...\n", noAuthDSN)
422 if client, err := kivik.New(driverName, noAuthDSN, opts); err == nil {
423 clients.NoAuth = client
424 } else {
425 return nil, err
426 }
427 return clients, nil
428 }
429
430
431 func DoTest(t *testing.T, suite, envName string) {
432 opts, _ := suites[suite].Interface(t, "Options").(kivik.Option)
433
434 dsn := os.Getenv(envName)
435 if dsn == "" {
436 t.Skipf("%s: %s DSN not set; skipping tests", envName, suite)
437 }
438 clients, err := ConnectClients(t, driverMap[suite], dsn, opts)
439 if err != nil {
440 t.Errorf("Failed to connect to %s: %s\n", suite, err)
441 return
442 }
443 clients.RW = true
444 RunTestsInternal(clients, suite)
445 }
446
View as plain text