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  }