| // Copyright 2019 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package memoize_test |
| |
| import ( |
| "context" |
| "sync" |
| "testing" |
| "time" |
| |
| "golang.org/x/tools/internal/memoize" |
| ) |
| |
| func TestGet(t *testing.T) { |
| var store memoize.Store |
| |
| evaled := 0 |
| |
| h, release := store.Promise("key", func(context.Context, interface{}) interface{} { |
| evaled++ |
| return "res" |
| }) |
| defer release() |
| expectGet(t, h, "res") |
| expectGet(t, h, "res") |
| if evaled != 1 { |
| t.Errorf("got %v calls to function, wanted 1", evaled) |
| } |
| } |
| |
| func expectGet(t *testing.T, h *memoize.Promise, wantV interface{}) { |
| t.Helper() |
| gotV, gotErr := h.Get(context.Background(), nil) |
| if gotV != wantV || gotErr != nil { |
| t.Fatalf("Get() = %v, %v, wanted %v, nil", gotV, gotErr, wantV) |
| } |
| } |
| |
| func TestNewPromise(t *testing.T) { |
| calls := 0 |
| f := func(context.Context, interface{}) interface{} { |
| calls++ |
| return calls |
| } |
| |
| // All calls to Get on the same promise return the same result. |
| p1 := memoize.NewPromise("debug", f) |
| expectGet(t, p1, 1) |
| expectGet(t, p1, 1) |
| |
| // A new promise calls the function again. |
| p2 := memoize.NewPromise("debug", f) |
| expectGet(t, p2, 2) |
| expectGet(t, p2, 2) |
| |
| // The original promise is unchanged. |
| expectGet(t, p1, 1) |
| } |
| |
| func TestStoredPromiseRefCounting(t *testing.T) { |
| var store memoize.Store |
| v1 := false |
| v2 := false |
| p1, release1 := store.Promise("key1", func(context.Context, interface{}) interface{} { |
| return &v1 |
| }) |
| p2, release2 := store.Promise("key2", func(context.Context, interface{}) interface{} { |
| return &v2 |
| }) |
| expectGet(t, p1, &v1) |
| expectGet(t, p2, &v2) |
| |
| expectGet(t, p1, &v1) |
| expectGet(t, p2, &v2) |
| |
| p2Copy, release2Copy := store.Promise("key2", func(context.Context, interface{}) interface{} { |
| return &v1 |
| }) |
| if p2 != p2Copy { |
| t.Error("Promise returned a new value while old is not destroyed yet") |
| } |
| expectGet(t, p2Copy, &v2) |
| |
| release2() |
| if got, want := v2, false; got != want { |
| t.Errorf("after destroying first v2 ref, got %v, want %v", got, want) |
| } |
| release2Copy() |
| if got, want := v1, false; got != want { |
| t.Errorf("after destroying v2, got %v, want %v", got, want) |
| } |
| release1() |
| |
| p2Copy, release2Copy = store.Promise("key2", func(context.Context, interface{}) interface{} { |
| return &v2 |
| }) |
| if p2 == p2Copy { |
| t.Error("Promise returned previously destroyed value") |
| } |
| release2Copy() |
| } |
| |
| func TestPromiseDestroyedWhileRunning(t *testing.T) { |
| // Test that calls to Promise.Get return even if the promise is destroyed while running. |
| |
| var store memoize.Store |
| c := make(chan int) |
| |
| var v int |
| h, release := store.Promise("key", func(ctx context.Context, _ interface{}) interface{} { |
| <-c |
| <-c |
| if err := ctx.Err(); err != nil { |
| t.Errorf("ctx.Err() = %v, want nil", err) |
| } |
| return &v |
| }) |
| |
| ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) // arbitrary timeout; may be removed if it causes flakes |
| defer cancel() |
| |
| var wg sync.WaitGroup |
| wg.Add(1) |
| var got interface{} |
| var err error |
| go func() { |
| got, err = h.Get(ctx, nil) |
| wg.Done() |
| }() |
| |
| c <- 0 // send once to enter the promise function |
| release() // release before the promise function returns |
| c <- 0 // let the promise function proceed |
| |
| wg.Wait() |
| |
| if err != nil { |
| t.Errorf("Get() failed: %v", err) |
| } |
| if got != &v { |
| t.Errorf("Get() = %v, want %v", got, v) |
| } |
| } |
| |
| func TestDoubleReleasePanics(t *testing.T) { |
| var store memoize.Store |
| _, release := store.Promise("key", func(ctx context.Context, _ interface{}) interface{} { return 0 }) |
| |
| panicked := false |
| |
| func() { |
| defer func() { |
| if recover() != nil { |
| panicked = true |
| } |
| }() |
| release() |
| release() |
| }() |
| |
| if !panicked { |
| t.Errorf("calling release() twice did not panic") |
| } |
| } |