package cache import ( "context" "reflect" "time" "github.com/mitchellh/go-testing-interface" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) // TestCacheGetCh returns a channel that returns the result of the Get call. // This is useful for testing timing and concurrency with Get calls. Any // error will be logged, so the result value should always be asserted. func TestCacheGetCh(t testing.T, c *Cache, typ string, r Request) <-chan interface{} { resultCh := make(chan interface{}) go func() { result, _, err := c.Get(context.Background(), typ, r) if err != nil { t.Logf("Error: %s", err) close(resultCh) return } resultCh <- result }() return resultCh } // TestCacheGetChResult tests that the result from TestCacheGetCh matches // within a reasonable period of time (it expects it to be "immediate" but // waits some milliseconds). func TestCacheGetChResult(t testing.T, ch <-chan interface{}, expected interface{}) { t.Helper() select { case result := <-ch: if !reflect.DeepEqual(result, expected) { t.Fatalf("Result doesn't match!\n\n%#v\n\n%#v", result, expected) } case <-time.After(50 * time.Millisecond): t.Fatalf("Result not sent on channel") } } // TestCacheNotifyChResult tests that the expected updated was delivered on a // Notify() chan within a reasonable period of time (it expects it to be // "immediate" but waits some milliseconds). Expected may be given multiple // times and if so these are all waited for and asserted to match but IN ANY // ORDER to ensure we aren't timing dependent. func TestCacheNotifyChResult(t testing.T, ch <-chan UpdateEvent, expected ...UpdateEvent) { t.Helper() expectLen := len(expected) if expectLen < 1 { panic("asserting nothing") } got := make([]UpdateEvent, 0, expectLen) timeoutCh := time.After(75 * time.Millisecond) OUT: for { select { case result := <-ch: // Ignore age as it's non-deterministic result.Meta.Age = 0 got = append(got, result) if len(got) == expectLen { break OUT } case <-timeoutCh: t.Fatalf("timeout while waiting for result: got %d results on chan, want %d", len(got), expectLen) } } // We already asserted len since you can only get here if we appended enough. // Just check all the results we got are in the expected slice require.ElementsMatch(t, expected, got) } // TestRequest returns a Request that returns the given cache key and index. // The Reset method can be called to reset it for custom usage. func TestRequest(t testing.T, info RequestInfo) *MockRequest { req := &MockRequest{} req.On("CacheInfo").Return(info) return req } // TestType returns a MockType that sets default RegisterOptions. func TestType(t testing.T) *MockType { typ := &MockType{} typ.On("RegisterOptions").Return(RegisterOptions{ SupportsBlocking: true, }) return typ } // TestTypeNonBlocking returns a MockType that returns false to SupportsBlocking. func TestTypeNonBlocking(t testing.T) *MockType { typ := &MockType{} typ.On("RegisterOptions").Return(RegisterOptions{ SupportsBlocking: false, }) return typ } // A bit weird, but we add methods to the auto-generated structs here so that // they don't get clobbered. The helper methods are conveniences. // Static sets a static value to return for a call to Fetch. func (m *MockType) Static(r FetchResult, err error) *mock.Call { return m.Mock.On("Fetch", mock.Anything, mock.Anything).Return(r, err) } func (m *MockRequest) Reset() { m.Mock = mock.Mock{} }