diff options
| author | Chris Lu <chrislusf@users.noreply.github.com> | 2025-07-16 12:43:08 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-16 12:43:08 -0700 |
| commit | 9982f91b4ca885f09c32d87b3c31fe76c1304e8e (patch) | |
| tree | 058cfa836389db65f489a473773f57ad1990af2b /test/fuse_integration/framework.go | |
| parent | 215c5de5799f9d71a63cd385f3db143cb4886692 (diff) | |
| download | seaweedfs-9982f91b4ca885f09c32d87b3c31fe76c1304e8e.tar.xz seaweedfs-9982f91b4ca885f09c32d87b3c31fe76c1304e8e.zip | |
Add more fuse tests (#6992)
* add more tests
* move to new package
* add github action
* Update fuse-integration.yml
* Update fuse-integration.yml
* Update test/fuse_integration/README.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update test/fuse_integration/README.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update test/fuse_integration/framework.go
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update test/fuse_integration/README.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update test/fuse_integration/README.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* fix
* Update test/fuse_integration/concurrent_operations_test.go
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Diffstat (limited to 'test/fuse_integration/framework.go')
| -rw-r--r-- | test/fuse_integration/framework.go | 384 |
1 files changed, 384 insertions, 0 deletions
diff --git a/test/fuse_integration/framework.go b/test/fuse_integration/framework.go new file mode 100644 index 000000000..9cff1badb --- /dev/null +++ b/test/fuse_integration/framework.go @@ -0,0 +1,384 @@ +package fuse_test + +import ( + "fmt" + "io/fs" + "os" + "os/exec" + "path/filepath" + "syscall" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +// FuseTestFramework provides utilities for FUSE integration testing +type FuseTestFramework struct { + t *testing.T + tempDir string + mountPoint string + dataDir string + masterProcess *os.Process + volumeProcess *os.Process + filerProcess *os.Process + mountProcess *os.Process + masterAddr string + volumeAddr string + filerAddr string + weedBinary string + isSetup bool +} + +// TestConfig holds configuration for FUSE tests +type TestConfig struct { + Collection string + Replication string + ChunkSizeMB int + CacheSizeMB int + NumVolumes int + EnableDebug bool + MountOptions []string + SkipCleanup bool // for debugging failed tests +} + +// DefaultTestConfig returns a default configuration for FUSE tests +func DefaultTestConfig() *TestConfig { + return &TestConfig{ + Collection: "", + Replication: "000", + ChunkSizeMB: 4, + CacheSizeMB: 100, + NumVolumes: 3, + EnableDebug: false, + MountOptions: []string{}, + SkipCleanup: false, + } +} + +// NewFuseTestFramework creates a new FUSE testing framework +func NewFuseTestFramework(t *testing.T, config *TestConfig) *FuseTestFramework { + if config == nil { + config = DefaultTestConfig() + } + + tempDir, err := os.MkdirTemp("", "seaweedfs_fuse_test_") + require.NoError(t, err) + + return &FuseTestFramework{ + t: t, + tempDir: tempDir, + mountPoint: filepath.Join(tempDir, "mount"), + dataDir: filepath.Join(tempDir, "data"), + masterAddr: "127.0.0.1:19333", + volumeAddr: "127.0.0.1:18080", + filerAddr: "127.0.0.1:18888", + weedBinary: findWeedBinary(), + isSetup: false, + } +} + +// Setup starts SeaweedFS cluster and mounts FUSE filesystem +func (f *FuseTestFramework) Setup(config *TestConfig) error { + if f.isSetup { + return fmt.Errorf("framework already setup") + } + + // Create directories + dirs := []string{f.mountPoint, f.dataDir} + for _, dir := range dirs { + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("failed to create directory %s: %v", dir, err) + } + } + + // Start master + if err := f.startMaster(config); err != nil { + return fmt.Errorf("failed to start master: %v", err) + } + + // Wait for master to be ready + if err := f.waitForService(f.masterAddr, 30*time.Second); err != nil { + return fmt.Errorf("master not ready: %v", err) + } + + // Start volume servers + if err := f.startVolumeServers(config); err != nil { + return fmt.Errorf("failed to start volume servers: %v", err) + } + + // Wait for volume server to be ready + if err := f.waitForService(f.volumeAddr, 30*time.Second); err != nil { + return fmt.Errorf("volume server not ready: %v", err) + } + + // Start filer + if err := f.startFiler(config); err != nil { + return fmt.Errorf("failed to start filer: %v", err) + } + + // Wait for filer to be ready + if err := f.waitForService(f.filerAddr, 30*time.Second); err != nil { + return fmt.Errorf("filer not ready: %v", err) + } + + // Mount FUSE filesystem + if err := f.mountFuse(config); err != nil { + return fmt.Errorf("failed to mount FUSE: %v", err) + } + + // Wait for mount to be ready + if err := f.waitForMount(30 * time.Second); err != nil { + return fmt.Errorf("FUSE mount not ready: %v", err) + } + + f.isSetup = true + return nil +} + +// Cleanup stops all processes and removes temporary files +func (f *FuseTestFramework) Cleanup() { + if f.mountProcess != nil { + f.unmountFuse() + } + + // Stop processes in reverse order + processes := []*os.Process{f.mountProcess, f.filerProcess, f.volumeProcess, f.masterProcess} + for _, proc := range processes { + if proc != nil { + proc.Signal(syscall.SIGTERM) + proc.Wait() + } + } + + // Remove temp directory + if !DefaultTestConfig().SkipCleanup { + os.RemoveAll(f.tempDir) + } +} + +// GetMountPoint returns the FUSE mount point path +func (f *FuseTestFramework) GetMountPoint() string { + return f.mountPoint +} + +// GetFilerAddr returns the filer address +func (f *FuseTestFramework) GetFilerAddr() string { + return f.filerAddr +} + +// startMaster starts the SeaweedFS master server +func (f *FuseTestFramework) startMaster(config *TestConfig) error { + args := []string{ + "master", + "-ip=127.0.0.1", + "-port=19333", + "-mdir=" + filepath.Join(f.dataDir, "master"), + "-raftBootstrap", + } + if config.EnableDebug { + args = append(args, "-v=4") + } + + cmd := exec.Command(f.weedBinary, args...) + cmd.Dir = f.tempDir + if err := cmd.Start(); err != nil { + return err + } + f.masterProcess = cmd.Process + return nil +} + +// startVolumeServers starts SeaweedFS volume servers +func (f *FuseTestFramework) startVolumeServers(config *TestConfig) error { + args := []string{ + "volume", + "-mserver=" + f.masterAddr, + "-ip=127.0.0.1", + "-port=18080", + "-dir=" + filepath.Join(f.dataDir, "volume"), + fmt.Sprintf("-max=%d", config.NumVolumes), + } + if config.EnableDebug { + args = append(args, "-v=4") + } + + cmd := exec.Command(f.weedBinary, args...) + cmd.Dir = f.tempDir + if err := cmd.Start(); err != nil { + return err + } + f.volumeProcess = cmd.Process + return nil +} + +// startFiler starts the SeaweedFS filer server +func (f *FuseTestFramework) startFiler(config *TestConfig) error { + args := []string{ + "filer", + "-master=" + f.masterAddr, + "-ip=127.0.0.1", + "-port=18888", + } + if config.EnableDebug { + args = append(args, "-v=4") + } + + cmd := exec.Command(f.weedBinary, args...) + cmd.Dir = f.tempDir + if err := cmd.Start(); err != nil { + return err + } + f.filerProcess = cmd.Process + return nil +} + +// mountFuse mounts the SeaweedFS FUSE filesystem +func (f *FuseTestFramework) mountFuse(config *TestConfig) error { + args := []string{ + "mount", + "-filer=" + f.filerAddr, + "-dir=" + f.mountPoint, + "-filer.path=/", + "-dirAutoCreate", + } + + if config.Collection != "" { + args = append(args, "-collection="+config.Collection) + } + if config.Replication != "" { + args = append(args, "-replication="+config.Replication) + } + if config.ChunkSizeMB > 0 { + args = append(args, fmt.Sprintf("-chunkSizeLimitMB=%d", config.ChunkSizeMB)) + } + if config.CacheSizeMB > 0 { + args = append(args, fmt.Sprintf("-cacheSizeMB=%d", config.CacheSizeMB)) + } + if config.EnableDebug { + args = append(args, "-v=4") + } + + args = append(args, config.MountOptions...) + + cmd := exec.Command(f.weedBinary, args...) + cmd.Dir = f.tempDir + if err := cmd.Start(); err != nil { + return err + } + f.mountProcess = cmd.Process + return nil +} + +// unmountFuse unmounts the FUSE filesystem +func (f *FuseTestFramework) unmountFuse() error { + if f.mountProcess != nil { + f.mountProcess.Signal(syscall.SIGTERM) + f.mountProcess.Wait() + f.mountProcess = nil + } + + // Also try system unmount as backup + exec.Command("umount", f.mountPoint).Run() + return nil +} + +// waitForService waits for a service to be available +func (f *FuseTestFramework) waitForService(addr string, timeout time.Duration) error { + deadline := time.Now().Add(timeout) + for time.Now().Before(deadline) { + conn, err := net.DialTimeout("tcp", addr, 1*time.Second) + if err == nil { + conn.Close() + return nil + } + time.Sleep(100 * time.Millisecond) + } + return fmt.Errorf("service at %s not ready within timeout", addr) +} + +// waitForMount waits for the FUSE mount to be ready +func (f *FuseTestFramework) waitForMount(timeout time.Duration) error { + deadline := time.Now().Add(timeout) + for time.Now().Before(deadline) { + // Check if mount point is accessible + if _, err := os.Stat(f.mountPoint); err == nil { + // Try to list directory + if _, err := os.ReadDir(f.mountPoint); err == nil { + return nil + } + } + time.Sleep(100 * time.Millisecond) + } + return fmt.Errorf("mount point not ready within timeout") +} + +// findWeedBinary locates the weed binary +func findWeedBinary() string { + // Try different possible locations + candidates := []string{ + "./weed", + "../weed", + "../../weed", + "weed", // in PATH + } + + for _, candidate := range candidates { + if _, err := exec.LookPath(candidate); err == nil { + return candidate + } + if _, err := os.Stat(candidate); err == nil { + abs, _ := filepath.Abs(candidate) + return abs + } + } + + // Default fallback + return "weed" +} + +// Helper functions for test assertions + +// AssertFileExists checks if a file exists in the mount point +func (f *FuseTestFramework) AssertFileExists(relativePath string) { + fullPath := filepath.Join(f.mountPoint, relativePath) + _, err := os.Stat(fullPath) + require.NoError(f.t, err, "file should exist: %s", relativePath) +} + +// AssertFileNotExists checks if a file does not exist in the mount point +func (f *FuseTestFramework) AssertFileNotExists(relativePath string) { + fullPath := filepath.Join(f.mountPoint, relativePath) + _, err := os.Stat(fullPath) + require.True(f.t, os.IsNotExist(err), "file should not exist: %s", relativePath) +} + +// AssertFileContent checks if a file has expected content +func (f *FuseTestFramework) AssertFileContent(relativePath string, expectedContent []byte) { + fullPath := filepath.Join(f.mountPoint, relativePath) + actualContent, err := os.ReadFile(fullPath) + require.NoError(f.t, err, "failed to read file: %s", relativePath) + require.Equal(f.t, expectedContent, actualContent, "file content mismatch: %s", relativePath) +} + +// AssertFileMode checks if a file has expected permissions +func (f *FuseTestFramework) AssertFileMode(relativePath string, expectedMode fs.FileMode) { + fullPath := filepath.Join(f.mountPoint, relativePath) + info, err := os.Stat(fullPath) + require.NoError(f.t, err, "failed to stat file: %s", relativePath) + require.Equal(f.t, expectedMode, info.Mode(), "file mode mismatch: %s", relativePath) +} + +// CreateTestFile creates a test file with specified content +func (f *FuseTestFramework) CreateTestFile(relativePath string, content []byte) { + fullPath := filepath.Join(f.mountPoint, relativePath) + dir := filepath.Dir(fullPath) + require.NoError(f.t, os.MkdirAll(dir, 0755), "failed to create directory: %s", dir) + require.NoError(f.t, os.WriteFile(fullPath, content, 0644), "failed to create file: %s", relativePath) +} + +// CreateTestDir creates a test directory +func (f *FuseTestFramework) CreateTestDir(relativePath string) { + fullPath := filepath.Join(f.mountPoint, relativePath) + require.NoError(f.t, os.MkdirAll(fullPath, 0755), "failed to create directory: %s", relativePath) +} |
