Files
2026-08-15 07:13:00 +00:00

116 lines
3.1 KiB
Go

//go:build unix
package cas
import (
"os"
"path/filepath"
"syscall"
"testing"
)
func nlink(t *testing.T, path string) uint64 {
t.Helper()
fi, err := os.Stat(path)
if err != nil {
t.Fatal(err)
}
st, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
t.Skip("no stat_t on this platform")
}
return uint64(st.Nlink)
}
// The deduplication claim, stated as a filesystem fact: a deployed file and its
// blob are the same inode, so a second deployment of the same content costs a
// directory entry and nothing more.
func TestLinkIntoSharesTheInode(t *testing.T) {
s, deployDir := newStore(t, LinkHard)
const content = "shared across deployments\n"
d := put(t, s, content)
if got := nlink(t, s.Path(d)); got != 1 {
t.Fatalf("a fresh blob has %d links, want 1", got)
}
first := filepath.Join(deployDir, "dpl_one")
second := filepath.Join(deployDir, "dpl_two")
for _, dir := range []string{first, second} {
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
if err := s.LinkInto(d, dir, "index.html"); err != nil {
t.Fatal(err)
}
}
if got := nlink(t, s.Path(d)); got != 3 {
t.Errorf("blob has %d links after two deployments, want 3 (blob + 2)", got)
}
blob, err := os.Stat(s.Path(d))
if err != nil {
t.Fatal(err)
}
for _, dir := range []string{first, second} {
deployed, err := os.Stat(filepath.Join(dir, "index.html"))
if err != nil {
t.Fatal(err)
}
if !os.SameFile(blob, deployed) {
t.Errorf("%s is a copy, not a hardlink to the blob", dir)
}
if got, err := os.ReadFile(filepath.Join(dir, "index.html")); err != nil || string(got) != content {
t.Errorf("%s: content = %q, %v", dir, got, err)
}
}
// Removing a deployment tree must not take the blob with it: the other
// deployment still references it, and so may other projects.
if err := os.RemoveAll(first); err != nil {
t.Fatal(err)
}
if got := nlink(t, s.Path(d)); got != 2 {
t.Errorf("blob has %d links after one tree was removed, want 2", got)
}
}
// The mode is what stops a deployed file from being written through into the
// blob every other project shares.
func TestDeployedFilesAreReadOnly(t *testing.T) {
for _, mode := range []LinkMode{LinkHard, LinkCopy} {
t.Run(string(mode), func(t *testing.T) {
s, deployDir := newStore(t, mode)
d := put(t, s, "content")
if err := os.MkdirAll(deployDir, 0o755); err != nil {
t.Fatal(err)
}
if err := s.LinkInto(d, deployDir, "index.html"); err != nil {
t.Fatal(err)
}
fi, err := os.Stat(filepath.Join(deployDir, "index.html"))
if err != nil {
t.Fatal(err)
}
if perm := fi.Mode().Perm(); perm != blobMode {
t.Errorf("deployed file mode = %04o, want %04o", perm, blobMode)
}
})
}
}
// A copy must be a copy: writing through it may not reach the blob.
func TestCopyModeDoesNotShareTheInode(t *testing.T) {
s, deployDir := newStore(t, LinkCopy)
d := put(t, s, "content")
if err := os.MkdirAll(deployDir, 0o755); err != nil {
t.Fatal(err)
}
if err := s.LinkInto(d, deployDir, "index.html"); err != nil {
t.Fatal(err)
}
if got := nlink(t, s.Path(d)); got != 1 {
t.Errorf("blob has %d links in copy mode, want 1", got)
}
}