github.com/onsi/ginkgo@v1.16.6-0.20211118180735-4e1925ba4c95/internal/test_helpers/run_tracker.go (about) 1 package test_helpers 2 3 import ( 4 "fmt" 5 "strings" 6 "sync" 7 8 "github.com/onsi/ginkgo/formatter" 9 . "github.com/onsi/gomega" 10 "github.com/onsi/gomega/types" 11 ) 12 13 /* 14 15 RunTracker tracks invocations of functions - useful to assert orders in which nodes run 16 17 */ 18 19 type RunTracker struct { 20 lock *sync.Mutex 21 trackedRuns []string 22 trackedData map[string]map[string]interface{} 23 } 24 25 func NewRunTracker() *RunTracker { 26 return &RunTracker{ 27 lock: &sync.Mutex{}, 28 trackedData: map[string]map[string]interface{}{}, 29 } 30 } 31 32 func (rt *RunTracker) Reset() { 33 rt.lock.Lock() 34 defer rt.lock.Unlock() 35 rt.trackedRuns = []string{} 36 } 37 38 func (rt *RunTracker) Run(text string) { 39 rt.lock.Lock() 40 defer rt.lock.Unlock() 41 rt.trackedRuns = append(rt.trackedRuns, text) 42 } 43 44 func (rt *RunTracker) RunWithData(text string, kv ...interface{}) { 45 rt.lock.Lock() 46 defer rt.lock.Unlock() 47 rt.trackedRuns = append(rt.trackedRuns, text) 48 data := map[string]interface{}{} 49 for i := 0; i < len(kv); i += 2 { 50 key := kv[i].(string) 51 value := kv[i+1] 52 data[key] = value 53 } 54 rt.trackedData[text] = data 55 } 56 57 func (rt *RunTracker) TrackedRuns() []string { 58 rt.lock.Lock() 59 defer rt.lock.Unlock() 60 trackedRuns := make([]string, len(rt.trackedRuns)) 61 copy(trackedRuns, rt.trackedRuns) 62 return trackedRuns 63 } 64 65 func (rt *RunTracker) DataFor(text string) map[string]interface{} { 66 rt.lock.Lock() 67 defer rt.lock.Unlock() 68 return rt.trackedData[text] 69 } 70 71 func (rt *RunTracker) T(text string, callback ...func()) func() { 72 return func() { 73 rt.Run(text) 74 if len(callback) > 0 { 75 callback[0]() 76 } 77 } 78 } 79 80 func (rt *RunTracker) C(text string, callback ...func()) func(args []string, additionalArgs []string) { 81 return func(args []string, additionalArgs []string) { 82 rt.RunWithData(text, "Args", args, "AdditionalArgs", additionalArgs) 83 if len(callback) > 0 { 84 callback[0]() 85 } 86 } 87 } 88 89 func HaveRun(run string) OmegaMatcher { 90 return WithTransform(func(rt *RunTracker) []string { 91 return rt.TrackedRuns() 92 }, ContainElement(run)) 93 } 94 95 func HaveRunWithData(run string, kv ...interface{}) OmegaMatcher { 96 matchers := []types.GomegaMatcher{} 97 for i := 0; i < len(kv); i += 2 { 98 matchers = append(matchers, HaveKeyWithValue(kv[i], kv[i+1])) 99 } 100 return And( 101 HaveRun(run), 102 WithTransform(func(rt *RunTracker) map[string]interface{} { 103 return rt.DataFor(run) 104 }, And(matchers...)), 105 ) 106 } 107 108 func HaveTrackedNothing() OmegaMatcher { 109 return WithTransform(func(rt *RunTracker) []string { 110 return rt.TrackedRuns() 111 }, BeEmpty()) 112 } 113 114 type HaveTrackedMatcher struct { 115 expectedRuns []string 116 message string 117 } 118 119 func (m *HaveTrackedMatcher) Match(actual interface{}) (bool, error) { 120 rt, ok := actual.(*RunTracker) 121 if !ok { 122 return false, fmt.Errorf("HaveTracked() must be passed a RunTracker - got %T instead", actual) 123 } 124 actualRuns := rt.TrackedRuns() 125 n := len(actualRuns) 126 if n < len(m.expectedRuns) { 127 n = len(m.expectedRuns) 128 } 129 failureMessage, success := &strings.Builder{}, true 130 fmt.Fprintf(failureMessage, "{{/}}%10s == %-10s{{/}}\n", "Actual", "Expected") 131 fmt.Fprintf(failureMessage, "{{/}}========================\n{{/}}") 132 for i := 0; i < n; i++ { 133 var expected, actual string 134 if i < len(actualRuns) { 135 actual = actualRuns[i] 136 } 137 if i < len(m.expectedRuns) { 138 expected = m.expectedRuns[i] 139 } 140 if actual != expected { 141 success = false 142 fmt.Fprintf(failureMessage, "{{red}}%10s != %-10s{{/}}\n", actual, expected) 143 } else { 144 fmt.Fprintf(failureMessage, "{{green}}%10s == %-10s{{/}}\n", actual, expected) 145 } 146 147 } 148 m.message = failureMessage.String() 149 return success, nil 150 151 } 152 func (m *HaveTrackedMatcher) FailureMessage(actual interface{}) string { 153 return "Expected runs did not match tracked runs:\n" + formatter.F(m.message) 154 155 } 156 func (m *HaveTrackedMatcher) NegatedFailureMessage(actual interface{}) string { 157 return "Expected runs matched tracked runs:\n" + formatter.F(m.message) 158 } 159 160 func HaveTracked(runs ...string) OmegaMatcher { 161 return &HaveTrackedMatcher{expectedRuns: runs} 162 }