pub mod collection;
pub mod error;
pub mod file;
pub mod metadata;
pub use crate::repository::file::{File, Origin, Source, Status};
pub use crate::repository::metadata::FileInfo;
#[doc(hidden)]
pub use crate::repository::collection::{Chain, Repository};
#[doc(hidden)]
pub use crate::repository::error::RepositoryError;
pub(crate) use crate::repository::file::scan_journal_files;
#[cfg(test)]
mod tests {
use super::*;
use crate::repository::collection::Chain;
use crate::repository::file::FileInner;
use journal_common::Seconds;
use journal_common::collections::VecDeque;
use std::sync::Arc;
use uuid::Uuid;
const USEC_PER_SEC: u64 = std::time::Duration::from_secs(1).as_micros() as u64;
fn create_test_origin() -> Origin {
Origin {
machine_id: Some(Uuid::new_v4()),
namespace: None,
source: Source::System,
}
}
fn create_archived_file(origin: &Origin, head_realtime: u64) -> File {
let inner = FileInner {
path: format!("/var/log/journal/system@{}.journal", head_realtime),
origin: origin.clone(),
status: Status::Archived {
seqnum_id: Uuid::new_v4(),
head_seqnum: 1000 + head_realtime,
head_realtime,
},
};
File {
inner: Arc::new(inner),
}
}
fn create_active_file(origin: &Origin) -> File {
let inner = FileInner {
path: "/var/log/journal/system.journal".to_string(),
origin: origin.clone(),
status: Status::Active,
};
File {
inner: Arc::new(inner),
}
}
fn create_disposed_file(origin: &Origin, timestamp: u64, number: u64) -> File {
let inner = FileInner {
path: format!("/var/log/journal/system@{}-{}.journal~", timestamp, number),
origin: origin.clone(),
status: Status::Disposed { timestamp, number },
};
File {
inner: Arc::new(inner),
}
}
#[test]
fn test_find_files_in_range_empty_chain() {
let chain = Chain {
files: VecDeque::new(),
};
let mut files = Vec::new();
chain.find_files_in_range(Seconds(100), Seconds(200), &mut files);
assert!(files.is_empty());
}
#[test]
fn test_find_files_in_range_invalid_range() {
let origin = create_test_origin();
let mut chain = Chain {
files: VecDeque::new(),
};
chain
.files
.push_back(create_archived_file(&origin, 100 * USEC_PER_SEC));
chain
.files
.push_back(create_archived_file(&origin, 200 * USEC_PER_SEC));
let mut files = Vec::new();
chain.find_files_in_range(Seconds(200), Seconds(200), &mut files);
assert!(files.is_empty());
chain.find_files_in_range(Seconds(200), Seconds(100), &mut files);
assert!(files.is_empty());
}
#[test]
fn test_find_files_in_range_single_archived() {
let origin = create_test_origin();
let mut chain = Chain {
files: VecDeque::new(),
};
let file = create_archived_file(&origin, 150 * USEC_PER_SEC);
chain.files.push_back(file.clone());
let mut files = Vec::new();
chain.find_files_in_range(Seconds(100), Seconds(200), &mut files);
assert_eq!(files.len(), 1);
assert_eq!(files[0], file);
files.clear();
chain.find_files_in_range(Seconds(150), Seconds(200), &mut files);
assert_eq!(files.len(), 1);
assert_eq!(files[0], file);
files.clear();
chain.find_files_in_range(Seconds(100), Seconds(150), &mut files);
assert!(files.is_empty());
files.clear();
chain.find_files_in_range(Seconds(50), Seconds(100), &mut files);
assert!(files.is_empty());
files.clear();
chain.find_files_in_range(Seconds(200), Seconds(300), &mut files);
assert_eq!(files.len(), 1);
assert_eq!(files[0], file);
}
#[test]
fn test_find_files_in_range_multiple_archived() {
let origin = create_test_origin();
let mut chain = Chain {
files: VecDeque::new(),
};
let file1 = create_archived_file(&origin, 100 * USEC_PER_SEC);
let file2 = create_archived_file(&origin, 200 * USEC_PER_SEC);
let file3 = create_archived_file(&origin, 300 * USEC_PER_SEC);
let file4 = create_archived_file(&origin, 400 * USEC_PER_SEC);
chain.files.push_back(file1.clone());
chain.files.push_back(file2.clone());
chain.files.push_back(file3.clone());
chain.files.push_back(file4.clone());
let mut files = Vec::new();
chain.find_files_in_range(Seconds(150), Seconds(350), &mut files);
assert_eq!(files.len(), 3);
assert_eq!(files[0], file1); assert_eq!(files[1], file2);
assert_eq!(files[2], file3);
files.clear();
chain.find_files_in_range(Seconds(200), Seconds(300), &mut files);
assert_eq!(files.len(), 1);
assert_eq!(files[0], file2);
files.clear();
chain.find_files_in_range(Seconds(250), Seconds(350), &mut files);
assert_eq!(files.len(), 2);
assert_eq!(files[0], file2);
assert_eq!(files[1], file3);
files.clear();
chain.find_files_in_range(Seconds(450), Seconds(500), &mut files);
assert_eq!(files.len(), 1);
assert_eq!(files[0], file4);
}
#[test]
fn test_find_files_in_range_with_active() {
let origin = create_test_origin();
let mut chain = Chain {
files: VecDeque::new(),
};
let file1 = create_archived_file(&origin, 100 * USEC_PER_SEC);
let file2 = create_archived_file(&origin, 200 * USEC_PER_SEC);
let active = create_active_file(&origin);
chain.files.push_back(file1.clone());
chain.files.push_back(file2.clone());
chain.files.push_back(active.clone());
let mut files = Vec::new();
chain.find_files_in_range(Seconds(150), Seconds(250), &mut files);
assert_eq!(files.len(), 3);
assert_eq!(files[0], file1); assert_eq!(files[1], file2);
assert_eq!(files[2], active);
files.clear();
chain.find_files_in_range(Seconds(250), Seconds(350), &mut files);
assert_eq!(files.len(), 2);
assert_eq!(files[0], file2);
assert_eq!(files[1], active);
files.clear();
chain.find_files_in_range(Seconds(50), Seconds(150), &mut files);
assert_eq!(files.len(), 1);
assert_eq!(files[0], file1);
}
#[test]
fn test_find_files_in_range_only_active() {
let origin = create_test_origin();
let mut chain = Chain {
files: VecDeque::new(),
};
let active = create_active_file(&origin);
chain.files.push_back(active.clone());
let mut files = Vec::new();
chain.find_files_in_range(Seconds(0), Seconds(100), &mut files);
assert_eq!(files.len(), 1);
assert_eq!(files[0], active);
files.clear();
let start = Seconds(u32::MAX - 100);
let end = Seconds(u32::MAX);
chain.find_files_in_range(start, end, &mut files);
assert_eq!(files.len(), 1);
assert_eq!(files[0], active);
}
#[test]
fn test_find_files_in_range_with_disposed() {
let origin = create_test_origin();
let mut chain = Chain {
files: VecDeque::new(),
};
let disposed1 = create_disposed_file(&origin, 50 * USEC_PER_SEC, 1);
let disposed2 = create_disposed_file(&origin, 60 * USEC_PER_SEC, 2);
let file1 = create_archived_file(&origin, 100 * USEC_PER_SEC);
let file2 = create_archived_file(&origin, 200 * USEC_PER_SEC);
chain.files.push_back(disposed1);
chain.files.push_back(disposed2);
chain.files.push_back(file1.clone());
chain.files.push_back(file2.clone());
let mut files = Vec::new();
chain.find_files_in_range(Seconds(0), Seconds(300), &mut files);
assert_eq!(files.len(), 2);
assert_eq!(files[0], file1); assert_eq!(files[1], file2);
}
#[test]
fn test_drain_handles_mixed_disposed_and_archived_timestamps() {
let origin = create_test_origin();
let mut chain = Chain {
files: VecDeque::new(),
};
let disposed_new = create_disposed_file(&origin, 200 * USEC_PER_SEC, 1);
let archived_old = create_archived_file(&origin, 50 * USEC_PER_SEC);
let archived_new = create_archived_file(&origin, 300 * USEC_PER_SEC);
let active = create_active_file(&origin);
chain.insert_file(disposed_new.clone());
chain.insert_file(archived_old.clone());
chain.insert_file(archived_new.clone());
chain.insert_file(active.clone());
let drained: Vec<_> = chain.drain(100 * USEC_PER_SEC).collect();
assert_eq!(drained, vec![archived_old]);
assert_eq!(
chain.files.into_iter().collect::<Vec<_>>(),
vec![disposed_new, archived_new, active]
);
}
#[test]
fn test_find_files_in_range_edge_cases() {
let origin = create_test_origin();
let mut chain = Chain {
files: VecDeque::new(),
};
let file1 = create_archived_file(&origin, 100 * USEC_PER_SEC);
let file2 = create_archived_file(&origin, 200 * USEC_PER_SEC);
let file3 = create_archived_file(&origin, 300 * USEC_PER_SEC);
chain.files.push_back(file1.clone());
chain.files.push_back(file2.clone());
chain.files.push_back(file3.clone());
let mut files = Vec::new();
chain.find_files_in_range(Seconds(100), Seconds(200), &mut files);
assert_eq!(files.len(), 1);
assert_eq!(files[0], file1);
files.clear();
chain.find_files_in_range(Seconds(200), Seconds(300), &mut files);
assert_eq!(files.len(), 1);
assert_eq!(files[0], file2);
files.clear();
chain.find_files_in_range(Seconds(300), Seconds(400), &mut files);
assert_eq!(files.len(), 1);
assert_eq!(files[0], file3);
files.clear();
chain.find_files_in_range(Seconds(199), Seconds(201), &mut files);
assert_eq!(files.len(), 2);
assert_eq!(files[0], file1);
assert_eq!(files[1], file2);
}
#[test]
fn test_find_files_in_range_complex_scenario() {
let origin = create_test_origin();
let mut chain = Chain {
files: VecDeque::new(),
};
let disposed = create_disposed_file(&origin, 10 * USEC_PER_SEC, 1);
let file1 = create_archived_file(&origin, 1000 * USEC_PER_SEC);
let file2 = create_archived_file(&origin, 2000 * USEC_PER_SEC);
let file3 = create_archived_file(&origin, 3000 * USEC_PER_SEC);
let file4 = create_archived_file(&origin, 4000 * USEC_PER_SEC);
let active = create_active_file(&origin);
chain.files.push_back(disposed);
chain.files.push_back(file1.clone());
chain.files.push_back(file2.clone());
chain.files.push_back(file3.clone());
chain.files.push_back(file4.clone());
chain.files.push_back(active.clone());
let mut files = Vec::new();
chain.find_files_in_range(Seconds(1500), Seconds(3500), &mut files);
assert_eq!(files.len(), 3);
assert_eq!(files[0], file1); assert_eq!(files[1], file2);
assert_eq!(files[2], file3);
files.clear();
chain.find_files_in_range(Seconds(4500), Seconds(5000), &mut files);
assert_eq!(files.len(), 2);
assert_eq!(files[0], file4); assert_eq!(files[1], active);
files.clear();
chain.find_files_in_range(Seconds(500), Seconds(1500), &mut files);
assert_eq!(files.len(), 1);
assert_eq!(files[0], file1);
files.clear();
chain.find_files_in_range(Seconds(0), Seconds(u32::MAX), &mut files);
assert_eq!(files.len(), 5); assert_eq!(files[0], file1); assert_eq!(files[1], file2);
assert_eq!(files[2], file3);
assert_eq!(files[3], file4);
assert_eq!(files[4], active);
}
}