@ 261101.7

261101.6 · d81d8f77dde7b2f932e94d374c52339ffce44bfc

Tests follow the code they cover

server_test.go and web_test.go kept their tests when server.go and web.go split. Tests now sit in the test file of the code they drive, and the shared helpers stay in the two original files.

Declarations moved unchanged, as a sorted comparison of every line outside the import blocks confirms. git blame -C -C traces them.

reading declarations

Changes to webdisk_test.go. All files

webdisk_test.go

new file mode 100644@@ -0,0 +1,71 @@+package main++import (+	"net/http"+	"os"+	"path/filepath"+	"strings"+	"syscall"+	"testing"+	"time"+)++// When the cache outgrows its limit, the repo read longest ago goes.+func TestWebTrimsTheCache(t *testing.T) {+	f := newForge(t, nil)+	f.repo(t, "acct/a", map[string]string{"README.md": strings.Repeat("a", 1<<16)})+	f.repo(t, "acct/b", map[string]string{"README.md": strings.Repeat("b", 1<<16)})+	w := testWeb(t, defaultDisk)+	read(t, w, "/"+f.host+"/acct/a/")+	past := time.Now().Add(-time.Hour)+	os.Chtimes(filepath.Join(w.root, f.host, "acct", "a", ".used"), past, past)+	w.limit = w.onDisk()[0].size * 3 / 2+	if rec := read(t, w, "/"+f.host+"/acct/b/"); rec.Code != http.StatusOK {+		t.Fatalf("b: %d\n%s", rec.Code, rec.Body)+	}+	if exists(filepath.Join(w.root, f.host, "acct", "a")) {+		t.Error("a, read longest ago, is still cached")+	}+	if rec := webGet(w, "/"+f.host+"/acct/a/README.md"); rec.Code != http.StatusAccepted {+		t.Errorf("a after it went: %d, want a new read", rec.Code)+	}+	read(t, w, "/"+f.host+"/acct/a/README.md")+	w.limit = 1+	f.repo(t, "acct/c", map[string]string{"README.md": "c\n"})+	rec := read(t, w, "/"+f.host+"/acct/c/")+	if rec.Code != http.StatusBadGateway || !strings.Contains(rec.Body.String(), "over the cache") {+		t.Errorf("a repo over the limit: %d\n%s", rec.Code, rec.Body)+	}+}++// The servers on every address share the cache, so a trim leaves a repo+// whose lock another server holds, eg while it fetches the repo.+func TestWebTrimLeavesALockedRepo(t *testing.T) {+	f := newForge(t, nil)+	f.repo(t, "acct/a", map[string]string{"README.md": strings.Repeat("a", 1<<16)})+	f.repo(t, "acct/b", map[string]string{"README.md": strings.Repeat("b", 1<<16)})+	w := testWeb(t, defaultDisk)+	read(t, w, "/"+f.host+"/acct/a/")+	a := filepath.Join(w.root, f.host, "acct", "a")+	past := time.Now().Add(-time.Hour)+	os.Chtimes(filepath.Join(a, ".used"), past, past)+	w.limit = w.onDisk()[0].size * 3 / 2+	// A lock of its own open file stands for another server's.+	unlock, err := flockDir(a, syscall.LOCK_EX)+	if err != nil {+		t.Fatal(err)+	}+	if rec := read(t, w, "/"+f.host+"/acct/b/"); rec.Code != http.StatusOK {+		t.Fatalf("b: %d\n%s", rec.Code, rec.Body)+	}+	if !exists(filepath.Join(a, ".git", "HEAD")) {+		t.Error("a went while another server held its lock")+	}+	unlock()+	if err := w.trim(""); err != nil {+		t.Fatal(err)+	}+	if exists(a) {+		t.Error("a stayed once its lock was free")+	}+}