tidaldb/tidal/tests/sandboxed_storage.rs
jordan 4f076c927d feat: M0p1 runtime skeleton, M0p2 tooling & diagnostics, m1p4 signal ledger
## M0p1 — Embeddable Runtime Skeleton (329 tests)
- TidalDb with builder(), health_check(), close(), and Drop-based cleanup
- TidalDbBuilder fluent API: ephemeral(), with_data_dir(), wal_dir(), cache_dir()
- Config, StorageMode, ConfigError types; Config(ConfigError) variant on LumenError
- Paths: single source of truth for directory layout (wal, items, users, creators, cache)
- TempTidalHome: test isolation helper gated behind #[cfg(test)] / test-utils feature
- 8 integration tests: tests/sandboxed_storage.rs

## M0p2 — Tooling & Diagnostics (349 tests)
- Workspace root Cargo.toml (members: ["tidal", "tidalctl"])
- tidal/build.rs: BUILD_HASH from GIT_HASH with option_env!() fallback to "dev"
- MetricsState: always-compiled Arc-shared atomics (uptime, health_ok)
- MetricsHandle (metrics feature): hand-rolled TcpListener HTTP, zero new deps
  - GET /healthz → {"status":"ok","uptime_secs":N}
  - GET /metrics → Prometheus text (tidaldb_uptime_seconds, health_ok, info)
- TidalDbBuilder.enable_metrics(addr) starts background metrics thread
- tidalctl binary: status + paths commands, manual std::env::args() parsing
- 7 metrics integration tests, 9 tidalctl CLI tests

## m1p4 Signal Ledger (in-progress)
- SignalLedger: DashMap<(EntityId, SignalTypeId), EntitySignalEntry>, WAL-first writes
- HotSignalState: #[repr(C, align(64))], lock-free CAS decay, out-of-order handling
- BucketedCounter: 60 per-minute + 168 per-hour circular buffers, trigger-based rotation
- CheckpointMeta + serialize/restore: 983-byte fixed records, atomic WriteBatch
- Property tests: running score matches analytical to 1e-6, decay monotonic, non-negative
- Proptest regression: signals/warm.txt

## Documentation and planning
- ROADMAP: m0p1 COMPLETE (329), m0p2 COMPLETE (349), product track milestones
- PRODUCT_ROADMAP: P0-P4 product milestone track (personal briefing beachhead)
- Milestone planning docs: milestone-0 (phases 1-3), milestone-p (phases 1-5)
- docs/research/tidaldb_tooling_and_diagnostics.md
- ARCHITECTURE.md, CLAUDE.md, VISION.md updates

## Site
- Blog: every-platform-builds-the-same-6-systems.mdx (new)
- Blog: why-tidaldb.mdx (updated)
- next.config.ts, layout.tsx, blog/page.tsx updates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 20:32:00 -07:00

174 lines
5.7 KiB
Rust

//! Integration tests for the sandboxed storage layout.
//!
//! Validates that [`Paths`] and [`TempTidalHome`] provide correct,
//! isolated filesystem sandboxes for tidalDB instances.
use tidaldb::TidalDb;
use tidaldb::db::temp::TempTidalHome;
// =============================================================================
// TempTidalHome isolation
// =============================================================================
#[test]
fn two_temp_homes_have_non_overlapping_paths() {
let home1 = TempTidalHome::new().unwrap();
let home2 = TempTidalHome::new().unwrap();
assert_ne!(
home1.path(),
home2.path(),
"two TempTidalHome instances must have different base paths"
);
// Verify neither is a prefix of the other (true isolation).
assert!(
!home1.path().starts_with(home2.path()),
"home1 should not be a subdirectory of home2"
);
assert!(
!home2.path().starts_with(home1.path()),
"home2 should not be a subdirectory of home1"
);
}
// =============================================================================
// Drop removes directory
// =============================================================================
#[test]
fn drop_removes_directory_with_contents() {
let path = {
let home = TempTidalHome::new().unwrap();
let p = home.path().to_owned();
// Create a file inside so the directory is non-empty.
std::fs::write(p.join("sentinel.txt"), b"test data").unwrap();
assert!(p.exists(), "directory should exist before drop");
p
// home dropped here
};
assert!(!path.exists(), "directory should be removed after drop");
}
// =============================================================================
// with_preserve keeps directory
// =============================================================================
#[test]
fn with_preserve_keeps_directory_after_drop() {
let path = {
let home = TempTidalHome::with_preserve().unwrap();
let p = home.path().to_owned();
std::fs::write(p.join("sentinel.txt"), b"test data").unwrap();
p
// home dropped here, but preserve=true
};
assert!(
path.exists(),
"directory should still exist after drop with preserve"
);
// Manual cleanup.
std::fs::remove_dir_all(&path).unwrap();
}
// =============================================================================
// Paths::ensure_all creates expected subdirectories
// =============================================================================
#[test]
fn ensure_all_creates_all_subdirectories() {
let home = TempTidalHome::new().unwrap();
let paths = home.paths();
// Before ensure_all, subdirectories should not exist.
assert!(!paths.wal_dir().exists());
assert!(!paths.items_dir().exists());
assert!(!paths.users_dir().exists());
assert!(!paths.creators_dir().exists());
assert!(!paths.cache_dir().exists());
paths.ensure_all().unwrap();
// After ensure_all, all subdirectories should exist.
assert!(paths.wal_dir().is_dir(), "wal dir should exist");
assert!(paths.items_dir().is_dir(), "items dir should exist");
assert!(paths.users_dir().is_dir(), "users dir should exist");
assert!(paths.creators_dir().is_dir(), "creators dir should exist");
assert!(paths.cache_dir().is_dir(), "cache dir should exist");
}
// =============================================================================
// Two builders with different TempTidalHome roots never collide
// =============================================================================
#[test]
fn two_builders_with_different_homes_are_isolated() {
let home1 = TempTidalHome::new().unwrap();
let home2 = TempTidalHome::new().unwrap();
let paths1 = home1.paths();
let paths2 = home2.paths();
paths1.ensure_all().unwrap();
paths2.ensure_all().unwrap();
// Write a file into home1's wal directory.
let sentinel1 = paths1.wal_dir().join("segment-001");
std::fs::write(&sentinel1, b"home1 wal data").unwrap();
// Write a file into home2's wal directory.
let sentinel2 = paths2.wal_dir().join("segment-001");
std::fs::write(&sentinel2, b"home2 wal data").unwrap();
// Verify each sees only its own data.
let data1 = std::fs::read(&sentinel1).unwrap();
let data2 = std::fs::read(&sentinel2).unwrap();
assert_eq!(data1, b"home1 wal data");
assert_eq!(data2, b"home2 wal data");
// Verify the other home's directory does not contain the wrong data.
assert_ne!(data1, data2);
}
// =============================================================================
// Builder integrates with Paths via TempTidalHome
// =============================================================================
#[test]
fn builder_with_temp_home_opens_successfully() {
let home = TempTidalHome::new().unwrap();
// The builder validates data_dir exists (TempTidalHome creates it).
// Resolved defaults (wal_dir, cache_dir) are populated after validation
// so they don't need to pre-exist.
let db = TidalDb::builder().with_data_dir(home.path()).open();
assert!(db.is_ok(), "builder with TempTidalHome should succeed");
}
#[test]
fn ensure_all_is_idempotent() {
let home = TempTidalHome::new().unwrap();
let paths = home.paths();
paths.ensure_all().unwrap();
// Write a file to verify it survives a second ensure_all.
std::fs::write(paths.wal_dir().join("segment-001"), b"data").unwrap();
paths.ensure_all().unwrap();
// File should still exist.
assert!(paths.wal_dir().join("segment-001").exists());
}
#[test]
fn paths_base_matches_temp_home_path() {
let home = TempTidalHome::new().unwrap();
let paths = home.paths();
assert_eq!(paths.base(), home.path());
}