master
go 100 lines 3.3 KB
Raw
1 //go:build (linux || darwin || freebsd) && !nofuse
2
3 // Package fusetest provides test helpers shared across FUSE test packages.
4 package fusetest
5
6 import (
7 "os"
8 "syscall"
9 "testing"
10
11 "github.com/hanwen/go-fuse/v2/fs"
12 "github.com/stretchr/testify/require"
13 )
14
15 // SkipUnlessFUSE skips the test when FUSE is not available.
16 //
17 // Decision order:
18 // 1. TEST_FUSE=0 (or legacy TEST_NO_FUSE=1) → skip
19 // 2. TEST_FUSE=1 → run (CI should set this after installing fuse3)
20 // 3. Neither set → auto-detect based on platform and fusermount in PATH;
21 // skip with a helpful message if not found
22 func SkipUnlessFUSE(t *testing.T) {
23 t.Helper()
24
25 if v := fuseFlagFromEnv(); v != "" {
26 if v == "0" {
27 t.Skip("FUSE tests disabled (TEST_FUSE=0)")
28 }
29 return // TEST_FUSE=1, run unconditionally
30 }
31
32 fuseAvailable(t) // skips with a helpful message if not available
33 }
34
35 // TestMount mounts root at a temp directory with the given options and
36 // registers an unmount cleanup. Returns the mount directory path.
37 // Callers set mount-specific options (timeouts, MaxReadAhead, etc.)
38 // before calling; this helper adds NullPermissions, UID, and GID.
39 func TestMount(t *testing.T, root fs.InodeEmbedder, opts *fs.Options) string {
40 t.Helper()
41 SkipUnlessFUSE(t)
42 mntDir := t.TempDir()
43 if opts == nil {
44 opts = &fs.Options{}
45 }
46 opts.NullPermissions = true
47 opts.UID = uint32(os.Getuid())
48 opts.GID = uint32(os.Getgid())
49 if opts.MountOptions.FsName == "" {
50 opts.MountOptions.FsName = "kubo-test"
51 }
52 server, err := fs.Mount(mntDir, root, opts)
53 MountError(t, err)
54 t.Cleanup(func() { _ = server.Unmount() })
55 return mntDir
56 }
57
58 // AssertStatfsNonZero calls syscall.Statfs on path and verifies the
59 // result contains real filesystem data (non-zero block counts with
60 // Bfree <= Blocks). This avoids the racy pattern of comparing two
61 // Statfs snapshots taken at different times.
62 func AssertStatfsNonZero(t *testing.T, path string) {
63 t.Helper()
64 var st syscall.Statfs_t
65 require.NoError(t, syscall.Statfs(path, &st))
66 require.NotZero(t, st.Blocks, "expected non-zero Blocks for a real filesystem")
67 require.LessOrEqual(t, st.Bfree, st.Blocks, "Bfree must not exceed Blocks")
68 }
69
70 // AssertStatBlocks stats path and checks that st_blocks matches the file
71 // size rounded up to 512-byte units (the POSIX stat convention) and that
72 // st_blksize matches wantBlksize. These are the fields du, ls -s, and
73 // stat read to report disk usage per entry.
74 func AssertStatBlocks(t *testing.T, path string, wantBlksize uint32) {
75 t.Helper()
76 fi, err := os.Stat(path)
77 require.NoError(t, err)
78 st, ok := fi.Sys().(*syscall.Stat_t)
79 require.True(t, ok, "expected *syscall.Stat_t from os.Stat on FUSE mount")
80
81 wantBlocks := int64((fi.Size() + 511) / 512)
82 require.Equal(t, wantBlocks, int64(st.Blocks),
83 "st_blocks mismatch for %s (size=%d)", path, fi.Size())
84 require.Equal(t, wantBlksize, uint32(st.Blksize),
85 "st_blksize mismatch for %s", path)
86 }
87
88 // MountError handles a FUSE mount error. When TEST_FUSE=1 (CI), a mount
89 // failure is fatal because the environment is expected to have working FUSE.
90 // When auto-detecting (no TEST_FUSE set), mount failures cause a skip.
91 func MountError(t *testing.T, err error) {
92 t.Helper()
93 if err == nil {
94 return
95 }
96 if fuseFlagFromEnv() == "1" {
97 t.Fatal("FUSE mount failed (TEST_FUSE=1, expected FUSE to work):", err)
98 }
99 t.Skip("FUSE mount failed:", err)
100 }