# S3 Compatibility Tests

This directory contains scripts for running S3 compatibility tests against RustFS.

## Quick Start

Run the local S3 compatibility test script:

```bash
./scripts/s3-tests/run.sh
```

The script will automatically:

1. **Check prerequisites**: Verify port availability (unless in existing mode)
2. **Build/Start RustFS**:
   - Build mode (default): Compile with `cargo build --release` (skips if binary is recent < 30 min)
   - Binary mode: Use specified or default binary path
   - Docker mode: Build Docker image and start container
   - Existing mode: Skip startup, connect to running service
3. **Wait for readiness**: Multi-step health check (process/container → port → log → S3 API)
4. **Prepare environment**:
   - Generate s3tests configuration from template
   - Provision alt user via admin API
   - Fetch s3-tests at the pinned revision (`S3TESTS_REV`)
   - Install missing dependencies (awscurl, tox, gettext)
5. **Run tests**: Execute ceph s3-tests via tox with configured filters
6. **Collect results**: Save logs and test results in `artifacts/s3tests-${TEST_MODE}/`

## Deployment Modes

The script supports four deployment modes, controlled via the `DEPLOY_MODE` environment variable:

### 1. Build Mode (Default)

Compile with `cargo build --release` and run:

```bash
DEPLOY_MODE=build ./scripts/s3-tests/run.sh
# Or simply (build is the default)
./scripts/s3-tests/run.sh

# Force rebuild even if binary exists and is recent
./scripts/s3-tests/run.sh --no-cache
```

**Behavior**:
- Automatically compiles RustFS binary if it doesn't exist
- If binary exists and was compiled less than **30 minutes** ago, compilation is skipped (unless `--no-cache` is specified)
- Automatically starts the service after compilation
- Automatically fetches s3-tests at the pinned revision if missing
- Automatically installs missing dependencies (awscurl, tox, gettext)
- **Automatic Cleanup**: Process is automatically stopped when script exits

### 2. Binary File Mode

Use pre-compiled binary file:

```bash
# Use default path (./target/release/rustfs)
DEPLOY_MODE=binary ./scripts/s3-tests/run.sh

# Specify custom binary path
DEPLOY_MODE=binary RUSTFS_BINARY=./target/release/rustfs ./scripts/s3-tests/run.sh
```

**Behavior**:
- Uses existing binary file (must exist, script will not compile)
- Automatically starts the service using the specified binary
- Automatically fetches s3-tests at the pinned revision if missing
- Automatically installs missing dependencies (awscurl, tox, gettext)
- **Automatic Cleanup**: Process is automatically stopped when script exits

### 3. Docker Mode

Build Docker image and run in container:

```bash
DEPLOY_MODE=docker ./scripts/s3-tests/run.sh
```

**Behavior**:
- Automatically builds Docker image using `Dockerfile.source`
- Creates Docker network (`rustfs-net`) if it doesn't exist
- Automatically fetches s3-tests at the pinned revision if missing
- Automatically installs missing dependencies (awscurl, tox, gettext)
- **Automatic Cleanup**: Container and network are automatically removed when script exits

### 4. Existing Service Mode

Connect to an already running RustFS service:

```bash
DEPLOY_MODE=existing S3_HOST=127.0.0.1 S3_PORT=9000 ./scripts/s3-tests/run.sh

# Connect to remote service
DEPLOY_MODE=existing S3_HOST=192.168.1.100 S3_PORT=9000 ./scripts/s3-tests/run.sh
```

**Behavior**:
- Skips service startup and port availability checks
- Connects directly to the specified service endpoint
- Automatically fetches s3-tests at the pinned revision if missing
- Automatically installs missing dependencies (awscurl, tox, gettext)
- **Note**: The service must already have the alt user (`rustfsalt`) provisioned, or the script will provision it automatically

**Automatic Cleanup**: The script uses trap handlers to automatically clean up resources when it exits (success or failure):
- Stops RustFS process (build/binary mode)
- Stops and removes Docker container (docker mode)
- Removes Docker network (docker mode)

## Configuration Options

### Command Line Options

- `-h, --help`: Show help message
- `--no-cache`: Force rebuild even if binary exists and is recent (for build mode)

### Deployment Configuration

- `DEPLOY_MODE`: Deployment mode, options:
  - `build`: Compile with `cargo build --release` and run (default)
  - `binary`: Use pre-compiled binary file
  - `docker`: Build Docker image and run in container
  - `existing`: Use already running service
- `RUSTFS_BINARY`: Path to binary file (for binary mode, default: `./target/release/rustfs`)
- `DATA_ROOT`: Root directory for test data storage (default: `target`)
  - Final path: `${DATA_ROOT}/test-data/${CONTAINER_NAME}`
  - Example: `DATA_ROOT=/tmp` stores data in `/tmp/test-data/rustfs-single/`

### Service Configuration

- `S3_ACCESS_KEY`: Main user access key (default: `rustfsadmin`)
- `S3_SECRET_KEY`: Main user secret key (default: `rustfsadmin`)
- `S3_ALT_ACCESS_KEY`: Alt user access key (default: `rustfsalt`)
- `S3_ALT_SECRET_KEY`: Alt user secret key (default: `rustfsalt`)
- `S3_REGION`: S3 region (default: `us-east-1`)
- `S3_HOST`: S3 service host (default: `127.0.0.1`)
- `S3_PORT`: S3 service port (default: `9000`)

### Test Parameters

- `TEST_MODE`: Test mode (default: `single`)
- `TEST_SCOPE`: Test scope (default: `implemented`)
  - `implemented`: run only the `implemented_tests.txt` whitelist (the PR gate)
  - `all`: run the entire upstream suite (used by the weekly full sweep)
- `MAXFAIL`: Stop after N failures, `0` = never stop (default: `1`)
- `XDIST`: Enable parallel execution with N workers (default: `0`, disabled)
- `S3TESTS_REPO`: s3-tests repository URL (default: `https://github.com/ceph/s3-tests.git`)
- `S3TESTS_REV`: Pinned s3-tests commit for reproducible runs
  - Bump deliberately: upstream changes can rename tests or change assertions,
    so a bump usually requires reclassifying the test list files
- `MARKEXPR`: pytest marker expression for filtering tests
  - Default: no marker filtering; file-based test lists control the selected tests
  - Can be customized to test specific marker groups
- `TESTEXPR`: optional pytest `-k` expression for custom runs
  - Default: exact pytest node ids loaded from `implemented_tests.txt`
  - Setting `TESTEXPR` overrides the implemented test list

### Configuration Files

- `S3TESTS_CONF_TEMPLATE`: Path to s3tests config template (default: `.github/s3tests/s3tests.conf`)
  - Relative to project root
  - Uses `envsubst` to substitute variables (e.g., `${S3_HOST}`)
- `S3TESTS_CONF`: Path to generated s3tests config (default: `s3tests.conf`)
  - Relative to project root
  - This file is generated from the template before running tests

## Examples

### Build Mode (Default)

```bash
# Basic usage - compiles and runs automatically
# Skips compilation if binary exists and is less than 30 minutes old
./scripts/s3-tests/run.sh

# Force rebuild (skip cache check, always compile)
./scripts/s3-tests/run.sh --no-cache

# Run all tests, stop after 50 failures
MAXFAIL=50 ./scripts/s3-tests/run.sh

# Enable parallel execution (4 worker processes)
# Automatically installs pytest-xdist if needed
XDIST=4 ./scripts/s3-tests/run.sh

# Use custom data storage location
# Data will be stored in /tmp/test-data/rustfs-single/
DATA_ROOT=/tmp ./scripts/s3-tests/run.sh

# Run specific test markers (e.g., test multipart uploads only)
MARKEXPR="multipart" ./scripts/s3-tests/run.sh
```

### Binary File Mode

```bash
# First compile the binary
cargo build --release

# Run with default path
DEPLOY_MODE=binary ./scripts/s3-tests/run.sh

# Specify custom path
DEPLOY_MODE=binary RUSTFS_BINARY=/path/to/rustfs ./scripts/s3-tests/run.sh

# Use binary with parallel tests
DEPLOY_MODE=binary XDIST=4 ./scripts/s3-tests/run.sh
```

### Docker Mode

```bash
# Build Docker image and run in container
DEPLOY_MODE=docker ./scripts/s3-tests/run.sh

# Run with parallel tests
DEPLOY_MODE=docker XDIST=4 ./scripts/s3-tests/run.sh
```

### Existing Service Mode

```bash
# Connect to locally running service (default: 127.0.0.1:9000)
DEPLOY_MODE=existing ./scripts/s3-tests/run.sh

# Connect to remote service
DEPLOY_MODE=existing S3_HOST=192.168.1.100 S3_PORT=9000 ./scripts/s3-tests/run.sh

# Test specific features (custom marker expression)
DEPLOY_MODE=existing MARKEXPR="not lifecycle and not versioning" ./scripts/s3-tests/run.sh

# Use custom credentials
DEPLOY_MODE=existing \
  S3_ACCESS_KEY=myaccesskey \
  S3_SECRET_KEY=mysecretkey \
  ./scripts/s3-tests/run.sh
```

### Custom Configuration Files

```bash
# Use custom config template and output path
S3TESTS_CONF_TEMPLATE=my-configs/s3tests.conf.template \
S3TESTS_CONF=my-s3tests.conf \
./scripts/s3-tests/run.sh
```

## Test Results

Test results are saved in the `artifacts/s3tests-${TEST_MODE}/` directory (default: `artifacts/s3tests-single/`):

- `junit.xml`: Test results in JUnit format (compatible with CI/CD systems)
- `pytest.log`: Detailed pytest logs with full test output
- `compat-report.md`: Classification report generated by `report_compat.py` —
  regressions against `implemented_tests.txt`, promotion candidates (tests
  that pass but are still listed as unimplemented/excluded), and tests missing
  from every list
- `rustfs-${TEST_MODE}/rustfs.log`: RustFS service logs
- `rustfs-${TEST_MODE}/inspect.json`: Service metadata (PID, binary path, mode, etc.)

View results:

```bash
# Check test summary
cat artifacts/s3tests-single/junit.xml | grep -E "testsuite|testcase"

# View test logs
less artifacts/s3tests-single/pytest.log

# View service logs
less artifacts/s3tests-single/rustfs-single/rustfs.log
```

## Prerequisites

### Required System Dependencies

The following dependencies must be installed manually on your system:

#### All Deployment Modes

- **Python 3**: Required for running s3-tests
  - Check: `python3 --version`
  - Install:
    - macOS: Usually pre-installed, or `brew install python3`
    - Linux: `apt-get install python3` or `yum install python3`

- **Git**: Required for cloning s3-tests repository
  - Check: `git --version`
  - Install:
    - macOS: Usually pre-installed, or `brew install git`
    - Linux: `apt-get install git` or `yum install git`

- **Port checking tools**: One of the following for port availability checks
  - `nc` (netcat): `apt-get install netcat` or `brew install netcat`
  - OR `timeout` command: Usually pre-installed on Linux
  - OR bash built-in TCP redirection support

#### Docker Mode Only

- **Docker**: Required only when using `DEPLOY_MODE=docker`
  - Check: `docker --version`
  - Install: [Docker Installation Guide](https://docs.docker.com/get-docker/)

#### Build Mode Only

- **Rust toolchain**: Required when using `DEPLOY_MODE=build` (default)
  - Check: `rustc --version` and `cargo --version`
  - Install: [Rust Installation Guide](https://www.rust-lang.org/tools/install)

### Auto-installed Dependencies

The script will automatically install the following dependencies if missing (no manual action required):

- **awscurl**: For S3 API calls and user provisioning
  - Installed via: `python3 -m pip install --user --upgrade pip awscurl`
  - Location: `$HOME/.local/bin/awscurl`

- **tox**: For running s3-tests in isolated Python environment
  - Installed via: `python3 -m pip install --user --upgrade pip tox`
  - Location: `$HOME/.local/bin/tox`

- **gettext-base**: For `envsubst` command (config file generation)
  - macOS: Automatically installs via `brew install gettext`
  - Linux: Automatically installs via `sudo apt-get install gettext-base`
  - **Note**: macOS installation may require manual intervention if brew fails

- **s3-tests repository**: Automatically fetched if not present
  - Source: `https://github.com/ceph/s3-tests.git`, pinned to the commit in
    `S3TESTS_REV` (see run.sh) for reproducible runs
  - Location: `${PROJECT_ROOT}/s3-tests`

**Note**: The script adds `$HOME/.local/bin` to `PATH` automatically, so auto-installed Python tools are accessible.

### Proxy Configuration

The script automatically disables proxy for localhost requests to avoid interference. All proxy environment variables (`http_proxy`, `https_proxy`, `HTTP_PROXY`, `HTTPS_PROXY`) are unset at script startup. The `NO_PROXY` variable is set to `127.0.0.1,localhost,::1`.

## Cleanup

The test script automatically cleans up processes and containers when it exits. However, if you need to manually clean up:

### Using the Cleanup Script

A dedicated cleanup script is available to clean up test resources:

```bash
# Report port 9000 usage and clean the test data directory
./scripts/s3-tests/cleanup.sh

# Use custom port
S3_PORT=9001 ./scripts/s3-tests/cleanup.sh
```

The cleanup script will:
- Report any process using the specified port (default: 9000)
- Clean test data directory at `target/test-data/rustfs-single/`
- Leave unrelated processes running

### Manual Cleanup

If the cleanup script doesn't work or you need more control:

```bash
# Kill process on port 9000
lsof -ti:9000 | xargs kill -9

# Or use netstat/ss
kill -9 $(netstat -tuln | grep :9000 | awk '{print $7}' | cut -d'/' -f1)

# Remove test data
rm -rf target/test-data/rustfs-single/

# Stop Docker container (if using docker mode)
docker rm -f rustfs-single

# Remove Docker network (if using docker mode)
docker network rm rustfs-net
```

## Troubleshooting

### Port Already in Use

If port 9000 is already in use, change the port:

```bash
S3_PORT=9001 ./scripts/s3-tests/run.sh
```

**Note**: The script automatically checks if the port is available before starting (except in `existing` mode). If the port is in use, the script will exit with an error message.

### Container Start Failure

Check Docker logs:

```bash
docker logs rustfs-single
```

### Binary Not Found

For binary mode, ensure the binary is compiled:

```bash
cargo build --release
```

Or specify the correct path:

```bash
DEPLOY_MODE=binary RUSTFS_BINARY=/path/to/rustfs ./scripts/s3-tests/run.sh
```

### Test Timeout

Increase wait time or check service status:

```bash
curl http://127.0.0.1:9000/health
```

### Existing Service Not Accessible

For existing mode, ensure the service is running and accessible:

```bash
# Check if service is reachable
curl http://192.168.1.100:9000/health

# Verify S3 API is responding
awscurl --service s3 --region us-east-1 \
  --access_key rustfsadmin \
  --secret_key rustfsadmin \
  -X GET "http://192.168.1.100:9000/"
```

## Workflow Integration

This script is the single source of truth for running ceph s3-tests against
RustFS. Two GitHub Actions workflows delegate to it:

- **PR gate** (`.github/workflows/ci.yml`, job `s3-implemented-tests`): runs
  the `implemented_tests.txt` whitelist against a single-node debug binary on
  every pull request (`DEPLOY_MODE=binary`, `MAXFAIL=0`, `XDIST=4`). Any
  failure blocks the PR.
- **Full sweep** (`.github/workflows/e2e-s3tests.yml`): weekly scheduled (and
  manually dispatchable) run of the ENTIRE upstream suite (`TEST_SCOPE=all`)
  against a Docker deployment — single node or a real 4-node distributed
  cluster behind HAProxy. The sweep fails only on regressions in the
  implemented whitelist; everything else is reported by `report_compat.py`
  as promotion candidates or unclassified tests.

Keeping both workflows on this script means local runs, the PR gate, and the
scheduled sweep always execute tests the same way (same pinned s3-tests
revision, same config template, same user provisioning).

A third workflow, **mint** (`.github/workflows/mint.yml`), complements ceph
s3-tests with MinIO Mint: the functional suites of many real client SDKs and
tools (awscli, mc, aws-sdk-java/go/php/ruby, minio-go/java/js/py, s3cmd, ...).
It runs weekly, is report-only for test failures, and publishes a per-suite
pass/fail table in the job summary.

## Companion Tools

- `report_compat.py` — diffs a junit.xml result against the classification
  lists; run automatically at the end of `run.sh`, and used by the weekly
  sweep to gate on whitelist regressions only (`--fail-on-regression`).
- `api_coverage.py` — quantifies S3 API surface coverage by comparing the
  s3s `S3` trait (at the revision pinned in Cargo.toml) against the methods
  RustFS overrides in `impl S3 for FS`:

  ```bash
  python3 scripts/s3-tests/api_coverage.py            # summary + missing ops
  python3 scripts/s3-tests/api_coverage.py --output api-coverage.md
  ```

- `compare_dual_targets.py` — sends one signed S3 request to two endpoints
  (e.g. RustFS and MinIO) and diffs status, headers, and body; useful when
  triaging a behavioral difference found by the suites above.

## See Also

- [GitHub Actions Workflow](../../.github/workflows/e2e-s3tests.yml)
- [S3 Tests Configuration](../../.github/s3tests/s3tests.conf)
- [Ceph S3 Tests Repository](https://github.com/ceph/s3-tests)
