-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathannotations.go
More file actions
90 lines (74 loc) · 2.38 KB
/
annotations.go
File metadata and controls
90 lines (74 loc) · 2.38 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
package testo
import (
"reflect"
"runtime"
"slices"
"strings"
"sync"
"github.com/ozontech/testo/internal/reflectutil"
"github.com/ozontech/testo/testoplugin"
)
type testID string
var (
globalAnnotationsMu sync.RWMutex
globalAnnotations = make(map[testID][]testoplugin.Option)
)
// ForEach applies static options (annotations) for each case of a given parametrized test.
//
// Similar to [For], but for parametrized tests.
//
// testo.ForEach(MySuite.TestFoo, someplugin.WithThis(...), otherplugin.WithThat(...))
// testo.ForEach((*Suite).TestFoo, myplugin.WithRetry())
func ForEach[Suite suite[T], T CommonT, P any](
test func(Suite, T, P),
options ...testoplugin.Option,
) struct{} {
annotate(getID[Suite](reflect.ValueOf(test)), options...)
return struct{}{}
}
// For applies static options (annotations) for a given test.
//
// Test annotations is a slice of options to be passed for this test.
// Test annotations are available to plugins before running an actual test,
// thus enhancing test planning features.
//
// Multiple annotation calls to the same test will append options.
//
// testo.For(MySuite.TestFoo, someplugin.WithThis(...), otherplugin.WithThat(...))
// testo.For((*Suite).TestFoo, myplugin.WithRetry())
//
// NOTE: it returns an empty struct{} value to enable the following usage:
//
// var _ = testo.For(MySuite.TestFoo, someplugin.WithSomeOption(true))
//
// func (MySuite) TestFoo(t T) { ... }
//
// That "var _ =" construction would not be possible otherwise.
// This is slightly less verbose than using an init function:
//
// func init() {
// testo.For(MySuite.TestFoo, someplugin.WithSomeOption(true))
// }
func For[Suite suite[T], T CommonT](
test func(Suite, T),
options ...testoplugin.Option,
) struct{} {
annotate(getID[Suite](reflect.ValueOf(test)), options...)
return struct{}{}
}
func annotate(id testID, options ...testoplugin.Option) {
globalAnnotationsMu.Lock()
defer globalAnnotationsMu.Unlock()
globalAnnotations[id] = append(globalAnnotations[id], options...)
}
func annotationsFor(id testID) []testoplugin.Option {
globalAnnotationsMu.RLock()
defer globalAnnotationsMu.RUnlock()
return slices.Clone(globalAnnotations[id])
}
func getID[Suite any](test reflect.Value) testID {
suiteName := reflectutil.NameOf[Suite]()
name := runtime.FuncForPC(test.Pointer()).Name()
name = strings.ReplaceAll(name, "(*"+suiteName+")", suiteName)
return testID(name)
}