Chris Lu 1 day ago
committed by GitHub
parent
commit
e8e015a0db
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 753
      .github/workflows/posix-compliance.yml
  2. 654
      test/fuse_integration/POSIX_COMPLIANCE.md
  3. 271
      test/fuse_integration/POSIX_IMPLEMENTATION_ROADMAP.md
  4. 12
      test/fuse_integration/atime_linux.go
  5. 12
      test/fuse_integration/atime_nonlinux.go
  6. 12
      test/fuse_integration/atime_windows.go
  7. 5
      test/fuse_integration/concurrent_operations_test.go
  8. 39
      test/fuse_integration/directio_darwin.go
  9. 21
      test/fuse_integration/directio_linux.go
  10. 29
      test/fuse_integration/directio_unsupported.go
  11. 2
      test/fuse_integration/directory_operations_test.go
  12. 79
      test/fuse_integration/fallocate_darwin.go
  13. 38
      test/fuse_integration/fallocate_linux.go
  14. 30
      test/fuse_integration/fallocate_unsupported.go
  15. 58
      test/fuse_integration/framework.go
  16. 11
      test/fuse_integration/go.mod
  17. 10
      test/fuse_integration/go.sum
  18. 2
      test/fuse_integration/minimal_test.go
  19. 85
      test/fuse_integration/mmap_unix.go
  20. 47
      test/fuse_integration/mmap_unsupported.go
  21. 509
      test/fuse_integration/posix_Makefile
  22. 710
      test/fuse_integration/posix_compliance_test.go
  23. 758
      test/fuse_integration/posix_extended_test.go
  24. 562
      test/fuse_integration/posix_external_test.go
  25. 43
      test/fuse_integration/sendfile_darwin.go
  26. 33
      test/fuse_integration/sendfile_linux.go
  27. 19
      test/fuse_integration/sendfile_unsupported.go
  28. 2
      test/fuse_integration/simple_test.go
  29. 126
      test/fuse_integration/vectored_io_unix.go
  30. 41
      test/fuse_integration/vectored_io_unsupported.go
  31. 2
      test/fuse_integration/working_demo_test.go
  32. 125
      test/fuse_integration/xattr_darwin.go
  33. 115
      test/fuse_integration/xattr_linux.go
  34. 31
      test/fuse_integration/xattr_unsupported.go

753
.github/workflows/posix-compliance.yml

@ -0,0 +1,753 @@
name: POSIX Compliance Tests
on:
push:
branches: [ main, master, develop ]
paths:
- 'weed/mount/**'
- 'weed/command/mount*'
- 'weed/command/fuse*'
- 'test/fuse_integration/**'
pull_request:
branches: [ main, master, develop ]
paths:
- 'weed/mount/**'
- 'weed/command/mount*'
- 'weed/command/fuse*'
- 'test/fuse_integration/**'
workflow_dispatch:
inputs:
test_type:
description: 'Type of POSIX tests to run (default: full for comprehensive testing)'
required: true
default: 'full'
type: choice
options:
- critical
- basic
- extended
- full
enable_external_tests:
description: 'Run external test suites (slower)'
required: false
default: false
type: boolean
env:
GO_VERSION: '1.24'
TIMEOUT: '45m'
jobs:
posix-compliance-ubuntu:
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
matrix:
fuse-version: ['2.9', '3.0']
fail-fast: false
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}
cache: true
cache-dependency-path: |
go.sum
test/fuse_integration/go.sum
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
attr \
acl \
build-essential \
git \
python3-pip
# Install FUSE version specific packages
if [ "${{ matrix.fuse-version }}" = "2.9" ]; then
echo "Installing FUSE 2.9..."
sudo apt-get install -y fuse libfuse-dev
elif [ "${{ matrix.fuse-version }}" = "3.0" ]; then
echo "Installing FUSE 3.0..."
sudo apt-get install -y fuse3 libfuse3-dev
# Also install fuse2 for compatibility if needed
sudo apt-get install -y fuse libfuse-dev
fi
- name: Set up user permissions for FUSE
run: |
# Create fuse group if it doesn't exist
sudo groupadd -f fuse
# Add user to fuse group
sudo usermod -a -G fuse $USER
# Set permissions on FUSE device
sudo chmod 666 /dev/fuse
# Ensure fuse module is loaded
sudo modprobe fuse || true
# Verify setup
echo "FUSE setup verification:"
ls -la /dev/fuse
groups $USER | grep fuse || echo "User not in fuse group yet (will be effective after login)"
lsmod | grep fuse || echo "FUSE module not loaded"
- name: Install external test tools
if: ${{ github.event.inputs.enable_external_tests == 'true' }}
run: |
# Install nfstest for POSIX API verification
pip3 install --user nfstest
# Install FIO for performance testing
sudo apt-get install -y fio
# Install additional test utilities
sudo apt-get install -y stress-ng
- name: Build SeaweedFS
run: |
make
# Verify binary exists and is executable (it's installed to GOPATH/bin)
weed version
# Make weed binary available in PATH (it's already there from go install)
sudo cp $(which weed) /usr/local/bin/weed
which weed
weed version
- name: Set up SeaweedFS cluster
run: |
start_and_wait() {
local name=$1
local url=$2
local pidfile=$3
shift 3
local cmd="$@"
echo "Starting $name..."
$cmd > "/tmp/seaweedfs/$name.log" 2>&1 &
local pid=$!
echo $pid > "$pidfile"
echo "Waiting for $name to start..."
for i in {1..30}; do
if curl -sf "$url" > /dev/null 2>&1; then
echo "$name is ready"
return 0
fi
if [ $i -eq 30 ]; then
echo "$name failed to start"
cat "/tmp/seaweedfs/$name.log"
exit 1
fi
sleep 2
done
}
mkdir -p /tmp/seaweedfs/{master,volume,filer,mount}
start_and_wait "master" "http://127.0.0.1:9333/cluster/status" "/tmp/seaweedfs/master.pid" \
"weed master -ip=127.0.0.1 -port=9333 -mdir=/tmp/seaweedfs/master -raftBootstrap=true"
start_and_wait "volume" "http://127.0.0.1:8080/status" "/tmp/seaweedfs/volume.pid" \
"weed volume -mserver=127.0.0.1:9333 -ip=127.0.0.1 -port=8080 -dir=/tmp/seaweedfs/volume -max=100"
start_and_wait "filer" "http://127.0.0.1:8888/" "/tmp/seaweedfs/filer.pid" \
"weed filer -master=127.0.0.1:9333 -ip=127.0.0.1 -port=8888"
echo "SeaweedFS cluster status:"
curl -s http://127.0.0.1:9333/cluster/status || true
- name: Set up FUSE mount
run: |
# Create mount point
MOUNT_POINT="/tmp/seaweedfs/mount"
mkdir -p $MOUNT_POINT
echo "Mounting SeaweedFS FUSE filesystem..."
# Mount SeaweedFS FUSE filesystem in background
weed mount \
-filer=127.0.0.1:8888 \
-dir=$MOUNT_POINT \
-filer.path=/ \
-dirAutoCreate=true \
-allowOthers=true \
-nonempty=true \
> /tmp/seaweedfs/mount.log 2>&1 &
MOUNT_PID=$!
echo $MOUNT_PID > /tmp/seaweedfs/mount.pid
# Wait for mount to be ready
echo "Waiting for FUSE mount to be ready..."
for i in {1..30}; do
if mountpoint -q $MOUNT_POINT 2>/dev/null; then
echo "FUSE mount is ready"
break
fi
# Alternative check - try to list the directory
if ls -la $MOUNT_POINT > /dev/null 2>&1; then
echo "FUSE mount is ready (directory accessible)"
break
fi
if [ $i -eq 30 ]; then
echo "FUSE mount failed to be ready"
cat /tmp/seaweedfs/mount.log
echo "Mount status:"
mount | grep fuse || echo "No FUSE mounts found"
ps aux | grep weed || echo "No weed processes found"
exit 1
fi
sleep 2
done
# Verify mount is working
echo "Testing FUSE mount functionality..."
echo "test" > $MOUNT_POINT/test_file.txt
if [ "$(cat $MOUNT_POINT/test_file.txt)" = "test" ]; then
echo "FUSE mount is working correctly"
rm $MOUNT_POINT/test_file.txt
else
echo "FUSE mount is not working properly"
exit 1
fi
# Export mount point for tests
echo "SEAWEEDFS_MOUNT_POINT=$MOUNT_POINT" >> $GITHUB_ENV
- name: Set up test environment
run: |
cd test/fuse_integration
# Initialize Go module for tests
if [ ! -f go.mod ]; then
go mod init seaweedfs-posix-tests
go mod tidy
fi
# Create reports directory
mkdir -p reports
# Set up external tools if requested
# Note: External tools setup is now optional and won't fail the build
if [ "${{ github.event.inputs.enable_external_tests }}" = "true" ]; then
echo "Setting up external POSIX testing tools..."
if make -f posix_Makefile setup-external-tools; then
# Make external tools mandatory when explicitly requested and successfully set up
export POSIX_REQUIRE_EXTERNAL_TOOLS="true"
echo "POSIX_REQUIRE_EXTERNAL_TOOLS=true" >> $GITHUB_ENV
echo "✅ External tools setup completed successfully"
else
echo "⚠️ External tools setup failed - continuing with basic tests only"
echo "External tools will be skipped but tests will continue"
echo "This is expected behavior when build tools are not available"
fi
fi
# Verify SeaweedFS cluster is accessible
echo "Verifying SeaweedFS cluster accessibility..."
curl -sf http://127.0.0.1:9333/cluster/status
curl -sf http://127.0.0.1:8080/status
curl -sf http://127.0.0.1:8888/
# Verify mount point
echo "Mount point: $SEAWEEDFS_MOUNT_POINT"
ls -la $SEAWEEDFS_MOUNT_POINT
# Verify mount is actually working by testing file operations
echo "Verifying SeaweedFS mount functionality..."
TEST_FILE="$SEAWEEDFS_MOUNT_POINT/.mount_verification_test"
echo "SeaweedFS mount test" > "$TEST_FILE"
if [ "$(cat "$TEST_FILE")" = "SeaweedFS mount test" ]; then
echo "✅ Mount verification successful"
rm -f "$TEST_FILE"
else
echo "❌ Mount verification failed - mount may not be working properly"
exit 1
fi
# Check if this is actually a FUSE mount
if mount | grep -q "$SEAWEEDFS_MOUNT_POINT"; then
echo "✅ FUSE mount detected:"
mount | grep "$SEAWEEDFS_MOUNT_POINT"
else
echo "⚠️ Warning: Mount point not found in mount table"
fi
- name: Run POSIX compliance tests
id: posix-tests
env:
SEAWEEDFS_MOUNT_POINT: ${{ env.SEAWEEDFS_MOUNT_POINT }}
POSIX_REQUIRE_EXTERNAL_TOOLS: ${{ env.POSIX_REQUIRE_EXTERNAL_TOOLS }}
run: |
cd test/fuse_integration
# Determine which tests to run
TEST_TYPE="${{ github.event.inputs.test_type }}"
if [ -z "$TEST_TYPE" ]; then
TEST_TYPE="full"
fi
echo "Running POSIX tests: $TEST_TYPE"
if [ "$TEST_TYPE" = "full" ]; then
echo "🚀 Running COMPREHENSIVE POSIX test suite (including external test suites)"
else
echo "Using mount point: $SEAWEEDFS_MOUNT_POINT"
fi
# Set test configuration to use our mounted filesystem
export TEST_MOUNT_POINT="$SEAWEEDFS_MOUNT_POINT"
export TEST_SKIP_CLUSTER_SETUP="true"
# Debug: Show environment variables
echo "🔍 Test Environment:"
echo " TEST_MOUNT_POINT=$TEST_MOUNT_POINT"
echo " TEST_SKIP_CLUSTER_SETUP=$TEST_SKIP_CLUSTER_SETUP"
echo " SEAWEEDFS_MOUNT_POINT=$SEAWEEDFS_MOUNT_POINT"
case "$TEST_TYPE" in
"critical")
echo "Running critical POSIX tests (basic compliance only)..."
make -f posix_Makefile test-posix-critical
;;
"basic")
echo "Running basic POSIX tests (core file operations)..."
make -f posix_Makefile test-posix-basic
;;
"extended")
echo "Running extended POSIX tests (including advanced features)..."
make -f posix_Makefile test-posix-extended
;;
"full")
echo "🚀 Running COMPREHENSIVE POSIX test suite (all tests including external)..."
echo " This includes: basic, extended, and external test suites"
echo " External suites: pjdfstest, nfstest_posix, and custom stress tests"
make -f posix_Makefile test-posix-full
;;
*)
echo "Unknown test type: $TEST_TYPE, defaulting to full suite"
echo "🚀 Running full POSIX test suite (all tests including external)..."
make -f posix_Makefile test-posix-full
;;
esac
- name: Run external test suites
if: ${{ github.event.inputs.enable_external_tests == 'true' }}
continue-on-error: true
run: |
cd test/fuse_integration
# Run external tests (may fail on some systems)
make -f posix_Makefile test-nfstest-posix || echo "nfstest failed or not available"
make -f posix_Makefile test-fio-posix || echo "FIO tests failed or not available"
- name: Generate compliance report
if: always()
run: |
cd test/fuse_integration
make -f posix_Makefile generate-report
- name: Cleanup SeaweedFS cluster and FUSE mount
if: always()
run: |
stop_process() {
local name=$1
local pidfile=$2
if [ -f "$pidfile" ]; then
local pid=$(cat "$pidfile")
if kill -0 $pid 2>/dev/null; then
echo "Stopping $name process (PID: $pid)..."
kill -TERM $pid || true
sleep 2
kill -KILL $pid 2>/dev/null || true
fi
fi
}
echo "Cleaning up SeaweedFS cluster and FUSE mount..."
MOUNT_POINT="/tmp/seaweedfs/mount"
if mountpoint -q $MOUNT_POINT 2>/dev/null; then
echo "Unmounting FUSE filesystem..."
fusermount -u $MOUNT_POINT || umount $MOUNT_POINT || true
fi
stop_process "mount" "/tmp/seaweedfs/mount.pid"
stop_process "filer" "/tmp/seaweedfs/filer.pid"
stop_process "volume" "/tmp/seaweedfs/volume.pid"
stop_process "master" "/tmp/seaweedfs/master.pid"
pkill -f "weed " || true
fusermount -u $MOUNT_POINT 2>/dev/null || true
umount $MOUNT_POINT 2>/dev/null || true
rm -rf /tmp/seaweedfs || true
echo "Cleanup completed"
- name: Collect system information for debugging
if: failure()
run: |
cd test/fuse_integration
mkdir -p reports/debug
echo "System Information" > reports/debug/system_info.txt
uname -a >> reports/debug/system_info.txt
echo "" >> reports/debug/system_info.txt
echo "Mount Information" >> reports/debug/system_info.txt
mount >> reports/debug/system_info.txt
echo "" >> reports/debug/system_info.txt
echo "Disk Space" >> reports/debug/system_info.txt
df -h >> reports/debug/system_info.txt
echo "" >> reports/debug/system_info.txt
echo "Memory Information" >> reports/debug/system_info.txt
free -h >> reports/debug/system_info.txt
echo "" >> reports/debug/system_info.txt
echo "FUSE Information" >> reports/debug/system_info.txt
ls -la /dev/fuse >> reports/debug/system_info.txt
lsmod | grep fuse >> reports/debug/system_info.txt
echo "" >> reports/debug/system_info.txt
echo "SeaweedFS Version" >> reports/debug/system_info.txt
weed version >> reports/debug/system_info.txt
echo "" >> reports/debug/system_info.txt
echo "Go Version" >> reports/debug/system_info.txt
go version >> reports/debug/system_info.txt
echo "" >> reports/debug/system_info.txt
echo "Running Processes" >> reports/debug/system_info.txt
ps aux | grep -E "(weed|fuse)" >> reports/debug/system_info.txt
echo "" >> reports/debug/system_info.txt
# Collect SeaweedFS service logs
echo "=== SeaweedFS Service Logs ===" >> reports/debug/system_info.txt
if [ -f /tmp/seaweedfs/master.log ]; then
echo "Master Log:" >> reports/debug/system_info.txt
tail -50 /tmp/seaweedfs/master.log >> reports/debug/system_info.txt
echo "" >> reports/debug/system_info.txt
fi
if [ -f /tmp/seaweedfs/volume.log ]; then
echo "Volume Log:" >> reports/debug/system_info.txt
tail -50 /tmp/seaweedfs/volume.log >> reports/debug/system_info.txt
echo "" >> reports/debug/system_info.txt
fi
if [ -f /tmp/seaweedfs/filer.log ]; then
echo "Filer Log:" >> reports/debug/system_info.txt
tail -50 /tmp/seaweedfs/filer.log >> reports/debug/system_info.txt
echo "" >> reports/debug/system_info.txt
fi
if [ -f /tmp/seaweedfs/mount.log ]; then
echo "Mount Log:" >> reports/debug/system_info.txt
tail -50 /tmp/seaweedfs/mount.log >> reports/debug/system_info.txt
echo "" >> reports/debug/system_info.txt
fi
# Copy full logs as separate files
if [ -f /tmp/seaweedfs/master.log ]; then
cp /tmp/seaweedfs/master.log reports/debug/master.log
fi
if [ -f /tmp/seaweedfs/volume.log ]; then
cp /tmp/seaweedfs/volume.log reports/debug/volume.log
fi
if [ -f /tmp/seaweedfs/filer.log ]; then
cp /tmp/seaweedfs/filer.log reports/debug/filer.log
fi
if [ -f /tmp/seaweedfs/mount.log ]; then
cp /tmp/seaweedfs/mount.log reports/debug/mount.log
fi
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: posix-test-results-ubuntu-fuse${{ matrix.fuse-version }}
path: |
test/fuse_integration/reports/
test/fuse_integration/*.log
retention-days: 30
- name: Upload test coverage
uses: actions/upload-artifact@v4
if: always() && (github.event.inputs.test_type == 'full' || github.event.inputs.test_type == 'extended')
with:
name: posix-coverage-ubuntu
path: test/fuse_integration/reports/posix_coverage.html
retention-days: 14
- name: Comment PR with results
if: github.event_name == 'pull_request' && always()
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const path = require('path');
const reportPath = 'test/fuse_integration/reports/posix_compliance_summary.txt';
if (fs.existsSync(reportPath)) {
const report = fs.readFileSync(reportPath, 'utf8');
const comment = `## POSIX Compliance Test Results
**Go Version:** ${{ env.GO_VERSION }}
**FUSE Version:** ${{ matrix.fuse-version }}
**Test Type:** ${{ github.event.inputs.test_type || 'full' }}
<details>
<summary>Test Summary</summary>
\`\`\`
${report}
\`\`\`
</details>
📊 Full results available in [test artifacts](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
}
posix-compliance-macos:
runs-on: macos-latest
timeout-minutes: 60
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}
cache: true
- name: Install macFUSE
run: |
# Install macFUSE
brew install macfuse
# Note: macFUSE may require system extension approval on macOS
# This step may need manual intervention in some cases
- name: Build SeaweedFS
run: |
make
# Verify binary exists and is executable (it's installed to GOPATH/bin)
weed version
- name: Run critical POSIX tests (macOS)
continue-on-error: true # macOS FUSE can be more restrictive
run: |
cd test/fuse_integration
if [ ! -f go.mod ]; then
go mod init seaweedfs-posix-tests
go mod tidy
fi
mkdir -p reports
# Run basic tests only on macOS due to FUSE limitations
make -f posix_Makefile test-posix-critical
- name: Upload macOS test results
uses: actions/upload-artifact@v4
if: always()
with:
name: posix-test-results-macos
path: |
test/fuse_integration/reports/
test/fuse_integration/*.log
retention-days: 30
security-analysis:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}
- name: Install security analysis tools
run: |
echo "Installing staticcheck for security analysis..."
go install honnef.co/go/tools/cmd/staticcheck@latest
- name: Run security analysis on FUSE code
run: |
# Analyze mount and FUSE-related code for security and quality issues
echo "Running staticcheck security analysis..."
# Run staticcheck on FUSE-related code
echo "Analyzing FUSE mount code..."
staticcheck ./weed/mount/... > staticcheck-report.txt 2>&1 || true
staticcheck ./weed/command/mount* ./weed/command/fuse* >> staticcheck-report.txt 2>&1 || true
# Convert to JSON format for consistency
echo "{" > gosec-report.json
echo " \"tool\": \"staticcheck\"," >> gosec-report.json
echo " \"timestamp\": \"$(date -Iseconds)\"," >> gosec-report.json
echo " \"issues\": [" >> gosec-report.json
if [ -s staticcheck-report.txt ]; then
echo " \"$(cat staticcheck-report.txt | head -20 | sed 's/"/\\"/g' | tr '\n' ' ')\"" >> gosec-report.json
fi
echo " ]," >> gosec-report.json
echo " \"stats\": {" >> gosec-report.json
echo " \"files_analyzed\": $(find ./weed/mount ./weed/command -name '*.go' | wc -l)," >> gosec-report.json
echo " \"issues_found\": $(wc -l < staticcheck-report.txt 2>/dev/null || echo 0)" >> gosec-report.json
echo " }" >> gosec-report.json
echo "}" >> gosec-report.json
echo "Security analysis completed"
echo "Issues found: $(wc -l < staticcheck-report.txt 2>/dev/null || echo 0)"
- name: Upload security analysis results
uses: actions/upload-artifact@v4
if: always()
with:
name: security-analysis-results
path: gosec-report.json
retention-days: 30
performance-baseline:
runs-on: ubuntu-latest
if: github.event.inputs.test_type == 'full' || github.event_name == 'schedule'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y fuse libfuse-dev fio
sudo usermod -a -G fuse $USER
sudo chmod 666 /dev/fuse
- name: Build SeaweedFS
run: make
- name: Run performance baseline tests
run: |
cd test/fuse_integration
if [ ! -f go.mod ]; then
go mod init seaweedfs-posix-tests
go mod tidy
fi
mkdir -p reports
# Run performance benchmarks
make -f posix_Makefile benchmark-posix
make -f posix_Makefile test-fio-posix
- name: Store performance baseline
uses: actions/upload-artifact@v4
with:
name: performance-baseline-results
path: |
test/fuse_integration/reports/posix_benchmark_results.log
test/fuse_integration/reports/fio_*_results.log
retention-days: 90
# Schedule regular compliance checks
scheduled-compliance-check:
runs-on: ubuntu-latest
if: github.event_name == 'schedule'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y fuse libfuse-dev
pip3 install --user nfstest
- name: Build SeaweedFS
run: make
- name: Run comprehensive compliance check
run: |
cd test/fuse_integration
make -f posix_Makefile test-posix-full
make -f posix_Makefile generate-report
- name: Create compliance issue if tests fail
if: failure()
uses: actions/github-script@v7
with:
script: |
const issue = await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'POSIX Compliance Check Failed - ' + new Date().toISOString().split('T')[0],
body: `## POSIX Compliance Check Failure
The scheduled POSIX compliance check has failed. This may indicate:
- Regression in FUSE mount functionality
- Changes affecting POSIX compatibility
- Infrastructure issues with the test environment
**Action Required:** Review the test results and investigate any failures.
**Test Run:** [Workflow Run](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})
**Date:** ${new Date().toISOString()}
---
This issue was automatically created by the POSIX compliance workflow.
`,
labels: ['bug', 'posix-compliance', 'automated-issue']
});
console.log('Created issue:', issue.data.number);
# Schedule this workflow to run weekly
# Uncomment the following lines to enable scheduled runs:
# schedule:
# - cron: '0 2 * * 1' # Every Monday at 2 AM UTC

654
test/fuse_integration/POSIX_COMPLIANCE.md

@ -0,0 +1,654 @@
# SeaweedFS FUSE POSIX Compliance Testing
## Overview
This comprehensive test suite provides full POSIX compliance testing for SeaweedFS FUSE mounts. It includes basic POSIX operations, extended features, external test suite integration, and performance benchmarking to ensure SeaweedFS meets filesystem standards.
## 🎯 Key Features
### ✅ **Comprehensive Test Coverage**
- **Basic POSIX Operations**: File/directory create, read, write, delete, rename
- **Advanced Features**: Extended attributes, file locking, memory mapping *(see roadmap)*
- **I/O Operations**: Synchronous/asynchronous I/O, direct I/O, vectored I/O *(see roadmap)*
- **Error Handling**: Comprehensive error condition testing
- **Concurrent Operations**: Multi-threaded stress testing
- **External Integration**: pjdfstest, nfstest, FIO integration
> **📋 Implementation Status**: Some advanced features are currently skipped pending platform-specific implementations. See [`POSIX_IMPLEMENTATION_ROADMAP.md`](./POSIX_IMPLEMENTATION_ROADMAP.md) for a detailed implementation plan and current status.
### 📊 **Performance Analysis**
- **Benchmarking**: Built-in performance benchmarks
- **Profiling**: CPU and memory profiling support
- **Coverage Analysis**: Code coverage reporting
- **Stress Testing**: High-load concurrent operation testing
### 🔧 **External Tool Integration**
- **pjdfstest**: Industry-standard POSIX filesystem test suite
- **nfstest_posix**: Network filesystem POSIX API verification
- **FIO**: Flexible I/O performance testing
- **Custom Litmus Tests**: Focused edge case testing
## 📋 Test Categories
### 1. Basic POSIX Compliance (`posix_compliance_test.go`)
#### File Operations
- ✅ File creation with O_CREAT, O_EXCL flags
- ✅ File truncation and size management
- ✅ File deletion and unlinking
- ✅ Atomic rename operations
- ✅ File permission management
#### Directory Operations
- ✅ Directory creation and removal
- ✅ Directory listing and traversal
- ✅ Directory rename operations
- ✅ Non-empty directory handling
#### Symlink Operations
- ✅ Symbolic link creation and resolution
- ✅ Broken symlink handling
- ✅ Symlink target verification
- ✅ Symlink permission handling
#### Permission Tests
- ✅ File permission setting and verification
- ✅ Directory permission enforcement
- ✅ Permission inheritance
- ✅ Access control validation
#### Timestamp Tests
- ✅ Access time (atime) updates
- ✅ Modification time (mtime) tracking
- ✅ Change time (ctime) management
- ✅ Timestamp precision and setting
#### I/O Operations
- ✅ Read/write operations
- ✅ File seeking and positioning
- ✅ Append mode operations
- ✅ Buffer management
#### File Descriptors
- ✅ File descriptor duplication
- ✅ File descriptor flags
- ✅ Close-on-exec handling
- ✅ File descriptor limits
#### Atomic Operations
- ✅ Atomic file operations
- ✅ Rename atomicity
- ✅ Link/unlink atomicity
#### Concurrent Access
- ✅ Multi-reader scenarios
- ✅ Concurrent write handling
- ✅ Reader-writer coordination
- ✅ Race condition prevention
#### Error Handling
- ✅ ENOENT (file not found) handling
- ✅ EACCES (permission denied) handling
- ✅ EBADF (bad file descriptor) handling
- ✅ ENOTEMPTY (directory not empty) handling
- ✅ Error code compliance
### 2. Extended POSIX Features (`posix_extended_test.go`)
#### Extended Attributes
- ✅ Setting extended attributes (setxattr)
- ✅ Getting extended attributes (getxattr)
- ✅ Listing extended attributes (listxattr)
- ✅ Removing extended attributes (removexattr)
- ✅ Extended attribute namespaces
#### File Locking
- ✅ Advisory locking (fcntl)
- ✅ Exclusive locks (F_WRLCK)
- ✅ Shared locks (F_RDLCK)
- ✅ Lock conflict detection
- ✅ Lock release and cleanup
#### Advanced I/O
- ✅ Vectored I/O (readv/writev)
- ✅ Positioned I/O (pread/pwrite)
- ✅ Asynchronous I/O patterns
- ✅ Scatter-gather operations
#### Sparse Files
- ✅ Sparse file creation
- ✅ Hole detection and handling
- ✅ Sparse file efficiency
- ✅ Seek beyond EOF
#### Large Files (>2GB)
- ✅ Large file operations
- ✅ 64-bit offset handling
- ✅ Large file seeking
- ✅ Large file truncation
#### Memory Mapping
- ✅ File memory mapping (mmap)
- ✅ Memory-mapped I/O
- ✅ Memory synchronization (msync)
- ✅ Memory unmapping (munmap)
- ✅ Shared/private mappings
#### Direct I/O
- ✅ Direct I/O operations (O_DIRECT)
- ✅ Buffer alignment requirements
- ✅ Direct I/O performance
- ✅ Bypass buffer cache
#### File Preallocation
- ✅ Space preallocation (fallocate)
- ✅ File hole punching
- ✅ Space reservation
- ✅ Allocation efficiency
#### Zero-Copy Operations
- ✅ Zero-copy file transfer (sendfile)
- ✅ Efficient data movement
- ✅ Cross-filesystem transfers
### 3. External Test Suite Integration (`posix_external_test.go`)
#### pjdfstest Integration
- ✅ Comprehensive POSIX filesystem test suite
- ✅ Industry-standard compliance verification
- ✅ Automated test execution
- ✅ Result analysis and reporting
#### nfstest_posix Integration
- ✅ POSIX API verification
- ✅ Network filesystem compliance
- ✅ API behavior validation
- ✅ Comprehensive coverage
#### Custom Litmus Tests
- ✅ Atomic rename verification
- ✅ Link count accuracy
- ✅ Symlink cycle detection
- ✅ Concurrent operation safety
- ✅ Directory consistency
#### Stress Testing
- ✅ High-frequency operations
- ✅ Concurrent access patterns
- ✅ Resource exhaustion scenarios
- ✅ Error recovery testing
#### Edge Case Testing
- ✅ Empty filenames
- ✅ Very long filenames
- ✅ Special characters
- ✅ Deep directory nesting
- ✅ Path length limits
## 🚀 Quick Start
### Prerequisites
```bash
# Install required tools
sudo apt-get install fuse # Linux
brew install macfuse # macOS
# Install Go 1.21+
go version
# Build SeaweedFS
cd /path/to/seaweedfs
make
```
### Basic Usage
```bash
# Navigate to test directory
cd test/fuse_integration
# Check prerequisites
make -f posix_Makefile check-prereqs
# Run basic POSIX compliance tests
make -f posix_Makefile test-posix-basic
# Run complete test suite
make -f posix_Makefile test-posix-full
# Generate compliance report
make -f posix_Makefile generate-report
```
## 🔧 Advanced Usage
### Test Categories
#### Critical Tests Only
```bash
# Run only critical POSIX compliance tests (faster)
make -f posix_Makefile test-posix-critical
```
#### Extended Feature Tests
```bash
# Test advanced POSIX features
make -f posix_Makefile test-posix-extended
```
#### External Tool Integration
```bash
# Setup and run external test suites
make -f posix_Makefile setup-external-tools
make -f posix_Makefile test-posix-external
```
#### Stress Testing
```bash
# Run stress tests for concurrent operations
make -f posix_Makefile test-posix-stress
```
### Performance Analysis
#### Benchmarking
```bash
# Run performance benchmarks
make -f posix_Makefile benchmark-posix
```
#### Profiling
```bash
# Profile CPU and memory usage
make -f posix_Makefile profile-posix
# View CPU profile
go tool pprof reports/posix.cpu.prof
```
#### Coverage Analysis
```bash
# Generate test coverage report
make -f posix_Makefile coverage-posix
# View: reports/posix_coverage.html
```
#### FIO Performance Testing
```bash
# Run FIO-based I/O performance tests
make -f posix_Makefile test-fio-posix
```
### External Test Suites
#### pjdfstest
```bash
# Setup and run pjdfstest
make -f posix_Makefile setup-pjdfstest
make -f posix_Makefile test-pjdfstest
```
#### nfstest_posix
```bash
# Install and run nfstest_posix
make -f posix_Makefile setup-nfstest
make -f posix_Makefile test-nfstest-posix
```
## 📊 Understanding Results
### Test Status Indicators
- ✅ **PASS**: Test completed successfully
- ❌ **FAIL**: Test failed - indicates non-compliance
- ⚠️ **SKIP**: Test skipped - feature not supported
- 🔄 **RETRY**: Test retried due to transient failure
### Report Formats
#### HTML Report (`reports/posix_compliance_report.html`)
- Interactive web-based report
- Test category breakdown
- Detailed results with navigation
- Visual status indicators
#### Text Summary (`reports/posix_compliance_summary.txt`)
- Concise text-based summary
- Key findings and recommendations
- Command-line friendly format
#### JSON Report (`reports/posix_compliance_report.json`)
- Machine-readable results
- Integration with CI/CD pipelines
- Automated result processing
### Log Files
- `posix_basic_results.log`: Basic POSIX test detailed output
- `posix_extended_results.log`: Extended feature test output
- `posix_external_results.log`: External test suite output
- `posix_benchmark_results.log`: Performance benchmark results
## 🐛 Debugging Failed Tests
### Common Issues
#### Permission Denied Errors
```bash
# Ensure proper FUSE permissions
sudo usermod -a -G fuse $USER
# Re-login or run: newgrp fuse
# Check mount options
make -f posix_Makefile test-posix-basic MOUNT_OPTIONS="-allowOthers"
```
#### Extended Attributes Not Supported
```bash
# Check if xattr is supported by underlying filesystem
getfattr --version
# May need to skip xattr tests on certain filesystems
```
#### File Locking Issues
```bash
# Verify file locking support
# Some network filesystems may not support full locking
```
#### Memory Issues
```bash
# Increase available memory for tests
# Large file tests may require significant RAM
```
### Debug Mode
```bash
# Run tests with verbose output
make -f posix_Makefile test-posix-basic VERBOSE=1
# Keep test files for inspection
make -f posix_Makefile test-posix-basic CLEANUP=false
# Enable debug logging
make -f posix_Makefile test-posix-basic DEBUG=true
```
### Manual Test Execution
```bash
# Run specific test
cd test/fuse_integration
go test -v -run TestPOSIXCompliance/FileOperations posix_compliance_test.go
# Run with custom timeout
go test -v -timeout 30m -run TestPOSIXExtended posix_extended_test.go
```
## 🔄 CI/CD Integration
### GitHub Actions
```yaml
name: POSIX Compliance Tests
on: [push, pull_request]
jobs:
posix-compliance:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Install FUSE
run: sudo apt-get update && sudo apt-get install -y fuse
- name: Build SeaweedFS
run: make
- name: Run POSIX Compliance Tests
run: |
cd test/fuse_integration
make -f posix_Makefile ci-posix-tests
- name: Upload Test Results
uses: actions/upload-artifact@v4
if: always()
with:
name: posix-test-results
path: test/fuse_integration/reports/
```
### Jenkins Pipeline
```groovy
pipeline {
agent any
stages {
stage('Setup') {
steps {
sh 'make'
}
}
stage('POSIX Compliance') {
steps {
dir('test/fuse_integration') {
sh 'make -f posix_Makefile ci-posix-tests'
}
}
post {
always {
archiveArtifacts 'test/fuse_integration/reports/**'
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'test/fuse_integration/reports',
reportFiles: 'posix_compliance_report.html',
reportName: 'POSIX Compliance Report'
])
}
}
}
}
}
```
### Docker-based Testing
```bash
# Build Docker image for testing
cd test/fuse_integration
make -f posix_Makefile docker-test-posix
```
## 🎯 POSIX Compliance Checklist
### Critical POSIX Features
- [ ] File creation, reading, writing, deletion
- [ ] Directory operations (create, remove, list)
- [ ] File permissions and ownership
- [ ] Symbolic links
- [ ] Hard links
- [ ] File timestamps
- [ ] Error handling compliance
- [ ] Atomic operations
### Advanced POSIX Features
- [ ] Extended attributes
- [ ] File locking (advisory)
- [ ] Memory-mapped I/O
- [ ] Vectored I/O (readv/writev)
- [ ] Positioned I/O (pread/pwrite)
- [ ] Direct I/O
- [ ] File preallocation
- [ ] Sparse files
### Performance Requirements
- [ ] Reasonable performance under load
- [ ] Concurrent access handling
- [ ] Memory usage optimization
- [ ] CPU efficiency
- [ ] I/O throughput
### Reliability Requirements
- [ ] Data consistency
- [ ] Error recovery
- [ ] Race condition prevention
- [ ] Resource cleanup
- [ ] Crash recovery
## 📈 Performance Benchmarks
### Expected Performance Characteristics
| Operation | Expected Range | Notes |
|-----------|----------------|-------|
| File Create | 1000-10000 ops/sec | Depends on chunk size |
| File Read | 100-1000 MB/sec | Network limited |
| File Write | 50-500 MB/sec | Depends on replication |
| Directory Ops | 500-5000 ops/sec | Metadata operations |
| Concurrent Ops | Scales with cores | Up to hardware limits |
### Benchmarking Commands
```bash
# Basic I/O performance
make -f posix_Makefile benchmark-posix
# FIO comprehensive testing
make -f posix_Makefile test-fio-posix
# Custom benchmark with specific parameters
go test -v -bench=. -benchtime=30s posix_compliance_test.go
```
## 🔍 Troubleshooting
### Common Test Failures
#### 1. Mount Permission Issues
```bash
# Error: Permission denied mounting
# Solution: Check FUSE permissions
sudo chmod 666 /dev/fuse
sudo usermod -a -G fuse $USER
```
#### 2. Extended Attribute Tests Fail
```bash
# Error: Operation not supported (ENOTSUP)
# Solution: Check filesystem xattr support
mount | grep $(df . | tail -1 | awk '{print $1}')
# May need to remount with xattr support
```
#### 3. File Locking Tests Fail
```bash
# Error: Function not implemented (ENOSYS)
# Solution: Some network filesystems don't support locking
# This may be expected behavior
```
#### 4. Large File Tests Fail
```bash
# Error: No space left on device
# Solution: Ensure sufficient disk space
df -h /tmp # Check temp directory space
# May need to clean up or use different temp directory
```
#### 5. Performance Tests Timeout
```bash
# Error: Test timeout exceeded
# Solution: Increase timeout or reduce test scope
go test -timeout 60m -run TestPOSIXCompliance
```
### Debug Information Collection
```bash
# Collect system information
uname -a > debug_info.txt
mount >> debug_info.txt
df -h >> debug_info.txt
free -h >> debug_info.txt
# Collect SeaweedFS information
./weed version >> debug_info.txt
ps aux | grep weed >> debug_info.txt
# Collect test logs
cp reports/*.log debug_logs/
```
## 📚 Additional Resources
### POSIX Standards
- [POSIX.1-2017 (IEEE Std 1003.1-2017)](https://pubs.opengroup.org/onlinepubs/9699919799/)
- [Single UNIX Specification](https://www.opengroup.org/openbrand/register/)
### External Test Suites
- [pjdfstest](https://github.com/pjd/pjdfstest) - Comprehensive POSIX filesystem tests
- [nfstest](https://nfstest.readthedocs.io/) - Network filesystem testing
- [FIO](https://fio.readthedocs.io/) - Flexible I/O tester
### SeaweedFS Documentation
- [SeaweedFS Wiki](https://github.com/seaweedfs/seaweedfs/wiki)
- [FUSE Mount Documentation](https://github.com/seaweedfs/seaweedfs/wiki/FUSE-Mount)
- [Performance Tuning Guide](https://github.com/seaweedfs/seaweedfs/wiki/Optimization)
## 🤝 Contributing
### Adding New Tests
1. **Basic Tests**: Add to `posix_compliance_test.go`
2. **Extended Tests**: Add to `posix_extended_test.go`
3. **External Integration**: Add to `posix_external_test.go`
### Test Guidelines
```go
func (s *POSIXTestSuite) TestNewFeature(t *testing.T) {
mountPoint := s.framework.GetMountPoint()
t.Run("SubTestName", func(t *testing.T) {
// Test setup
testFile := filepath.Join(mountPoint, "test.txt")
// Test execution
err := someOperation(testFile)
// Assertions
require.NoError(t, err)
// Cleanup (if needed)
defer os.Remove(testFile)
})
}
```
### Submitting Improvements
1. Fork the repository
2. Create a feature branch
3. Add comprehensive tests
4. Ensure all existing tests pass
5. Submit a pull request with detailed description
## 📞 Support
For questions, issues, or contributions:
- **GitHub Issues**: [SeaweedFS Issues](https://github.com/seaweedfs/seaweedfs/issues)
- **Discussions**: [SeaweedFS Discussions](https://github.com/seaweedfs/seaweedfs/discussions)
- **Documentation**: [SeaweedFS Wiki](https://github.com/seaweedfs/seaweedfs/wiki)
---
*This comprehensive POSIX compliance test suite ensures SeaweedFS FUSE mounts meet industry standards for filesystem behavior, performance, and reliability.*

271
test/fuse_integration/POSIX_IMPLEMENTATION_ROADMAP.md

@ -0,0 +1,271 @@
# POSIX Compliance Implementation Roadmap
This document tracks the implementation status of POSIX features in the SeaweedFS FUSE mount compliance test suite and provides a roadmap for completing comprehensive POSIX compliance testing.
## Overview
The POSIX compliance test suite currently has several tests that are skipped due to requiring platform-specific implementations. This roadmap outlines the steps needed to implement these tests and achieve comprehensive POSIX compliance validation.
## Current Status
### ✅ Implemented Features
- Basic file operations (create, read, write, delete)
- Directory operations (mkdir, rmdir, rename)
- File permissions and ownership
- Symbolic and hard links
- Basic I/O operations (seek, append, positioned I/O)
- File descriptors and atomic operations
- Concurrent access patterns
- Error handling compliance
- Timestamp operations
### 🚧 Partially Implemented
- Cross-platform compatibility (basic implementation with platform-specific access time handling)
### ❌ Missing Implementations
#### 1. Extended Attributes (xattr)
**Priority: High**
**Platforms: Linux, macOS, FreeBSD**
**Current Status:** All xattr tests are skipped
- `TestExtendedAttributes/SetExtendedAttribute`
- `TestExtendedAttributes/ListExtendedAttributes`
- `TestExtendedAttributes/RemoveExtendedAttribute`
**Implementation Plan:**
```go
// Linux implementation
//go:build linux
func setXattr(path, name string, value []byte) error {
return syscall.Setxattr(path, name, value, 0)
}
// macOS implementation
//go:build darwin
func setXattr(path, name string, value []byte) error {
return syscall.Setxattr(path, name, value, 0, 0)
}
```
**Required Files:**
- `xattr_linux.go` - Linux syscall implementations
- `xattr_darwin.go` - macOS syscall implementations
- `xattr_freebsd.go` - FreeBSD syscall implementations
- `xattr_unsupported.go` - Fallback for unsupported platforms
#### 2. Memory Mapping (mmap)
**Priority: High**
**Platforms: All POSIX systems**
**Current Status:** All mmap tests are skipped
- `TestMemoryMapping/MemoryMappedRead`
- `TestMemoryMapping/MemoryMappedWrite`
**Implementation Plan:**
```go
func testMmap(t *testing.T, filePath string) {
fd, err := syscall.Open(filePath, syscall.O_RDWR, 0)
require.NoError(t, err)
defer syscall.Close(fd)
// Platform-specific mmap implementation
data, err := syscall.Mmap(fd, 0, size, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
require.NoError(t, err)
defer syscall.Munmap(data)
}
```
**Required Files:**
- `mmap_unix.go` - Unix-like systems implementation
- `mmap_windows.go` - Windows implementation (if needed)
#### 3. Vectored I/O (readv/writev)
**Priority: Medium**
**Platforms: All POSIX systems**
**Current Status:** Vectored I/O test is skipped
- `TestAdvancedIO/VectoredIO`
**Implementation Plan:**
```go
func testVectoredIO(t *testing.T, filePath string) {
// Use syscall.Syscall for readv/writev
// Linux: SYS_READV, SYS_WRITEV
// Other platforms: platform-specific syscall numbers
}
```
#### 4. Direct I/O
**Priority: Medium**
**Platforms: Linux, some Unix variants**
**Current Status:** Direct I/O test is skipped
- `TestDirectIO/DirectIO`
**Implementation Plan:**
```go
//go:build linux
func testDirectIO(t *testing.T, filePath string) {
fd, err := syscall.Open(filePath, syscall.O_RDWR|syscall.O_DIRECT, 0)
// Test with aligned buffers
}
```
#### 5. File Preallocation (fallocate)
**Priority: Medium**
**Platforms: Linux, some Unix variants**
**Current Status:** fallocate test is skipped
- `TestFilePreallocation/Fallocate`
**Implementation Plan:**
```go
//go:build linux
func testFallocate(t *testing.T, filePath string) {
fd, err := syscall.Open(filePath, syscall.O_RDWR, 0)
err = syscall.Syscall6(syscall.SYS_FALLOCATE, uintptr(fd), 0, 0, uintptr(size), 0, 0)
}
```
#### 6. Zero-Copy Transfer (sendfile)
**Priority: Low**
**Platforms: Linux, FreeBSD, some Unix variants**
**Current Status:** sendfile test is skipped
- `TestZeroCopyTransfer/Sendfile`
**Implementation Plan:**
```go
//go:build linux
func testSendfile(t *testing.T, srcPath, dstPath string) {
// Use syscall.Syscall for sendfile
// Platform-specific implementations
}
```
#### 7. File Sealing (Linux-specific)
**Priority: Low**
**Platforms: Linux only**
**Current Status:** File sealing test is skipped
- `TestFileSealing`
**Implementation Plan:**
```go
//go:build linux
func testFileSealing(t *testing.T) {
// Use fcntl with F_ADD_SEALS, F_GET_SEALS
// Test various seal types: F_SEAL_WRITE, F_SEAL_SHRINK, etc.
}
```
## Implementation Strategy
### Phase 1: Core Features (High Priority)
1. **Extended Attributes** - Essential for many applications
2. **Memory Mapping** - Critical for performance-sensitive applications
### Phase 2: Advanced I/O (Medium Priority)
3. **Vectored I/O** - Important for efficient bulk operations
4. **Direct I/O** - Needed for database and high-performance applications
5. **File Preallocation** - Important for preventing fragmentation
### Phase 3: Specialized Features (Low Priority)
6. **Zero-Copy Transfer** - Performance optimization feature
7. **File Sealing** - Security feature, Linux-specific
## Platform Support Matrix
| Feature | Linux | macOS | FreeBSD | Windows | Notes |
|---------|-------|-------|---------|---------|-------|
| Extended Attributes | ✅ | ✅ | ✅ | ❌ | Different syscall signatures |
| Memory Mapping | ✅ | ✅ | ✅ | ✅ | Standard POSIX |
| Vectored I/O | ✅ | ✅ | ✅ | ❌ | readv/writev syscalls |
| Direct I/O | ✅ | ❌ | ✅ | ❌ | O_DIRECT flag |
| fallocate | ✅ | ❌ | ❌ | ❌ | Linux-specific |
| sendfile | ✅ | ❌ | ✅ | ❌ | Platform-specific |
| File Sealing | ✅ | ❌ | ❌ | ❌ | Linux-only |
## Development Guidelines
### 1. Platform-Specific Implementation Pattern
```go
// feature_linux.go
//go:build linux
package fuse
func platformSpecificFunction() { /* Linux implementation */ }
// feature_darwin.go
//go:build darwin
package fuse
func platformSpecificFunction() { /* macOS implementation */ }
// feature_unsupported.go
//go:build !linux && !darwin
package fuse
func platformSpecificFunction() { /* Skip or error */ }
```
### 2. Test Structure Pattern
```go
func (s *POSIXExtendedTestSuite) TestFeature(t *testing.T) {
if !isFeatureSupported() {
t.Skip("Feature not supported on this platform")
return
}
t.Run("FeatureTest", func(t *testing.T) {
// Actual test implementation
})
}
```
### 3. Error Handling
- Use platform-specific error checking
- Provide meaningful error messages
- Gracefully handle unsupported features
## Testing Strategy
### 1. Continuous Integration
- Run tests on multiple platforms (Linux, macOS)
- Use build tags to enable/disable platform-specific tests
- Ensure graceful degradation on unsupported platforms
### 2. Feature Detection
- Implement runtime feature detection where possible
- Skip tests gracefully when features are unavailable
- Log clear messages about skipped functionality
### 3. Documentation
- Document platform-specific behavior
- Provide examples for each implemented feature
- Maintain compatibility matrices
## Contributing
When implementing these features:
1. **Start with high-priority items** (Extended Attributes, Memory Mapping)
2. **Follow the platform-specific pattern** outlined above
3. **Add comprehensive tests** for each feature
4. **Update this roadmap** as features are implemented
5. **Document any platform-specific quirks** or limitations
## Future Enhancements
Beyond the current roadmap, consider:
- **File locking** (flock, fcntl locks)
- **Asynchronous I/O** (aio_read, aio_write)
- **File change notifications** (inotify, kqueue)
- **POSIX ACLs** (Access Control Lists)
- **Sparse file operations**
- **File hole punching**
## References
- [POSIX.1-2017 Standard](https://pubs.opengroup.org/onlinepubs/9699919799/)
- [Linux Programmer's Manual](https://man7.org/linux/man-pages/)
- [FreeBSD System Calls](https://www.freebsd.org/cgi/man.cgi)
- [macOS System Calls](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/)

12
test/fuse_integration/atime_linux.go

@ -0,0 +1,12 @@
//go:build linux
package fuse
import (
"syscall"
)
// getAtimeNano returns the access time in nanoseconds for Linux systems
func getAtimeNano(stat *syscall.Stat_t) int64 {
return stat.Atim.Sec*1e9 + stat.Atim.Nsec
}

12
test/fuse_integration/atime_nonlinux.go

@ -0,0 +1,12 @@
//go:build !linux && !windows
package fuse
import (
"syscall"
)
// getAtimeNano returns the access time in nanoseconds for non-Linux systems (macOS, BSD, etc.)
func getAtimeNano(stat *syscall.Stat_t) int64 {
return stat.Atimespec.Sec*1e9 + stat.Atimespec.Nsec
}

12
test/fuse_integration/atime_windows.go

@ -0,0 +1,12 @@
//go:build windows
package fuse
import (
"syscall"
)
// getAtimeNano returns the access time in nanoseconds for Windows systems
func getAtimeNano(stat *syscall.Win32FileAttributeData) int64 {
return stat.LastAccessTime.Nanoseconds()
}

5
test/fuse_integration/concurrent_operations_test.go

@ -1,4 +1,4 @@
package fuse_test
package fuse
import ( import (
"bytes" "bytes"
@ -394,6 +394,9 @@ func testHighFrequencySmallWrites(t *testing.T, framework *FuseTestFramework) {
} }
file.Close() file.Close()
// Calculate expected total size
totalSize := int64(numWrites * writeSize)
// Verify file size // Verify file size
info, err := os.Stat(mountPath) info, err := os.Stat(mountPath)
require.NoError(t, err) require.NoError(t, err)

39
test/fuse_integration/directio_darwin.go

@ -0,0 +1,39 @@
//go:build darwin
package fuse
import (
"syscall"
)
// Direct I/O support for macOS
const (
// macOS doesn't have O_DIRECT, but we can use fcntl with F_NOCACHE
F_NOCACHE = 48
)
func openDirectIO(path string, flags int, mode uint32) (int, error) {
// Open file normally first
fd, err := syscall.Open(path, flags, mode)
if err != nil {
return -1, err
}
// Set F_NOCACHE to bypass buffer cache (similar to O_DIRECT)
_, _, errno := syscall.Syscall(syscall.SYS_FCNTL,
uintptr(fd),
F_NOCACHE,
1) // enable
if errno != 0 {
syscall.Close(fd)
return -1, errno
}
return fd, nil
}
func isDirectIOSupported() bool {
return true
}

21
test/fuse_integration/directio_linux.go

@ -0,0 +1,21 @@
//go:build linux
package fuse
import (
"syscall"
)
// Direct I/O support for Linux
const (
O_DIRECT = 0x4000 // Direct I/O flag for Linux
)
func openDirectIO(path string, flags int, mode uint32) (int, error) {
return syscall.Open(path, flags|O_DIRECT, mode)
}
func isDirectIOSupported() bool {
return true
}

29
test/fuse_integration/directio_unsupported.go

@ -0,0 +1,29 @@
//go:build !linux && !darwin
package fuse
import (
"errors"
"syscall"
)
// Direct I/O support for unsupported platforms
var ErrDirectIONotSupported = errors.New("direct I/O not supported on this platform")
const (
O_DIRECT = 0x4000 // Dummy flag for compatibility
)
func openDirectIO(path string, flags int, mode uint32) (int, error) {
// Fall back to regular open
fd, err := syscall.Open(path, flags, mode)
if err != nil {
return -1, err
}
return int(fd), nil
}
func isDirectIOSupported() bool {
return false
}

2
test/fuse_integration/directory_operations_test.go

@ -1,4 +1,4 @@
package fuse_test
package fuse
import ( import (
"fmt" "fmt"

79
test/fuse_integration/fallocate_darwin.go

@ -0,0 +1,79 @@
//go:build darwin
package fuse
import (
"syscall"
"unsafe"
)
// File allocation support for macOS using fcntl
const (
// macOS doesn't have fallocate, but we can use fcntl with F_PREALLOCATE
F_ALLOCATECONTIG = 0x02
F_ALLOCATEALL = 0x04
// Dummy flags for compatibility
FALLOC_FL_KEEP_SIZE = 0x01
FALLOC_FL_PUNCH_HOLE = 0x02
FALLOC_FL_NO_HIDE_STALE = 0x04
FALLOC_FL_COLLAPSE_RANGE = 0x08
FALLOC_FL_ZERO_RANGE = 0x10
FALLOC_FL_INSERT_RANGE = 0x20
FALLOC_FL_UNSHARE_RANGE = 0x40
)
// fstore_t structure for F_PREALLOCATE
type fstore struct {
flags uint32
posmode int16
offset int64
length int64
bytesalloc int64
}
func fallocateFile(fd int, mode int, offset int64, length int64) error {
// Check for unsupported modes on macOS
unsupportedModes := FALLOC_FL_PUNCH_HOLE | FALLOC_FL_NO_HIDE_STALE | FALLOC_FL_COLLAPSE_RANGE | FALLOC_FL_ZERO_RANGE | FALLOC_FL_INSERT_RANGE | FALLOC_FL_UNSHARE_RANGE
if mode&unsupportedModes != 0 {
return syscall.ENOTSUP // Operation not supported
}
// On macOS, we use fcntl with F_PREALLOCATE
store := fstore{
flags: F_ALLOCATECONTIG,
posmode: syscall.F_PEOFPOSMODE, // Allocate from EOF
offset: 0,
length: length,
}
_, _, errno := syscall.Syscall(syscall.SYS_FCNTL,
uintptr(fd),
syscall.F_PREALLOCATE,
uintptr(unsafe.Pointer(&store)))
if errno != 0 {
// If contiguous allocation fails, try non-contiguous
store.flags = F_ALLOCATEALL
_, _, errno = syscall.Syscall(syscall.SYS_FCNTL,
uintptr(fd),
syscall.F_PREALLOCATE,
uintptr(unsafe.Pointer(&store)))
if errno != 0 {
return errno
}
}
// Set file size if not keeping size
if mode&FALLOC_FL_KEEP_SIZE == 0 {
return syscall.Ftruncate(fd, offset+length)
}
return nil
}
func isFallocateSupported() bool {
return true
}

38
test/fuse_integration/fallocate_linux.go

@ -0,0 +1,38 @@
//go:build linux
package fuse
import (
"syscall"
)
// File allocation support for Linux
const (
// Fallocate flags
FALLOC_FL_KEEP_SIZE = 0x01
FALLOC_FL_PUNCH_HOLE = 0x02
FALLOC_FL_NO_HIDE_STALE = 0x04
FALLOC_FL_COLLAPSE_RANGE = 0x08
FALLOC_FL_ZERO_RANGE = 0x10
FALLOC_FL_INSERT_RANGE = 0x20
FALLOC_FL_UNSHARE_RANGE = 0x40
)
func fallocateFile(fd int, mode int, offset int64, length int64) error {
_, _, errno := syscall.Syscall6(syscall.SYS_FALLOCATE,
uintptr(fd),
uintptr(mode),
uintptr(offset),
uintptr(length),
0, 0)
if errno != 0 {
return errno
}
return nil
}
func isFallocateSupported() bool {
return true
}

30
test/fuse_integration/fallocate_unsupported.go

@ -0,0 +1,30 @@
//go:build !linux && !darwin
package fuse
import (
"errors"
)
// File allocation support for unsupported platforms
var ErrFallocateNotSupported = errors.New("fallocate not supported on this platform")
const (
// Dummy flags for compatibility
FALLOC_FL_KEEP_SIZE = 0x01
FALLOC_FL_PUNCH_HOLE = 0x02
FALLOC_FL_NO_HIDE_STALE = 0x04
FALLOC_FL_COLLAPSE_RANGE = 0x08
FALLOC_FL_ZERO_RANGE = 0x10
FALLOC_FL_INSERT_RANGE = 0x20
FALLOC_FL_UNSHARE_RANGE = 0x40
)
func fallocateFile(fd int, mode int, offset int64, length int64) error {
return ErrFallocateNotSupported
}
func isFallocateSupported() bool {
return false
}

58
test/fuse_integration/framework.go

@ -1,8 +1,9 @@
package fuse_test
package fuse
import ( import (
"fmt" "fmt"
"io/fs" "io/fs"
"net"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@ -84,7 +85,54 @@ func (f *FuseTestFramework) Setup(config *TestConfig) error {
return fmt.Errorf("framework already setup") return fmt.Errorf("framework already setup")
} }
// Create directories
// Check if we should skip cluster setup and use existing mount
if os.Getenv("TEST_SKIP_CLUSTER_SETUP") == "true" {
// Use existing mount point from environment
if existingMount := os.Getenv("TEST_MOUNT_POINT"); existingMount != "" {
f.mountPoint = existingMount
f.t.Logf("Using existing mount point: %s", f.mountPoint)
// Verify mount point is accessible
if _, err := os.Stat(f.mountPoint); err != nil {
return fmt.Errorf("existing mount point not accessible: %v", err)
}
// Test basic functionality
testFile := filepath.Join(f.mountPoint, ".framework_test")
if err := os.WriteFile(testFile, []byte("test"), 0644); err != nil {
return fmt.Errorf("mount point not writable: %v", err)
}
if err := os.Remove(testFile); err != nil {
f.t.Logf("Warning: failed to cleanup test file: %v", err)
}
// Verify this is actually a SeaweedFS mount by checking for SeaweedFS-specific behavior
// Create a test file and verify it appears in the filer
verifyFile := filepath.Join(f.mountPoint, ".seaweedfs_mount_verification")
if err := os.WriteFile(verifyFile, []byte("SeaweedFS mount verification"), 0644); err != nil {
return fmt.Errorf("mount point verification failed - cannot write: %v", err)
}
// Read it back to ensure it's working
if data, err := os.ReadFile(verifyFile); err != nil {
return fmt.Errorf("mount point verification failed - cannot read: %v", err)
} else if string(data) != "SeaweedFS mount verification" {
return fmt.Errorf("mount point verification failed - data mismatch")
}
// Clean up verification file
if err := os.Remove(verifyFile); err != nil {
f.t.Logf("Warning: failed to cleanup verification file: %v", err)
}
f.t.Logf("✅ SeaweedFS mount point verified and working: %s", f.mountPoint)
f.isSetup = true
return nil
}
}
// Create directories for full cluster setup
dirs := []string{f.mountPoint, f.dataDir} dirs := []string{f.mountPoint, f.dataDir}
for _, dir := range dirs { for _, dir := range dirs {
if err := os.MkdirAll(dir, 0755); err != nil { if err := os.MkdirAll(dir, 0755); err != nil {
@ -138,6 +186,12 @@ func (f *FuseTestFramework) Setup(config *TestConfig) error {
// Cleanup stops all processes and removes temporary files // Cleanup stops all processes and removes temporary files
func (f *FuseTestFramework) Cleanup() { func (f *FuseTestFramework) Cleanup() {
// Skip cleanup if using external cluster
if os.Getenv("TEST_SKIP_CLUSTER_SETUP") == "true" {
f.t.Logf("Skipping cleanup - using external SeaweedFS cluster")
return
}
if f.mountProcess != nil { if f.mountProcess != nil {
f.unmountFuse() f.unmountFuse()
} }

11
test/fuse_integration/go.mod

@ -1,11 +0,0 @@
module seaweedfs-fuse-tests
go 1.21
require github.com/stretchr/testify v1.8.4
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

10
test/fuse_integration/go.sum

@ -1,10 +0,0 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

2
test/fuse_integration/minimal_test.go

@ -1,4 +1,4 @@
package fuse_test
package fuse
import "testing" import "testing"

85
test/fuse_integration/mmap_unix.go

@ -0,0 +1,85 @@
//go:build unix
package fuse
import (
"syscall"
"unsafe"
)
// Memory mapping support for Unix-like systems
func mmapFile(fd int, offset int64, length int, prot int, flags int) ([]byte, error) {
addr, _, errno := syscall.Syscall6(syscall.SYS_MMAP,
0, // addr (let kernel choose)
uintptr(length),
uintptr(prot),
uintptr(flags),
uintptr(fd),
uintptr(offset))
if errno != 0 {
return nil, errno
}
// Convert the address to a byte slice
return (*[1 << 30]byte)(unsafe.Pointer(addr))[:length:length], nil
}
func munmapFile(data []byte) error {
if len(data) == 0 {
return nil
}
_, _, errno := syscall.Syscall(syscall.SYS_MUNMAP,
uintptr(unsafe.Pointer(&data[0])),
uintptr(len(data)),
0)
if errno != 0 {
return errno
}
return nil
}
func msyncFile(data []byte, flags int) error {
if len(data) == 0 {
return nil
}
_, _, errno := syscall.Syscall(syscall.SYS_MSYNC,
uintptr(unsafe.Pointer(&data[0])),
uintptr(len(data)),
uintptr(flags))
if errno != 0 {
return errno
}
return nil
}
func isMmapSupported() bool {
return true
}
// Memory protection flags
const (
PROT_READ = syscall.PROT_READ
PROT_WRITE = syscall.PROT_WRITE
PROT_EXEC = syscall.PROT_EXEC
PROT_NONE = syscall.PROT_NONE
)
// Memory mapping flags
const (
MAP_SHARED = syscall.MAP_SHARED
MAP_PRIVATE = syscall.MAP_PRIVATE
MAP_ANONYMOUS = syscall.MAP_ANON
)
// Memory sync flags
const (
MS_ASYNC = syscall.MS_ASYNC
MS_SYNC = syscall.MS_SYNC
MS_INVALIDATE = syscall.MS_INVALIDATE
)

47
test/fuse_integration/mmap_unsupported.go

@ -0,0 +1,47 @@
//go:build !unix
package fuse
import (
"errors"
)
// Memory mapping support for unsupported platforms
var ErrMmapNotSupported = errors.New("memory mapping not supported on this platform")
func mmapFile(fd int, offset int64, length int, prot int, flags int) ([]byte, error) {
return nil, ErrMmapNotSupported
}
func munmapFile(data []byte) error {
return ErrMmapNotSupported
}
func msyncFile(data []byte, flags int) error {
return ErrMmapNotSupported
}
func isMmapSupported() bool {
return false
}
// Dummy constants for unsupported platforms
const (
PROT_READ = 0x1
PROT_WRITE = 0x2
PROT_EXEC = 0x4
PROT_NONE = 0x0
)
const (
MAP_SHARED = 0x01
MAP_PRIVATE = 0x02
MAP_ANONYMOUS = 0x20
)
const (
MS_ASYNC = 0x1
MS_SYNC = 0x4
MS_INVALIDATE = 0x2
)

509
test/fuse_integration/posix_Makefile

@ -0,0 +1,509 @@
# SeaweedFS POSIX Compliance Testing Makefile
# Configuration
WEED_BINARY := $(shell which weed 2>/dev/null || echo "../../weed")
GO_VERSION := 1.21
TEST_TIMEOUT := 45m
COVERAGE_FILE := posix_coverage.out
REPORT_DIR := reports
EXTERNAL_TOOLS_DIR := external_tools
MOUNT_POINT ?= /tmp/seaweedfs_mount
# Go test command prefix for external mount points
GO_TEST_PREFIX :=
ifneq ($(TEST_MOUNT_POINT),)
GO_TEST_PREFIX := TEST_MOUNT_POINT="$(TEST_MOUNT_POINT)" TEST_SKIP_CLUSTER_SETUP="true"
endif
# Test categories
POSIX_BASIC_TESTS := posix_compliance_test.go
POSIX_EXTENDED_TESTS := posix_extended_test.go
POSIX_EXTERNAL_TESTS := posix_external_test.go
# Colors for output
RED := \033[31m
GREEN := \033[32m
YELLOW := \033[33m
BLUE := \033[34m
MAGENTA := \033[35m
CYAN := \033[36m
WHITE := \033[37m
RESET := \033[0m
.DEFAULT_GOAL := help
# Prerequisites checks
check-binary:
@if [ ! -f "$(WEED_BINARY)" ]; then \
echo "$(RED)❌ SeaweedFS binary not found at $(WEED_BINARY)$(RESET)"; \
echo " Please run 'make' in the root directory first"; \
exit 1; \
fi
@echo "$(GREEN)[OK] SeaweedFS binary found at $(WEED_BINARY)$(RESET)"
check-fuse:
@if command -v fusermount >/dev/null 2>&1; then \
echo "$(GREEN)[OK] FUSE is installed (Linux)$(RESET)"; \
elif command -v umount >/dev/null 2>&1 && [ "$$(uname)" = "Darwin" ]; then \
echo "$(GREEN)[OK] FUSE is available (macOS)$(RESET)"; \
else \
echo "$(RED)❌ FUSE not found. Please install:$(RESET)"; \
echo " Ubuntu/Debian: sudo apt-get install fuse"; \
echo " CentOS/RHEL: sudo yum install fuse"; \
echo " macOS: brew install macfuse"; \
exit 1; \
fi
check-go:
@go version | grep -q "go1\.[2-9][0-9]" || \
go version | grep -q "go1\.2[1-9]" || \
(echo "$(RED)❌ Go $(GO_VERSION)+ required. Current: $$(go version)$(RESET)" && exit 1)
@echo "$(GREEN)[OK] Go version check passed$(RESET)"
check-prereqs: check-go check-fuse check-binary
@echo "$(GREEN)[OK] All prerequisites satisfied$(RESET)"
# Setup and initialization
setup-reports:
@mkdir -p $(REPORT_DIR)
@mkdir -p $(EXTERNAL_TOOLS_DIR)
setup-external-tools: setup-reports
@echo "$(BLUE)🛠️ Setting up external POSIX test tools...$(RESET)"
@$(MAKE) setup-pjdfstest || echo "$(YELLOW)[WARNING] pjdfstest setup failed - continuing without it$(RESET)"
@$(MAKE) setup-nfstest || echo "$(YELLOW)[WARNING] nfstest setup failed - continuing without it$(RESET)"
@$(MAKE) setup-fio || echo "$(YELLOW)[WARNING] FIO setup failed - continuing without it$(RESET)"
# External tools setup
setup-pjdfstest:
@if [ ! -d "$(EXTERNAL_TOOLS_DIR)/pjdfstest" ]; then \
echo "$(BLUE)[SETUP] Setting up pjdfstest...$(RESET)"; \
if ! command -v autoreconf >/dev/null 2>&1; then \
echo "$(YELLOW)[WARNING] autoreconf not found - installing build-essential...$(RESET)"; \
if command -v apt-get >/dev/null 2>&1; then \
sudo apt-get update && sudo apt-get install -y build-essential autoconf automake libtool; \
elif command -v yum >/dev/null 2>&1; then \
sudo yum install -y gcc make autoconf automake libtool; \
else \
echo "$(YELLOW)[WARNING] Please install build tools manually$(RESET)"; \
exit 1; \
fi; \
fi; \
cd $(EXTERNAL_TOOLS_DIR) && \
git clone https://github.com/pjd/pjdfstest.git && \
cd pjdfstest && \
autoreconf -ifs && \
./configure && \
make; \
else \
echo "$(GREEN)[OK] pjdfstest already setup$(RESET)"; \
fi
setup-nfstest:
@echo "$(BLUE)[INSTALL] Installing nfstest...$(RESET)"
@pip3 install --user nfstest || \
echo "$(YELLOW)[WARNING] nfstest installation failed. Install manually: pip3 install nfstest$(RESET)"
setup-fio:
@if ! command -v fio >/dev/null 2>&1; then \
echo "$(BLUE)[SETUP] Installing FIO...$(RESET)"; \
if command -v apt-get >/dev/null 2>&1; then \
sudo apt-get update && sudo apt-get install -y fio; \
elif command -v yum >/dev/null 2>&1; then \
sudo yum install -y fio; \
elif command -v brew >/dev/null 2>&1; then \
brew install fio; \
else \
echo "$(YELLOW)[WARNING] Please install FIO manually$(RESET)"; \
fi; \
else \
echo "$(GREEN)[OK] FIO already installed$(RESET)"; \
fi
# Core test execution
test-posix-basic: check-prereqs setup-reports
@echo "$(CYAN)[TEST] Running basic POSIX compliance tests...$(RESET)"
ifneq ($(TEST_MOUNT_POINT),)
@echo "$(BLUE)Using external mount point: $(TEST_MOUNT_POINT)$(RESET)"
endif
@$(GO_TEST_PREFIX) go test -v -timeout $(TEST_TIMEOUT) -failfast=false -run TestPOSIXCompliance . 2>&1 | \
tee $(REPORT_DIR)/posix_basic_results.log
test-posix-extended: check-prereqs setup-reports
@echo "$(CYAN)[TEST] Running extended POSIX compliance tests...$(RESET)"
ifneq ($(TEST_MOUNT_POINT),)
@echo "$(BLUE)Using external mount point: $(TEST_MOUNT_POINT)$(RESET)"
endif
@$(GO_TEST_PREFIX) go test -v -timeout $(TEST_TIMEOUT) -failfast=false -run TestPOSIXExtended . 2>&1 | \
tee $(REPORT_DIR)/posix_extended_results.log
test-posix-external: check-prereqs setup-reports
@echo "$(CYAN)[TEST] Running external POSIX test suite integration...$(RESET)"
ifneq ($(TEST_MOUNT_POINT),)
@echo "$(BLUE)Using external mount point: $(TEST_MOUNT_POINT)$(RESET)"
endif
@if [ -d "$(EXTERNAL_TOOLS_DIR)/pjdfstest" ] || command -v nfstest_posix >/dev/null 2>&1; then \
echo "$(GREEN)[OK] External tools available - running external tests$(RESET)"; \
$(GO_TEST_PREFIX) go test -v -timeout $(TEST_TIMEOUT) -failfast=false -run TestExternalPOSIXSuites . 2>&1 | \
tee $(REPORT_DIR)/posix_external_results.log; \
else \
echo "$(YELLOW)[WARNING] External tools not available - skipping external tests$(RESET)"; \
echo "External tests skipped - tools not available" > $(REPORT_DIR)/posix_external_results.log; \
fi
# Comprehensive test suites
test-posix-full: test-posix-basic test-posix-extended test-posix-external
@echo "$(GREEN)[OK] Full POSIX compliance test suite completed$(RESET)"
@$(MAKE) generate-report
test-posix-critical: check-prereqs setup-reports
@echo "$(CYAN)[TEST] Running critical POSIX compliance tests...$(RESET)"
ifneq ($(TEST_MOUNT_POINT),)
@echo "$(BLUE)Using external mount point: $(TEST_MOUNT_POINT)$(RESET)"
endif
@$(GO_TEST_PREFIX) go test -v -timeout 15m -failfast=false \
-run "TestPOSIXCompliance/(FileOperations|DirectoryOperations|PermissionTests|IOOperations)" \
. 2>&1 | tee $(REPORT_DIR)/posix_critical_results.log
test-posix-stress: check-prereqs setup-reports
@echo "$(CYAN)[TEST] Running POSIX stress tests...$(RESET)"
ifneq ($(TEST_MOUNT_POINT),)
@echo "$(BLUE)Using external mount point: $(TEST_MOUNT_POINT)$(RESET)"
endif
@if [ -d "$(EXTERNAL_TOOLS_DIR)/pjdfstest" ] || command -v nfstest_posix >/dev/null 2>&1; then \
echo "$(GREEN)[OK] External tools available - running stress tests$(RESET)"; \
$(GO_TEST_PREFIX) go test -v -timeout $(TEST_TIMEOUT) -failfast=false \
-run "TestExternalPOSIXSuites/CustomPOSIXTests" \
. 2>&1 | tee $(REPORT_DIR)/posix_stress_results.log; \
else \
echo "$(YELLOW)[WARNING] External tools not available - skipping stress tests$(RESET)"; \
echo "Stress tests skipped - tools not available" > $(REPORT_DIR)/posix_stress_results.log; \
fi
# Performance and benchmarks
benchmark-posix: check-prereqs setup-reports
@echo "$(CYAN)📈 Running POSIX performance benchmarks...$(RESET)"
ifneq ($(TEST_MOUNT_POINT),)
@echo "$(BLUE)Using external mount point: $(TEST_MOUNT_POINT)$(RESET)"
endif
@$(GO_TEST_PREFIX) go test -v -timeout $(TEST_TIMEOUT) -bench=. -benchmem \
. 2>&1 | \
tee $(REPORT_DIR)/posix_benchmark_results.log
profile-posix: check-prereqs setup-reports
@echo "$(CYAN)[PROFILE] Running POSIX tests with profiling...$(RESET)"
ifneq ($(TEST_MOUNT_POINT),)
@echo "$(BLUE)Using external mount point: $(TEST_MOUNT_POINT)$(RESET)"
endif
@$(GO_TEST_PREFIX) go test -v -timeout $(TEST_TIMEOUT) -cpuprofile $(REPORT_DIR)/posix.cpu.prof \
-memprofile $(REPORT_DIR)/posix.mem.prof -run TestPOSIXCompliance .
@echo "$(GREEN)[PROFILE] Profiles generated:$(RESET)"
@echo " CPU: $(REPORT_DIR)/posix.cpu.prof"
@echo " Memory: $(REPORT_DIR)/posix.mem.prof"
@echo "$(BLUE)View with: go tool pprof $(REPORT_DIR)/posix.cpu.prof$(RESET)"
# Coverage analysis
coverage-posix: check-prereqs setup-reports
@echo "$(CYAN)[COVERAGE] Running POSIX tests with coverage analysis...$(RESET)"
ifneq ($(TEST_MOUNT_POINT),)
@echo "$(BLUE)Using external mount point: $(TEST_MOUNT_POINT)$(RESET)"
endif
@$(GO_TEST_PREFIX) go test -v -timeout $(TEST_TIMEOUT) -coverprofile=$(REPORT_DIR)/$(COVERAGE_FILE) \
.
@go tool cover -html=$(REPORT_DIR)/$(COVERAGE_FILE) -o $(REPORT_DIR)/posix_coverage.html
@echo "$(GREEN)[COVERAGE] Coverage report generated: $(REPORT_DIR)/posix_coverage.html$(RESET)"
# External tool tests
test-pjdfstest: setup-pjdfstest setup-reports
@echo "$(CYAN)[TEST] Running pjdfstest suite...$(RESET)"
@if [ -d "$(EXTERNAL_TOOLS_DIR)/pjdfstest" ]; then \
mkdir -p $(MOUNT_POINT)/pjdfstest_workdir; \
cd $(MOUNT_POINT)/pjdfstest_workdir && \
prove -r $(CURDIR)/$(EXTERNAL_TOOLS_DIR)/pjdfstest/tests/ 2>&1 | tee $(CURDIR)/$(REPORT_DIR)/pjdfstest_results.log; \
else \
echo "$(RED)❌ pjdfstest not setup$(RESET)"; \
exit 1; \
fi
test-nfstest-posix: setup-nfstest setup-reports
@echo "$(CYAN)[TEST] Running nfstest_posix...$(RESET)"
@if command -v nfstest_posix >/dev/null 2>&1; then \
mkdir -p $(MOUNT_POINT); \
nfstest_posix --path $(MOUNT_POINT) --verbose 2>&1 | \
tee $(REPORT_DIR)/nfstest_results.log; \
else \
echo "$(YELLOW)[WARNING] nfstest_posix not available$(RESET)"; \
fi
# FIO-based performance tests
test-fio-posix: setup-reports
@echo "$(CYAN)[TEST] Running FIO-based POSIX I/O tests...$(RESET)"
@$(MAKE) create-fio-configs
@if command -v fio >/dev/null 2>&1; then \
for config in $(REPORT_DIR)/fio_*.conf; do \
echo "$(BLUE)Running FIO config: $$config$(RESET)"; \
fio $$config --output=$(REPORT_DIR)/$$(basename $$config .conf)_results.log; \
done; \
else \
echo "$(YELLOW)[WARNING] FIO not available$(RESET)"; \
fi
create-fio-configs: setup-reports
@echo "$(BLUE)[CONFIG] Creating FIO test configurations...$(RESET)"
@echo "[global]" > $(REPORT_DIR)/fio_random_rw.conf
@echo "name=posix_random_rw" >> $(REPORT_DIR)/fio_random_rw.conf
@echo "ioengine=sync" >> $(REPORT_DIR)/fio_random_rw.conf
@echo "iodepth=1" >> $(REPORT_DIR)/fio_random_rw.conf
@echo "rw=randrw" >> $(REPORT_DIR)/fio_random_rw.conf
@echo "bs=4k" >> $(REPORT_DIR)/fio_random_rw.conf
@echo "direct=0" >> $(REPORT_DIR)/fio_random_rw.conf
@echo "size=100m" >> $(REPORT_DIR)/fio_random_rw.conf
@echo "numjobs=4" >> $(REPORT_DIR)/fio_random_rw.conf
@echo "runtime=30" >> $(REPORT_DIR)/fio_random_rw.conf
@echo "time_based" >> $(REPORT_DIR)/fio_random_rw.conf
@echo "" >> $(REPORT_DIR)/fio_random_rw.conf
@echo "[random_rw_test]" >> $(REPORT_DIR)/fio_random_rw.conf
@echo "directory=$(MOUNT_POINT)" >> $(REPORT_DIR)/fio_random_rw.conf
@echo "[global]" > $(REPORT_DIR)/fio_sequential.conf
@echo "name=posix_sequential" >> $(REPORT_DIR)/fio_sequential.conf
@echo "ioengine=sync" >> $(REPORT_DIR)/fio_sequential.conf
@echo "iodepth=1" >> $(REPORT_DIR)/fio_sequential.conf
@echo "bs=1m" >> $(REPORT_DIR)/fio_sequential.conf
@echo "direct=0" >> $(REPORT_DIR)/fio_sequential.conf
@echo "size=500m" >> $(REPORT_DIR)/fio_sequential.conf
@echo "runtime=60" >> $(REPORT_DIR)/fio_sequential.conf
@echo "time_based" >> $(REPORT_DIR)/fio_sequential.conf
@echo "" >> $(REPORT_DIR)/fio_sequential.conf
@echo "[seq_write]" >> $(REPORT_DIR)/fio_sequential.conf
@echo "rw=write" >> $(REPORT_DIR)/fio_sequential.conf
@echo "directory=$(MOUNT_POINT)" >> $(REPORT_DIR)/fio_sequential.conf
@echo "" >> $(REPORT_DIR)/fio_sequential.conf
@echo "[seq_read]" >> $(REPORT_DIR)/fio_sequential.conf
@echo "rw=read" >> $(REPORT_DIR)/fio_sequential.conf
@echo "directory=$(MOUNT_POINT)" >> $(REPORT_DIR)/fio_sequential.conf
# Reporting and analysis
generate-report: setup-reports
@echo "$(BLUE)[REPORT] Generating comprehensive POSIX compliance report...$(RESET)"
@$(MAKE) -f posix_Makefile generate-html-report
@$(MAKE) -f posix_Makefile generate-text-report
@$(MAKE) -f posix_Makefile generate-json-report
generate-html-report:
@echo "$(BLUE)[REPORT] Generating HTML report...$(RESET)"
@echo "<!DOCTYPE html>" > $(REPORT_DIR)/posix_compliance_report.html
@echo "<html><head><title>SeaweedFS POSIX Compliance Report</title>" >> $(REPORT_DIR)/posix_compliance_report.html
@echo "<style>body{font-family:Arial;margin:20px}.pass{color:green}.fail{color:red}.summary{background:#f5f5f5;padding:10px;margin:10px 0}.test-section{margin:20px 0;border-left:3px solid #ddd;padding-left:15px}</style>" >> $(REPORT_DIR)/posix_compliance_report.html
@echo "</head><body><h1>SeaweedFS POSIX Compliance Report</h1>" >> $(REPORT_DIR)/posix_compliance_report.html
@echo "<p>Generated on: $$(date)</p>" >> $(REPORT_DIR)/posix_compliance_report.html
@echo "<div class='summary'>" >> $(REPORT_DIR)/posix_compliance_report.html
@echo "<h2>Test Summary</h2>" >> $(REPORT_DIR)/posix_compliance_report.html
@if [ -f "$(REPORT_DIR)/posix_basic_results.log" ]; then \
TOTAL=$$(grep -c "RUN\|PASS\|FAIL" $(REPORT_DIR)/posix_basic_results.log 2>/dev/null || echo "0"); \
PASSED=$$(grep -c "PASS:" $(REPORT_DIR)/posix_basic_results.log 2>/dev/null || echo "0"); \
FAILED=$$(grep -c "FAIL:" $(REPORT_DIR)/posix_basic_results.log 2>/dev/null || echo "0"); \
echo "<p>Basic Tests: <span class='pass'>$$PASSED passed</span>, <span class='fail'>$$FAILED failed</span></p>" >> $(REPORT_DIR)/posix_compliance_report.html; \
fi
@if [ -f "$(REPORT_DIR)/posix_critical_results.log" ]; then \
TOTAL=$$(grep -c "RUN\|PASS\|FAIL" $(REPORT_DIR)/posix_critical_results.log 2>/dev/null || echo "0"); \
PASSED=$$(grep -c "PASS:" $(REPORT_DIR)/posix_critical_results.log 2>/dev/null || echo "0"); \
FAILED=$$(grep -c "FAIL:" $(REPORT_DIR)/posix_critical_results.log 2>/dev/null || echo "0"); \
echo "<p>Critical Tests: <span class='pass'>$$PASSED passed</span>, <span class='fail'>$$FAILED failed</span></p>" >> $(REPORT_DIR)/posix_compliance_report.html; \
fi
@echo "</div>" >> $(REPORT_DIR)/posix_compliance_report.html
@echo "<h2>Detailed Results</h2>" >> $(REPORT_DIR)/posix_compliance_report.html
@for logfile in $(REPORT_DIR)/*.log; do \
if [ -f "$$logfile" ]; then \
basename=$$(basename "$$logfile" .log); \
echo "<div class='test-section'>" >> $(REPORT_DIR)/posix_compliance_report.html; \
echo "<h3>$$basename</h3>" >> $(REPORT_DIR)/posix_compliance_report.html; \
echo "<pre>" >> $(REPORT_DIR)/posix_compliance_report.html; \
tail -50 "$$logfile" | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g' >> $(REPORT_DIR)/posix_compliance_report.html; \
echo "</pre></div>" >> $(REPORT_DIR)/posix_compliance_report.html; \
fi; \
done
@echo "</body></html>" >> $(REPORT_DIR)/posix_compliance_report.html
@echo "$(GREEN)[OK] HTML report generated: $(REPORT_DIR)/posix_compliance_report.html$(RESET)"
generate-text-report:
@echo "$(BLUE)[REPORT] Generating text report...$(RESET)"
@echo "SeaweedFS POSIX Compliance Report" > $(REPORT_DIR)/posix_compliance_summary.txt
@echo "=================================" >> $(REPORT_DIR)/posix_compliance_summary.txt
@echo "" >> $(REPORT_DIR)/posix_compliance_summary.txt
@echo "Generated: $$(date)" >> $(REPORT_DIR)/posix_compliance_summary.txt
@echo "SeaweedFS Version: $$($(WEED_BINARY) version 2>/dev/null || echo 'Unknown')" >> $(REPORT_DIR)/posix_compliance_summary.txt
@echo "" >> $(REPORT_DIR)/posix_compliance_summary.txt
@echo "Test Summary: See individual log files for detailed results" >> $(REPORT_DIR)/posix_compliance_summary.txt
@echo "$(GREEN)[OK] Text report generated: $(REPORT_DIR)/posix_compliance_summary.txt$(RESET)"
generate-json-report:
@echo "$(BLUE)[REPORT] Generating JSON report...$(RESET)"
@echo "{" > $(REPORT_DIR)/posix_compliance_report.json
@echo " \"report_type\": \"posix_compliance\"," >> $(REPORT_DIR)/posix_compliance_report.json
@echo " \"timestamp\": \"$$(date -u +"%Y-%m-%dT%H:%M:%SZ")\"," >> $(REPORT_DIR)/posix_compliance_report.json
@echo " \"seaweedfs_version\": \"$$($(WEED_BINARY) version 2>/dev/null | head -n1 | sed 's/\\/\\\\/g; s/\"/\\\"/g' || echo 'Unknown')\"," >> $(REPORT_DIR)/posix_compliance_report.json
@echo " \"test_environment\": { \"os\": \"$$(uname -s)\", \"arch\": \"$$(uname -m)\" }," >> $(REPORT_DIR)/posix_compliance_report.json
@echo " \"test_suites\": {" >> $(REPORT_DIR)/posix_compliance_report.json
@FIRST=true; \
for logfile in $(REPORT_DIR)/*.log; do \
if [ -f "$$logfile" ]; then \
basename=$$(basename "$$logfile" .log); \
if [ "$$FIRST" = "true" ]; then \
FIRST=false; \
else \
echo " ," >> $(REPORT_DIR)/posix_compliance_report.json; \
fi; \
PASSED=$$(grep -c "PASS:" "$$logfile" 2>/dev/null || echo 0); \
FAILED=$$(grep -c "FAIL:" "$$logfile" 2>/dev/null || echo 0); \
TOTAL=$$(expr "$$PASSED" + "$$FAILED" 2>/dev/null || echo 0); \
echo " \"$$basename\": {" >> $(REPORT_DIR)/posix_compliance_report.json; \
echo " \"total_tests\": $$TOTAL," >> $(REPORT_DIR)/posix_compliance_report.json; \
echo " \"passed\": $$PASSED," >> $(REPORT_DIR)/posix_compliance_report.json; \
echo " \"failed\": $$FAILED," >> $(REPORT_DIR)/posix_compliance_report.json; \
if [ "$$TOTAL" -gt 0 ] 2>/dev/null; then \
SUCCESS_RATE=$$(awk "BEGIN {printf \"%.2f\", ($$PASSED/$$TOTAL)*100}" 2>/dev/null || echo "0.00"); \
else \
SUCCESS_RATE="0.00"; \
fi; \
echo " \"success_rate\": \"$$SUCCESS_RATE%\"" >> $(REPORT_DIR)/posix_compliance_report.json; \
echo " }" >> $(REPORT_DIR)/posix_compliance_report.json; \
fi; \
done
@echo " }," >> $(REPORT_DIR)/posix_compliance_report.json
@TOTAL_PASSED=0; TOTAL_FAILED=0; \
for logfile in $(REPORT_DIR)/*.log; do \
if [ -f "$$logfile" ]; then \
PASSED=$$(grep -c "PASS:" "$$logfile" 2>/dev/null || echo 0); \
FAILED=$$(grep -c "FAIL:" "$$logfile" 2>/dev/null || echo 0); \
TOTAL_PASSED=$$(expr "$$TOTAL_PASSED" + "$$PASSED" 2>/dev/null || echo "$$TOTAL_PASSED"); \
TOTAL_FAILED=$$(expr "$$TOTAL_FAILED" + "$$FAILED" 2>/dev/null || echo "$$TOTAL_FAILED"); \
fi; \
done; \
GRAND_TOTAL=$$(expr "$$TOTAL_PASSED" + "$$TOTAL_FAILED" 2>/dev/null || echo 0); \
if [ "$$GRAND_TOTAL" -gt 0 ] 2>/dev/null; then \
OVERALL_SUCCESS=$$(awk "BEGIN {printf \"%.2f\", ($$TOTAL_PASSED/$$GRAND_TOTAL)*100}" 2>/dev/null || echo "0.00"); \
else \
OVERALL_SUCCESS="0.00"; \
fi; \
echo " \"summary\": {" >> $(REPORT_DIR)/posix_compliance_report.json; \
echo " \"total_tests\": $$GRAND_TOTAL," >> $(REPORT_DIR)/posix_compliance_report.json; \
echo " \"total_passed\": $$TOTAL_PASSED," >> $(REPORT_DIR)/posix_compliance_report.json; \
echo " \"total_failed\": $$TOTAL_FAILED," >> $(REPORT_DIR)/posix_compliance_report.json; \
echo " \"overall_success_rate\": \"$$OVERALL_SUCCESS%\"" >> $(REPORT_DIR)/posix_compliance_report.json; \
echo " }" >> $(REPORT_DIR)/posix_compliance_report.json
@echo "}" >> $(REPORT_DIR)/posix_compliance_report.json
@echo "$(GREEN)[OK] JSON report generated: $(REPORT_DIR)/posix_compliance_report.json$(RESET)"
# Cleanup and maintenance
clean:
@echo "$(YELLOW)🧹 Cleaning up test artifacts...$(RESET)"
@rm -rf $(REPORT_DIR)
@rm -rf /tmp/seaweedfs_*_test_*
@go clean -testcache
@echo "$(GREEN)[OK] Cleanup complete$(RESET)"
clean-external-tools:
@echo "$(YELLOW)🧹 Cleaning up external tools...$(RESET)"
@rm -rf $(EXTERNAL_TOOLS_DIR)
clean-all: clean clean-external-tools
# Development and debugging
validate:
@echo "$(BLUE)[OK] Validating test files...$(RESET)"
@go build -o /dev/null ./...
@echo "$(GREEN)[OK] All test files compile successfully$(RESET)"
fmt:
@echo "$(BLUE)🎨 Formatting Go code...$(RESET)"
@go fmt ./...
lint:
@echo "$(BLUE)🔍 Running linter...$(RESET)"
@if command -v golangci-lint >/dev/null 2>&1; then \
golangci-lint run; \
else \
echo "$(YELLOW)[WARNING] golangci-lint not found, running go vet instead$(RESET)"; \
go vet ./...; \
fi
# CI/CD integration
ci-posix-tests: check-prereqs setup-external-tools
@echo "$(CYAN)🚀 Running POSIX tests for CI/CD...$(RESET)"
@$(MAKE) test-posix-critical
@$(MAKE) generate-report
# Documentation and help
list-tests:
@echo "$(BLUE)📋 Available POSIX test functions:$(RESET)"
@grep -r "^func Test" *.go 2>/dev/null | sed 's/.*func \(Test[^(]*\).*/ \1/' | sort || echo "No test files found"
test-info:
@echo "$(BLUE)[INFO] POSIX Test Information:$(RESET)"
@echo "Test files:"
@echo " - $(POSIX_BASIC_TESTS): Core POSIX compliance tests"
@echo " - $(POSIX_EXTENDED_TESTS): Extended POSIX feature tests"
@echo " - $(POSIX_EXTERNAL_TESTS): External test suite integration"
@echo ""
@echo "External tools:"
@echo " - pjdfstest: Comprehensive POSIX filesystem test suite"
@echo " - nfstest: Network filesystem POSIX API verification"
@echo " - FIO: Flexible I/O performance testing"
help:
@echo "$(CYAN)SeaweedFS POSIX Compliance Testing$(RESET)"
@echo "=================================="
@echo ""
@echo "$(WHITE)Prerequisites:$(RESET)"
@echo " $(GREEN)make check-prereqs$(RESET) - Check all prerequisites"
@echo " $(GREEN)make setup-external-tools$(RESET) - Setup external POSIX test tools"
@echo ""
@echo "$(WHITE)Core POSIX Tests:$(RESET)"
@echo " $(GREEN)make test-posix-basic$(RESET) - Run basic POSIX compliance tests"
@echo " $(GREEN)make test-posix-extended$(RESET) - Run extended POSIX feature tests"
@echo " $(GREEN)make test-posix-external$(RESET) - Run external test suite integration"
@echo " $(GREEN)make test-posix-full$(RESET) - Run complete POSIX test suite"
@echo ""
@echo "$(WHITE)Focused Tests:$(RESET)"
@echo " $(GREEN)make test-posix-critical$(RESET) - Run critical POSIX compliance tests"
@echo " $(GREEN)make test-posix-stress$(RESET) - Run POSIX stress tests"
@echo ""
@echo "$(WHITE)Performance & Analysis:$(RESET)"
@echo " $(GREEN)make benchmark-posix$(RESET) - Run POSIX performance benchmarks"
@echo " $(GREEN)make profile-posix$(RESET) - Run tests with performance profiling"
@echo " $(GREEN)make coverage-posix$(RESET) - Run tests with coverage analysis"
@echo " $(GREEN)make test-fio-posix$(RESET) - Run FIO-based I/O performance tests"
@echo ""
@echo "$(WHITE)External Test Suites:$(RESET)"
@echo " $(GREEN)make test-pjdfstest$(RESET) - Run pjdfstest suite"
@echo " $(GREEN)make test-nfstest-posix$(RESET) - Run nfstest_posix"
@echo ""
@echo "$(WHITE)Reporting:$(RESET)"
@echo " $(GREEN)make generate-report$(RESET) - Generate comprehensive compliance report"
@echo ""
@echo "$(WHITE)Development:$(RESET)"
@echo " $(GREEN)make validate$(RESET) - Validate test file compilation"
@echo " $(GREEN)make fmt$(RESET) - Format Go code"
@echo " $(GREEN)make lint$(RESET) - Run linter"
@echo " $(GREEN)make list-tests$(RESET) - List available test functions"
@echo ""
@echo "$(WHITE)Maintenance:$(RESET)"
@echo " $(GREEN)make clean$(RESET) - Clean up test artifacts"
@echo " $(GREEN)make clean-all$(RESET) - Clean everything including external tools"
@echo ""
@echo "$(WHITE)CI/CD Integration:$(RESET)"
@echo " $(GREEN)make ci-posix-tests$(RESET) - Run POSIX tests optimized for CI/CD"
.PHONY: help check-prereqs check-binary check-fuse check-go setup-reports \
setup-external-tools setup-pjdfstest setup-nfstest setup-fio \
test-posix-basic test-posix-extended test-posix-external test-posix-full \
test-posix-critical test-posix-stress benchmark-posix profile-posix coverage-posix \
test-pjdfstest test-nfstest-posix test-fio-posix create-fio-configs \
generate-report generate-html-report generate-text-report generate-json-report \
clean clean-external-tools clean-all validate fmt lint ci-posix-tests \
list-tests test-info

710
test/fuse_integration/posix_compliance_test.go

@ -0,0 +1,710 @@
//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))
})
}

758
test/fuse_integration/posix_extended_test.go

@ -0,0 +1,758 @@
//go:build !windows
package fuse
import (
"fmt"
"os"
"path/filepath"
"strings"
"syscall"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// POSIXExtendedTestSuite provides additional POSIX compliance tests
// covering extended attributes, file locking, and advanced features.
//
// NOTE: Some tests in this suite may be skipped or may have different behavior
// depending on the FUSE implementation and platform support. This is expected
// behavior for comprehensive POSIX compliance testing.
//
// See POSIX_IMPLEMENTATION_ROADMAP.md for a comprehensive plan to implement
// these missing features.
type POSIXExtendedTestSuite struct {
framework *FuseTestFramework
t *testing.T
}
// ErrorCollector helps collect multiple test errors without stopping execution
type ErrorCollector struct {
errors []string
t *testing.T
}
// NewErrorCollector creates a new error collector
func NewErrorCollector(t *testing.T) *ErrorCollector {
return &ErrorCollector{
errors: make([]string, 0),
t: t,
}
}
// Add adds an error to the collection
func (ec *ErrorCollector) Add(format string, args ...interface{}) {
ec.errors = append(ec.errors, fmt.Sprintf(format, args...))
}
// Check checks a condition and adds an error if it fails
func (ec *ErrorCollector) Check(condition bool, format string, args ...interface{}) {
if !condition {
ec.Add(format, args...)
}
}
// CheckError checks if an error is nil and adds it if not
func (ec *ErrorCollector) CheckError(err error, format string, args ...interface{}) {
if err != nil {
ec.Add(format+": %v", append(args, err)...)
}
}
// HasErrors returns true if any errors were collected
func (ec *ErrorCollector) HasErrors() bool {
return len(ec.errors) > 0
}
// Report reports all collected errors to the test
func (ec *ErrorCollector) Report() {
if len(ec.errors) > 0 {
ec.t.Errorf("Test completed with %d errors:\n%s", len(ec.errors), strings.Join(ec.errors, "\n"))
}
}
// NewPOSIXExtendedTestSuite creates a new extended POSIX compliance test suite
func NewPOSIXExtendedTestSuite(t *testing.T, framework *FuseTestFramework) *POSIXExtendedTestSuite {
return &POSIXExtendedTestSuite{
framework: framework,
t: t,
}
}
// TestPOSIXExtended runs extended POSIX compliance tests
func TestPOSIXExtended(t *testing.T) {
config := DefaultTestConfig()
config.EnableDebug = true
config.MountOptions = []string{"-allowOthers", "-nonempty"}
framework := NewFuseTestFramework(t, config)
defer framework.Cleanup()
// Setup framework with better error handling
err := framework.Setup(config)
if err != nil {
t.Logf("Framework setup failed: %v", err)
t.Skip("Skipping extended tests due to framework setup failure")
return
}
// Verify framework is working
mountPoint := framework.GetMountPoint()
if mountPoint == "" {
t.Log("Framework mount point is empty")
t.Skip("Skipping extended tests due to invalid mount point")
return
}
suite := NewPOSIXExtendedTestSuite(t, framework)
// Run extended POSIX compliance test categories
// Use a resilient approach that continues even if individual tests fail
testCategories := []struct {
name string
fn func(*testing.T)
}{
{"ExtendedAttributes", suite.TestExtendedAttributes},
{"FileLocking", suite.TestFileLocking},
{"AdvancedIO", suite.TestAdvancedIO},
{"SparseFiles", suite.TestSparseFiles},
{"LargeFiles", suite.TestLargeFiles},
{"MMap", suite.TestMemoryMapping},
{"DirectIO", suite.TestDirectIO},
{"FileSealing", suite.TestFileSealing},
{"Fallocate", suite.TestFallocate},
{"Sendfile", suite.TestSendfile},
}
// Run all test categories and collect results
var failedCategories []string
for _, category := range testCategories {
t.Run(category.name, func(subT *testing.T) {
// Capture panics and convert them to test failures
defer func() {
if r := recover(); r != nil {
subT.Errorf("Test category %s panicked: %v", category.name, r)
failedCategories = append(failedCategories, category.name)
}
}()
category.fn(subT)
if subT.Failed() {
failedCategories = append(failedCategories, category.name)
}
})
}
// Report overall results
if len(failedCategories) > 0 {
t.Logf("Extended POSIX tests completed. Failed categories: %v", failedCategories)
} else {
t.Logf("All extended POSIX test categories passed!")
}
}
// TestExtendedAttributes tests POSIX extended attribute support
func (s *POSIXExtendedTestSuite) TestExtendedAttributes(t *testing.T) {
mountPoint := s.framework.GetMountPoint()
t.Run("SetAndGetXattr", func(t *testing.T) {
if !isXattrSupported() {
t.Skip("Extended attributes not supported on this platform")
return
}
testFile := filepath.Join(mountPoint, "xattr_test.txt")
// Create test file
err := os.WriteFile(testFile, []byte("xattr test"), 0644)
if !assert.NoError(t, err, "Failed to create test file") {
return
}
// Set extended attribute
attrName := "user.test_attr"
attrValue := []byte("test_value")
err = setXattr(testFile, attrName, attrValue, 0)
assert.NoError(t, err, "Failed to set extended attribute")
// Verify attribute was set
readValue := make([]byte, 256)
size, err := getXattr(testFile, attrName, readValue)
assert.NoError(t, err, "Failed to get extended attribute")
assert.Equal(t, len(attrValue), size, "Extended attribute size mismatch")
assert.Equal(t, attrValue, readValue[:size], "Extended attribute value mismatch")
})
t.Run("ListXattrs", func(t *testing.T) {
if !isXattrSupported() {
t.Skip("Extended attributes not supported on this platform")
return
}
testFile := filepath.Join(mountPoint, "xattr_list_test.txt")
// Create test file
err := os.WriteFile(testFile, []byte("list xattr test"), 0644)
if !assert.NoError(t, err, "Failed to create test file") {
return
}
// Set multiple extended attributes
attrs := map[string][]byte{
"user.attr1": []byte("value1"),
"user.attr2": []byte("value2"),
"user.attr3": []byte("value3"),
}
for name, value := range attrs {
err = setXattr(testFile, name, value, 0)
assert.NoError(t, err, "Failed to set extended attribute %s", name)
}
// List all attributes
listBuf := make([]byte, 1024)
size, err := listXattr(testFile, listBuf)
assert.NoError(t, err, "Failed to list extended attributes")
assert.Greater(t, size, 0, "No extended attributes found")
// Parse the null-separated list and verify attributes
if size > 0 {
attrList := parseXattrList(listBuf[:size])
expectedAttrs := []string{"user.attr1", "user.attr2", "user.attr3"}
for _, expectedAttr := range expectedAttrs {
assert.Contains(t, attrList, expectedAttr, "Expected attribute %s should be in the list", expectedAttr)
}
}
})
t.Run("RemoveXattr", func(t *testing.T) {
if !isXattrSupported() {
t.Skip("Extended attributes not supported on this platform")
return
}
testFile := filepath.Join(mountPoint, "xattr_remove_test.txt")
// Create test file
err := os.WriteFile(testFile, []byte("remove xattr test"), 0644)
require.NoError(t, err)
// Set extended attribute
attrName := "user.remove_test"
attrValue := []byte("to_be_removed")
err = setXattr(testFile, attrName, attrValue, 0)
require.NoError(t, err)
// Verify attribute exists
readValue := make([]byte, 256)
size, err := getXattr(testFile, attrName, readValue)
require.NoError(t, err)
require.Equal(t, len(attrValue), size)
// Remove the attribute
err = removeXattr(testFile, attrName)
require.NoError(t, err)
// Verify attribute is gone
_, err = getXattr(testFile, attrName, readValue)
require.Error(t, err) // Should fail with ENODATA or similar
})
}
// TestFileLocking tests POSIX file locking mechanisms
// Note: File locking behavior may vary between FUSE implementations.
// Some implementations may not enforce locks between file descriptors
// from the same process, which is a known limitation.
func (s *POSIXExtendedTestSuite) TestFileLocking(t *testing.T) {
mountPoint := s.framework.GetMountPoint()
t.Run("AdvisoryLocking", func(t *testing.T) {
testFile := filepath.Join(mountPoint, "lock_test.txt")
// Create test file
err := os.WriteFile(testFile, []byte("locking test"), 0644)
if !assert.NoError(t, err, "Failed to create test file") {
return
}
// Open file
file, err := os.OpenFile(testFile, os.O_RDWR, 0644)
if !assert.NoError(t, err, "Failed to open test file") {
return
}
defer file.Close()
// Apply exclusive lock
flock := syscall.Flock_t{
Type: syscall.F_WRLCK,
Whence: 0,
Start: 0,
Len: 0, // Lock entire file
}
err = syscall.FcntlFlock(file.Fd(), syscall.F_SETLK, &flock)
assert.NoError(t, err, "Failed to acquire exclusive lock")
// Try to lock from another file descriptor (should fail)
file2, err := os.OpenFile(testFile, os.O_RDWR, 0644)
if !assert.NoError(t, err, "Failed to open second file descriptor") {
return
}
defer file2.Close()
flock2 := syscall.Flock_t{
Type: syscall.F_WRLCK,
Whence: 0,
Start: 0,
Len: 0,
}
// Note: Some FUSE implementations may not enforce file locking between file descriptors
// from the same process. This is a known limitation in some FUSE implementations.
err = syscall.FcntlFlock(file2.Fd(), syscall.F_SETLK, &flock2)
if err == syscall.EAGAIN {
// Lock was properly enforced
t.Log("File locking properly enforced between file descriptors")
} else if err == nil {
// Lock was not enforced (common in some FUSE implementations)
t.Log("File locking not enforced between file descriptors from same process (FUSE limitation)")
} else {
// Some other error occurred
t.Logf("File locking attempt resulted in error: %v", err)
}
// Log the actual error for debugging
t.Logf("File locking test completed with result: %v", err)
// Release lock
flock.Type = syscall.F_UNLCK
err = syscall.FcntlFlock(file.Fd(), syscall.F_SETLK, &flock)
assert.NoError(t, err, "Failed to release lock")
// Now second lock should succeed
err = syscall.FcntlFlock(file2.Fd(), syscall.F_SETLK, &flock2)
assert.NoError(t, err, "Failed to acquire lock after release")
})
t.Run("SharedLocking", func(t *testing.T) {
testFile := filepath.Join(mountPoint, "shared_lock_test.txt")
// Create test file
err := os.WriteFile(testFile, []byte("shared locking test"), 0644)
if !assert.NoError(t, err, "Failed to create test file") {
return
}
// Open file for reading
file1, err := os.Open(testFile)
if !assert.NoError(t, err, "Failed to open first file descriptor") {
return
}
defer file1.Close()
file2, err := os.Open(testFile)
if !assert.NoError(t, err, "Failed to open second file descriptor") {
return
}
defer file2.Close()
// Apply shared locks (should both succeed)
flock1 := syscall.Flock_t{
Type: syscall.F_RDLCK,
Whence: 0,
Start: 0,
Len: 0,
}
flock2 := syscall.Flock_t{
Type: syscall.F_RDLCK,
Whence: 0,
Start: 0,
Len: 0,
}
err = syscall.FcntlFlock(file1.Fd(), syscall.F_SETLK, &flock1)
assert.NoError(t, err, "Failed to acquire first shared lock")
err = syscall.FcntlFlock(file2.Fd(), syscall.F_SETLK, &flock2)
assert.NoError(t, err, "Failed to acquire second shared lock")
})
}
// TestAdvancedIO tests advanced I/O operations
// Note: Vectored I/O support may vary between FUSE implementations.
// Some implementations may not properly support readv/writev operations.
func (s *POSIXExtendedTestSuite) TestAdvancedIO(t *testing.T) {
mountPoint := s.framework.GetMountPoint()
t.Run("ReadWriteV", func(t *testing.T) {
if !isVectoredIOSupported() {
t.Skip("Vectored I/O not supported on this platform")
return
}
testFile := filepath.Join(mountPoint, "readwritev_test.txt")
// Create file
fd, err := syscall.Open(testFile, syscall.O_CREAT|syscall.O_RDWR, 0644)
if !assert.NoError(t, err, "Failed to create test file") {
return
}
defer syscall.Close(fd)
// Prepare test data in multiple buffers
writeBuffers := [][]byte{
[]byte("Hello "),
[]byte("vectored "),
[]byte("I/O "),
[]byte("world!"),
}
// Write using writev
writeIOVs := makeIOVecs(writeBuffers)
t.Logf("Attempting vectored I/O write with %d buffers", len(writeIOVs))
totalWritten, err := writevFile(fd, writeIOVs)
if err != nil {
// Some FUSE implementations may not support vectored I/O properly
t.Logf("Vectored I/O write failed: %v - this may be a FUSE limitation", err)
t.Skip("Vectored I/O not properly supported by this FUSE implementation")
return
}
expectedTotal := 0
for _, buf := range writeBuffers {
expectedTotal += len(buf)
}
assert.Equal(t, expectedTotal, totalWritten, "Vectored write size mismatch")
// Seek back to beginning
_, err = syscall.Seek(fd, 0, 0)
if !assert.NoError(t, err, "Failed to seek to beginning") {
return
}
// Read using readv into multiple buffers
readBuffers := [][]byte{
make([]byte, 6), // "Hello "
make([]byte, 9), // "vectored "
make([]byte, 3), // "I/O "
make([]byte, 6), // "world!"
}
readIOVs := makeIOVecs(readBuffers)
t.Logf("Attempting vectored I/O read with %d buffers", len(readIOVs))
totalRead, err := readvFile(fd, readIOVs)
if err != nil {
t.Logf("Vectored I/O read failed: %v - this may be a FUSE limitation", err)
t.Skip("Vectored I/O not properly supported by this FUSE implementation")
return
}
assert.Equal(t, expectedTotal, totalRead, "Vectored read size mismatch")
// Verify data matches
for i, expected := range writeBuffers {
assert.Equal(t, expected, readBuffers[i], "Vectored I/O data mismatch at buffer %d", i)
}
})
}
// TestSparseFiles tests sparse file handling
func (s *POSIXExtendedTestSuite) TestSparseFiles(t *testing.T) {
mountPoint := s.framework.GetMountPoint()
t.Run("CreateSparseFile", func(t *testing.T) {
testFile := filepath.Join(mountPoint, "sparse_test.txt")
// Open file
fd, err := syscall.Open(testFile, syscall.O_CREAT|syscall.O_RDWR, 0644)
require.NoError(t, err)
defer syscall.Close(fd)
// Create a sparse file by seeking beyond end and writing
const sparseSize = 1024 * 1024 // 1MB
_, err = syscall.Seek(fd, sparseSize, 0)
require.NoError(t, err)
// Write at the end
endData := []byte("end")
n, err := syscall.Write(fd, endData)
require.NoError(t, err)
require.Equal(t, len(endData), n)
// Verify file size
stat, err := os.Stat(testFile)
require.NoError(t, err)
require.Equal(t, int64(sparseSize+len(endData)), stat.Size())
// Read from the beginning (should be zeros)
_, err = syscall.Seek(fd, 0, 0)
require.NoError(t, err)
buffer := make([]byte, 100)
n, err = syscall.Read(fd, buffer)
require.NoError(t, err)
require.Equal(t, 100, n)
// Should be all zeros
for _, b := range buffer {
require.Equal(t, byte(0), b)
}
})
}
// TestLargeFiles tests large file handling (>2GB)
func (s *POSIXExtendedTestSuite) TestLargeFiles(t *testing.T) {
mountPoint := s.framework.GetMountPoint()
t.Run("LargeFileOperations", func(t *testing.T) {
testFile := filepath.Join(mountPoint, "large_file_test.txt")
// Open file
fd, err := syscall.Open(testFile, syscall.O_CREAT|syscall.O_RDWR, 0644)
require.NoError(t, err)
defer syscall.Close(fd)
// Seek to position > 2GB
const largeOffset = 3 * 1024 * 1024 * 1024 // 3GB
pos, err := syscall.Seek(fd, largeOffset, 0)
require.NoError(t, err)
require.Equal(t, int64(largeOffset), pos)
// Write at large offset
testData := []byte("large file data")
n, err := syscall.Write(fd, testData)
require.NoError(t, err)
require.Equal(t, len(testData), n)
// Read back from large offset
_, err = syscall.Seek(fd, largeOffset, 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)
})
}
// TestMemoryMapping tests memory mapping functionality
func (s *POSIXExtendedTestSuite) TestMemoryMapping(t *testing.T) {
mountPoint := s.framework.GetMountPoint()
t.Run("MmapFile", func(t *testing.T) {
if !isMmapSupported() {
t.Skip("Memory mapping not supported on this platform")
return
}
testFile := filepath.Join(mountPoint, "mmap_test.txt")
testData := make([]byte, 4096)
for i := range testData {
testData[i] = byte(i % 256)
}
// Create test file
err := os.WriteFile(testFile, testData, 0644)
require.NoError(t, err)
// Open file for reading
fd, err := syscall.Open(testFile, syscall.O_RDONLY, 0)
require.NoError(t, err)
defer syscall.Close(fd)
// Memory map the file
mappedData, err := mmapFile(fd, 0, len(testData), PROT_READ, MAP_SHARED)
require.NoError(t, err)
defer munmapFile(mappedData)
// Verify mapped content matches original
require.Equal(t, testData, mappedData)
})
t.Run("MmapWrite", func(t *testing.T) {
if !isMmapSupported() {
t.Skip("Memory mapping not supported on this platform")
return
}
testFile := filepath.Join(mountPoint, "mmap_write_test.txt")
size := 4096
// Create empty file of specific size
fd, err := syscall.Open(testFile, syscall.O_CREAT|syscall.O_RDWR, 0644)
require.NoError(t, err)
defer syscall.Close(fd)
err = syscall.Ftruncate(fd, int64(size))
require.NoError(t, err)
// Memory map the file for writing
mappedData, err := mmapFile(fd, 0, size, PROT_READ|PROT_WRITE, MAP_SHARED)
require.NoError(t, err)
defer munmapFile(mappedData)
// Write test pattern to mapped memory
testPattern := []byte("Hello, mmap world!")
copy(mappedData, testPattern)
// Sync changes to disk
err = msyncFile(mappedData, MS_SYNC)
require.NoError(t, err)
// Verify changes were written by reading file directly
readData, err := os.ReadFile(testFile)
require.NoError(t, err)
require.True(t, len(readData) >= len(testPattern))
require.Equal(t, testPattern, readData[:len(testPattern)])
})
}
// TestDirectIO tests direct I/O operations
func (s *POSIXExtendedTestSuite) TestDirectIO(t *testing.T) {
mountPoint := s.framework.GetMountPoint()
t.Run("DirectIO", func(t *testing.T) {
if !isDirectIOSupported() {
t.Skip("Direct I/O not supported on this platform")
return
}
testFile := filepath.Join(mountPoint, "directio_test.txt")
// Open file with direct I/O
fd, err := openDirectIO(testFile, syscall.O_CREAT|syscall.O_RDWR, 0644)
require.NoError(t, err)
defer syscall.Close(fd)
// For direct I/O, data must be aligned to sector boundaries
// Use 4KB aligned buffer (common sector size)
blockSize := 4096
testData := make([]byte, blockSize)
for i := range testData {
testData[i] = byte(i % 256)
}
// Write data using direct I/O
n, err := syscall.Write(fd, testData)
require.NoError(t, err)
require.Equal(t, len(testData), n)
// Seek back to beginning
_, err = syscall.Seek(fd, 0, 0)
require.NoError(t, err)
// Read data using direct I/O
readData := make([]byte, blockSize)
n, err = syscall.Read(fd, readData)
require.NoError(t, err)
require.Equal(t, len(testData), n)
require.Equal(t, testData, readData)
})
}
// TestFileSealing tests file sealing mechanisms (Linux-specific)
func (s *POSIXExtendedTestSuite) TestFileSealing(t *testing.T) {
// This test is Linux-specific and may not be applicable to all systems
t.Skip("File sealing tests require Linux-specific features")
}
// TestFallocate tests file preallocation
func (s *POSIXExtendedTestSuite) TestFallocate(t *testing.T) {
mountPoint := s.framework.GetMountPoint()
t.Run("FallocateSpace", func(t *testing.T) {
if !isFallocateSupported() {
t.Skip("fallocate not supported on this platform")
return
}
testFile := filepath.Join(mountPoint, "fallocate_test.txt")
// Open file
fd, err := syscall.Open(testFile, syscall.O_CREAT|syscall.O_RDWR, 0644)
require.NoError(t, err)
defer syscall.Close(fd)
// Preallocate 1MB of space
allocSize := int64(1024 * 1024)
err = fallocateFile(fd, 0, 0, allocSize)
require.NoError(t, err)
// Verify file size was extended
var stat syscall.Stat_t
err = syscall.Fstat(fd, &stat)
require.NoError(t, err)
require.GreaterOrEqual(t, stat.Size, allocSize)
// Write some data and verify it works
testData := []byte("fallocate test data")
n, err := syscall.Write(fd, testData)
require.NoError(t, err)
require.Equal(t, len(testData), n)
})
}
// TestSendfile tests zero-copy file transfer
func (s *POSIXExtendedTestSuite) TestSendfile(t *testing.T) {
mountPoint := s.framework.GetMountPoint()
t.Run("SendfileCopy", func(t *testing.T) {
sourceFile := filepath.Join(mountPoint, "sendfile_source.txt")
targetFile := filepath.Join(mountPoint, "sendfile_target.txt")
testData := make([]byte, 8192)
for i := range testData {
testData[i] = byte(i % 256)
}
// Create source file
err := os.WriteFile(sourceFile, testData, 0644)
require.NoError(t, err)
// Open source for reading
srcFd, err := syscall.Open(sourceFile, syscall.O_RDONLY, 0)
require.NoError(t, err)
defer syscall.Close(srcFd)
// Create target file
dstFd, err := syscall.Open(targetFile, syscall.O_CREAT|syscall.O_WRONLY, 0644)
require.NoError(t, err)
defer syscall.Close(dstFd)
// Sendfile test
if !isSendfileSupported() {
t.Skip("sendfile not supported on this platform")
return
}
// Use sendfile to copy data
var offset int64 = 0
transferred, err := sendfileTransfer(dstFd, srcFd, &offset, len(testData))
require.NoError(t, err)
require.Equal(t, len(testData), transferred)
// Verify copy
copiedData, err := os.ReadFile(targetFile)
require.NoError(t, err)
require.Equal(t, testData, copiedData)
})
}
// Helper function to parse null-separated xattr list
func parseXattrList(data []byte) []string {
var attrs []string
start := 0
for i, b := range data {
if b == 0 {
if i > start {
attrs = append(attrs, string(data[start:i]))
}
start = i + 1
}
}
return attrs
}

562
test/fuse_integration/posix_external_test.go

@ -0,0 +1,562 @@
package fuse
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// ExternalPOSIXTestSuite manages integration with external POSIX test suites
type ExternalPOSIXTestSuite struct {
framework *FuseTestFramework
t *testing.T
workDir string
}
// NewExternalPOSIXTestSuite creates a new external POSIX test suite runner
func NewExternalPOSIXTestSuite(t *testing.T, framework *FuseTestFramework) *ExternalPOSIXTestSuite {
workDir := filepath.Join(os.TempDir(), fmt.Sprintf("posix_external_tests_%d", time.Now().Unix()))
os.MkdirAll(workDir, 0755)
return &ExternalPOSIXTestSuite{
framework: framework,
t: t,
workDir: workDir,
}
}
// Cleanup removes temporary test directories
func (s *ExternalPOSIXTestSuite) Cleanup() {
os.RemoveAll(s.workDir)
}
// TestExternalPOSIXSuites runs integration tests with external POSIX test suites
func TestExternalPOSIXSuites(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 := NewExternalPOSIXTestSuite(t, framework)
defer suite.Cleanup()
// Run various external POSIX test suites
t.Run("PjdFsTest", suite.TestPjdFsTest)
t.Run("NFSTestPOSIX", suite.TestNFSTestPOSIX)
t.Run("LitmusTests", suite.TestLitmusTests)
t.Run("CustomPOSIXTests", suite.TestCustomPOSIXTests)
}
// TestPjdFsTest runs the comprehensive pjdfstest POSIX filesystem test suite
func (s *ExternalPOSIXTestSuite) TestPjdFsTest(t *testing.T) {
mountPoint := s.framework.GetMountPoint()
// Check if pjdfstest is available
_, err := exec.LookPath("pjdfstest")
if err != nil {
// For comprehensive POSIX compliance testing, external tools should be mandatory
if os.Getenv("POSIX_REQUIRE_EXTERNAL_TOOLS") == "true" {
t.Fatalf("pjdfstest is required for comprehensive POSIX compliance testing but not found. Install from: https://github.com/pjd/pjdfstest")
}
t.Skip("pjdfstest not found. Install from: https://github.com/pjd/pjdfstest")
}
// Create test directory within mount point
testDir := filepath.Join(mountPoint, "pjdfstest")
err = os.MkdirAll(testDir, 0755)
require.NoError(t, err)
// List of critical POSIX operations to test
pjdTests := []struct {
name string
testPath string
critical bool
}{
{"chflags", "tests/chflags", false},
{"chmod", "tests/chmod", true},
{"chown", "tests/chown", true},
{"create", "tests/create", true},
{"link", "tests/link", true},
{"mkdir", "tests/mkdir", true},
{"mkfifo", "tests/mkfifo", false},
{"mknod", "tests/mknod", false},
{"open", "tests/open", true},
{"rename", "tests/rename", true},
{"rmdir", "tests/rmdir", true},
{"symlink", "tests/symlink", true},
{"truncate", "tests/truncate", true},
{"unlink", "tests/unlink", true},
}
// Download and setup pjdfstest if needed
pjdDir := filepath.Join(s.workDir, "pjdfstest")
if _, err := os.Stat(pjdDir); os.IsNotExist(err) {
t.Logf("Setting up pjdfstest...")
err = s.setupPjdFsTest(pjdDir)
if err != nil {
t.Skipf("Failed to setup pjdfstest: %v", err)
}
}
// Run each test category
for _, test := range pjdTests {
t.Run(test.name, func(t *testing.T) {
s.runPjdTest(t, pjdDir, test.testPath, testDir, test.critical)
})
}
}
// TestNFSTestPOSIX runs nfstest_posix for POSIX API verification
func (s *ExternalPOSIXTestSuite) TestNFSTestPOSIX(t *testing.T) {
mountPoint := s.framework.GetMountPoint()
// Check if nfstest_posix is available
_, err := exec.LookPath("nfstest_posix")
if err != nil {
// For comprehensive POSIX compliance testing, external tools should be mandatory
if os.Getenv("POSIX_REQUIRE_EXTERNAL_TOOLS") == "true" {
t.Fatalf("nfstest_posix is required for comprehensive POSIX compliance testing but not found. Install via: pip install nfstest")
}
t.Skip("nfstest_posix not found. Install via: pip install nfstest")
}
testDir := filepath.Join(mountPoint, "nfstest")
err = os.MkdirAll(testDir, 0755)
require.NoError(t, err)
// Run nfstest_posix with comprehensive API testing
cmd := exec.Command("nfstest_posix",
"--path", testDir,
"--verbose",
"--createlog",
"--runid", fmt.Sprintf("seaweedfs_%d", time.Now().Unix()),
)
output, err := cmd.CombinedOutput()
t.Logf("nfstest_posix output:\n%s", string(output))
if err != nil {
t.Logf("nfstest_posix failed: %v", err)
// Don't fail the test completely, just log the failure
}
}
// TestLitmusTests runs focused POSIX compliance litmus tests
func (s *ExternalPOSIXTestSuite) TestLitmusTests(t *testing.T) {
mountPoint := s.framework.GetMountPoint()
// Create litmus test scripts for critical POSIX behaviors
litmusTests := []struct {
name string
script string
}{
{
name: "AtomicRename",
script: `#!/bin/bash
set -e
cd "$1"
echo "test data" > temp_file
echo "original" > target_file
mv temp_file target_file
[ "$(cat target_file)" = "test data" ]
echo "PASS: Atomic rename works"
`,
},
{
name: "LinkCount",
script: `#!/bin/bash
set -e
cd "$1"
echo "test" > original
ln original hardlink
if [[ "$(uname)" == "Darwin" ]]; then
[ $(stat -f %l original) -eq 2 ]
else
[ $(stat -c %h original) -eq 2 ]
fi
rm hardlink
if [[ "$(uname)" == "Darwin" ]]; then
[ $(stat -f %l original) -eq 1 ]
else
[ $(stat -c %h original) -eq 1 ]
fi
echo "PASS: Hard link counting works"
`,
},
{
name: "SymlinkCycles",
script: `#!/bin/bash
set -e
cd "$1"
ln -s link1 link2
ln -s link2 link1
# Try to access the file, which should fail due to too many symbolic links
if cat link1 2>/dev/null; then
echo "FAIL: Symlink cycle not detected"
exit 1
fi
echo "PASS: Symlink cycle handling works"
`,
},
{
name: "ConcurrentCreate",
script: `#!/bin/bash
set -e
cd "$1"
for i in {1..10}; do
(echo "process $i" > "file_$i") &
done
wait
[ $(ls file_* | wc -l) -eq 10 ]
echo "PASS: Concurrent file creation works"
`,
},
{
name: "DirectoryConsistency",
script: `#!/bin/bash
set -e
cd "$1"
mkdir testdir
cd testdir
touch file1 file2 file3
cd ..
entries=$(ls testdir | wc -l)
[ $entries -eq 3 ]
rmdir testdir 2>/dev/null && echo "FAIL: Non-empty directory removed" && exit 1
rm testdir/*
rmdir testdir
echo "PASS: Directory consistency works"
`,
},
}
testDir := filepath.Join(mountPoint, "litmus")
err := os.MkdirAll(testDir, 0755)
require.NoError(t, err)
// Run each litmus test
for _, test := range litmusTests {
t.Run(test.name, func(t *testing.T) {
s.runLitmusTest(t, test.name, test.script, testDir)
})
}
}
// TestCustomPOSIXTests runs custom POSIX compliance tests
func (s *ExternalPOSIXTestSuite) TestCustomPOSIXTests(t *testing.T) {
mountPoint := s.framework.GetMountPoint()
// Custom stress tests for POSIX compliance
t.Run("StressRename", func(t *testing.T) {
s.stressTestRename(t, mountPoint)
})
t.Run("StressCreate", func(t *testing.T) {
s.stressTestCreate(t, mountPoint)
})
t.Run("StressDirectory", func(t *testing.T) {
s.stressTestDirectory(t, mountPoint)
})
t.Run("EdgeCases", func(t *testing.T) {
s.testEdgeCases(t, mountPoint)
})
}
// setupPjdFsTest downloads and sets up the pjdfstest suite
func (s *ExternalPOSIXTestSuite) setupPjdFsTest(targetDir string) error {
// Clone pjdfstest repository
cmd := exec.Command("git", "clone", "https://github.com/pjd/pjdfstest.git", targetDir)
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to clone pjdfstest: %w", err)
}
// Build pjdfstest
cmd = exec.Command("make", "-C", targetDir)
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to build pjdfstest: %w", err)
}
return nil
}
// runPjdTest executes a specific pjdfstest
func (s *ExternalPOSIXTestSuite) runPjdTest(t *testing.T, pjdDir, testPath, mountDir string, critical bool) {
fullTestPath := filepath.Join(pjdDir, testPath)
// Check if test path exists
if _, err := os.Stat(fullTestPath); os.IsNotExist(err) {
t.Skipf("Test path does not exist: %s", fullTestPath)
}
cmd := exec.Command("prove", "-r", fullTestPath)
cmd.Dir = mountDir
cmd.Env = append(os.Environ(), "FSTEST="+filepath.Join(pjdDir, "pjdfstest"))
output, err := cmd.CombinedOutput()
t.Logf("pjdfstest %s output:\n%s", testPath, string(output))
if err != nil {
if critical {
t.Errorf("Critical pjdfstest failed: %s - %v", testPath, err)
} else {
t.Logf("Non-critical pjdfstest failed: %s - %v", testPath, err)
}
}
}
// runLitmusTest executes a litmus test script
func (s *ExternalPOSIXTestSuite) runLitmusTest(t *testing.T, name, script, testDir string) {
scriptPath := filepath.Join(s.workDir, name+".sh")
// Write script to file
err := os.WriteFile(scriptPath, []byte(script), 0755)
require.NoError(t, err)
// Create isolated test directory
isolatedDir := filepath.Join(testDir, name)
err = os.MkdirAll(isolatedDir, 0755)
require.NoError(t, err)
defer os.RemoveAll(isolatedDir)
// Execute script
cmd := exec.Command("bash", scriptPath, isolatedDir)
output, err := cmd.CombinedOutput()
t.Logf("Litmus test %s output:\n%s", name, string(output))
if err != nil {
t.Errorf("Litmus test %s failed: %v", name, err)
}
}
// stressTestRename tests rename operations under stress
func (s *ExternalPOSIXTestSuite) stressTestRename(t *testing.T, mountPoint string) {
testDir := filepath.Join(mountPoint, "stress_rename")
err := os.MkdirAll(testDir, 0755)
require.NoError(t, err)
defer os.RemoveAll(testDir)
// Create files and rename them concurrently
const numFiles = 100
const numWorkers = 10
// Create initial files
for i := 0; i < numFiles; i++ {
fileName := filepath.Join(testDir, fmt.Sprintf("file_%d.txt", i))
err := os.WriteFile(fileName, []byte(fmt.Sprintf("content_%d", i)), 0644)
if !assert.NoError(t, err, "Failed to create initial file %d", i) {
return // Skip test if setup fails
}
}
// Concurrent rename operations
results := make(chan error, numWorkers)
for w := 0; w < numWorkers; w++ {
go func(worker int) {
for i := worker; i < numFiles; i += numWorkers {
oldName := filepath.Join(testDir, fmt.Sprintf("file_%d.txt", i))
newName := filepath.Join(testDir, fmt.Sprintf("renamed_%d.txt", i))
err := os.Rename(oldName, newName)
if err != nil {
results <- err
return
}
}
results <- nil
}(w)
}
// Wait for all workers and collect errors
var errorCount int
for w := 0; w < numWorkers; w++ {
err := <-results
if err != nil {
assert.NoError(t, err, "Worker %d failed", w)
errorCount++
}
}
// Verify all files were renamed (if no errors occurred)
if errorCount == 0 {
files, err := filepath.Glob(filepath.Join(testDir, "renamed_*.txt"))
assert.NoError(t, err, "Failed to list renamed files")
assert.Equal(t, numFiles, len(files), "Not all files were renamed successfully")
}
}
// stressTestCreate tests file creation under stress
func (s *ExternalPOSIXTestSuite) stressTestCreate(t *testing.T, mountPoint string) {
testDir := filepath.Join(mountPoint, "stress_create")
err := os.MkdirAll(testDir, 0755)
require.NoError(t, err)
defer os.RemoveAll(testDir)
const numFiles = 1000
const numWorkers = 20
results := make(chan error, numWorkers)
for w := 0; w < numWorkers; w++ {
go func(worker int) {
for i := worker; i < numFiles; i += numWorkers {
fileName := filepath.Join(testDir, fmt.Sprintf("stress_%d_%d.txt", worker, i))
err := os.WriteFile(fileName, []byte(fmt.Sprintf("data_%d_%d", worker, i)), 0644)
if err != nil {
results <- err
return
}
}
results <- nil
}(w)
}
// Wait for all workers
for w := 0; w < numWorkers; w++ {
err := <-results
require.NoError(t, err)
}
// Verify all files were created
files, err := filepath.Glob(filepath.Join(testDir, "stress_*.txt"))
require.NoError(t, err)
require.Equal(t, numFiles, len(files))
}
// stressTestDirectory tests directory operations under stress
func (s *ExternalPOSIXTestSuite) stressTestDirectory(t *testing.T, mountPoint string) {
testDir := filepath.Join(mountPoint, "stress_directory")
err := os.MkdirAll(testDir, 0755)
require.NoError(t, err)
defer os.RemoveAll(testDir)
const numDirs = 500
const numWorkers = 10
results := make(chan error, numWorkers)
// Create directories concurrently
for w := 0; w < numWorkers; w++ {
go func(worker int) {
for i := worker; i < numDirs; i += numWorkers {
dirName := filepath.Join(testDir, fmt.Sprintf("dir_%d_%d", worker, i))
err := os.MkdirAll(dirName, 0755)
if err != nil {
results <- err
return
}
// Create a file in each directory
fileName := filepath.Join(dirName, "test.txt")
err = os.WriteFile(fileName, []byte("test"), 0644)
if err != nil {
results <- err
return
}
}
results <- nil
}(w)
}
// Wait for all workers
for w := 0; w < numWorkers; w++ {
err := <-results
require.NoError(t, err)
}
// Verify directory structure
dirs, err := filepath.Glob(filepath.Join(testDir, "dir_*"))
require.NoError(t, err)
require.Equal(t, numDirs, len(dirs))
}
// testEdgeCases tests various POSIX edge cases
func (s *ExternalPOSIXTestSuite) testEdgeCases(t *testing.T, mountPoint string) {
testDir := filepath.Join(mountPoint, "edge_cases")
err := os.MkdirAll(testDir, 0755)
require.NoError(t, err)
defer os.RemoveAll(testDir)
t.Run("WriteToDirectoryAsFile", func(t *testing.T) {
// Test writing to a directory as if it were a file (should fail)
// Note: filepath.Join(testDir, "") returns testDir itself
err := os.WriteFile(testDir, []byte("test"), 0644)
require.Error(t, err)
// Verify the error is specifically about the target being a directory
var pathErr *os.PathError
require.ErrorAs(t, err, &pathErr)
require.Equal(t, syscall.EISDIR, pathErr.Err)
})
t.Run("VeryLongFileName", func(t *testing.T) {
// Test very long file names
longName := strings.Repeat("a", 255) // NAME_MAX is typically 255
longFile := filepath.Join(testDir, longName)
err := os.WriteFile(longFile, []byte("long name test"), 0644)
// This might succeed or fail depending on filesystem limits
if err == nil {
defer os.Remove(longFile)
t.Logf("Very long filename accepted")
} else {
t.Logf("Very long filename rejected: %v", err)
}
})
t.Run("SpecialCharacters", func(t *testing.T) {
// Test files with special characters
specialFiles := []string{
"file with spaces.txt",
"file-with-dashes.txt",
"file_with_underscores.txt",
"file.with.dots.txt",
}
for _, fileName := range specialFiles {
filePath := filepath.Join(testDir, fileName)
err := os.WriteFile(filePath, []byte("special char test"), 0644)
require.NoError(t, err, "Failed to create file with special chars: %s", fileName)
// Verify we can read it back
_, err = os.ReadFile(filePath)
require.NoError(t, err, "Failed to read file with special chars: %s", fileName)
}
})
t.Run("DeepDirectoryNesting", func(t *testing.T) {
// Test deep directory nesting
deepPath := testDir
for i := 0; i < 100; i++ {
deepPath = filepath.Join(deepPath, fmt.Sprintf("level_%d", i))
}
err := os.MkdirAll(deepPath, 0755)
// This might fail due to PATH_MAX limits
if err != nil {
t.Logf("Deep directory nesting failed at some level: %v", err)
} else {
t.Logf("Deep directory nesting succeeded")
// Test creating a file in the deep path
testFile := filepath.Join(deepPath, "deep_file.txt")
err = os.WriteFile(testFile, []byte("deep test"), 0644)
if err == nil {
t.Logf("File creation in deep path succeeded")
} else {
t.Logf("File creation in deep path failed: %v", err)
}
}
})
}

43
test/fuse_integration/sendfile_darwin.go

@ -0,0 +1,43 @@
//go:build darwin
package fuse
import (
"syscall"
"unsafe"
)
// Sendfile support for macOS
func sendfileTransfer(outFd int, inFd int, offset *int64, count int) (int, error) {
// macOS sendfile has different signature: sendfile(in_fd, out_fd, offset, len, hdtr, flags)
var off int64
if offset != nil {
off = *offset
}
length := int64(count)
_, _, errno := syscall.Syscall6(syscall.SYS_SENDFILE,
uintptr(inFd), // input fd
uintptr(outFd), // output fd
uintptr(off), // offset
uintptr(unsafe.Pointer(&length)), // length (in/out parameter)
0, // hdtr (headers/trailers)
0) // flags
if errno != 0 {
return 0, errno
}
// Update offset if provided
if offset != nil {
*offset += length
}
return int(length), nil
}
func isSendfileSupported() bool {
return true
}

33
test/fuse_integration/sendfile_linux.go

@ -0,0 +1,33 @@
//go:build linux
package fuse
import (
"syscall"
"unsafe"
)
// Sendfile support for Linux
func sendfileTransfer(outFd int, inFd int, offset *int64, count int) (int, error) {
var offsetPtr uintptr
if offset != nil {
offsetPtr = uintptr(unsafe.Pointer(offset))
}
n, _, errno := syscall.Syscall6(syscall.SYS_SENDFILE,
uintptr(outFd),
uintptr(inFd),
offsetPtr,
uintptr(count),
0, 0)
if errno != 0 {
return 0, errno
}
return int(n), nil
}
func isSendfileSupported() bool {
return true
}

19
test/fuse_integration/sendfile_unsupported.go

@ -0,0 +1,19 @@
//go:build !linux && !darwin
package fuse
import (
"errors"
)
// Sendfile support for unsupported platforms
var ErrSendfileNotSupported = errors.New("sendfile not supported on this platform")
func sendfileTransfer(outFd int, inFd int, offset *int64, count int) (int, error) {
return 0, ErrSendfileNotSupported
}
func isSendfileSupported() bool {
return false
}

2
test/fuse_integration/simple_test.go

@ -1,4 +1,4 @@
package fuse_test
package fuse
import ( import (
"testing" "testing"

126
test/fuse_integration/vectored_io_unix.go

@ -0,0 +1,126 @@
//go:build unix
package fuse
import (
"syscall"
"unsafe"
)
// Vectored I/O support for Unix-like systems
// IOVec represents an I/O vector for readv/writev operations
type IOVec struct {
Base *byte
Len uint64
}
func readvFile(fd int, iovs []IOVec) (int, error) {
if len(iovs) == 0 {
return 0, nil
}
n, _, errno := syscall.Syscall(syscall.SYS_READV,
uintptr(fd),
uintptr(unsafe.Pointer(&iovs[0])),
uintptr(len(iovs)))
if errno != 0 {
return 0, errno
}
return int(n), nil
}
func writevFile(fd int, iovs []IOVec) (int, error) {
if len(iovs) == 0 {
return 0, nil
}
n, _, errno := syscall.Syscall(syscall.SYS_WRITEV,
uintptr(fd),
uintptr(unsafe.Pointer(&iovs[0])),
uintptr(len(iovs)))
if errno != 0 {
return 0, errno
}
return int(n), nil
}
func preadvFile(fd int, iovs []IOVec, offset int64) (int, error) {
if len(iovs) == 0 {
return 0, nil
}
// preadv/pwritev may not be available on all Unix systems
// Fall back to individual pread calls
totalRead := 0
currentOffset := offset
for _, iov := range iovs {
if iov.Len == 0 {
continue
}
buf := (*[1 << 30]byte)(unsafe.Pointer(iov.Base))[:iov.Len:iov.Len]
n, err := syscall.Pread(fd, buf, currentOffset)
if err != nil {
return totalRead, err
}
totalRead += n
currentOffset += int64(n)
if n < int(iov.Len) {
break // EOF or partial read
}
}
return totalRead, nil
}
func pwritevFile(fd int, iovs []IOVec, offset int64) (int, error) {
if len(iovs) == 0 {
return 0, nil
}
// preadv/pwritev may not be available on all Unix systems
// Fall back to individual pwrite calls
totalWritten := 0
currentOffset := offset
for _, iov := range iovs {
if iov.Len == 0 {
continue
}
buf := (*[1 << 30]byte)(unsafe.Pointer(iov.Base))[:iov.Len:iov.Len]
n, err := syscall.Pwrite(fd, buf, currentOffset)
if err != nil {
return totalWritten, err
}
totalWritten += n
currentOffset += int64(n)
if n < int(iov.Len) {
break // Partial write
}
}
return totalWritten, nil
}
// Helper function to create IOVec from byte slices
func makeIOVecs(buffers [][]byte) []IOVec {
iovs := make([]IOVec, len(buffers))
for i, buf := range buffers {
if len(buf) > 0 {
iovs[i].Base = &buf[0]
iovs[i].Len = uint64(len(buf))
}
}
return iovs
}
func isVectoredIOSupported() bool {
return true
}

41
test/fuse_integration/vectored_io_unsupported.go

@ -0,0 +1,41 @@
//go:build !unix
package fuse
import (
"errors"
)
// Vectored I/O support for unsupported platforms
var ErrVectoredIONotSupported = errors.New("vectored I/O not supported on this platform")
// IOVec represents an I/O vector for readv/writev operations
type IOVec struct {
Base *byte
Len uint64
}
func readvFile(fd int, iovs []IOVec) (int, error) {
return 0, ErrVectoredIONotSupported
}
func writevFile(fd int, iovs []IOVec) (int, error) {
return 0, ErrVectoredIONotSupported
}
func preadvFile(fd int, iovs []IOVec, offset int64) (int, error) {
return 0, ErrVectoredIONotSupported
}
func pwritevFile(fd int, iovs []IOVec, offset int64) (int, error) {
return 0, ErrVectoredIONotSupported
}
func makeIOVecs(buffers [][]byte) []IOVec {
return nil
}
func isVectoredIOSupported() bool {
return false
}

2
test/fuse_integration/working_demo_test.go

@ -1,4 +1,4 @@
package fuse_test
package fuse
import ( import (
"os" "os"

125
test/fuse_integration/xattr_darwin.go

@ -0,0 +1,125 @@
//go:build darwin
package fuse
import (
"syscall"
"unsafe"
)
// Extended attributes support for macOS
const (
// macOS-specific flags
XATTR_NOFOLLOW = 0x0001
XATTR_CREATE = 0x0002
XATTR_REPLACE = 0x0004
)
func setXattr(path, name string, value []byte, flags int) error {
pathBytes, err := syscall.BytePtrFromString(path)
if err != nil {
return err
}
nameBytes, err := syscall.BytePtrFromString(name)
if err != nil {
return err
}
var valuePtr unsafe.Pointer
if len(value) > 0 {
valuePtr = unsafe.Pointer(&value[0])
}
_, _, errno := syscall.Syscall6(syscall.SYS_SETXATTR,
uintptr(unsafe.Pointer(pathBytes)),
uintptr(unsafe.Pointer(nameBytes)),
uintptr(valuePtr),
uintptr(len(value)),
uintptr(0), // position (not used for regular files)
uintptr(flags))
if errno != 0 {
return errno
}
return nil
}
func getXattr(path, name string, value []byte) (int, error) {
pathBytes, err := syscall.BytePtrFromString(path)
if err != nil {
return 0, err
}
nameBytes, err := syscall.BytePtrFromString(name)
if err != nil {
return 0, err
}
var valuePtr unsafe.Pointer
if len(value) > 0 {
valuePtr = unsafe.Pointer(&value[0])
}
size, _, errno := syscall.Syscall6(syscall.SYS_GETXATTR,
uintptr(unsafe.Pointer(pathBytes)),
uintptr(unsafe.Pointer(nameBytes)),
uintptr(valuePtr),
uintptr(len(value)),
uintptr(0), // position
uintptr(0)) // options
if errno != 0 {
return 0, errno
}
return int(size), nil
}
func listXattr(path string, list []byte) (int, error) {
pathBytes, err := syscall.BytePtrFromString(path)
if err != nil {
return 0, err
}
var listPtr unsafe.Pointer
if len(list) > 0 {
listPtr = unsafe.Pointer(&list[0])
}
size, _, errno := syscall.Syscall6(syscall.SYS_LISTXATTR,
uintptr(unsafe.Pointer(pathBytes)),
uintptr(listPtr),
uintptr(len(list)),
uintptr(0), // options
0, 0)
if errno != 0 {
return 0, errno
}
return int(size), nil
}
func removeXattr(path, name string) error {
pathBytes, err := syscall.BytePtrFromString(path)
if err != nil {
return err
}
nameBytes, err := syscall.BytePtrFromString(name)
if err != nil {
return err
}
_, _, errno := syscall.Syscall6(syscall.SYS_REMOVEXATTR,
uintptr(unsafe.Pointer(pathBytes)),
uintptr(unsafe.Pointer(nameBytes)),
uintptr(0), // options
0, 0, 0)
if errno != 0 {
return errno
}
return nil
}
func isXattrSupported() bool {
return true
}

115
test/fuse_integration/xattr_linux.go

@ -0,0 +1,115 @@
//go:build linux
package fuse
import (
"syscall"
"unsafe"
)
// Extended attributes support for Linux
func setXattr(path, name string, value []byte, flags int) error {
pathBytes, err := syscall.BytePtrFromString(path)
if err != nil {
return err
}
nameBytes, err := syscall.BytePtrFromString(name)
if err != nil {
return err
}
var valuePtr unsafe.Pointer
if len(value) > 0 {
valuePtr = unsafe.Pointer(&value[0])
}
_, _, errno := syscall.Syscall6(syscall.SYS_SETXATTR,
uintptr(unsafe.Pointer(pathBytes)),
uintptr(unsafe.Pointer(nameBytes)),
uintptr(valuePtr),
uintptr(len(value)),
uintptr(flags),
0)
if errno != 0 {
return errno
}
return nil
}
func getXattr(path, name string, value []byte) (int, error) {
pathBytes, err := syscall.BytePtrFromString(path)
if err != nil {
return 0, err
}
nameBytes, err := syscall.BytePtrFromString(name)
if err != nil {
return 0, err
}
var valuePtr unsafe.Pointer
if len(value) > 0 {
valuePtr = unsafe.Pointer(&value[0])
}
size, _, errno := syscall.Syscall6(syscall.SYS_GETXATTR,
uintptr(unsafe.Pointer(pathBytes)),
uintptr(unsafe.Pointer(nameBytes)),
uintptr(valuePtr),
uintptr(len(value)),
0,
0)
if errno != 0 {
return 0, errno
}
return int(size), nil
}
func listXattr(path string, list []byte) (int, error) {
pathBytes, err := syscall.BytePtrFromString(path)
if err != nil {
return 0, err
}
var listPtr unsafe.Pointer
if len(list) > 0 {
listPtr = unsafe.Pointer(&list[0])
}
size, _, errno := syscall.Syscall(syscall.SYS_LISTXATTR,
uintptr(unsafe.Pointer(pathBytes)),
uintptr(listPtr),
uintptr(len(list)))
if errno != 0 {
return 0, errno
}
return int(size), nil
}
func removeXattr(path, name string) error {
pathBytes, err := syscall.BytePtrFromString(path)
if err != nil {
return err
}
nameBytes, err := syscall.BytePtrFromString(name)
if err != nil {
return err
}
_, _, errno := syscall.Syscall(syscall.SYS_REMOVEXATTR,
uintptr(unsafe.Pointer(pathBytes)),
uintptr(unsafe.Pointer(nameBytes)),
0)
if errno != 0 {
return errno
}
return nil
}
func isXattrSupported() bool {
return true
}

31
test/fuse_integration/xattr_unsupported.go

@ -0,0 +1,31 @@
//go:build !linux && !darwin
package fuse
import (
"errors"
)
// Extended attributes support for unsupported platforms
var ErrXattrNotSupported = errors.New("extended attributes not supported on this platform")
func setXattr(path, name string, value []byte, flags int) error {
return ErrXattrNotSupported
}
func getXattr(path, name string, value []byte) (int, error) {
return 0, ErrXattrNotSupported
}
func listXattr(path string, list []byte) (int, error) {
return 0, ErrXattrNotSupported
}
func removeXattr(path, name string) error {
return ErrXattrNotSupported
}
func isXattrSupported() bool {
return false
}
Loading…
Cancel
Save