package buildstep import ( "strings" "testing" ) // The shared warm caches are read under a throwaway writable layer (novox/hq ADR 0448; issue 1309): a candidate's step // writes into them as a build does, and nothing it writes outlives it or reaches a later build; only a trunk build // writes them. func TestACandidatesStepWritesTheCachesOnlyIntoAThrowawayLayer(t *testing.T) { fixedSystem(t) iso := Isolation{Workspace: "/w", Caches: []string{"/var/cache/go-build", "/var/cache/npm"}} argv, err := iso.Command("go", "test") if err != nil { t.Fatal(err) } for _, c := range iso.Caches { if !holds(argv, "--overlay-src", c, "--tmp-overlay", c) { t.Errorf("the cache %s is not under a throwaway layer: %v", c, argv) } if holds(argv, "--bind", c, c) { t.Errorf("a candidate's step writes the cache %s: %v", c, argv) } } iso.WriteCaches = true argv, _ = iso.Command("go", "build") for _, c := range iso.Caches { if !holds(argv, "--bind", c, c) || holds(argv, "--tmp-overlay", c) { t.Errorf("a trunk build does not write the cache %s: %v", c, argv) } } // 1921/S2: with a cache layer, a command reads the ask's kept layer above the shared cache and writes into a layer // on disk of the ask's own, never into the shared cache and never into a throwaway tmpfs. // Only the layered caches (the build cache) are; the module cache keeps its throwaway layer. iso = Isolation{Workspace: "/w/ws1", Caches: []string{"/cache/gomod", "/cache/gobuild"}, CacheLayer: "/w/ws1.cachelayer", LayerCaches: []string{"/cache/gobuild"}} argv, err = iso.Command("go", "vet") if err != nil { t.Fatal(err) } if !holds(argv, "--overlay-src", "/cache/gobuild", "--overlay-src", keptLayer(iso.CacheLayer, 1), "--overlay") { t.Errorf("the build cache is not read beneath the ask's kept layer: %v", argv) } if up := overlayOf(argv, "/cache/gobuild"); up == "" || !strings.HasPrefix(up, "/w/ws1.cachelayer/") { t.Errorf("the build cache is written into %q, not a layer of the ask's own: %v", up, argv) } if !holds(argv, "--overlay-src", "/cache/gomod", "--tmp-overlay", "/cache/gomod") || overlayOf(argv, "/cache/gomod") != "" { t.Errorf("the module cache is not under a throwaway layer: %v", argv) } // A layer a command could reach is refused: in the workspace (bound read-write), in a writable directory, in a // cache, or above one of them; so is a relative one, and a layered cache that is no cache. for _, bad := range []Isolation{ {Workspace: "/w/ws1", CacheLayer: "/w/ws1/cachelayer"}, {Workspace: "/w/ws1", CacheLayer: "/w"}, {Workspace: "/w/ws1", Writable: []string{"/out"}, CacheLayer: "/out/layer"}, {Workspace: "/w/ws1", Caches: []string{"/cache/gobuild"}, CacheLayer: "/cache/gobuild/layer"}, {Workspace: "/w/ws1", CacheLayer: "cachelayer"}, {Workspace: "/w/ws1", Caches: []string{"/cache/gobuild"}, CacheLayer: "/w/ws1.cachelayer", LayerCaches: []string{"/cache/other"}}, } { if _, err := bad.Command("go"); err == nil { t.Errorf("the cache layer %s of %+v was taken", bad.CacheLayer, bad) } } } // overlayOf answers the upper directory a command line gives the cache c, "" when it gives none. func overlayOf(argv []string, c string) string { for i := 0; i+3 < len(argv); i++ { if argv[i] == "--overlay" && argv[i+3] == c { return argv[i+1] } } return "" }