diff options
| author | chrislu <chris.lu@gmail.com> | 2025-08-30 20:21:38 -0700 |
|---|---|---|
| committer | chrislu <chris.lu@gmail.com> | 2025-08-30 20:21:38 -0700 |
| commit | 478d550cba30affd2f4d211317bdfe3352a96e29 (patch) | |
| tree | b63a293c2605b459a8cdbd007789d4b886eb2021 /test/fuse_integration/posix_compliance_test.go | |
| parent | 879d512b552d834136cfb746a239e6168e5c4ffb (diff) | |
| download | seaweedfs-478d550cba30affd2f4d211317bdfe3352a96e29.tar.xz seaweedfs-478d550cba30affd2f4d211317bdfe3352a96e29.zip | |
add posix tests
Diffstat (limited to 'test/fuse_integration/posix_compliance_test.go')
| -rw-r--r-- | test/fuse_integration/posix_compliance_test.go | 663 |
1 files changed, 663 insertions, 0 deletions
diff --git a/test/fuse_integration/posix_compliance_test.go b/test/fuse_integration/posix_compliance_test.go new file mode 100644 index 000000000..9a9fb152f --- /dev/null +++ b/test/fuse_integration/posix_compliance_test.go @@ -0,0 +1,663 @@ +package fuse_test + +import ( + "fmt" + "os" + "path/filepath" + "syscall" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +// POSIXComplianceTestSuite provides comprehensive POSIX compliance testing for FUSE mounts +type POSIXComplianceTestSuite struct { + framework *FuseTestFramework + t *testing.T +} + +// NewPOSIXComplianceTestSuite creates a new POSIX compliance test suite +func NewPOSIXComplianceTestSuite(t *testing.T, framework *FuseTestFramework) *POSIXComplianceTestSuite { + return &POSIXComplianceTestSuite{ + framework: framework, + t: t, + } +} + +// TestPOSIXCompliance runs all POSIX compliance tests +func TestPOSIXCompliance(t *testing.T) { + config := DefaultTestConfig() + config.EnableDebug = true + config.MountOptions = []string{"-allowOthers", "-nonempty"} + + framework := NewFuseTestFramework(t, config) + defer framework.Cleanup() + require.NoError(t, framework.Setup(config)) + + suite := NewPOSIXComplianceTestSuite(t, framework) + + // Run all POSIX compliance test categories + t.Run("FileOperations", suite.TestFileOperations) + t.Run("DirectoryOperations", suite.TestDirectoryOperations) + t.Run("SymlinkOperations", suite.TestSymlinkOperations) + t.Run("PermissionTests", suite.TestPermissions) + t.Run("TimestampTests", suite.TestTimestamps) + t.Run("IOOperations", suite.TestIOOperations) + t.Run("FileDescriptorTests", suite.TestFileDescriptors) + t.Run("AtomicOperations", suite.TestAtomicOperations) + t.Run("ConcurrentAccess", suite.TestConcurrentAccess) + t.Run("ErrorHandling", suite.TestErrorHandling) +} + +// TestFileOperations tests POSIX file operation compliance +func (s *POSIXComplianceTestSuite) TestFileOperations(t *testing.T) { + mountPoint := s.framework.GetMountPoint() + + t.Run("CreateFile", func(t *testing.T) { + filepath := filepath.Join(mountPoint, "test_create.txt") + + // Test file creation with O_CREAT + fd, err := syscall.Open(filepath, syscall.O_CREAT|syscall.O_WRONLY, 0644) + require.NoError(t, err) + require.Greater(t, fd, 0) + + err = syscall.Close(fd) + require.NoError(t, err) + + // Verify file exists + _, err = os.Stat(filepath) + require.NoError(t, err) + }) + + t.Run("CreateExclusiveFile", func(t *testing.T) { + filepath := filepath.Join(mountPoint, "test_excl.txt") + + // First creation should succeed + fd, err := syscall.Open(filepath, syscall.O_CREAT|syscall.O_EXCL|syscall.O_WRONLY, 0644) + require.NoError(t, err) + syscall.Close(fd) + + // Second creation should fail with EEXIST + _, err = syscall.Open(filepath, syscall.O_CREAT|syscall.O_EXCL|syscall.O_WRONLY, 0644) + require.Error(t, err) + require.Equal(t, syscall.EEXIST, err) + }) + + t.Run("TruncateFile", func(t *testing.T) { + filepath := filepath.Join(mountPoint, "test_truncate.txt") + content := []byte("Hello, World! This is a test file for truncation.") + + // Create file with content + err := os.WriteFile(filepath, content, 0644) + require.NoError(t, err) + + // Truncate to 5 bytes + err = syscall.Truncate(filepath, 5) + require.NoError(t, err) + + // Verify truncation + readContent, err := os.ReadFile(filepath) + require.NoError(t, err) + require.Equal(t, []byte("Hello"), readContent) + }) + + t.Run("UnlinkFile", func(t *testing.T) { + filepath := filepath.Join(mountPoint, "test_unlink.txt") + + // Create file + err := os.WriteFile(filepath, []byte("test"), 0644) + require.NoError(t, err) + + // Unlink file + err = syscall.Unlink(filepath) + require.NoError(t, err) + + // Verify file no longer exists + _, err = os.Stat(filepath) + require.True(t, os.IsNotExist(err)) + }) +} + +// TestDirectoryOperations tests POSIX directory operation compliance +func (s *POSIXComplianceTestSuite) TestDirectoryOperations(t *testing.T) { + mountPoint := s.framework.GetMountPoint() + + t.Run("CreateDirectory", func(t *testing.T) { + dirPath := filepath.Join(mountPoint, "test_mkdir") + + err := syscall.Mkdir(dirPath, 0755) + require.NoError(t, err) + + // Verify directory exists and has correct type + stat, err := os.Stat(dirPath) + require.NoError(t, err) + require.True(t, stat.IsDir()) + }) + + t.Run("RemoveDirectory", func(t *testing.T) { + dirPath := filepath.Join(mountPoint, "test_rmdir") + + // Create directory + err := os.Mkdir(dirPath, 0755) + require.NoError(t, err) + + // Remove directory + err = syscall.Rmdir(dirPath) + require.NoError(t, err) + + // Verify directory no longer exists + _, err = os.Stat(dirPath) + require.True(t, os.IsNotExist(err)) + }) + + t.Run("RemoveNonEmptyDirectory", func(t *testing.T) { + dirPath := filepath.Join(mountPoint, "test_rmdir_nonempty") + filePath := filepath.Join(dirPath, "file.txt") + + // Create directory and file + err := os.Mkdir(dirPath, 0755) + require.NoError(t, err) + err = os.WriteFile(filePath, []byte("test"), 0644) + require.NoError(t, err) + + // Attempt to remove non-empty directory should fail + err = syscall.Rmdir(dirPath) + require.Error(t, err) + require.Equal(t, syscall.ENOTEMPTY, err) + }) + + t.Run("RenameDirectory", func(t *testing.T) { + oldPath := filepath.Join(mountPoint, "old_dir") + newPath := filepath.Join(mountPoint, "new_dir") + + // Create directory + err := os.Mkdir(oldPath, 0755) + require.NoError(t, err) + + // Rename directory + err = os.Rename(oldPath, newPath) + require.NoError(t, err) + + // Verify old path doesn't exist and new path does + _, err = os.Stat(oldPath) + require.True(t, os.IsNotExist(err)) + stat, err := os.Stat(newPath) + require.NoError(t, err) + require.True(t, stat.IsDir()) + }) +} + +// TestSymlinkOperations tests POSIX symlink operation compliance +func (s *POSIXComplianceTestSuite) TestSymlinkOperations(t *testing.T) { + mountPoint := s.framework.GetMountPoint() + + t.Run("CreateSymlink", func(t *testing.T) { + targetFile := filepath.Join(mountPoint, "target.txt") + linkFile := filepath.Join(mountPoint, "link.txt") + + // Create target file + err := os.WriteFile(targetFile, []byte("target content"), 0644) + require.NoError(t, err) + + // Create symlink + err = os.Symlink(targetFile, linkFile) + require.NoError(t, err) + + // Verify symlink properties + linkStat, err := os.Lstat(linkFile) + require.NoError(t, err) + require.Equal(t, os.ModeSymlink, linkStat.Mode()&os.ModeType) + + // Verify symlink content + linkTarget, err := os.Readlink(linkFile) + require.NoError(t, err) + require.Equal(t, targetFile, linkTarget) + + // Verify following symlink works + content, err := os.ReadFile(linkFile) + require.NoError(t, err) + require.Equal(t, []byte("target content"), content) + }) + + t.Run("BrokenSymlink", func(t *testing.T) { + nonexistentTarget := filepath.Join(mountPoint, "nonexistent.txt") + linkFile := filepath.Join(mountPoint, "broken_link.txt") + + // Create symlink to nonexistent file + err := os.Symlink(nonexistentTarget, linkFile) + require.NoError(t, err) + + // Lstat should work (doesn't follow symlink) + _, err = os.Lstat(linkFile) + require.NoError(t, err) + + // Stat should fail (follows symlink to nonexistent target) + _, err = os.Stat(linkFile) + require.Error(t, err) + require.True(t, os.IsNotExist(err)) + }) +} + +// TestPermissions tests POSIX permission compliance +func (s *POSIXComplianceTestSuite) TestPermissions(t *testing.T) { + mountPoint := s.framework.GetMountPoint() + + t.Run("FilePermissions", func(t *testing.T) { + testFile := filepath.Join(mountPoint, "perm_test.txt") + + // Create file with specific permissions + fd, err := syscall.Open(testFile, syscall.O_CREAT|syscall.O_WRONLY, 0642) + require.NoError(t, err) + syscall.Close(fd) + + // Verify permissions + stat, err := os.Stat(testFile) + require.NoError(t, err) + require.Equal(t, os.FileMode(0642), stat.Mode()&os.ModePerm) + }) + + t.Run("ChangeFilePermissions", func(t *testing.T) { + testFile := filepath.Join(mountPoint, "chmod_test.txt") + + // Create file + err := os.WriteFile(testFile, []byte("test"), 0644) + require.NoError(t, err) + + // Change permissions + err = os.Chmod(testFile, 0755) + require.NoError(t, err) + + // Verify new permissions + stat, err := os.Stat(testFile) + require.NoError(t, err) + require.Equal(t, os.FileMode(0755), stat.Mode()&os.ModePerm) + }) + + t.Run("DirectoryPermissions", func(t *testing.T) { + testDir := filepath.Join(mountPoint, "perm_dir") + + // Create directory with specific permissions + err := syscall.Mkdir(testDir, 0750) + require.NoError(t, err) + + // Verify permissions + stat, err := os.Stat(testDir) + require.NoError(t, err) + require.Equal(t, os.FileMode(0750)|os.ModeDir, stat.Mode()) + }) +} + +// TestTimestamps tests POSIX timestamp compliance +func (s *POSIXComplianceTestSuite) TestTimestamps(t *testing.T) { + mountPoint := s.framework.GetMountPoint() + + t.Run("AccessTime", func(t *testing.T) { + testFile := filepath.Join(mountPoint, "atime_test.txt") + + // Create file + err := os.WriteFile(testFile, []byte("test content"), 0644) + require.NoError(t, err) + + // Get initial timestamps + stat1, err := os.Stat(testFile) + require.NoError(t, err) + + // Wait a bit to ensure time difference + time.Sleep(100 * time.Millisecond) + + // Read file (should update access time) + _, err = os.ReadFile(testFile) + require.NoError(t, err) + + // Get new timestamps + stat2, err := os.Stat(testFile) + require.NoError(t, err) + + // Access time should have changed (or at least not be earlier) + require.True(t, stat2.ModTime().Equal(stat1.ModTime()) || stat2.ModTime().After(stat1.ModTime())) + }) + + t.Run("ModificationTime", func(t *testing.T) { + testFile := filepath.Join(mountPoint, "mtime_test.txt") + + // Create file + err := os.WriteFile(testFile, []byte("initial content"), 0644) + require.NoError(t, err) + + // Get initial timestamp + stat1, err := os.Stat(testFile) + require.NoError(t, err) + + // Wait to ensure time difference + time.Sleep(100 * time.Millisecond) + + // Modify file + err = os.WriteFile(testFile, []byte("modified content"), 0644) + require.NoError(t, err) + + // Get new timestamp + stat2, err := os.Stat(testFile) + require.NoError(t, err) + + // Modification time should have changed + require.True(t, stat2.ModTime().After(stat1.ModTime())) + }) + + t.Run("SetTimestamps", func(t *testing.T) { + testFile := filepath.Join(mountPoint, "utime_test.txt") + + // Create file + err := os.WriteFile(testFile, []byte("test"), 0644) + require.NoError(t, err) + + // Set specific timestamps + atime := time.Now().Add(-24 * time.Hour) + mtime := time.Now().Add(-12 * time.Hour) + + err = os.Chtimes(testFile, atime, mtime) + require.NoError(t, err) + + // Verify timestamps were set + stat, err := os.Stat(testFile) + require.NoError(t, err) + require.True(t, stat.ModTime().Equal(mtime)) + }) +} + +// TestIOOperations tests POSIX I/O operation compliance +func (s *POSIXComplianceTestSuite) TestIOOperations(t *testing.T) { + mountPoint := s.framework.GetMountPoint() + + t.Run("ReadWrite", func(t *testing.T) { + testFile := filepath.Join(mountPoint, "rw_test.txt") + testData := []byte("Hello, POSIX World!") + + // Write data + fd, err := syscall.Open(testFile, syscall.O_CREAT|syscall.O_WRONLY, 0644) + require.NoError(t, err) + + n, err := syscall.Write(fd, testData) + require.NoError(t, err) + require.Equal(t, len(testData), n) + + err = syscall.Close(fd) + require.NoError(t, err) + + // Read data back + fd, err = syscall.Open(testFile, syscall.O_RDONLY, 0) + require.NoError(t, err) + + readBuffer := make([]byte, len(testData)) + n, err = syscall.Read(fd, readBuffer) + require.NoError(t, err) + require.Equal(t, len(testData), n) + require.Equal(t, testData, readBuffer) + + err = syscall.Close(fd) + require.NoError(t, err) + }) + + t.Run("Seek", func(t *testing.T) { + testFile := filepath.Join(mountPoint, "seek_test.txt") + testData := []byte("0123456789ABCDEF") + + // Create file with test data + err := os.WriteFile(testFile, testData, 0644) + require.NoError(t, err) + + // Open for reading + fd, err := syscall.Open(testFile, syscall.O_RDONLY, 0) + require.NoError(t, err) + defer syscall.Close(fd) + + // Seek to position 5 + pos, err := syscall.Seek(fd, 5, 0) // SEEK_SET + require.NoError(t, err) + require.Equal(t, int64(5), pos) + + // Read 3 bytes + buffer := make([]byte, 3) + n, err := syscall.Read(fd, buffer) + require.NoError(t, err) + require.Equal(t, 3, n) + require.Equal(t, []byte("567"), buffer) + + // Seek from current position + pos, err = syscall.Seek(fd, 2, 1) // SEEK_CUR + require.NoError(t, err) + require.Equal(t, int64(10), pos) + + // Read 1 byte + buffer = make([]byte, 1) + n, err = syscall.Read(fd, buffer) + require.NoError(t, err) + require.Equal(t, 1, n) + require.Equal(t, []byte("A"), buffer) + }) + + t.Run("AppendMode", func(t *testing.T) { + testFile := filepath.Join(mountPoint, "append_test.txt") + + // Create file with initial content + err := os.WriteFile(testFile, []byte("initial"), 0644) + require.NoError(t, err) + + // Open in append mode + fd, err := syscall.Open(testFile, syscall.O_WRONLY|syscall.O_APPEND, 0) + require.NoError(t, err) + + // Write additional content + appendData := []byte(" appended") + n, err := syscall.Write(fd, appendData) + require.NoError(t, err) + require.Equal(t, len(appendData), n) + + err = syscall.Close(fd) + require.NoError(t, err) + + // Verify content + content, err := os.ReadFile(testFile) + require.NoError(t, err) + require.Equal(t, []byte("initial appended"), content) + }) +} + +// TestFileDescriptors tests POSIX file descriptor behavior +func (s *POSIXComplianceTestSuite) TestFileDescriptors(t *testing.T) { + mountPoint := s.framework.GetMountPoint() + + t.Run("DuplicateFileDescriptors", func(t *testing.T) { + testFile := filepath.Join(mountPoint, "dup_test.txt") + testData := []byte("duplicate test") + + // Create and open file + fd, err := syscall.Open(testFile, syscall.O_CREAT|syscall.O_RDWR, 0644) + require.NoError(t, err) + defer syscall.Close(fd) + + // Write initial data + n, err := syscall.Write(fd, testData) + require.NoError(t, err) + require.Equal(t, len(testData), n) + + // Duplicate file descriptor + dupFd, err := syscall.Dup(fd) + require.NoError(t, err) + defer syscall.Close(dupFd) + + // Both descriptors should refer to the same file + // Seek on one should affect the other + pos, err := syscall.Seek(fd, 0, 0) // SEEK_SET + require.NoError(t, err) + require.Equal(t, int64(0), pos) + + // Read from duplicate descriptor should start from position 0 + buffer := make([]byte, 9) + n, err = syscall.Read(dupFd, buffer) + require.NoError(t, err) + require.Equal(t, 9, n) + require.Equal(t, []byte("duplicate"), buffer) + }) + + t.Run("FileDescriptorFlags", func(t *testing.T) { + testFile := filepath.Join(mountPoint, "flags_test.txt") + + // Create file + err := os.WriteFile(testFile, []byte("test"), 0644) + require.NoError(t, err) + + // Open with close-on-exec flag + fd, err := syscall.Open(testFile, syscall.O_RDONLY|syscall.O_CLOEXEC, 0) + require.NoError(t, err) + defer syscall.Close(fd) + + // Verify close-on-exec flag is set + // Note: FcntlInt is not available on all platforms, this test may need platform-specific implementation + t.Skip("FcntlInt not available on this platform") + }) +} + +// TestAtomicOperations tests POSIX atomic operation compliance +func (s *POSIXComplianceTestSuite) TestAtomicOperations(t *testing.T) { + mountPoint := s.framework.GetMountPoint() + + t.Run("AtomicRename", func(t *testing.T) { + oldFile := filepath.Join(mountPoint, "atomic_old.txt") + newFile := filepath.Join(mountPoint, "atomic_new.txt") + testData := []byte("atomic test data") + + // Create source file + err := os.WriteFile(oldFile, testData, 0644) + require.NoError(t, err) + + // Create existing target file + err = os.WriteFile(newFile, []byte("old content"), 0644) + require.NoError(t, err) + + // Atomic rename should replace target + err = os.Rename(oldFile, newFile) + require.NoError(t, err) + + // Verify source no longer exists + _, err = os.Stat(oldFile) + require.True(t, os.IsNotExist(err)) + + // Verify target has new content + content, err := os.ReadFile(newFile) + require.NoError(t, err) + require.Equal(t, testData, content) + }) +} + +// TestConcurrentAccess tests concurrent access patterns +func (s *POSIXComplianceTestSuite) TestConcurrentAccess(t *testing.T) { + mountPoint := s.framework.GetMountPoint() + + t.Run("ConcurrentReads", func(t *testing.T) { + testFile := filepath.Join(mountPoint, "concurrent_read.txt") + testData := []byte("concurrent read test data") + + // Create test file + err := os.WriteFile(testFile, testData, 0644) + require.NoError(t, err) + + // Launch multiple concurrent readers + const numReaders = 10 + results := make(chan error, numReaders) + + for i := 0; i < numReaders; i++ { + go func() { + content, err := os.ReadFile(testFile) + if err != nil { + results <- err + return + } + if string(content) != string(testData) { + results <- fmt.Errorf("content mismatch") + return + } + results <- nil + }() + } + + // Check all readers succeeded + for i := 0; i < numReaders; i++ { + err := <-results + require.NoError(t, err) + } + }) + + t.Run("ConcurrentWrites", func(t *testing.T) { + testFile := filepath.Join(mountPoint, "concurrent_write.txt") + + // Launch multiple concurrent writers + const numWriters = 5 + results := make(chan error, numWriters) + + for i := 0; i < numWriters; i++ { + go func(id int) { + content := fmt.Sprintf("writer %d data", id) + err := os.WriteFile(fmt.Sprintf("%s_%d", testFile, id), []byte(content), 0644) + results <- err + }(i) + } + + // Check all writers succeeded + for i := 0; i < numWriters; i++ { + err := <-results + require.NoError(t, err) + } + + // Verify all files were created + for i := 0; i < numWriters; i++ { + fileName := fmt.Sprintf("%s_%d", testFile, i) + _, err := os.Stat(fileName) + require.NoError(t, err) + } + }) +} + +// TestErrorHandling tests POSIX error handling compliance +func (s *POSIXComplianceTestSuite) TestErrorHandling(t *testing.T) { + mountPoint := s.framework.GetMountPoint() + + t.Run("AccessNonexistentFile", func(t *testing.T) { + nonexistentFile := filepath.Join(mountPoint, "does_not_exist.txt") + + // Reading nonexistent file should return ENOENT + _, err := os.ReadFile(nonexistentFile) + require.Error(t, err) + require.True(t, os.IsNotExist(err)) + }) + + t.Run("CreateFileInNonexistentDirectory", func(t *testing.T) { + fileInNonexistentDir := filepath.Join(mountPoint, "nonexistent_dir", "file.txt") + + // Creating file in nonexistent directory should fail + err := os.WriteFile(fileInNonexistentDir, []byte("test"), 0644) + require.Error(t, err) + require.True(t, os.IsNotExist(err)) + }) + + t.Run("InvalidFileDescriptor", func(t *testing.T) { + // Using an invalid file descriptor should return appropriate error + buffer := make([]byte, 10) + _, err := syscall.Read(999, buffer) // 999 is likely an invalid fd + require.Error(t, err) + require.Equal(t, syscall.EBADF, err) + }) + + t.Run("PermissionDenied", func(t *testing.T) { + testFile := filepath.Join(mountPoint, "readonly.txt") + + // Create read-only file + err := os.WriteFile(testFile, []byte("readonly"), 0444) + require.NoError(t, err) + + // Attempting to write to read-only file should fail + err = os.WriteFile(testFile, []byte("modified"), 0444) + require.Error(t, err) + require.True(t, os.IsPermission(err)) + }) +} |
