You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
710 lines
20 KiB
710 lines
20 KiB
//go:build !windows
|
|
|
|
package fuse
|
|
|
|
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)
|
|
|
|
// Note: The final file permissions are affected by the system's umask.
|
|
// We check against the requested mode and the mode as affected by a common umask (e.g. 0022).
|
|
actualMode := stat.Mode() & os.ModePerm
|
|
expectedMode := os.FileMode(0642)
|
|
expectedModeWithUmask := os.FileMode(0640) // e.g., 0642 with umask 0002 or 0022
|
|
|
|
// Accept either the exact permissions or permissions as modified by umask
|
|
if actualMode != expectedMode && actualMode != expectedModeWithUmask {
|
|
t.Errorf("Expected file permissions %o or %o, but got %o",
|
|
expectedMode, expectedModeWithUmask, actualMode)
|
|
}
|
|
})
|
|
|
|
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 been updated, and modification time should be unchanged.
|
|
stat1Sys := stat1.Sys().(*syscall.Stat_t)
|
|
stat2Sys := stat2.Sys().(*syscall.Stat_t)
|
|
require.True(t, getAtimeNano(stat2Sys) >= getAtimeNano(stat1Sys), "access time should be updated or stay the same")
|
|
require.Equal(t, stat1.ModTime(), stat2.ModTime(), "modification time should not change on read")
|
|
})
|
|
|
|
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)
|
|
|
|
// Test positioned I/O operations (pread/pwrite)
|
|
syscall.Close(fd)
|
|
|
|
// Open for read/write to test pwrite
|
|
fd, err = syscall.Open(testFile, syscall.O_RDWR, 0)
|
|
require.NoError(t, err)
|
|
defer syscall.Close(fd)
|
|
|
|
// Positioned write test
|
|
writeData := []byte("XYZ")
|
|
n, err = syscall.Pwrite(fd, writeData, 5) // pwrite at offset 5
|
|
require.NoError(t, err)
|
|
require.Equal(t, len(writeData), n)
|
|
|
|
// Verify file position is unchanged by pwrite
|
|
currentPos, err := syscall.Seek(fd, 0, 1) // SEEK_CUR
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(0), currentPos, "file offset should not be changed by pwrite")
|
|
|
|
// Read back with pread
|
|
readBuffer := make([]byte, len(writeData))
|
|
n, err = syscall.Pread(fd, readBuffer, 5) // pread at offset 5
|
|
require.NoError(t, err)
|
|
require.Equal(t, len(writeData), n)
|
|
require.Equal(t, writeData, readBuffer)
|
|
|
|
// Verify file position is still unchanged by pread
|
|
currentPos, err = syscall.Seek(fd, 0, 1) // SEEK_CUR
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(0), currentPos, "file offset should not be changed by pread")
|
|
})
|
|
|
|
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("ConcurrentFileCreations", 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))
|
|
})
|
|
}
|