This document explains how to run the sync safety end-to-end tests and interpret their output. These tests verify that br sync operations adhere to strict safety invariants.
The e2e sync test suite verifies several critical safety properties:
- No Git Operations -
br syncnever executes git commands, creates commits, or modifies.git/ - Path Confinement - Sync only touches files within
.beads/(with a strict allowlist) - Atomic Writes - Export uses write-to-temp + atomic rename; failures preserve original files
- Preflight Validation - Import validates JSONL before any database changes
- No Partial Writes - Failed operations leave state unchanged
| File | Purpose |
|---|---|
tests/e2e_sync_git_safety.rs |
Verifies sync never creates commits or mutates .git/ |
tests/e2e_sync_artifacts.rs |
Tests with detailed logging and artifact preservation |
tests/e2e_sync_fuzz_edge_cases.rs |
Malformed JSONL, path traversal, conflict markers |
tests/e2e_sync_failure_injection.rs |
Read-only dirs, permission errors, atomic guarantees |
tests/e2e_sync_preflight_integration.rs |
Preflight checks catch safety issues before writes |
# Run all sync-related e2e tests
cargo test e2e_sync --release
# Run with verbose output
cargo test e2e_sync --release -- --nocapture
# Run specific test file
cargo test --test e2e_sync_git_safety --release -- --nocapture# Git safety regression tests
cargo test --test e2e_sync_git_safety --release
# Artifact preservation tests (detailed logging)
cargo test --test e2e_sync_artifacts --release
# Fuzz and edge case tests
cargo test --test e2e_sync_fuzz_edge_cases --release
# Failure injection tests
cargo test --test e2e_sync_failure_injection --release
# Preflight integration tests
cargo test --test e2e_sync_preflight_integration --release# Run a specific test by name
cargo test regression_sync_export_does_not_create_commits --release -- --nocapture
# Run tests matching a pattern
cargo test conflict_marker --release -- --nocaptureFor debugging test failures, omit --release to get better stack traces:
cargo test e2e_sync --release -- --nocapture 2>&1 | tee test_output.logTests produce various artifacts for postmortem analysis:
Each test creates a temporary workspace:
/tmp/tmp.XXXXX/ # BrWorkspace.root
├── .beads/ # Beads directory
│ ├── beads.db # SQLite database
│ ├── issues.jsonl # JSONL export
│ └── .manifest.json # Optional manifest
├── logs/ # Test logs (BrWorkspace.log_dir)
│ ├── init.log # br init output
│ ├── create1.log # br create output
│ ├── sync_export.log # br sync --flush-only output
│ └── artifacts/ # Detailed artifact captures
│ ├── *_snapshots.txt
│ ├── *_commands.log
│ └── *.jsonl
└── src/ # Simulated source files (some tests)
Location: target/test-artifacts/failure-injection/<test_name>/
target/test-artifacts/failure-injection/
├── export_readonly_dir/
│ └── test.log # Detailed failure logs
├── import_malformed_json/
│ └── test.log
└── ...
After a test failure:
# Find temp directories (may already be cleaned up)
ls -la /tmp/tmp.* 2>/dev/null
# Find persisted test artifacts
ls -la target/test-artifacts/failure-injection/Each test prints structured output:
[TEST 1] Testing sync export...
Snapshot before export: 15 files
Snapshot after export: 17 files
[PASS] Export modified 2 allowed files, 0 violations
[PASS] e2e_sync_export_with_artifacts
- Artifacts saved to: /tmp/tmpXXX/logs/artifacts
- JSONL size: 1234 bytes
- Files in .beads/: 3
Individual command logs contain:
label: sync_export
started: SystemTime { ... }
duration: 45.123ms
status: exit status: 0
args: ["sync", "--flush-only"]
cwd: /tmp/tmp.XXXXX
stdout:
Exported 3 issues to .beads/issues.jsonl
stderr:
[DEBUG beads_rust::sync] Starting export...
[INFO beads_rust::sync] Export complete: 3 issues
=== CREATED FILES (2) ===
CREATED: .beads/issues.jsonl (size: 1234 bytes, hash: a1b2c3d4...)
CREATED: .beads/.manifest.json (size: 56 bytes, hash: e5f6g7h8...)
=== SUMMARY ===
Created: 2
Modified: 0
Deleted: 0
Unchanged: 15
If a test detects a safety violation:
SAFETY VIOLATION: sync export modified files outside allowed list!
MODIFIED: src/main.rs
Before: a1b2c3d4e5f6...
After: f7g8h9i0j1k2...
Detailed log: /tmp/tmpXXX/logs/sync_export_diff.log
Verifies the core safety invariant: sync never touches git.
Tests:
regression_sync_export_does_not_create_commits- Export leaves HEAD unchangedregression_sync_import_does_not_create_commits- Import leaves HEAD unchangedregression_full_sync_cycle_does_not_touch_git- Full cycle preserves git stateregression_sync_manifest_does_not_touch_git- Manifest generation is git-saferegression_sync_never_touches_source_files- Source files are never modifiedintegration_sync_only_touches_allowed_files- Comprehensive allowlist verification
Tests with detailed logging for debugging:
e2e_sync_export_with_artifacts- Export with full artifact capturee2e_sync_import_with_artifacts- Import with full artifact capturee2e_sync_full_cycle_with_artifacts- Complete cycle with artifactse2e_sync_status_with_artifacts- Status command logginge2e_sync_error_conflict_markers- Conflict marker rejectione2e_sync_export_empty_db- Empty database handlinge2e_sync_deterministic_export- Export ordering consistency
Tests malformed input handling:
- Partial/truncated JSONL lines
- Invalid JSON syntax
- Conflict markers (various patterns)
- Path traversal attempts
- Symlink escape attempts
- Huge lines (1MB+ titles)
- Invalid UTF-8
- Whitespace-only files
- Empty files
- Deeply nested JSON
- Partial write prevention
Tests atomic operation guarantees:
- Read-only directory exports
- Blocked temp file creation
- Missing file imports
- Malformed JSON imports
- Conflict marker imports
- Prefix mismatch imports
- Multiple sequential failures
- Large JSONL preservation
Tests early validation:
- Conflict marker detection
- Path validation (outside .beads, .git paths)
- Path traversal rejection
- Export safety checks
- Actionable error messages
This indicates a genuine safety regression. Steps:
- Read the full error message for the specific violation
- Check the log file path provided in the error
- Review the snapshot diff to see exactly what changed
- Check if
is_allowed_sync_file()insrc/sync/path.rsmatches the test's allowlist
# Run with timeout
timeout 120 cargo test e2e_sync --release
# Check for lock contention
lsof +D /tmp/tmp.* 2>/dev/null | grep -E '\.db'Some tests (failure injection) require filesystem permission manipulation:
# Ensure tests have permission to chmod
ls -la /tmp/
# Some CI environments may restrict this - check stderr for details
cargo test e2e_sync_failure_injection -- --nocaptureIf tests pass/fail intermittently:
- Check for race conditions in parallel test execution
- Run with
--test-threads=1:cargo test e2e_sync --release -- --test-threads=1
Tests require the br binary to be built:
# Ensure binary is built
cargo build --release
# Verify binary exists
ls -la target/release/brSome tests use git to verify safety invariants:
# Check git is available
git --version
# Install if missing (Ubuntu/Debian)
sudo apt-get install gitTests should clean up, but if space is low:
# Remove old test temp directories
rm -rf /tmp/tmp.* 2>/dev/null
# Remove test artifacts
rm -rf target/test-artifacts/For CI pipelines:
# GitHub Actions example
- name: Run sync safety tests
run: |
cargo test e2e_sync --release -- --nocapture 2>&1 | tee sync_test_output.log
- name: Upload test artifacts on failure
if: failure()
uses: actions/upload-artifact@v3
with:
name: sync-test-artifacts
path: |
target/test-artifacts/
sync_test_output.log- SYNC_SAFETY.md - Sync safety model and design
.beads/SYNC_SAFETY_INVARIANTS.md- Safety invariants specification.beads/SYNC_CLI_FLAG_SEMANTICS.md- CLI flag behavior.beads/SYNC_THREAT_MODEL.md- Threat model for sync operations