mirror of
https://github.com/ipfs/kubo.git
synced 2026-02-22 02:47:48 +08:00
* refactor: remove goprocess The `goprocess` package is no longer needed. It can be replaces by modern `context` and `context.AfterFunc`. * mod tidy * log unmount errors on shutdown * Do not log non-mounted errors on shutdown * Use WaitGroup associated with IPFS node to wait for services to whutdown * Prefer explicit Close to context.ArterFunc * Do not use node-level WaitGroup * Unmount for non-supported platforms * fix return values * test: daemon shuts down gracefully make sure ongoing operations dont block shutdown * test(cli): add TestFUSE * test: smarter RequiresFUSE opportunistically run FUSE tests if env has fusermount and TEST_FUSE was not explicitly set * docs: changelog --------- Co-authored-by: gammazero <gammazero@users.noreply.github.com> Co-authored-by: Marcin Rataj <lidel@lidel.org>
167 lines
5.2 KiB
Go
167 lines
5.2 KiB
Go
package cli
|
|
|
|
import (
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/ipfs/kubo/test/cli/harness"
|
|
"github.com/ipfs/kubo/test/cli/testutils"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestFUSE(t *testing.T) {
|
|
testutils.RequiresFUSE(t)
|
|
t.Parallel()
|
|
|
|
t.Run("mount and unmount work correctly", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Create a node and start daemon
|
|
node := harness.NewT(t).NewNode().Init()
|
|
node.StartDaemon()
|
|
|
|
// Create mount directories in the node's working directory
|
|
nodeDir := node.Dir
|
|
ipfsMount := filepath.Join(nodeDir, "ipfs")
|
|
ipnsMount := filepath.Join(nodeDir, "ipns")
|
|
mfsMount := filepath.Join(nodeDir, "mfs")
|
|
|
|
err := os.MkdirAll(ipfsMount, 0755)
|
|
require.NoError(t, err)
|
|
err = os.MkdirAll(ipnsMount, 0755)
|
|
require.NoError(t, err)
|
|
err = os.MkdirAll(mfsMount, 0755)
|
|
require.NoError(t, err)
|
|
|
|
// Ensure any existing mounts are cleaned up first
|
|
failOnError := false // mount points might not exist from previous runs
|
|
doUnmount(t, ipfsMount, failOnError)
|
|
doUnmount(t, ipnsMount, failOnError)
|
|
doUnmount(t, mfsMount, failOnError)
|
|
|
|
// Test mount operation
|
|
result := node.IPFS("mount", "-f", ipfsMount, "-n", ipnsMount, "-m", mfsMount)
|
|
|
|
// Verify mount output
|
|
expectedOutput := "IPFS mounted at: " + ipfsMount + "\n" +
|
|
"IPNS mounted at: " + ipnsMount + "\n" +
|
|
"MFS mounted at: " + mfsMount + "\n"
|
|
require.Equal(t, expectedOutput, result.Stdout.String())
|
|
|
|
// Test basic MFS functionality via FUSE mount
|
|
testFile := filepath.Join(mfsMount, "testfile")
|
|
testContent := "hello fuse world"
|
|
|
|
// Create file via FUSE mount
|
|
err = os.WriteFile(testFile, []byte(testContent), 0644)
|
|
require.NoError(t, err)
|
|
|
|
// Verify file appears in MFS via IPFS commands
|
|
result = node.IPFS("files", "ls", "/")
|
|
require.Contains(t, result.Stdout.String(), "testfile")
|
|
|
|
// Read content back via MFS FUSE mount
|
|
readContent, err := os.ReadFile(testFile)
|
|
require.NoError(t, err)
|
|
require.Equal(t, testContent, string(readContent))
|
|
|
|
// Get the CID of the MFS file
|
|
result = node.IPFS("files", "stat", "/testfile", "--format=<hash>")
|
|
fileCID := strings.TrimSpace(result.Stdout.String())
|
|
require.NotEmpty(t, fileCID, "should have a CID for the MFS file")
|
|
|
|
// Read the same content via IPFS FUSE mount using the CID
|
|
ipfsFile := filepath.Join(ipfsMount, fileCID)
|
|
ipfsContent, err := os.ReadFile(ipfsFile)
|
|
require.NoError(t, err)
|
|
require.Equal(t, testContent, string(ipfsContent), "content should match between MFS and IPFS mounts")
|
|
|
|
// Verify both FUSE mounts return identical data
|
|
require.Equal(t, readContent, ipfsContent, "MFS and IPFS FUSE mounts should return identical data")
|
|
|
|
// Test that mount directories cannot be removed while mounted
|
|
err = os.Remove(ipfsMount)
|
|
require.Error(t, err, "should not be able to remove mounted directory")
|
|
|
|
// Stop daemon - this should trigger automatic unmount via context cancellation
|
|
node.StopDaemon()
|
|
|
|
// Daemon shutdown should handle unmount synchronously via context.AfterFunc
|
|
|
|
// Verify directories can now be removed (indicating successful unmount)
|
|
err = os.Remove(ipfsMount)
|
|
require.NoError(t, err, "should be able to remove directory after unmount")
|
|
err = os.Remove(ipnsMount)
|
|
require.NoError(t, err, "should be able to remove directory after unmount")
|
|
err = os.Remove(mfsMount)
|
|
require.NoError(t, err, "should be able to remove directory after unmount")
|
|
})
|
|
|
|
t.Run("explicit unmount works", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
node := harness.NewT(t).NewNode().Init()
|
|
node.StartDaemon()
|
|
|
|
// Create mount directories
|
|
nodeDir := node.Dir
|
|
ipfsMount := filepath.Join(nodeDir, "ipfs")
|
|
ipnsMount := filepath.Join(nodeDir, "ipns")
|
|
mfsMount := filepath.Join(nodeDir, "mfs")
|
|
|
|
err := os.MkdirAll(ipfsMount, 0755)
|
|
require.NoError(t, err)
|
|
err = os.MkdirAll(ipnsMount, 0755)
|
|
require.NoError(t, err)
|
|
err = os.MkdirAll(mfsMount, 0755)
|
|
require.NoError(t, err)
|
|
|
|
// Clean up any existing mounts
|
|
failOnError := false // mount points might not exist from previous runs
|
|
doUnmount(t, ipfsMount, failOnError)
|
|
doUnmount(t, ipnsMount, failOnError)
|
|
doUnmount(t, mfsMount, failOnError)
|
|
|
|
// Mount
|
|
node.IPFS("mount", "-f", ipfsMount, "-n", ipnsMount, "-m", mfsMount)
|
|
|
|
// Explicit unmount via platform-specific command
|
|
failOnError = true // test that explicit unmount works correctly
|
|
doUnmount(t, ipfsMount, failOnError)
|
|
doUnmount(t, ipnsMount, failOnError)
|
|
doUnmount(t, mfsMount, failOnError)
|
|
|
|
// Verify directories can be removed after explicit unmount
|
|
err = os.Remove(ipfsMount)
|
|
require.NoError(t, err)
|
|
err = os.Remove(ipnsMount)
|
|
require.NoError(t, err)
|
|
err = os.Remove(mfsMount)
|
|
require.NoError(t, err)
|
|
|
|
node.StopDaemon()
|
|
})
|
|
}
|
|
|
|
// doUnmount performs platform-specific unmount, similar to sharness do_umount
|
|
// failOnError: if true, unmount errors cause test failure; if false, errors are ignored (useful for cleanup)
|
|
func doUnmount(t *testing.T, mountPoint string, failOnError bool) {
|
|
t.Helper()
|
|
var cmd *exec.Cmd
|
|
if runtime.GOOS == "linux" {
|
|
// fusermount -u: unmount filesystem (strict - fails if busy)
|
|
cmd = exec.Command("fusermount", "-u", mountPoint)
|
|
} else {
|
|
cmd = exec.Command("umount", mountPoint)
|
|
}
|
|
|
|
err := cmd.Run()
|
|
if err != nil && failOnError {
|
|
t.Fatalf("failed to unmount %s: %v", mountPoint, err)
|
|
}
|
|
}
|