## 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>
174 lines
5.7 KiB
Rust
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());
|
|
}
|