• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

couchbase / sync_gateway / 473

24 Jun 2025 09:27AM UTC coverage: 64.215% (-0.01%) from 64.228%
473

push

jenkins

web-flow
CBG-4689 ensure all indexes are deleted (#7602)

0 of 11 new or added lines in 1 file covered. (0.0%)

7 existing lines in 3 files now uncovered.

36697 of 57147 relevant lines covered (64.22%)

0.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

45.92
/base/util_testing.go
1
/*
2
Copyright 2017-Present Couchbase, Inc.
3

4
Use of this software is governed by the Business Source License included in
5
the file licenses/BSL-Couchbase.txt.  As of the Change Date specified in that
6
file, in accordance with the Business Source License, use of this software will
7
be governed by the Apache License, Version 2.0, included in the file
8
licenses/APL2.txt.
9
*/
10

11
package base
12

13
import (
14
        "bytes"
15
        "context"
16
        "crypto/x509"
17
        "errors"
18
        "fmt"
19
        "io"
20
        "io/fs"
21
        "log"
22
        "math/rand"
23
        "os"
24
        "path/filepath"
25
        "runtime"
26
        "runtime/pprof"
27
        "sort"
28
        "strconv"
29
        "strings"
30
        "sync"
31
        "testing"
32
        "time"
33

34
        "github.com/couchbase/gocb/v2"
35
        sgbucket "github.com/couchbase/sg-bucket"
36
        "github.com/couchbaselabs/rosmar"
37
        "github.com/stretchr/testify/assert"
38
        "github.com/stretchr/testify/require"
39
)
40

41
// Code that is test-related that needs to be accessible from non-base packages, and therefore can't live in
42
// util_test.go, which is only accessible from the base package.
43

44
var TestExternalRevStorage = false
45

46
type TestBucket struct {
47
        Bucket
48
        BucketSpec BucketSpec
49
        closeFn    func(context.Context)
50
        t          testing.TB
51
}
52

53
var _ Bucket = &TestBucket{}
54

55
// DefaultDataStore is intentionally not implemented for TestBucket
56
// DEPRECATED: Should use GetSingleDataStore
57
func (b *TestBucket) DefaultDataStore() sgbucket.DataStore {
1✔
58
        return b.Bucket.DefaultDataStore()
1✔
59
}
1✔
60

61
// NamedDataStore is intentionally not implemented for TestBucket
62
// DEPRECATED: Should use GetNamedDataStore
63
func (b *TestBucket) NamedDataStore(name sgbucket.DataStoreName) (sgbucket.DataStore, error) {
1✔
64
        return b.Bucket.NamedDataStore(name)
1✔
65
}
1✔
66

67
func (tb TestBucket) Close(ctx context.Context) {
1✔
68
        tb.closeFn(ctx)
1✔
69
}
1✔
70

71
func (tb *TestBucket) GetUnderlyingBucket() Bucket {
1✔
72
        return tb.Bucket
1✔
73
}
1✔
74

75
// LeakyBucketClone wraps the underlying bucket on the TestBucket with a LeakyBucket and returns a new TestBucket handle.
76
func (tb *TestBucket) LeakyBucketClone(c LeakyBucketConfig) *TestBucket {
1✔
77
        return &TestBucket{
1✔
78
                Bucket:     NewLeakyBucket(tb.Bucket, c),
1✔
79
                BucketSpec: tb.BucketSpec,
1✔
80
                closeFn:    tb.Close,
1✔
81
                t:          tb.t,
1✔
82
        }
1✔
83
}
1✔
84

85
// NoCloseClone returns a leaky bucket with a no-op close function for the given bucket.
86
func NoCloseClone(b Bucket) *LeakyBucket {
1✔
87
        return NewLeakyBucket(b, LeakyBucketConfig{IgnoreClose: true})
1✔
88
}
1✔
89

90
// NoCloseClone returns a new test bucket referencing the same underlying bucket and bucketspec, but
91
// with an IgnoreClose leaky bucket, and a no-op close function.  Used when multiple references to the same bucket are needed.
92
func (tb *TestBucket) NoCloseClone() *TestBucket {
1✔
93
        return &TestBucket{
1✔
94
                Bucket:     NoCloseClone(tb.Bucket),
1✔
95
                BucketSpec: tb.BucketSpec,
1✔
96
                closeFn:    func(context.Context) {},
2✔
97
                t:          tb.t,
98
        }
99
}
100

101
// GetTestBucket returns a test bucket from a pool.
102
func GetTestBucket(t testing.TB) *TestBucket {
1✔
103
        return getTestBucket(t, false)
1✔
104
}
1✔
105

106
// getTestBucket returns a bucket from the bucket pool.  Persistent flag determines behaviour for walrus
107
// buckets only - Couchbase bucket behaviour is defined by the bucket pool readier/init.
108
func getTestBucket(t testing.TB, persistent bool) *TestBucket {
1✔
109
        bucket, spec, closeFn := GTestBucketPool.getTestBucketAndSpec(t, persistent)
1✔
110
        return &TestBucket{
1✔
111
                Bucket:     bucket,
1✔
112
                BucketSpec: spec,
1✔
113
                closeFn:    closeFn,
1✔
114
                t:          t,
1✔
115
        }
1✔
116
}
1✔
117

118
// GetNamedDataStore returns a named datastore from the TestBucket. Each number (starting from 0, indicates which data store you'll get.
119
func (tb *TestBucket) GetNamedDataStore(count int) (DataStore, error) {
1✔
120
        dataStoreNames := tb.GetNonDefaultDatastoreNames()
1✔
121
        if len(dataStoreNames) == 0 {
1✔
122
                tb.t.Fatalf("You are requesting a named datastore on TestBucket that has none.")
×
123
        }
×
124
        if count > len(dataStoreNames) {
1✔
125
                tb.t.Fatalf("You are requesting more datastores %d than are available on this test instance %d", count, len(dataStoreNames))
×
126
        }
×
127
        return tb.Bucket.NamedDataStore(dataStoreNames[count])
1✔
128
}
129

130
// Return a sorted list of data store names
131
func (tb *TestBucket) GetNonDefaultDatastoreNames() []sgbucket.DataStoreName {
1✔
132
        return GetNonDefaultDatastoreNames(tb.t, tb)
1✔
133
}
1✔
134

135
// GetSingleDataStore returns a DataStore that can be used for testing.
136
// This may be the default collection, or a named collection depending on whether SG_TEST_USE_DEFAULT_COLLECTION is set.
137
func (b *TestBucket) GetSingleDataStore() sgbucket.DataStore {
1✔
138
        if TestsUseNamedCollections() {
2✔
139
                ds, err := b.GetNamedDataStore(0)
1✔
140
                require.NoError(b.t, err)
1✔
141
                return ds
1✔
142
        }
1✔
143
        return b.Bucket.DefaultDataStore()
×
144
}
145

146
func (b *TestBucket) GetMetadataStore() sgbucket.DataStore {
1✔
147
        return b.Bucket.DefaultDataStore()
1✔
148
}
1✔
149

150
func (b *TestBucket) CreateDataStore(ctx context.Context, name sgbucket.DataStoreName) error {
1✔
151
        dynamicDataStore, ok := b.Bucket.(sgbucket.DynamicDataStoreBucket)
1✔
152
        if !ok {
1✔
153
                return fmt.Errorf("Bucket %T doesn't support dynamic collection creation", b.Bucket)
×
154
        }
×
155
        return dynamicDataStore.CreateDataStore(ctx, name)
1✔
156
}
157

158
func (b *TestBucket) DropDataStore(name sgbucket.DataStoreName) error {
1✔
159
        dynamicDataStore, ok := b.GetUnderlyingBucket().(sgbucket.DynamicDataStoreBucket)
1✔
160
        if !ok {
1✔
161
                return fmt.Errorf("Bucket %T doesn't support dynamic collection creation", b.GetUnderlyingBucket())
×
162
        }
×
163
        return dynamicDataStore.DropDataStore(name)
1✔
164
}
165

166
// GetDefaultDataStore returns the default DataStore. This is likely never actually wanted over GetSingleDataStore, so is left commented until absolutely required.
167
// func (b *TestBucket) GetDefaultDataStore() sgbucket.DataStore {
168
//         b.t.Logf("Using default collection - Are you sure you want this instead of GetSingleDataStore() ?")
169
//         return b.Bucket.DefaultDataStore()
170
// }
171

172
// rosmarUriFromPath works to convert a path to a rosmar uri.
173
func rosmarUriFromPath(path string) string {
×
174
        // convert windows paths to unix style paths for rosmar, plus leading /
×
175
        uri := rosmar.URLScheme + "://"
×
176
        if runtime.GOOS == "windows" {
×
177
                if filepath.IsAbs(path) {
×
178
                        uri += "/"
×
179
                }
×
180
        }
181
        return uri + strings.ReplaceAll(path, `\`, `/`)
×
182
}
183

184
// Should Sync Gateway use XATTRS functionality when running unit tests?
185
func TestUseXattrs() bool {
1✔
186
        useXattrs, isSet := os.LookupEnv(TestEnvSyncGatewayUseXattrs)
1✔
187
        if !isSet {
2✔
188
                return true
1✔
189
        }
1✔
190

191
        val, err := strconv.ParseBool(useXattrs)
×
192
        if err != nil {
×
193
                panic(fmt.Sprintf("unable to parse %q value %q: %v", TestEnvSyncGatewayUseXattrs, useXattrs, err))
×
194
        }
195

196
        return val
×
197
}
198

199
// Should Sync Gateway skip TLS verification. Default: DefaultTestTLSSkipVerify
200
func TestTLSSkipVerify() bool {
1✔
201
        tlsSkipVerify, isSet := os.LookupEnv(TestEnvTLSSkipVerify)
1✔
202
        if !isSet {
2✔
203
                return DefaultTestTLSSkipVerify
1✔
204
        }
1✔
205

206
        val, err := strconv.ParseBool(tlsSkipVerify)
×
207
        if err != nil {
×
208
                panic(fmt.Sprintf("unable to parse %q value %q: %v", TestEnvTLSSkipVerify, tlsSkipVerify, err))
×
209
        }
210

211
        return val
×
212
}
213

214
func TestUseCouchbaseServerDockerName() (bool, string) {
×
215
        testX509CouchbaseServerDockerName, isSet := os.LookupEnv(TestEnvCouchbaseServerDockerName)
×
216
        if !isSet {
×
217
                return false, ""
×
218
        }
×
219
        return true, testX509CouchbaseServerDockerName
×
220
}
221

222
func TestX509LocalServer() (bool, string) {
×
223
        testX509LocalServer, isSet := os.LookupEnv(TestEnvX509Local)
×
224
        if !isSet {
×
225
                return false, ""
×
226
        }
×
227

228
        val, err := strconv.ParseBool(testX509LocalServer)
×
229
        if err != nil {
×
230
                panic(fmt.Sprintf("unable to parse %q value %q: %v", TestEnvX509Local, testX509LocalServer, err))
×
231
        }
232

233
        username, isSet := os.LookupEnv(TestEnvX509LocalUser)
×
234
        if !isSet {
×
235
                panic(fmt.Sprintf("TestEnvX509LocalUser must be set when TestEnvX509Local=true"))
×
236
        }
237

238
        return val, username
×
239
}
240

241
// TestSupportsMobileXDCR returns true to mobile XDCR bucket setting has been set to true for test bucket, false otherwise
242
func TestSupportsMobileXDCR() bool {
1✔
243
        if GTestBucketPool.skipMobileXDCR {
1✔
244
                return false
×
245
        }
×
246
        return true
1✔
247
}
248

249
// Should tests try to drop GSI indexes before flushing buckets?
250
// See SG #3422
251
func TestsShouldDropIndexes() bool {
×
252

×
253
        // First check if the SG_TEST_USE_XATTRS env variable is set
×
254
        dropIndexes := os.Getenv(TestEnvSyncGatewayDropIndexes)
×
255

×
256
        if strings.EqualFold(dropIndexes, TestEnvSyncGatewayTrue) {
×
257
                return true
×
258
        }
×
259

260
        // Otherwise fallback to hardcoded default
261
        return DefaultDropIndexes
×
262

263
}
264

265
// TestsDisableGSI returns true if tests should be forced to avoid any GSI-specific code.
266
func TestsDisableGSI() bool {
1✔
267
        // Disable GSI when running with Walrus
1✔
268
        if !TestUseCouchbaseServer() && UnitTestUrlIsWalrus() {
2✔
269
                return true
1✔
270
        }
1✔
271

272
        // Default to disabling GSI, but allow with SG_TEST_USE_GSI=true
273
        useGSI := false
×
274
        if envUseGSI := os.Getenv(TestEnvSyncGatewayDisableGSI); envUseGSI != "" {
×
275
                useGSI, _ = strconv.ParseBool(envUseGSI)
×
276
        }
×
277

278
        return !useGSI
×
279
}
280

281
// Check the whether tests are being run with SG_TEST_BACKING_STORE=Couchbase
282
func TestUseCouchbaseServer() bool {
1✔
283
        backingStore := os.Getenv(TestEnvSyncGatewayBackingStore)
1✔
284
        return strings.EqualFold(backingStore, TestEnvBackingStoreCouchbase)
1✔
285
}
1✔
286

287
func TestUseWalrus() bool {
×
288
        backingStore := os.Getenv(TestEnvSyncGatewayBackingStore)
×
289
        return strings.EqualFold(backingStore, TestEnvBackingStoreWalrus)
×
290
}
×
291

292
// Check the whether tests are being run with SG_TEST_BACKING_STORE=Couchbase
293
func TestUseExistingBucket() bool {
1✔
294
        return TestUseExistingBucketName() != ""
1✔
295
}
1✔
296

297
func TestUseExistingBucketName() string {
1✔
298
        return os.Getenv(TestEnvUseExistingBucket)
1✔
299
}
1✔
300

301
type TestAuthenticator struct {
302
        Username   string
303
        Password   string
304
        BucketName string
305
}
306

307
func (t TestAuthenticator) GetCredentials() (username, password, bucketname string) {
1✔
308
        return t.Username, t.Password, t.BucketName
1✔
309
}
1✔
310

311
// DropAllIndexes removes all indexes defined on the bucket or collection
312
func DropAllIndexes(ctx context.Context, n1QLStore N1QLStore) error {
×
313

×
314
        // Retrieve all indexes on the bucket/collection
×
315
        indexes, err := n1QLStore.GetIndexes()
×
316
        if err != nil {
×
317
                return err
×
318
        }
×
319

320
        wg := sync.WaitGroup{}
×
321
        wg.Add(len(indexes))
×
322

×
323
        asyncErrors := make(chan error, len(indexes))
×
324
        defer close(asyncErrors)
×
325

×
326
        for _, index := range indexes {
×
327

×
328
                go func(indexToDrop string) {
×
329

×
330
                        defer wg.Done()
×
331

×
332
                        InfofCtx(ctx, KeySGTest, "Dropping index %s on bucket %s...", indexToDrop, n1QLStore.GetName())
×
333
                        dropErr := n1QLStore.DropIndex(ctx, indexToDrop)
×
334
                        if dropErr != nil {
×
335
                                // Retry dropping index if first try fails before returning error
×
336
                                dropRetry := n1QLStore.DropIndex(ctx, indexToDrop)
×
337
                                if dropRetry != nil {
×
338
                                        asyncErrors <- dropErr
×
339
                                        ErrorfCtx(ctx, "...failed to drop index %s on bucket %s: %s", indexToDrop, n1QLStore.GetName(), dropErr)
×
340
                                        return
×
341
                                }
×
342
                        }
343
                        InfofCtx(ctx, KeySGTest, "...successfully dropped index %s on bucket %s", indexToDrop, n1QLStore.GetName())
×
344
                }(index)
345

346
        }
347

348
        // Wait until all goroutines finish
349
        wg.Wait()
×
350

×
351
        // Check if any errors were put into the asyncErrors channel.  If any, just return the first one
×
352
        select {
×
353
        case asyncError := <-asyncErrors:
×
354
                return asyncError
×
355
        default:
×
356
        }
357
        // DROP INDEX is asynchronous, but generally quick. Wait for all indexes to disappear as part of the test harness.
NEW
358
        err, _ = RetryLoop(ctx, "Waiting for no indexes on the bucket", func() (shouldRetry bool, err error, _ any) {
×
NEW
359
                // Retrieve all indexes on the bucket/collection
×
NEW
360
                indexes, err := n1QLStore.GetIndexes()
×
NEW
361
                if err != nil {
×
NEW
362
                        return false, err, nil
×
NEW
363
                }
×
NEW
364
                if len(indexes) == 0 {
×
NEW
365
                        return false, nil, nil
×
NEW
366
                }
×
NEW
367
                return true, nil, nil
×
368
        }, CreateSleeperFunc(500, 100))
NEW
369
        return err
×
370
}
371

372
// Generates a string of size int
373
const alphaNumeric = "0123456789abcdefghijklmnopqrstuvwxyz"
374

375
func CreateProperty(size int) (result string) {
1✔
376
        resultBytes := make([]byte, size)
1✔
377
        for i := 0; i < size; i++ {
2✔
378
                resultBytes[i] = alphaNumeric[i%len(alphaNumeric)]
1✔
379
        }
1✔
380
        return string(resultBytes)
1✔
381
}
382

383
// SetUpTestGoroutineDump will collect a goroutine pprof profile when teardownFn is called. Intended to be run at the end of TestMain to give us insight into goroutine leaks.
384
func SetUpTestGoroutineDump(m *testing.M) (teardownFn func()) {
1✔
385
        const numExpected = 1
1✔
386

1✔
387
        if ok, _ := strconv.ParseBool(os.Getenv(TestEnvGoroutineDump)); !ok {
2✔
388
                return func() {}
1✔
389
        }
390

391
        timestamp := time.Now().Unix()
×
392
        filename := fmt.Sprintf("test-pprof-%s-%d.pb.gz", "goroutine", timestamp)
×
393
        // create the file upfront so we know we're able to write to it before we run tests
×
394
        file, err := os.Create(filename)
×
395
        if err != nil {
×
396
                panic(err)
×
397
        }
398

399
        wd, err := os.Getwd()
×
400
        if err != nil {
×
401
                panic(err)
×
402
        }
403

404
        return func() {
×
405
                if n := runtime.NumGoroutine(); n > numExpected {
×
406
                        if err := pprof.Lookup("goroutine").WriteTo(os.Stderr, 2); err != nil {
×
407
                                panic(err)
×
408
                        }
409
                        if err := pprof.Lookup("goroutine").WriteTo(file, 0); err != nil {
×
410
                                panic(err)
×
411
                        }
412
                        log.Printf(color("\n"+
×
413
                                "TEST: =================================================\n"+
×
414
                                "TEST: Leaked goroutines after testing: got %d expected %d\n"+
×
415
                                "TEST: =================================================\n", LevelError), n, numExpected)
×
416
                        log.Printf("TEST: Written goroutine profile to: %s%c%s", wd, os.PathSeparator, file.Name())
×
417
                } else {
×
418
                        log.Print(color("TEST: No leaked goroutines found", LevelDebug))
×
419
                }
×
420
        }
421
}
422

423
// SetUpGlobalTestMemoryWatermark will periodically write an in-use memory watermark,
424
// and will cause the tests to fail on teardown if the watermark has exceeded the threshold.
425
func SetUpGlobalTestMemoryWatermark(m *testing.M, memWatermarkThresholdMB uint64) (teardownFn func()) {
1✔
426
        sampleFrequency := time.Second * 5
1✔
427
        if freq := os.Getenv("SG_TEST_PROFILE_FREQUENCY"); freq != "" {
1✔
428
                var err error
×
429
                sampleFrequency, err = time.ParseDuration(freq)
×
430
                if err != nil {
×
431
                        log.Fatalf("TEST: profile frequency %q was not a valid duration: %v", freq, err)
×
432
                } else if sampleFrequency == 0 {
×
433
                        // disabled
×
434
                        return func() {}
×
435
                }
436
        }
437

438
        var inuseHighWaterMarkMB float64
1✔
439

1✔
440
        ctx, ctxCancel := context.WithCancel(context.Background())
1✔
441
        wg := sync.WaitGroup{}
1✔
442
        wg.Add(1)
1✔
443

1✔
444
        go func(ctx context.Context) {
2✔
445
                defer wg.Done()
1✔
446

1✔
447
                sampleFn := func() {
2✔
448
                        var ms runtime.MemStats
1✔
449
                        runtime.ReadMemStats(&ms)
1✔
450
                        heapInuseMB := float64(ms.HeapInuse) / float64(1024*1024)
1✔
451
                        stackInuseMB := float64(ms.StackInuse) / float64(1024*1024)
1✔
452
                        totalInuseMB := heapInuseMB + stackInuseMB
1✔
453
                        // log.Printf("TEST: Memory usage recorded heap: %.2f MB stack: %.2f MB", heapInuseMB, stackInuseMB)
1✔
454
                        if totalInuseMB > inuseHighWaterMarkMB {
2✔
455
                                log.Printf("TEST: Memory high water mark increased to %.2f MB (heap: %.2f MB stack: %.2f MB)", totalInuseMB, heapInuseMB, stackInuseMB)
1✔
456
                                inuseHighWaterMarkMB = totalInuseMB
1✔
457
                        }
1✔
458
                }
459

460
                t := time.NewTicker(sampleFrequency)
1✔
461
                defer t.Stop()
1✔
462

1✔
463
                for {
2✔
464
                        select {
1✔
465
                        case <-ctx.Done():
×
466
                                sampleFn() // one last reading just before we exit
×
467
                                return
×
468
                        case <-t.C:
1✔
469
                                sampleFn()
1✔
470
                        }
471
                }
472
        }(ctx)
473

474
        return func() {
1✔
475
                ctxCancel()
×
476
                wg.Wait()
×
477

×
478
                if inuseHighWaterMarkMB > float64(memWatermarkThresholdMB) {
×
479
                        // Exit during teardown to fail the suite if they exceeded the threshold
×
480
                        log.Fatalf("FATAL - TEST: Memory high water mark %.2f MB exceeded threshold (%d MB)", inuseHighWaterMarkMB, memWatermarkThresholdMB)
×
481
                } else {
×
482
                        log.Printf("TEST: Memory high water mark %.2f MB", inuseHighWaterMarkMB)
×
483
                }
×
484
        }
485
}
486

487
// SetUpGlobalTestProfiling will cause a packages tests to periodically write a profiles to the package's directory.
488
func SetUpGlobalTestProfiling(m *testing.M) (teardownFn func()) {
1✔
489
        freq := os.Getenv("SG_TEST_PROFILE_FREQUENCY")
1✔
490
        if freq == "" {
2✔
491
                return func() {}
1✔
492
        }
493

494
        d, err := time.ParseDuration(freq)
×
495
        if err != nil {
×
496
                log.Fatalf("TEST: profile frequency %q was not a valid duration: %v", freq, err)
×
497
        } else if d == 0 {
×
498
                // disabled
×
499
                return func() {}
×
500
        }
501

502
        profiles := []string{
×
503
                "goroutine",
×
504
                // "threadcreate",
×
505
                "heap",
×
506
                // "allocs",
×
507
                // "block",
×
508
                // "mutex",
×
509
        }
×
510

×
511
        log.Printf("TEST: profiling for %v with frequency: %v", profiles, freq)
×
512

×
513
        ctx, ctxCancel := context.WithCancel(context.Background())
×
514
        wg := sync.WaitGroup{}
×
515
        wg.Add(1)
×
516

×
517
        go func(ctx context.Context) {
×
518
                defer wg.Done()
×
519

×
520
                sampleFn := func() {
×
521
                        timestamp := time.Now().Unix()
×
522
                        for _, profile := range profiles {
×
523
                                filename := fmt.Sprintf("test-pprof-%s-%d.pb.gz", profile, timestamp)
×
524
                                f, err := os.Create(filename)
×
525
                                if err != nil {
×
526
                                        log.Fatalf("TEST: couldn't open pprof %s file: %v", profile, err)
×
527
                                }
×
528
                                err = pprof.Lookup(profile).WriteTo(f, 0)
×
529
                                if err != nil {
×
530
                                        log.Fatalf("TEST: couldn't write pprof %s file: %v", profile, err)
×
531
                                }
×
532
                                err = f.Close()
×
533
                                if err != nil {
×
534
                                        log.Fatalf("TEST: couldn't close pprof %s file: %v", profile, err)
×
535
                                }
×
536
                                log.Printf("TEST: %s profile written to: %v", profile, filename)
×
537
                        }
538
                }
539

540
                t := time.NewTicker(d)
×
541
                defer t.Stop()
×
542

×
543
                for {
×
544
                        select {
×
545
                        case <-ctx.Done():
×
546
                                sampleFn() // one last reading just before we exit
×
547
                                return
×
548
                        case <-t.C:
×
549
                                sampleFn()
×
550
                        }
551
                }
552
        }(ctx)
553

554
        return func() {
×
555
                ctxCancel()
×
556
                wg.Wait()
×
557
        }
×
558
}
559

560
var GlobalTestLoggingSet = AtomicBool{}
561

562
// SetUpGlobalTestLogging sets a global log level at runtime by using the SG_TEST_LOG_LEVEL environment variable.
563
// This global level overrides any tests that specify their own test log level with SetUpTestLogging.
564
func SetUpGlobalTestLogging(ctx context.Context) (teardownFn func()) {
1✔
565
        if logLevel := os.Getenv(TestEnvGlobalLogLevel); logLevel != "" {
1✔
566
                var l LogLevel
×
567
                err := l.UnmarshalText([]byte(logLevel))
×
568
                if err != nil {
×
569
                        FatalfCtx(ctx, "TEST: Invalid log level used for %q: %s", TestEnvGlobalLogLevel, err)
×
570
                }
×
571
                caller := GetCallersName(1, false)
×
572
                InfofCtx(context.Background(), KeyAll, "%s: Setup logging: level: %v - keys: %v", caller, logLevel, KeyAll)
×
573
                teardown := setTestLogging(l, caller, KeyAll)
×
574
                GlobalTestLoggingSet.Set(true)
×
575
                return func() {
×
576
                        teardown()
×
577
                        GlobalTestLoggingSet.Set(false)
×
578
                }
×
579
        }
580
        // noop
581
        return func() { GlobalTestLoggingSet.Set(false) }
1✔
582
}
583

584
// SetUpTestLogging will set the given log level and log keys, and revert the changes at the end of the current test.
585
//
586
// This function will panic if called multiple times in the same test.
587
func SetUpTestLogging(tb testing.TB, logLevel LogLevel, logKeys ...LogKey) {
1✔
588
        caller := GetCallersName(1, false)
1✔
589
        InfofCtx(context.Background(), KeyAll, "%s: Setup logging: level: %v - keys: %v", caller, logLevel, logKeys)
1✔
590
        cleanup := setTestLogging(logLevel, caller, logKeys...)
1✔
591
        tb.Cleanup(cleanup)
1✔
592
}
1✔
593

594
// ResetGlobalTestLogging will ensure that the loggers are replaced at the end of the the test.
595
func ResetGlobalTestLogging(t *testing.T) {
1✔
596
        t.Cleanup(func() {
2✔
597
                for _, logger := range getFileLoggers() {
2✔
598
                        assert.NoError(t, logger.Close())
1✔
599
                }
1✔
600
                ctx := TestCtx(t)
1✔
601
                initializeLoggers(ctx)
1✔
602
                SetUpGlobalTestLogging(ctx)
1✔
603

604
        })
605
}
606

607
// DisableTestLogging is an alias for SetUpTestLogging(LevelNone, KeyNone)
608
// This function will panic if called multiple times in the same test.
609
func DisableTestLogging(tb testing.TB) {
×
610
        caller := ""
×
611
        cleanup := setTestLogging(LevelNone, caller, KeyNone)
×
612
        tb.Cleanup(cleanup)
×
613
}
×
614

615
// SetUpBenchmarkLogging will set the given log level and key, and do log processing for that configuration,
616
// but discards the output, instead of writing it to console.
617
func SetUpBenchmarkLogging(tb testing.TB, logLevel LogLevel, logKeys ...LogKey) {
×
618
        teardownFnOrig := setTestLogging(logLevel, "", logKeys...)
×
619

×
620
        // discard all logging output for benchmarking (but still execute logging as normal)
×
621
        consoleLogger.Load().logger.SetOutput(io.Discard)
×
622
        tb.Cleanup(func() {
×
623
                logger := consoleLogger.Load()
×
624
                // revert back to original output
×
625
                if logger != nil && logger.output != nil {
×
626
                        logger.logger.SetOutput(logger.output)
×
627
                } else {
×
628
                        logger.logger.SetOutput(os.Stderr)
×
629
                }
×
630
                teardownFnOrig()
×
631
        })
632
}
633

634
func setTestLogging(logLevel LogLevel, caller string, logKeys ...LogKey) (teardownFn func()) {
1✔
635
        if GlobalTestLoggingSet.IsTrue() {
1✔
636
                // noop, test log level is already set globally
×
637
                return func() {
×
638
                        if caller != "" {
×
639
                                InfofCtx(context.Background(), KeyAll, "%v: Reset logging", caller)
×
640
                        }
×
641
                }
642
        }
643

644
        logger := consoleLogger.Load()
1✔
645
        initialLogLevel := LevelInfo
1✔
646
        initialLogKey := logKeyMask(KeyHTTP)
1✔
647

1✔
648
        // Check that a previous invocation has not forgotten to call teardownFn
1✔
649
        if *logger.LogLevel != initialLogLevel ||
1✔
650
                *logger.LogKeyMask != *initialLogKey {
2✔
651
                panic(fmt.Sprintf("Logging is in an unexpected state! Did a previous test forget to call the teardownFn of SetUpTestLogging?, LogLevel=%s, expected=%s; LogKeyMask=%s, expected=%s", logger.LogLevel, initialLogLevel, logger.LogKeyMask, initialLogKey))
1✔
652
        }
653
        if errorLogger.Load() != nil {
1✔
654
                panic("Logging is in an expected state, an earlier test possibly needed to call ResetGlobalTestLogging")
×
655
        }
656
        logger.LogLevel.Set(logLevel)
1✔
657
        logger.LogKeyMask.Set(logKeyMask(logKeys...))
1✔
658
        updateExternalLoggers()
1✔
659

1✔
660
        return func() {
2✔
661
                logger := consoleLogger.Load()
1✔
662
                // Return logging to a default state
1✔
663
                logger.LogLevel.Set(initialLogLevel)
1✔
664
                logger.LogKeyMask.Set(initialLogKey)
1✔
665
                updateExternalLoggers()
1✔
666
                if caller != "" {
2✔
667
                        InfofCtx(context.Background(), KeyAll, "%v: Reset logging", caller)
1✔
668
                }
1✔
669
        }
670
}
671

672
// Make a deep copy from src into dst.
673
// Copied from https://github.com/getlantern/deepcopy, commit 7f45deb8130a0acc553242eb0e009e3f6f3d9ce3 (Apache 2 licensed)
674
func DeepCopyInefficient(dst interface{}, src interface{}) error {
1✔
675
        if dst == nil {
1✔
676
                return fmt.Errorf("dst cannot be nil")
×
677
        }
×
678
        if src == nil {
1✔
679
                return fmt.Errorf("src cannot be nil")
×
680
        }
×
681
        b, err := JSONMarshal(src)
1✔
682
        if err != nil {
1✔
683
                return fmt.Errorf("Unable to marshal src: %s", err)
×
684
        }
×
685
        d := JSONDecoder(bytes.NewBuffer(b))
1✔
686
        d.UseNumber()
1✔
687
        err = d.Decode(dst)
1✔
688
        if err != nil {
1✔
689
                return fmt.Errorf("Unable to unmarshal into dst: %s", err)
×
690
        }
×
691
        return nil
1✔
692
}
693

694
// testRetryUntilTrue performs a short sleep-based retry loop until the timeout is reached or the
695
// criteria in RetryUntilTrueFunc is met. Intended to
696
// avoid arbitrarily long sleeps in tests that don't have any alternative to polling.
697
// Default sleep time is 50ms, timeout is 10s.  Can be customized with testRetryUntilTrueCustom
698
type RetryUntilTrueFunc func() bool
699

700
func testRetryUntilTrue(t *testing.T, retryFunc RetryUntilTrueFunc) {
×
701
        testRetryUntilTrueCustom(t, retryFunc, 100, 10000)
×
702
}
×
703

704
func testRetryUntilTrueCustom(t *testing.T, retryFunc RetryUntilTrueFunc, waitTimeMs int, timeoutMs int) {
×
705
        timeElapsedMs := 0
×
706
        for timeElapsedMs < timeoutMs {
×
707
                if retryFunc() {
×
708
                        return
×
709
                }
×
710
                time.Sleep(time.Duration(waitTimeMs) * time.Millisecond)
×
711
                timeElapsedMs += waitTimeMs
×
712
        }
713
        assert.Fail(t, fmt.Sprintf("Retry until function didn't succeed within timeout (%d ms)", timeoutMs))
×
714
}
715

716
func FileExists(filename string) bool {
1✔
717
        info, err := os.Stat(filename)
1✔
718
        if err != nil {
2✔
719
                return false
1✔
720
        }
1✔
721
        return !info.IsDir()
1✔
722
}
723

724
func DirExists(filename string) bool {
×
725
        info, err := os.Stat(filename)
×
726
        if err != nil {
×
727
                return false
×
728
        }
×
729
        return info.IsDir()
×
730
}
731

732
// AssertWaitForStat will retry for up to 20 seconds until the result of getStatFunc is equal to the expected value.
733
func AssertWaitForStat(t testing.TB, getStatFunc func() int64, expected int64) (val int64) {
×
734
        assert.EventuallyWithT(t, func(c *assert.CollectT) {
×
735
                val = getStatFunc()
×
736
                assert.Equal(c, expected, val)
×
737
        }, 20*time.Second, 100*time.Millisecond)
×
738
        return val
×
739
}
740

741
// RequireWaitForStat will retry for up to 20 seconds until the result of getStatFunc is equal to the expected value.
742
func RequireWaitForStat(t testing.TB, getStatFunc func() int64, expected int64) (val int64) {
1✔
743
        require.EventuallyWithT(t, func(c *assert.CollectT) {
2✔
744
                val = getStatFunc()
1✔
745
                assert.Equal(c, expected, val)
1✔
746
        }, 20*time.Second, 100*time.Millisecond)
1✔
747
        return val
1✔
748
}
749

750
// TestRequiresCollections will skip the current test if the Couchbase Server version it is running against does not
751
// support collections.
752
func TestRequiresCollections(t testing.TB) {
1✔
753
        if ok, err := GTestBucketPool.canUseNamedCollections(TestCtx(t)); err != nil {
1✔
754
                t.Skipf("Skipping test - collections not supported: %v", err)
×
755
        } else if !ok {
1✔
756
                t.Skipf("Skipping test - collections not enabled")
×
757
        }
×
758
}
759

760
// TestRequiresDCPResync will skip the current test DCP sync is not supported.
761
func TestRequiresDCPResync(t testing.TB) {
1✔
762
        if UnitTestUrlIsWalrus() {
2✔
763
                t.Skip("Walrus doesn't support DCP resync CBG-2661")
1✔
764
        }
1✔
765
}
766

767
// TestRequiresGocbDCPClient will skip the current test if using rosmar.
768
func TestRequiresGocbDCPClient(t testing.TB) {
1✔
769
        if UnitTestUrlIsWalrus() {
2✔
770
                t.Skip("rosmar doesn't support base.DCPClient")
1✔
771
        }
1✔
772
}
773

774
// RequireDocNotFoundError asserts that the given error represents a document not found error.
775
func RequireDocNotFoundError(t testing.TB, e error) {
1✔
776
        require.True(t, IsDocNotFoundError(e), fmt.Sprintf("Expected error to be a doc not found error, but was: %v", e))
1✔
777
}
1✔
778

779
func requireCasMismatchError(t testing.TB, err error) {
1✔
780
        require.Error(t, err, "Expected an error of type IsCasMismatch %+v\n", err)
1✔
781
        require.True(t, IsCasMismatch(err), "Expected error of type IsCasMismatch but got %+v\n", err)
1✔
782
}
1✔
783

784
// SkipImportTestsIfNotEnabled skips test that exercise import features
785
func SkipImportTestsIfNotEnabled(t *testing.T) {
1✔
786

1✔
787
        if !TestUseXattrs() {
1✔
788
                t.Skip("XATTR based tests not enabled.  Enable via SG_TEST_USE_XATTRS=true environment variable")
×
789
        }
×
790
}
791

792
// CreateBucketScopesAndCollections will create the given scopes and collections within the given BucketSpec.
793
func CreateBucketScopesAndCollections(ctx context.Context, bucketSpec BucketSpec, scopes map[string][]string) error {
×
794
        atLeastOneScope := false
×
795
        for _, collections := range scopes {
×
796
                for range collections {
×
797
                        atLeastOneScope = true
×
798
                        break
×
799
                }
800
                break
×
801
        }
802
        if !atLeastOneScope {
×
803
                // nothing to do here
×
804
                return nil
×
805
        }
×
806

807
        un, pw, _ := bucketSpec.Auth.GetCredentials()
×
808
        var rootCAs *x509.CertPool
×
809
        if tlsConfig := bucketSpec.TLSConfig(ctx); tlsConfig != nil {
×
810
                rootCAs = tlsConfig.RootCAs
×
811
        }
×
812
        cluster, err := gocb.Connect(bucketSpec.Server, gocb.ClusterOptions{
×
813
                Username: un,
×
814
                Password: pw,
×
815
                SecurityConfig: gocb.SecurityConfig{
×
816
                        TLSSkipVerify: bucketSpec.TLSSkipVerify,
×
817
                        TLSRootCAs:    rootCAs,
×
818
                },
×
819
        })
×
820
        if err != nil {
×
821
                return fmt.Errorf("failed to connect to cluster: %w", err)
×
822
        }
×
823
        defer func() { _ = cluster.Close(nil) }()
×
824

825
        cm := cluster.Bucket(bucketSpec.BucketName).Collections()
×
826

×
827
        for scopeName, collections := range scopes {
×
828
                if err := cm.CreateScope(scopeName, nil); err != nil && !errors.Is(err, gocb.ErrScopeExists) {
×
829
                        return fmt.Errorf("failed to create scope %s: %w", scopeName, err)
×
830
                }
×
831
                DebugfCtx(ctx, KeySGTest, "Created scope %s", scopeName)
×
832
                for _, collectionName := range collections {
×
833
                        if err := cm.CreateCollection(
×
834
                                gocb.CollectionSpec{
×
835
                                        Name:      collectionName,
×
836
                                        ScopeName: scopeName,
×
837
                                }, nil); err != nil && !errors.Is(err, gocb.ErrCollectionExists) {
×
838
                                return fmt.Errorf("failed to create collection %s in scope %s: %w", collectionName, scopeName, err)
×
839
                        }
×
840
                        DebugfCtx(ctx, KeySGTest, "Created collection %s.%s", scopeName, collectionName)
×
841
                        if err := WaitForNoError(ctx, func() error {
×
842
                                _, err := cluster.Bucket(bucketSpec.BucketName).Scope(scopeName).Collection(collectionName).Exists("WaitForExists", nil)
×
843
                                return err
×
844
                        }); err != nil {
×
845
                                return fmt.Errorf("failed to wait for collection %s.%s to exist: %w", scopeName, collectionName, err)
×
846
                        }
×
847
                        DebugfCtx(ctx, KeySGTest, "Collection now exists %s.%s", scopeName, collectionName)
×
848
                }
849
        }
850

851
        return nil
×
852
}
853

854
// RequireAllAssertions ensures that all assertion results were true/ok, and fails the test if any were not.
855
// Usage:
856
//
857
//        RequireAllAssertions(t,
858
//            assert.True(t, condition1),
859
//            assert.True(t, condition2),
860
//        )
861
func RequireAllAssertions(t *testing.T, assertionResults ...bool) {
1✔
862
        var failed bool
1✔
863
        for _, ok := range assertionResults {
2✔
864
                if !ok {
1✔
865
                        failed = true
×
866
                        break
×
867
                }
868
        }
869
        require.Falsef(t, failed, "One or more assertions failed: %v", assertionResults)
1✔
870
}
871

872
// LongRunningTest skips the test if running in -short mode, and logs if the test completed quickly under other circumstances.
873
func LongRunningTest(t *testing.T) {
1✔
874
        const (
1✔
875
                shortTestThreshold = time.Second
1✔
876
        )
1✔
877
        if testing.Short() {
1✔
878
                t.Skip("skipping long running test in short mode")
×
879
                return
×
880
        }
×
881
        start := time.Now()
1✔
882
        t.Cleanup(func() {
2✔
883
                testDuration := time.Since(start)
1✔
884
                if !t.Failed() && !t.Skipped() && testDuration < shortTestThreshold {
2✔
885
                        t.Logf("TEST: %q was marked as long running, but finished in %v (less than %v) - consider removing LongRunningTest", t.Name(), testDuration, shortTestThreshold)
1✔
886
                }
1✔
887
        })
888
}
889

890
func AssertTimeGreaterThan(t *testing.T, e1, e2 time.Time, msgAndArgs ...interface{}) bool {
1✔
891
        return AssertTimestampGreaterThan(t, e1.UnixNano(), e2.UnixNano(), msgAndArgs...)
1✔
892
}
1✔
893

894
func AssertTimestampGreaterThan(t *testing.T, e1, e2 int64, msgAndArgs ...interface{}) bool {
1✔
895
        // time.Nanoseconds has poor precision on Windows - equal is good enough there...
1✔
896
        if runtime.GOOS == "windows" {
1✔
897
                return assert.GreaterOrEqual(t, e1, e2, msgAndArgs...)
×
898
        }
×
899
        return assert.Greater(t, e1, e2, msgAndArgs...)
1✔
900
}
901

902
func GetVbucketForKey(bucket Bucket, key string) (vbNo uint32, err error) {
×
903

×
904
        cbBucket, ok := AsCouchbaseBucketStore(bucket)
×
905
        if !ok {
×
906
                return 0, fmt.Errorf("GetVbucketForKey not supported for non-Couchbase bucket")
×
907
        }
×
908

909
        maxVbNo, err := cbBucket.GetMaxVbno()
×
910
        if err != nil {
×
911
                return 0, err
×
912
        }
×
913

914
        return sgbucket.VBHash(key, maxVbNo), nil
×
915
}
916

917
// MoveDocument moves the document from src to dst
918
// Note: does not handle xattr contents
919
func MoveDocument(t testing.TB, docID string, dst, src DataStore) {
1✔
920
        var data interface{}
1✔
921

1✔
922
        srcCAS, err := src.Get(docID, &data)
1✔
923
        require.NoError(t, err)
1✔
924

1✔
925
        ok, err := dst.Add(docID, 0, data)
1✔
926
        require.NoError(t, err)
1✔
927
        require.True(t, ok)
1✔
928

1✔
929
        _, err = src.Remove(docID, srcCAS)
1✔
930
        require.NoError(t, err)
1✔
931
}
1✔
932

933
// FastRandBytes returns a set of random bytes. Uses a low quality random generator.
934
func FastRandBytes(t testing.TB, size int) []byte {
1✔
935
        b := make([]byte, size)
1✔
936
        // staticcheck wants to use crypto/rand as math/rand is deprecated in go 1.20, but we don't need that for testing
1✔
937
        _, err := rand.Read(b) // nolint:staticcheck
1✔
938
        require.NoError(t, err)
1✔
939
        return b
1✔
940
}
1✔
941

942
// MustJSONMarshal marshals the given value to JSON, and errors the test if it can not be turned into json.
943
func MustJSONMarshal(t testing.TB, v interface{}) []byte {
1✔
944
        b, err := JSONMarshal(v)
1✔
945
        require.NoError(t, err)
1✔
946
        return b
1✔
947
}
1✔
948

949
// numFilesInDir counts the number of files in a given directory
950
func numFilesInDir(t *testing.T, dir string, recursive bool) int {
1✔
951
        numFiles := 0
1✔
952
        err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
2✔
953
                if d.Name() == filepath.Base(dir) {
2✔
954
                        // skip counting the root directory
1✔
955
                        return nil
1✔
956
                }
1✔
957
                if !recursive && d.IsDir() {
1✔
958
                        return fs.SkipDir
×
959
                }
×
960
                numFiles++
1✔
961
                return nil
1✔
962
        })
963
        require.NoError(t, err)
1✔
964
        return numFiles
1✔
965
}
966

967
// CreateTestBucketName will create a test bucket name using the test bucket prefix and the suffix you pass in
968
func CreateTestBucketName(suffix string) string {
×
969
        return fmt.Sprintf("%s%s", tbpBucketNamePrefix, suffix)
×
970
}
×
971

972
// GetNonDefaultDatastoreNames returns a list of non-default datastore names from the given bucket.
973
func GetNonDefaultDatastoreNames(t testing.TB, bucket Bucket) []sgbucket.DataStoreName {
1✔
974
        allDataStoreNames, err := bucket.ListDataStores()
1✔
975
        require.NoError(t, err)
1✔
976
        var keyspaces []string
1✔
977
        for _, name := range allDataStoreNames {
2✔
978
                if IsDefaultCollection(name.ScopeName(), name.CollectionName()) {
2✔
979
                        continue
1✔
980
                }
981
                keyspaces = append(keyspaces, fmt.Sprintf("%s.%s", name.ScopeName(), name.CollectionName()))
1✔
982
        }
983
        sort.Strings(keyspaces)
1✔
984
        var nonDefaultDataStoreNames []sgbucket.DataStoreName
1✔
985
        for _, keyspace := range keyspaces {
2✔
986
                scopeAndCollection := strings.Split(keyspace, ScopeCollectionSeparator)
1✔
987
                nonDefaultDataStoreNames = append(nonDefaultDataStoreNames,
1✔
988
                        ScopeAndCollectionName{
1✔
989
                                Scope:      scopeAndCollection[0],
1✔
990
                                Collection: scopeAndCollection[1]})
1✔
991
        }
1✔
992
        return nonDefaultDataStoreNames
1✔
993
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc