2019-05-10 14:36:20 -06:00
|
|
|
// 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 (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"runtime"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"golang.org/x/tools/internal/memoize"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestStore(t *testing.T) {
|
|
|
|
ctx := context.Background()
|
|
|
|
s := &memoize.Store{}
|
|
|
|
logBuffer := &bytes.Buffer{}
|
2019-07-18 11:32:40 -06:00
|
|
|
ctx = context.WithValue(ctx, "logger", logBuffer)
|
2019-06-11 19:16:35 -06:00
|
|
|
|
|
|
|
// These tests check the behavior of the Bind and Get functions.
|
|
|
|
// They confirm that the functions only ever run once for a given value.
|
|
|
|
for _, test := range []struct {
|
|
|
|
name, key, want string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "nothing",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "get 1",
|
|
|
|
key: "_1",
|
|
|
|
want: `
|
2019-05-10 14:36:20 -06:00
|
|
|
start @1
|
|
|
|
simple a = A
|
|
|
|
simple b = B
|
|
|
|
simple c = C
|
|
|
|
end @1 = A B C
|
2019-06-11 19:16:35 -06:00
|
|
|
`[1:],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "redo 1",
|
|
|
|
key: "_1",
|
|
|
|
want: ``,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "get 2",
|
|
|
|
key: "_2",
|
|
|
|
want: `
|
2019-05-10 14:36:20 -06:00
|
|
|
start @2
|
|
|
|
simple d = D
|
|
|
|
simple e = E
|
|
|
|
simple f = F
|
|
|
|
end @2 = D E F
|
2019-06-11 19:16:35 -06:00
|
|
|
`[1:],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "redo 2",
|
|
|
|
key: "_2",
|
|
|
|
want: ``,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "get 3",
|
|
|
|
key: "_3",
|
|
|
|
want: `
|
2019-05-10 14:36:20 -06:00
|
|
|
start @3
|
|
|
|
end @3 = @1[ A B C] @2[ D E F]
|
2019-06-11 19:16:35 -06:00
|
|
|
`[1:],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "get 4",
|
|
|
|
key: "_4",
|
|
|
|
want: `
|
2019-05-10 14:36:20 -06:00
|
|
|
start @3
|
|
|
|
simple g = G
|
|
|
|
error ERR = fail
|
|
|
|
simple h = H
|
|
|
|
end @3 = G !fail H
|
2019-06-11 19:16:35 -06:00
|
|
|
`[1:],
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
s.Bind(test.key, generate(s, test.key)).Get(ctx)
|
|
|
|
got := logBuffer.String()
|
|
|
|
if got != test.want {
|
|
|
|
t.Errorf("at %q expected:\n%v\ngot:\n%s", test.name, test.want, got)
|
|
|
|
}
|
|
|
|
logBuffer.Reset()
|
|
|
|
}
|
|
|
|
|
|
|
|
// This test checks that values are garbage collected and removed from the
|
|
|
|
// store when they are no longer used.
|
2019-05-10 14:36:20 -06:00
|
|
|
|
2019-06-11 19:16:35 -06:00
|
|
|
pinned := []string{"b", "_1", "_3"} // keys to pin in memory
|
|
|
|
unpinned := []string{"a", "c", "d", "_2", "_4"} // keys to garbage collect
|
|
|
|
|
|
|
|
// Handles maintain a strong reference to their values.
|
|
|
|
// By generating handles for the pinned keys and keeping the pins alive in memory,
|
|
|
|
// we ensure these keys stay cached.
|
2019-05-10 14:36:20 -06:00
|
|
|
var pins []*memoize.Handle
|
|
|
|
for _, key := range pinned {
|
|
|
|
h := s.Bind(key, generate(s, key))
|
|
|
|
h.Get(ctx)
|
|
|
|
pins = append(pins, h)
|
|
|
|
}
|
|
|
|
|
2019-06-11 19:16:35 -06:00
|
|
|
// Force the garbage collector to run.
|
2019-05-10 14:36:20 -06:00
|
|
|
runAllFinalizers(t)
|
|
|
|
|
2019-06-11 19:16:35 -06:00
|
|
|
// Confirm our expectation that pinned values should remain cached,
|
|
|
|
// and unpinned values should be garbage collected.
|
2019-05-10 14:36:20 -06:00
|
|
|
for _, k := range pinned {
|
2019-07-18 11:32:40 -06:00
|
|
|
if v := s.Find(k); v == nil {
|
2019-06-11 19:16:35 -06:00
|
|
|
t.Errorf("pinned value %q was nil", k)
|
2019-05-10 14:36:20 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, k := range unpinned {
|
2019-07-18 11:32:40 -06:00
|
|
|
if v := s.Find(k); v != nil {
|
|
|
|
t.Errorf("unpinned value %q should be nil, was %v", k, v)
|
2019-05-10 14:36:20 -06:00
|
|
|
}
|
|
|
|
}
|
2019-06-11 19:16:35 -06:00
|
|
|
|
|
|
|
// This forces the pins to stay alive until this point in the function.
|
2019-05-10 14:36:20 -06:00
|
|
|
runtime.KeepAlive(pins)
|
|
|
|
}
|
|
|
|
|
|
|
|
func runAllFinalizers(t *testing.T) {
|
2019-06-11 19:16:35 -06:00
|
|
|
// The following is very tricky, so be very when careful changing it.
|
|
|
|
// It relies on behavior of finalizers that is not guaranteed.
|
|
|
|
|
|
|
|
// First, run the GC to queue the finalizers.
|
2019-05-10 14:36:20 -06:00
|
|
|
runtime.GC()
|
2019-06-11 19:16:35 -06:00
|
|
|
|
|
|
|
// wait is used to signal that the finalizers are all done.
|
2019-05-10 14:36:20 -06:00
|
|
|
wait := make(chan struct{})
|
2019-06-11 19:16:35 -06:00
|
|
|
|
|
|
|
// Register a finalizer against an immediately collectible object.
|
|
|
|
//
|
|
|
|
// The finalizer will signal on the wait channel once it executes,
|
|
|
|
// and it was the most recently registered finalizer,
|
|
|
|
// so the wait channel will be closed when all of the finalizers have run.
|
2019-05-10 14:36:20 -06:00
|
|
|
runtime.SetFinalizer(&struct{ s string }{"obj"}, func(_ interface{}) { close(wait) })
|
2019-06-11 19:16:35 -06:00
|
|
|
|
|
|
|
// Now, run the GC again to pick up the tracker object above.
|
2019-05-10 14:36:20 -06:00
|
|
|
runtime.GC()
|
2019-06-11 19:16:35 -06:00
|
|
|
|
|
|
|
// Wait for the finalizers to run or a timeout.
|
2019-05-10 14:36:20 -06:00
|
|
|
select {
|
|
|
|
case <-wait:
|
|
|
|
case <-time.Tick(time.Second):
|
2019-06-11 19:16:35 -06:00
|
|
|
t.Fatalf("finalizers had not run after 1 second")
|
2019-05-10 14:36:20 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type stringOrError struct {
|
|
|
|
value string
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *stringOrError) String() string {
|
|
|
|
if v.err != nil {
|
|
|
|
return v.err.Error()
|
|
|
|
}
|
|
|
|
return v.value
|
|
|
|
}
|
|
|
|
|
|
|
|
func asValue(v interface{}) *stringOrError {
|
|
|
|
if v == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return v.(*stringOrError)
|
|
|
|
}
|
|
|
|
|
|
|
|
func generate(s *memoize.Store, key interface{}) memoize.Function {
|
|
|
|
return func(ctx context.Context) interface{} {
|
|
|
|
name := key.(string)
|
|
|
|
switch name {
|
2019-06-11 19:16:35 -06:00
|
|
|
case "":
|
|
|
|
return nil
|
2019-05-10 14:36:20 -06:00
|
|
|
case "err":
|
|
|
|
return logGenerator(ctx, s, "ERR", "", fmt.Errorf("fail"))
|
|
|
|
case "_1":
|
|
|
|
return joinValues(ctx, s, "@1", "a", "b", "c")
|
|
|
|
case "_2":
|
|
|
|
return joinValues(ctx, s, "@2", "d", "e", "f")
|
|
|
|
case "_3":
|
|
|
|
return joinValues(ctx, s, "@3", "_1", "_2")
|
|
|
|
case "_4":
|
|
|
|
return joinValues(ctx, s, "@3", "g", "err", "h")
|
|
|
|
default:
|
|
|
|
return logGenerator(ctx, s, name, strings.ToUpper(name), nil)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-11 19:16:35 -06:00
|
|
|
// logGenerator generates a *stringOrError value, while logging to the store's logger.
|
2019-05-10 14:36:20 -06:00
|
|
|
func logGenerator(ctx context.Context, s *memoize.Store, name string, v string, err error) *stringOrError {
|
2019-07-18 11:32:40 -06:00
|
|
|
// Get the logger from the context.
|
|
|
|
w := ctx.Value("logger").(io.Writer)
|
2019-06-11 19:16:35 -06:00
|
|
|
|
2019-05-10 14:36:20 -06:00
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(w, "error %v = %v\n", name, err)
|
|
|
|
} else {
|
|
|
|
fmt.Fprintf(w, "simple %v = %v\n", name, v)
|
|
|
|
}
|
|
|
|
return &stringOrError{value: v, err: err}
|
|
|
|
}
|
|
|
|
|
2019-06-11 19:16:35 -06:00
|
|
|
// joinValues binds a list of keys to their values, while logging to the store's logger.
|
2019-05-10 14:36:20 -06:00
|
|
|
func joinValues(ctx context.Context, s *memoize.Store, name string, keys ...string) *stringOrError {
|
2019-07-18 11:32:40 -06:00
|
|
|
// Get the logger from the context.
|
|
|
|
w := ctx.Value("logger").(io.Writer)
|
2019-06-11 19:16:35 -06:00
|
|
|
|
2019-05-10 14:36:20 -06:00
|
|
|
fmt.Fprintf(w, "start %v\n", name)
|
|
|
|
value := ""
|
|
|
|
for _, key := range keys {
|
|
|
|
v := asValue(s.Bind(key, generate(s, key)).Get(ctx))
|
|
|
|
if v == nil {
|
|
|
|
value = value + " <nil>"
|
|
|
|
} else if v.err != nil {
|
|
|
|
value = value + " !" + v.err.Error()
|
|
|
|
} else {
|
|
|
|
value = value + " " + v.value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fmt.Fprintf(w, "end %v = %v\n", name, value)
|
|
|
|
return &stringOrError{value: fmt.Sprintf("%s[%v]", name, value)}
|
|
|
|
}
|