"-f={{if .Stale}}\t{{.ImportPath}}: {{.StaleReason}}{{end}}",
}, targets...)...)
if out != "" {
- os.Setenv("GOCMDDEBUGHASH", "1")
+ os.Setenv("GODEBUG", "gocachehash=1")
for _, target := range []string{"runtime/internal/sys", "cmd/dist", "cmd/link"} {
if strings.Contains(out, target) {
run(goroot, ShowOutput|CheckExit, goBinary, "list", "-f={{.ImportPath}} {{.Stale}}", target)
"os"
"path/filepath"
"strconv"
+ "strings"
)
// An ActionID is a cache action key, the hash of a complete description of a
entrySize = 2 + 1 + hexSize + 1 + hexSize + 1 + 20 + 1
)
+// verify controls whether to run the cache in verify mode.
+// In verify mode, the cache always returns errMissing from Get
+// but then double-checks in Put that the data being written
+// exactly matches any existing entry. This provides an easy
+// way to detect program behavior that would have been different
+// had the cache entry been returned from Get.
+//
+// verify is enabled by setting the environment variable
+// GODEBUG=gocacheverify=1.
+var verify = false
+
+func init() { initEnv() }
+
+func initEnv() {
+ verify = false
+ debugHash = false
+ debug := strings.Split(os.Getenv("GODEBUG"), ",")
+ for _, f := range debug {
+ if f == "gocacheverify=1" {
+ verify = true
+ }
+ if f == "gocachehash=1" {
+ debugHash = true
+ }
+ }
+}
+
// Get looks up the action ID in the cache,
// returning the corresponding output ID and file size, if any.
// Note that finding an output ID does not guarantee that the
// saved file for that output ID is still available.
func (c *Cache) Get(id ActionID) (OutputID, int64, error) {
+ if verify {
+ return OutputID{}, 0, errMissing
+ }
+ return c.get(id)
+}
+
+// get is Get but does not respect verify mode, so that Put can use it.
+func (c *Cache) get(id ActionID) (OutputID, int64, error) {
missing := func() (OutputID, int64, error) {
// TODO: log miss
return OutputID{}, 0, errMissing
// While not ideal, this is also not a correctness problem, so we
// don't make a big deal about it. In particular, we leave the action
// cache entries writable specifically so that they can be overwritten.
+ //
+ // Setting GODEBUG=gocacheverify=1 does make a big deal:
+ // in verify mode we are double-checking that the cache entries
+ // are entirely reproducible. As just noted, this may be unrealistic
+ // in some cases but the check is also useful for shaking out real bugs.
entry := []byte(fmt.Sprintf("v1 %x %x %20d\n", id, out, size))
+ if verify {
+ oldOut, oldSize, err := c.get(id)
+ if err == nil && (oldOut != out || oldSize != size) {
+ fmt.Fprintf(os.Stderr, "go: internal cache error: id=%x changed:\nold: %x %d\nnew: %x %d\n", id, out, size, oldOut, oldSize)
+ // panic to show stack trace, so we can see what code is generating this cache entry.
+ panic("cache verify failed")
+ }
+ }
return ioutil.WriteFile(c.fileName(id, "a"), entry, 0666)
}
"testing"
)
+func init() {
+ verify = false // even if GODEBUG is set
+}
+
func TestBasic(t *testing.T) {
dir, err := ioutil.TempDir("", "cachetest-")
if err != nil {
}
}
+func TestVerifyPanic(t *testing.T) {
+ os.Setenv("GODEBUG", "gocacheverify=1")
+ initEnv()
+ defer func() {
+ os.Unsetenv("GODEBUG")
+ verify = false
+ }()
+
+ if !verify {
+ t.Fatal("initEnv did not set verify")
+ }
+
+ dir, err := ioutil.TempDir("", "cachetest-")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(dir)
+
+ c, err := Open(dir)
+ if err != nil {
+ t.Fatalf("Open: %v", err)
+ }
+
+ id := ActionID(dummyID(1))
+ if err := c.PutBytes(id, []byte("abc")); err != nil {
+ t.Fatal(err)
+ }
+
+ defer func() {
+ if err := recover(); err != nil {
+ t.Log(err)
+ return
+ }
+ }()
+ c.PutBytes(id, []byte("def"))
+ t.Fatal("mismatched Put did not panic in verify mode")
+}
+
func dummyID(x int) [HashSize]byte {
var out [HashSize]byte
binary.LittleEndian.PutUint64(out[:], uint64(x))