ci / ci (pull_request) Successful in 3m20s
`depot status` and `depot get` chose a local depot tree with `--target local`, resolving the root from `DEPOT_DIR`. That split one decision over two flags and left `--target local` valid with no directory anywhere. Adopt the nwsync convention instead: `--out DIR` names the tree on disk in the same breath as choosing it, and `--target` now names remote backends only. The two are mutually exclusive. `--target local` stays as a deprecated alias for `--out $DEPOT_DIR` so existing callers keep working, and `status --source` is now ignored with a warning instead of silently accepted. Also split positionals off by hand in `depot get`, so the documented `depot get <sha> <dest> --out DIR` form parses its flags at all — Go's flag package stops at the first positional. Closes #66
278 lines
8.6 KiB
Go
278 lines
8.6 KiB
Go
package depot
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func testGetenv(vals map[string]string) func(string) string {
|
|
return func(k string) string { return vals[k] }
|
|
}
|
|
|
|
func TestRunUsage(t *testing.T) {
|
|
t.Run("no args", func(t *testing.T) {
|
|
var out, errb bytes.Buffer
|
|
code := Run(nil, &out, &errb, testGetenv(nil))
|
|
if code != 64 {
|
|
t.Fatalf("expected 64, got %d (stderr=%s)", code, errb.String())
|
|
}
|
|
})
|
|
|
|
t.Run("unknown subcommand", func(t *testing.T) {
|
|
var out, errb bytes.Buffer
|
|
code := Run([]string{"bogus"}, &out, &errb, testGetenv(nil))
|
|
if code != 64 {
|
|
t.Fatalf("expected 64, got %d (stderr=%s)", code, errb.String())
|
|
}
|
|
})
|
|
|
|
t.Run("status --target local without DEPOT_DIR", func(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeManifest(t, dir, shaOf("blob-a"), 5)
|
|
var out, errb bytes.Buffer
|
|
code := Run([]string{"status", "--manifests", dir, "--target", "local"}, &out, &errb, testGetenv(nil))
|
|
if code != 64 {
|
|
t.Fatalf("expected 64, got %d (stderr=%s)", code, errb.String())
|
|
}
|
|
if !bytesContains(errb.String(), "DEPOT_DIR") {
|
|
t.Fatalf("expected stderr to mention DEPOT_DIR, got %s", errb.String())
|
|
}
|
|
})
|
|
|
|
t.Run("push --target cdn rejected", func(t *testing.T) {
|
|
dir := t.TempDir()
|
|
var out, errb bytes.Buffer
|
|
code := Run([]string{"push", "--manifests", dir, "--source", dir, "--target", "cdn"}, &out, &errb, testGetenv(nil))
|
|
if code != 64 {
|
|
t.Fatalf("expected 64, got %d (stderr=%s)", code, errb.String())
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestOutFlag(t *testing.T) {
|
|
t.Run("status --out reads the given tree", func(t *testing.T) {
|
|
sha := shaOf("out-blob")
|
|
manifests := t.TempDir()
|
|
writeManifest(t, manifests, sha, 8)
|
|
depotDir := t.TempDir()
|
|
writeBlob(t, depotDir, sha, "out-blob")
|
|
|
|
var out, errb bytes.Buffer
|
|
code := Run([]string{"status", "--manifests", manifests, "--out", depotDir}, &out, &errb, testGetenv(nil))
|
|
if code != 0 {
|
|
t.Fatalf("expected 0, got %d (stdout=%s stderr=%s)", code, out.String(), errb.String())
|
|
}
|
|
if !bytesContains(out.String(), "present=1") {
|
|
t.Fatalf("expected present=1, got %s", out.String())
|
|
}
|
|
})
|
|
|
|
t.Run("get --out fetches from the given tree", func(t *testing.T) {
|
|
sha := shaOf("get-blob")
|
|
depotDir := t.TempDir()
|
|
writeBlob(t, depotDir, sha, "get-blob")
|
|
dest := filepath.Join(t.TempDir(), "fetched")
|
|
|
|
var out, errb bytes.Buffer
|
|
code := Run([]string{"get", sha, dest, "--out", depotDir}, &out, &errb, testGetenv(nil))
|
|
if code != 0 {
|
|
t.Fatalf("expected 0, got %d (stderr=%s)", code, errb.String())
|
|
}
|
|
got, err := os.ReadFile(dest)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(got) != "get-blob" {
|
|
t.Fatalf("got %q", got)
|
|
}
|
|
})
|
|
|
|
t.Run("--out with any --target is rejected", func(t *testing.T) {
|
|
for _, target := range []string{"bunny", "cdn", "local"} {
|
|
var out, errb bytes.Buffer
|
|
code := Run([]string{"status", "--manifests", t.TempDir(), "--out", t.TempDir(), "--target", target}, &out, &errb, testGetenv(nil))
|
|
if code != 64 {
|
|
t.Fatalf("--target %s: expected 64, got %d (stderr=%s)", target, code, errb.String())
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("get accepts flags before the positionals", func(t *testing.T) {
|
|
sha := shaOf("flags-first-blob")
|
|
depotDir := t.TempDir()
|
|
writeBlob(t, depotDir, sha, "flags-first-blob")
|
|
dest := filepath.Join(t.TempDir(), "fetched")
|
|
|
|
var out, errb bytes.Buffer
|
|
code := Run([]string{"get", "--out", depotDir, sha, dest}, &out, &errb, testGetenv(nil))
|
|
if code != 0 {
|
|
t.Fatalf("expected 0, got %d (stderr=%s)", code, errb.String())
|
|
}
|
|
})
|
|
|
|
t.Run("neither --out nor --target is rejected", func(t *testing.T) {
|
|
var out, errb bytes.Buffer
|
|
code := Run([]string{"status", "--manifests", t.TempDir()}, &out, &errb, testGetenv(nil))
|
|
if code != 64 {
|
|
t.Fatalf("expected 64, got %d (stderr=%s)", code, errb.String())
|
|
}
|
|
})
|
|
|
|
t.Run("--target local still works as a hidden alias", func(t *testing.T) {
|
|
sha := shaOf("alias-blob")
|
|
manifests := t.TempDir()
|
|
writeManifest(t, manifests, sha, 11)
|
|
depotDir := t.TempDir()
|
|
writeBlob(t, depotDir, sha, "alias-blob")
|
|
|
|
var out, errb bytes.Buffer
|
|
code := Run([]string{"status", "--manifests", manifests, "--target", "local"}, &out, &errb,
|
|
testGetenv(map[string]string{"DEPOT_DIR": depotDir}))
|
|
if code != 0 {
|
|
t.Fatalf("expected 0, got %d (stdout=%s stderr=%s)", code, out.String(), errb.String())
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestGetInvalidSHA(t *testing.T) {
|
|
var out, errb bytes.Buffer
|
|
dir := t.TempDir()
|
|
code := Run([]string{"get", "not-a-sha", dir + "/dest", "--target", "local"}, &out, &errb, testGetenv(map[string]string{"DEPOT_DIR": dir}))
|
|
if code != 64 {
|
|
t.Fatalf("expected 64, got %d (stderr=%s)", code, errb.String())
|
|
}
|
|
}
|
|
|
|
func TestNoKeyStatusBunnyFailsClosedNoStdin(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeManifest(t, dir, shaOf("blob-a"), 5)
|
|
|
|
var out, errb bytes.Buffer
|
|
code := Run([]string{"status", "--manifests", dir, "--target", "bunny"}, &out, &errb, testGetenv(nil))
|
|
if code != 70 {
|
|
t.Fatalf("expected 70, got %d (stdout=%s stderr=%s)", code, out.String(), errb.String())
|
|
}
|
|
if !bytesContains(errb.String(), "BUNNY_STORAGE_HOST") {
|
|
t.Fatalf("expected stderr to mention BUNNY_STORAGE_HOST, got %s", errb.String())
|
|
}
|
|
}
|
|
|
|
func TestStatusExitCodes(t *testing.T) {
|
|
t.Run("drift", func(t *testing.T) {
|
|
f := newFakeBunny()
|
|
srv := httptest.NewServer(f.handler())
|
|
defer srv.Close()
|
|
|
|
sha := shaOf("missing-blob")
|
|
dir := t.TempDir()
|
|
writeManifest(t, dir, sha, 5)
|
|
|
|
var out, errb bytes.Buffer
|
|
code := Run([]string{"status", "--manifests", dir, "--target", "bunny"}, &out, &errb,
|
|
testGetenv(map[string]string{
|
|
"BUNNY_STORAGE_HOST": hostPort(srv),
|
|
"BUNNY_STORAGE_READ_PASSWORD": "readkey",
|
|
}))
|
|
if code != 1 {
|
|
t.Fatalf("expected 1, got %d (stdout=%s stderr=%s)", code, out.String(), errb.String())
|
|
}
|
|
if !bytesContains(out.String(), "missing="+"1") {
|
|
t.Fatalf("expected missing=1 in output, got %s", out.String())
|
|
}
|
|
if !bytesContains(out.String(), "missing "+sha) {
|
|
t.Fatalf("expected missing line for sha, got %s", out.String())
|
|
}
|
|
})
|
|
|
|
t.Run("unconfirmed-only", func(t *testing.T) {
|
|
stubProbeSleep(t)
|
|
f := newFakeBunny()
|
|
sha := shaOf("flaky-blob")
|
|
f.statusFor[sha] = 428
|
|
srv := httptest.NewServer(f.handler())
|
|
defer srv.Close()
|
|
|
|
dir := t.TempDir()
|
|
writeManifest(t, dir, sha, 5)
|
|
|
|
var out, errb bytes.Buffer
|
|
code := Run([]string{"status", "--manifests", dir, "--target", "bunny"}, &out, &errb,
|
|
testGetenv(map[string]string{
|
|
"BUNNY_STORAGE_HOST": hostPort(srv),
|
|
"BUNNY_STORAGE_READ_PASSWORD": "readkey",
|
|
"DEPOT_CONFIRM_RETRIES": "1",
|
|
}))
|
|
if code != 2 {
|
|
t.Fatalf("expected 2, got %d (stdout=%s stderr=%s)", code, out.String(), errb.String())
|
|
}
|
|
if !bytesContains(out.String(), "unconfirmed="+"1") {
|
|
t.Fatalf("expected unconfirmed=1 in output, got %s", out.String())
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPushTransportErrorCountsFailed(t *testing.T) {
|
|
// Probes 404 (blob absent), PUTs 500 (transport-level upload failure).
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == http.MethodPut {
|
|
w.WriteHeader(500)
|
|
return
|
|
}
|
|
w.WriteHeader(404)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
sha := shaOf("push-fail-blob")
|
|
manifestsDir := t.TempDir()
|
|
writeManifest(t, manifestsDir, sha, 14)
|
|
sourceDir := t.TempDir()
|
|
blobPath := filepath.Join(sourceDir, BlobKey(sha))
|
|
if err := os.MkdirAll(filepath.Dir(blobPath), 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(blobPath, []byte("push-fail-blob"), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var out, errb bytes.Buffer
|
|
code := Run([]string{"push", "--manifests", manifestsDir, "--source", sourceDir, "--target", "bunny"}, &out, &errb,
|
|
testGetenv(map[string]string{
|
|
"BUNNY_STORAGE_HOST": hostPort(srv),
|
|
"BUNNY_STORAGE_PASSWORD": "writekey",
|
|
}))
|
|
if code != 70 {
|
|
t.Fatalf("expected 70, got %d (stdout=%s stderr=%s)", code, out.String(), errb.String())
|
|
}
|
|
if !bytesContains(out.String(), "uploaded=0 failed=1") {
|
|
t.Fatalf("expected uploaded=0 failed=1, got %s", out.String())
|
|
}
|
|
}
|
|
|
|
func bytesContains(s, substr string) bool {
|
|
return bytes.Contains([]byte(s), []byte(substr))
|
|
}
|
|
|
|
func writeBlob(t *testing.T, root, sha, content string) {
|
|
t.Helper()
|
|
path := filepath.Join(root, BlobKey(sha))
|
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func writeManifest(t *testing.T, dir, sha string, size int64) {
|
|
t.Helper()
|
|
content := fmt.Sprintf("assets:\n - path: foo\n sha256: %s\n size: %d\n", sha, size)
|
|
if err := os.WriteFile(filepath.Join(dir, "manifest.yml"), []byte(content), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|