master
go 123 lines 3.03 KB
Raw
1 // go-fuse only builds on linux, darwin, and freebsd.
2 //go:build (linux || darwin || freebsd) && !nofuse
3
4 package node
5
6 import (
7 "os"
8 "testing"
9 "time"
10
11 core "github.com/ipfs/kubo/core"
12 coremock "github.com/ipfs/kubo/core/mock"
13 "github.com/ipfs/kubo/fuse/fusetest"
14 ipns "github.com/ipfs/kubo/fuse/ipns"
15 mount "github.com/ipfs/kubo/fuse/mount"
16 )
17
18 func mkdir(t *testing.T, path string) {
19 err := os.Mkdir(path, os.ModeDir|os.ModePerm)
20 if err != nil {
21 t.Fatal(err)
22 }
23 }
24
25 // TestExternalUnmount runs an external unmount on each of the three
26 // FUSE mounts (/ipfs, /ipns, /mfs) and confirms the corresponding
27 // Mount.IsActive flips to false and Unmount returns ErrNotMounted.
28 // This exercises the goroutine in fuse/mount/fuse.go that watches
29 // fuse.Server.Wait() to detect out-of-band unmounts.
30 func TestExternalUnmount(t *testing.T) {
31 fusetest.SkipUnlessFUSE(t)
32
33 cases := []struct {
34 name string
35 target func(node *core.IpfsNode, paths mountPaths) (string, mount.Mount)
36 }{
37 {
38 name: "ipfs",
39 target: func(node *core.IpfsNode, p mountPaths) (string, mount.Mount) {
40 return p.ipfs, node.Mounts.Ipfs
41 },
42 },
43 {
44 name: "ipns",
45 target: func(node *core.IpfsNode, p mountPaths) (string, mount.Mount) {
46 return p.ipns, node.Mounts.Ipns
47 },
48 },
49 {
50 name: "mfs",
51 target: func(node *core.IpfsNode, p mountPaths) (string, mount.Mount) {
52 return p.mfs, node.Mounts.Mfs
53 },
54 },
55 }
56
57 for _, tc := range cases {
58 t.Run(tc.name, func(t *testing.T) {
59 node, paths := setupAllMounts(t)
60 mountpoint, target := tc.target(node, paths)
61
62 // Run shell command to externally unmount the directory.
63 cmd, err := mount.UnmountCmd(mountpoint)
64 if err != nil {
65 t.Fatal(err)
66 }
67 if err := cmd.Run(); err != nil {
68 t.Fatal(err)
69 }
70
71 // The goroutine watching fuse.Server.Wait() needs a moment
72 // to observe the kernel-side unmount and flip IsActive.
73 time.Sleep(100 * time.Millisecond)
74
75 if target.IsActive() {
76 t.Fatal("mount should be inactive after external unmount")
77 }
78 if err := target.Unmount(); err != mount.ErrNotMounted {
79 t.Fatalf("expected ErrNotMounted, got %v", err)
80 }
81 })
82 }
83 }
84
85 type mountPaths struct {
86 ipfs, ipns, mfs string
87 }
88
89 // setupAllMounts builds an IpfsNode and mounts all three FUSE filesystems
90 // under a fresh temp directory. Cleanup unmounts whatever is still active.
91 //
92 // The node is built via coremock.NewMockNode so it is online: doMount
93 // only mounts /ipns when node.IsOnline is true, and the test needs all
94 // three mounts populated.
95 func setupAllMounts(t *testing.T) (*core.IpfsNode, mountPaths) {
96 t.Helper()
97
98 node, err := coremock.NewMockNode()
99 if err != nil {
100 t.Fatal(err)
101 }
102 if err := ipns.InitializeKeyspace(node, node.PrivateKey); err != nil {
103 t.Fatal(err)
104 }
105
106 dir := t.TempDir()
107 paths := mountPaths{
108 ipfs: dir + "/ipfs",
109 ipns: dir + "/ipns",
110 mfs: dir + "/mfs",
111 }
112 mkdir(t, paths.ipfs)
113 mkdir(t, paths.ipns)
114 mkdir(t, paths.mfs)
115
116 err = Mount(node, paths.ipfs, paths.ipns, paths.mfs)
117 fusetest.MountError(t, err)
118
119 t.Cleanup(func() {
120 Unmount(node)
121 })
122 return node, paths
123 }