master
go 105 lines 3.62 KB
Raw
1 //go:build (linux || darwin || freebsd) && !nofuse
2
3 package writable
4
5 import (
6 "testing"
7
8 "github.com/hanwen/go-fuse/v2/fuse"
9 dag "github.com/ipfs/boxo/ipld/merkledag"
10 fusemnt "github.com/ipfs/kubo/fuse/mount"
11 )
12
13 // TestNewDirNormalizesBlksize verifies that callers who don't plumb
14 // Import.UnixFSChunker through (e.g. test-only mounts) get the FUSE
15 // default so stat still advertises a usable st_blksize.
16 func TestNewDirNormalizesBlksize(t *testing.T) {
17 t.Run("zero falls back to DefaultBlksize", func(t *testing.T) {
18 cfg := &Config{DAG: dag.NewDAGService(nil)}
19 NewDir(nil, cfg)
20 if cfg.Blksize != fusemnt.DefaultBlksize {
21 t.Fatalf("Blksize = %d, want DefaultBlksize (%d)",
22 cfg.Blksize, fusemnt.DefaultBlksize)
23 }
24 })
25
26 t.Run("explicit value passes through", func(t *testing.T) {
27 cfg := &Config{DAG: dag.NewDAGService(nil), Blksize: 65536}
28 NewDir(nil, cfg)
29 if cfg.Blksize != 65536 {
30 t.Fatalf("Blksize = %d, want 65536", cfg.Blksize)
31 }
32 })
33 }
34
35 // TestSymlinkSetattrChmodNoError verifies that Setattr on a symlink
36 // with only a mode change is silently accepted. POSIX symlinks have no
37 // meaningful permission bits (access control uses the target's mode),
38 // so handlers must not return an error when the kernel forwards a
39 // chmod-on-symlink request (e.g. via BSD lchmod or fchmodat with
40 // AT_SYMLINK_NOFOLLOW). Tools like rsync depend on this contract.
41 //
42 // This is a unit test rather than an integration test because Linux
43 // usually rejects fchmodat(AT_SYMLINK_NOFOLLOW) at the VFS layer with
44 // EOPNOTSUPP and never forwards it to the FUSE filesystem, so a
45 // userspace test would not actually exercise this code path.
46 func TestSymlinkSetattrChmodNoError(t *testing.T) {
47 // MFSFile is nil: Setattr must still succeed without dereferencing
48 // it. StoreMode is true to confirm that even when persistence is
49 // enabled, mode changes on symlinks are silently dropped.
50 s := &Symlink{
51 Target: "/some/target",
52 Cfg: &Config{StoreMode: true},
53 }
54
55 in := &fuse.SetAttrIn{}
56 in.Valid = fuse.FATTR_MODE
57 in.Mode = 0o600
58
59 out := &fuse.AttrOut{}
60 if errno := s.Setattr(t.Context(), nil, in, out); errno != 0 {
61 t.Fatalf("Symlink.Setattr returned errno %v, want 0", errno)
62 }
63
64 // fillAttr must report the POSIX symlink mode (0o777), not the
65 // caller-supplied value, because the request is not stored.
66 if got := out.Attr.Mode & 0o777; got != 0o777 {
67 t.Fatalf("Symlink mode = 0o%o, want 0o777", got)
68 }
69 }
70
71 // TestStatfsReportsSpace verifies that Dir.Statfs proxies the
72 // disk-space statistics of the repo's backing filesystem, and that an
73 // empty RepoPath produces zeroed (but successful) results.
74 func TestStatfsReportsSpace(t *testing.T) {
75 t.Run("matches repo filesystem", func(t *testing.T) {
76 dir := t.TempDir()
77 d := &Dir{Cfg: &Config{RepoPath: dir}}
78 out := &fuse.StatfsOut{}
79 if errno := d.Statfs(t.Context(), out); errno != 0 {
80 t.Fatalf("Statfs returned errno %v, want 0", errno)
81 }
82
83 // Verify we got real filesystem data (non-zero) and that
84 // free blocks don't exceed total blocks. Exact comparison
85 // against a second syscall.Statfs call is racy because CI
86 // writes can change block counts between the two calls.
87 if out.Blocks == 0 {
88 t.Fatal("Blocks = 0, expected non-zero for a real filesystem")
89 }
90 if out.Bfree > out.Blocks {
91 t.Fatalf("Bfree (%d) > Blocks (%d)", out.Bfree, out.Blocks)
92 }
93 })
94
95 t.Run("empty repo path", func(t *testing.T) {
96 d := &Dir{Cfg: &Config{}}
97 out := &fuse.StatfsOut{}
98 if errno := d.Statfs(t.Context(), out); errno != 0 {
99 t.Fatalf("Statfs returned errno %v, want 0", errno)
100 }
101 if out.Blocks != 0 {
102 t.Fatalf("expected zeroed Blocks when RepoPath is empty, got %d", out.Blocks)
103 }
104 })
105 }