pub mod diff;
pub mod engine;
pub mod history;
pub mod parser;
pub mod resolver;
pub mod restore;
pub mod types;
pub mod walker;
pub mod zpl_adapter;
#[cfg(test)]
use alloc::vec;
pub use engine::{TimeTravelEngine, TimeTravelProvider};
pub use types::{
AggregateFunc, AggregateResult, ChangeType, Column, DiffEntry, FileType, FileVersion, Filter,
OrderBy, QueryResult, QueryRow, SnapshotInfo, TimeError, TimeQuery, TimeSpec, Value,
};
pub use diff::{DiffEngine, DiffOptions, DiffSummary};
pub use history::{VersionComparison, VersionHistory, VersionHistoryProvider};
pub use parser::QueryParser;
pub use resolver::{InMemoryTxgHistory, TimeSpecResolver, TxgHistoryProvider, TxgTimestamp};
pub use restore::{RestoreEngine, RestoreOptions, RestoreResult, RestoreTarget};
pub use walker::{HistoricalEntry, HistoricalTreeProvider, HistoricalTreeWalker, WalkOptions};
pub use zpl_adapter::{
TIME_TRAVEL_ADAPTER, ZplTimeTravelAdapter, create_snapshot, current_txg, delete_snapshot,
get_snapshot, list_snapshots, lookup_at_txg, readdir_at_txg, record_file_change,
record_txg_sync,
};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_public_types() {
let _spec = TimeSpec::Now;
let _query = TimeQuery::ShowSnapshots { path: "/".into() };
let _result = QueryResult::Empty;
let _error = TimeError::NoHistory;
let _col = Column::All;
let _filter = Filter::IsNull {
column: "test".into(),
};
}
#[test]
fn test_query_parser() {
let query = QueryParser::parse("SHOW SNAPSHOTS FOR /data").unwrap();
assert!(matches!(query, TimeQuery::ShowSnapshots { .. }));
}
#[test]
fn test_timespec_variants() {
assert!(TimeSpec::Relative("1 hour ago".into()).is_relative());
assert!(TimeSpec::Snapshot("backup".into()).is_snapshot());
assert!(!TimeSpec::Txg(100).is_relative());
assert!(!TimeSpec::Now.is_snapshot());
}
#[test]
fn test_query_result_len() {
assert!(QueryResult::Empty.is_empty());
assert_eq!(QueryResult::Empty.len(), 0);
let rows = QueryResult::Rows(vec![QueryRow {
path: "/test".into(),
object_id: 1,
size: 100,
mtime: 1000,
ctime: 1000,
atime: 1000,
mode: 0o644,
uid: 1000,
gid: 1000,
txg: 100,
file_type: FileType::Regular,
checksum: [0; 4],
}]);
assert_eq!(rows.len(), 1);
}
#[test]
fn test_change_type_parse() {
assert!(matches!(
ChangeType::from_str("created"),
Some(ChangeType::Created)
));
assert!(matches!(
ChangeType::from_str("MODIFIED"),
Some(ChangeType::Modified)
));
assert!(ChangeType::from_str("invalid").is_none());
}
#[test]
fn test_diff_summary() {
let summary = DiffSummary {
created: 5,
modified: 3,
deleted: 2,
renamed: 1,
metadata_changed: 0,
};
assert_eq!(summary.total(), 11);
assert!(!summary.is_empty());
}
#[test]
fn test_restore_options_default() {
let opts = RestoreOptions::default();
assert!(!opts.overwrite);
assert!(opts.preserve_metadata);
assert!(opts.recursive);
assert!(!opts.dry_run);
}
#[test]
fn test_walk_options_default() {
let opts = WalkOptions::default();
assert_eq!(opts.max_depth, -1);
assert!(!opts.follow_symlinks);
assert!(opts.include_hidden);
}
#[test]
fn test_file_type_names() {
assert_eq!(FileType::Regular.name(), "file");
assert_eq!(FileType::Directory.name(), "directory");
assert_eq!(FileType::Symlink.name(), "symlink");
}
#[test]
fn test_value_conversions() {
let v = Value::String("test".into());
assert_eq!(v.as_str(), Some("test"));
let v = Value::Integer(-42);
assert_eq!(v.as_i64(), Some(-42));
let v = Value::Unsigned(100);
assert_eq!(v.as_u64(), Some(100));
}
}