docs(comments): rewrite comments across all crates to the guidelines
Sweep every first-party crate source (1956 .rs files) to the project comment guidelines: delete redundant restatements, decorative banners, change narration, and end-of-line comments; keep and tighten the crucial ones (invariants, bug rationale, SAFETY blocks, ported-source attribution). No functional code changed. Every edit is proven comment-only against the prior tree by a comment-stripping lexer (string/char/raw-string aware) plus a separate doctest-fence check. Where removing a comment made rustfmt or clippy want to re-lay-out adjacent code, the minimal triggering comment is restored so code tokens stay byte-identical. Gates green: cargo fmt --all --check (0 diffs), cargo check and cargo clippy --workspace --all-targets (0 warnings). Adds scripts/check_codegen_comment_guidelines.py — the enforcement gate for these guidelines (flags banners, end-of-line comments, change narration, and commented-out code).
This commit is contained in:
1 parent
ff0fb56c67
commit
a02b555e66
1458 files changed
+10729
-21750
No files matched your search
@@ -30,9 +30,7 @@ async fn replace_file_async(tmp: PathBuf, target: PathBuf) -> io::Result<()> {
|
||||
/// Used for subagent child sessions whose files live under the parent's session dir.
|
||||
#[derive(Clone)]
|
||||
enum SessionDirMode {
|
||||
/// Existing behavior: root + sessions/{cwd}/{id}/
|
||||
FromRoot(PathBuf),
|
||||
/// New: use this directory directly (for subagent children).
|
||||
Explicit(PathBuf),
|
||||
}
|
||||
/// JSONL-based storage adapter (legacy format)
|
||||
@@ -319,7 +317,6 @@ impl JsonlStorageAdapter {
|
||||
}
|
||||
Ok(items)
|
||||
}
|
||||
/// Append a session update to the updates.jsonl file, wrapping it in an envelope with timestamp.
|
||||
async fn append_update_to_file(
|
||||
&self,
|
||||
path: PathBuf,
|
||||
@@ -605,7 +602,6 @@ impl JsonlStorageAdapter {
|
||||
.map_err(io::Error::other)?
|
||||
}
|
||||
}
|
||||
/// Transform session ID in a SessionUpdate
|
||||
fn transform_session_id_in_update(
|
||||
update: super::SessionUpdate,
|
||||
new_id: &acp::SessionId,
|
||||
|
||||
@@ -1968,7 +1968,6 @@ async fn list_sessions_recent_skips_corrupt_summary() {
|
||||
assert_eq!(recent.len(), 1);
|
||||
assert_eq!(recent[0].info.id, acp::SessionId::new("good"));
|
||||
}
|
||||
/// Helper: set the mtime of a file to a specific chrono DateTime.
|
||||
fn set_mtime(path: &std::path::Path, time: chrono::DateTime<chrono::Utc>) {
|
||||
use std::time::{Duration, UNIX_EPOCH};
|
||||
let secs = time.timestamp() as u64;
|
||||
@@ -2719,10 +2718,10 @@ async fn append_update_terminates_torn_trailing_line() {
|
||||
/// End-to-end resume-path regression for the incident: a live session
|
||||
/// whose `chat_history.jsonl` contains a merged record (crash mid-append,
|
||||
/// then log-and-continue appended the next record onto the partial line)
|
||||
/// must still load via `load_session_without_updates` — previously this
|
||||
/// returned InvalidData ("expected `,` or `}` at line 1 column N"),
|
||||
/// surfacing to the user as "Couldn't load session: … FS_OTHER" and
|
||||
/// permanently bricking the session.
|
||||
/// must still load via `load_session_without_updates` instead of failing
|
||||
/// with InvalidData ("expected `,` or `}` at line 1 column N"), which
|
||||
/// surfaced to the user as "Couldn't load session: … FS_OTHER" and
|
||||
/// permanently bricked the session.
|
||||
#[tokio::test]
|
||||
async fn load_session_without_updates_survives_merged_chat_line() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
||||
@@ -73,7 +73,8 @@ impl Iterator for UpdatesIterator {
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.line_buffer.clear();
|
||||
match self.reader.read_line(&mut self.line_buffer) {
|
||||
Ok(0) => None, // EOF
|
||||
// EOF
|
||||
Ok(0) => None,
|
||||
Ok(_) => {
|
||||
let line = self.line_buffer.trim();
|
||||
if line.is_empty() {
|
||||
@@ -332,7 +333,7 @@ pub struct CopySessionOptions {
|
||||
/// summary so the prompt-facing cwd survives session restore/reload.
|
||||
pub prompt_display_cwd: Option<String>,
|
||||
|
||||
// ── Generic fork extensions (used by subagent + worktree forks) ──
|
||||
// Generic fork extensions (used by subagent + worktree forks).
|
||||
/// Override `session_kind` in the forked summary. Defaults to `"fork"`.
|
||||
/// Subagent resume sets `"subagent_resume"`.
|
||||
pub session_kind: Option<String>,
|
||||
@@ -1233,10 +1234,6 @@ pub(crate) fn filter_delta_replay_lines(contents: &str) -> Vec<&str> {
|
||||
filter_rewind_lines(live)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Selective prompt-extraction parser
|
||||
// ============================================================================
|
||||
|
||||
/// An event yielded by [`PromptExtractIterator`].
|
||||
///
|
||||
/// Each event represents the minimal information extracted from one
|
||||
@@ -1323,7 +1320,8 @@ impl Iterator for PromptExtractIterator {
|
||||
loop {
|
||||
self.line_buffer.clear();
|
||||
match std::io::BufRead::read_line(&mut self.reader, &mut self.line_buffer) {
|
||||
Ok(0) => return None, // EOF
|
||||
// EOF
|
||||
Ok(0) => return None,
|
||||
Err(_) => return Some(PromptExtractEvent::NotUserMessage),
|
||||
Ok(_) => {}
|
||||
}
|
||||
@@ -1622,10 +1620,6 @@ pub fn collect_tool_metadata(iter: impl Iterator<Item = io::Result<SessionUpdate
|
||||
meta
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Selective serde structs — only the fields we care about
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Peek inside ACP or xAI `params` to read the `update.sessionUpdate` tag and
|
||||
/// any fields relevant to `user_message_chunk` or `rewind_marker`.
|
||||
///
|
||||
@@ -1745,16 +1739,10 @@ pub(crate) fn parse_prompt_extract_event(line: &str) -> PromptExtractEvent {
|
||||
PromptExtractEvent::NotUserMessage
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Tests
|
||||
// ============================================================================
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// ── helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
/// Wrap an ACP notification as the envelope stored in updates.jsonl.
|
||||
fn acp_envelope(session_update_json: &str) -> String {
|
||||
format!(
|
||||
@@ -1769,8 +1757,6 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
// ── parse_prompt_extract_event unit tests ─────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn acp_user_text_chunk_yields_user_text() {
|
||||
let line = acp_envelope(
|
||||
@@ -1793,8 +1779,8 @@ mod tests {
|
||||
parse_prompt_extract_event(&line),
|
||||
PromptExtractEvent::user_text("multi\nline \"quoted\" caf\u{e9}")
|
||||
);
|
||||
// An escaped bash command now parses too and must be excluded by the
|
||||
// bash_command predicate (it used to be excluded by the parse failure).
|
||||
// An escaped bash command parses successfully, so it must be excluded
|
||||
// via the bash_command predicate rather than a parse failure.
|
||||
let bash = acp_envelope(
|
||||
r#"{"sessionUpdate":"user_message_chunk","content":{"type":"text","text":"! echo \"hi\"","_meta":{"bash_command":"echo \"hi\""}}}"#,
|
||||
);
|
||||
@@ -1880,7 +1866,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// Empty string — the iterator skips blanks, but a direct call must still
|
||||
/// classify conservatively (the parser always yields an event now).
|
||||
/// classify conservatively (the parser always yields an event).
|
||||
#[test]
|
||||
fn empty_string_yields_not_user() {
|
||||
assert_eq!(
|
||||
@@ -1921,8 +1907,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// ── PromptExtractIterator integration tests via tempfile ──────────────────
|
||||
|
||||
use std::io::Write as _;
|
||||
|
||||
fn write_updates_file(lines: &[&str]) -> tempfile::NamedTempFile {
|
||||
@@ -2321,8 +2305,6 @@ mod tests {
|
||||
assert_eq!(texts, vec!["P0", "phantom", "P1", "after"]);
|
||||
}
|
||||
|
||||
// ── filter_rewind_lines tests ────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn filter_rewind_removes_dead_branch() {
|
||||
let u1 = acp_envelope(
|
||||
@@ -2449,8 +2431,6 @@ mod tests {
|
||||
assert!(result[2].contains("final"));
|
||||
}
|
||||
|
||||
// ── prepare_replay_lines tests ───────────────────────────────────────────
|
||||
|
||||
/// Envelope with _meta at the params level (where the real agent puts it).
|
||||
fn acp_envelope_with_meta(session_update_json: &str, meta_json: &str) -> String {
|
||||
format!(
|
||||
@@ -2491,7 +2471,8 @@ mod tests {
|
||||
|
||||
let prepared = prepare_replay_lines(&raw, Some("nonexistent"));
|
||||
assert_eq!(prepared.lines.len(), 1);
|
||||
assert!(prepared.mark_replay); // fallback to full replay
|
||||
// fallback to full replay
|
||||
assert!(prepared.mark_replay);
|
||||
}
|
||||
|
||||
/// A resolved cursor is refused when the tail contains an eventId-less
|
||||
@@ -2582,8 +2563,6 @@ mod tests {
|
||||
assert_eq!(prepared.max_event_seq, None);
|
||||
}
|
||||
|
||||
// ── available_commands_update skip (T1) + single-pass equivalence ─────────
|
||||
|
||||
#[test]
|
||||
fn acu_line_detection_exact_and_no_false_positive() {
|
||||
let acu =
|
||||
@@ -2769,7 +2748,8 @@ mod tests {
|
||||
let prepared = prepare_replay_lines(&raw, None);
|
||||
assert_eq!(prepared.lines, reference);
|
||||
assert_eq!(prepared.total_live, reference.len());
|
||||
assert_eq!(prepared.last_tokens, 11); // last kept line carrying tokens
|
||||
// last kept line carrying tokens
|
||||
assert_eq!(prepared.last_tokens, 11);
|
||||
}
|
||||
|
||||
/// The prompt-extract fast-reject must not be fooled by lines that merely
|
||||
@@ -2941,8 +2921,10 @@ mod tests {
|
||||
assert!(!prepared.mark_replay);
|
||||
assert_eq!(prepared.lines.len(), 1);
|
||||
assert!(prepared.lines[0].contains("a1"));
|
||||
assert_eq!(prepared.last_tokens, 12); // last token-bearing survivor
|
||||
assert_eq!(prepared.total_live, 2); // ACU-free survivors: u1, a1
|
||||
// last token-bearing survivor
|
||||
assert_eq!(prepared.last_tokens, 12);
|
||||
// ACU-free survivors: u1, a1
|
||||
assert_eq!(prepared.total_live, 2);
|
||||
}
|
||||
|
||||
/// The delta-replay helper (shared with the initial path) drops blanks + ACUs
|
||||
@@ -3058,8 +3040,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// ── collect_assistant_text / collect_tool_metadata tests ──────────────────
|
||||
|
||||
#[test]
|
||||
fn collect_assistant_text_extracts_chunks() {
|
||||
let lines = vec![
|
||||
@@ -3081,8 +3061,10 @@ mod tests {
|
||||
#[test]
|
||||
fn collect_assistant_text_caps_at_100k() {
|
||||
// Two 60k chunks with non-ASCII, separator, and truncation
|
||||
let chunk1 = "x".repeat(60_000) + "café"; // 60k + 5 bytes (café is 5 UTF-8 bytes)
|
||||
let chunk2 = "日本語".repeat(20_000); // 60k bytes (3 bytes per char)
|
||||
// chunk1: 60k + 5 bytes (café is 5 UTF-8 bytes)
|
||||
let chunk1 = "x".repeat(60_000) + "café";
|
||||
// chunk2: 60k bytes (3 bytes per char)
|
||||
let chunk2 = "日本語".repeat(20_000);
|
||||
let lines = vec![
|
||||
acp_envelope(&format!(
|
||||
r#"{{"sessionUpdate":"agent_message_chunk","content":{{"type":"text","text":"{chunk1}"}}}}"#
|
||||
|
||||
@@ -62,7 +62,7 @@ impl Default for BootstrapConfig {
|
||||
Self {
|
||||
max_concurrent: 4,
|
||||
per_session_timeout: Duration::from_secs(30),
|
||||
max_file_size: 30 * 1024 * 1024, // 30 MB
|
||||
max_file_size: 30 * 1024 * 1024,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,7 +222,6 @@ impl SearchIndexManager {
|
||||
let _ = self.tx.send(SearchManagerCmd::BootstrapOnce { root });
|
||||
}
|
||||
|
||||
/// Get current bootstrap progress status.
|
||||
pub fn status(&self) -> SearchIndexStatus {
|
||||
SearchIndexStatus {
|
||||
bootstrapping: self.progress.bootstrapping.load(Ordering::Relaxed),
|
||||
@@ -233,7 +232,6 @@ impl SearchIndexManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Queue an index update for a single session.
|
||||
pub fn enqueue(&self, root: PathBuf, session_id: String, cwd: String) {
|
||||
let key = SessionSearchKey { session_id, cwd };
|
||||
let _ = self.tx.send(SearchManagerCmd::Enqueue {
|
||||
@@ -515,7 +513,6 @@ async fn upsert_session(
|
||||
) -> io::Result<UpsertOutcome> {
|
||||
// Single-pass direct file I/O: bypass StorageAdapter and open updates.jsonl
|
||||
// once, extracting prompts, assistant text, and tool metadata in one pass.
|
||||
// Reduces I/O by 3x vs the old 3-call pattern.
|
||||
let (content, bytes_read) = if let Some(updates_path) = storage.updates_file_path(info) {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
collect_all_indexable_content_single_pass(&updates_path)
|
||||
@@ -532,7 +529,6 @@ async fn upsert_session(
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let index = SessionSearchIndex::open_or_create(&db_path).map_err(sqlite_to_io_error)?;
|
||||
|
||||
// Skip if content hasn't changed
|
||||
if let Ok(Some(existing_hash)) = index.get_content_hash(&doc.session_id)
|
||||
&& existing_hash == doc.content_hash
|
||||
{
|
||||
@@ -739,7 +735,6 @@ async fn reindex_all(root_dir: &Path, storage: &dyn StorageAdapter) -> io::Resul
|
||||
});
|
||||
}
|
||||
|
||||
// Drain the JoinSet — wait for all tasks to complete
|
||||
while let Some(result) = join_set.join_next().await {
|
||||
if let Err(e) = result {
|
||||
tracing::warn!(error = %e, "session indexing task panicked");
|
||||
@@ -786,14 +781,12 @@ async fn reindex_all(root_dir: &Path, storage: &dyn StorageAdapter) -> io::Resul
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Zero-copy peek structs for single-pass content collection
|
||||
// Zero-copy peek structs for single-pass content collection.
|
||||
//
|
||||
// Text-bearing fields are `Cow`, not `&str`: serde cannot borrow `&str` from
|
||||
// JSON strings containing escapes (\n, \", \\, \uXXXX), so borrowing would
|
||||
// error and silently drop the message from the index. Discriminant-only
|
||||
// fields (never escaped) stay `&'a str` for the zero-copy fast path.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Selective peek for assistant text extraction (agent_message_chunk content.text).
|
||||
#[derive(serde::Deserialize)]
|
||||
@@ -861,9 +854,6 @@ struct ToolLocationPeek<'a> {
|
||||
/// zero-copy `RawLinePeek` discriminant, and extracts only the fields
|
||||
/// needed for search indexing. Never materializes full
|
||||
/// `acp::SessionNotification` objects.
|
||||
///
|
||||
/// Replaces the old 3-call pattern (`load_prompts_only` + `load_assistant_text`
|
||||
/// + `load_tool_metadata`), reducing I/O by 3x and deserialization cost significantly.
|
||||
fn collect_all_indexable_content_single_pass(updates_path: &Path) -> io::Result<(String, u64)> {
|
||||
let file = match std::fs::File::open(updates_path) {
|
||||
Ok(f) => f,
|
||||
@@ -940,7 +930,7 @@ fn collect_all_indexable_content_single_pass(updates_path: &Path) -> io::Result<
|
||||
// Control events (rewind markers) come from xAI extensions
|
||||
// ("_kigi/session/update"). Dispatch on source first, then tag.
|
||||
if !is_xai {
|
||||
// ── ACP content events ──────────────────────────────────
|
||||
// ACP content events
|
||||
match tag {
|
||||
Some(t) if t == *USER_MESSAGE_CHUNK => {
|
||||
flush_assistant(&mut current_assistant, &mut assistant_texts);
|
||||
@@ -1050,7 +1040,7 @@ fn collect_all_indexable_content_single_pass(updates_path: &Path) -> io::Result<
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ── xAI control events ──────────────────────────────────
|
||||
// xAI control events
|
||||
match tag {
|
||||
Some(t) if t == *REWIND_MARKER => {
|
||||
flush_assistant(&mut current_assistant, &mut assistant_texts);
|
||||
@@ -1369,9 +1359,8 @@ mod tests {
|
||||
assert_eq!(doc.content_hash, doc2.content_hash);
|
||||
}
|
||||
|
||||
// ── helpers for single-pass tests ──────────────────────────────────────
|
||||
// helpers for single-pass tests
|
||||
|
||||
/// Write an updates.jsonl temp file from envelope strings.
|
||||
fn write_updates_jsonl(lines: &[String]) -> tempfile::NamedTempFile {
|
||||
use std::io::Write as _;
|
||||
let mut f = tempfile::NamedTempFile::new().unwrap();
|
||||
@@ -1393,7 +1382,7 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
// ── single-pass content collection tests ─────────────────────────────
|
||||
// single-pass content collection tests
|
||||
|
||||
#[test]
|
||||
fn test_single_pass_extracts_user_prompts() {
|
||||
@@ -1695,7 +1684,7 @@ mod tests {
|
||||
assert_eq!(doc2.title, "session summary");
|
||||
}
|
||||
|
||||
// ── bootstrap config tests ─────────────────────────────────────────────
|
||||
// bootstrap config tests
|
||||
|
||||
#[test]
|
||||
fn test_bootstrap_config_defaults() {
|
||||
@@ -1705,7 +1694,7 @@ mod tests {
|
||||
assert_eq!(config.max_file_size, 30 * 1024 * 1024);
|
||||
}
|
||||
|
||||
// ── should_skip_session tests ──────────────────────────────────────────
|
||||
// should_skip_session tests
|
||||
|
||||
#[test]
|
||||
fn test_should_skip_session_large_file() {
|
||||
@@ -1745,7 +1734,7 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
// ── progress and status tests ──────────────────────────────────────────
|
||||
// progress and status tests
|
||||
|
||||
#[test]
|
||||
fn test_bootstrap_progress_extended_defaults() {
|
||||
@@ -1773,7 +1762,7 @@ mod tests {
|
||||
assert!(json.contains("\"bootstrapping\":true"));
|
||||
}
|
||||
|
||||
// ── single-pass bytes_read tests ───────────────────────────────────────
|
||||
// single-pass bytes_read tests
|
||||
|
||||
#[test]
|
||||
fn test_single_pass_reports_bytes_read() {
|
||||
@@ -1794,7 +1783,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
// ── delta indexing tests ───────────────────────────────────────────────
|
||||
// delta indexing tests
|
||||
|
||||
/// Append new lines to an existing updates.jsonl file and return the
|
||||
/// byte offset where the new content starts.
|
||||
@@ -1928,7 +1917,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
// ── bootstrap_once eager flag tests ────────────────────────────────────
|
||||
// bootstrap_once eager flag tests
|
||||
// NOTE: SEARCH_INDEX_MANAGER is a process-wide singleton, so tests
|
||||
// that depend on the `bootstrapping` flag transitioning to `false`
|
||||
// are racy when run in parallel (another test's bootstrap_once()
|
||||
@@ -1962,7 +1951,7 @@ mod tests {
|
||||
assert!(resp.results.is_empty());
|
||||
}
|
||||
|
||||
// ── bootstrap freshness recheck tests ──────────────────────────────────
|
||||
// bootstrap freshness recheck tests
|
||||
// These test the free functions directly (per-tmp-root DB state), not the
|
||||
// global SEARCH_INDEX_MANAGER, whose `bootstrapping` flag is process-wide
|
||||
// and racy across parallel tests (see NOTE above).
|
||||
|
||||
@@ -39,7 +39,7 @@ pub struct SessionDoc {
|
||||
pub last_indexed_offset: u64,
|
||||
}
|
||||
|
||||
/// State of a previously indexed session, returned by
|
||||
/// State of an already-indexed session, returned by
|
||||
/// [`SessionSearchIndex::get_session_index_state`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SessionIndexState {
|
||||
@@ -100,7 +100,6 @@ impl SessionSearchIndex {
|
||||
// busy_timeout + journal pragma live in the helper (see JournalMode::open).
|
||||
let db = journal_mode.open(db_path)?;
|
||||
|
||||
// Check existing schema version
|
||||
let stored_version: Option<String> = db
|
||||
.query_row(
|
||||
"SELECT value FROM meta WHERE key = 'session_search_schema_version'",
|
||||
@@ -155,7 +154,6 @@ impl SessionSearchIndex {
|
||||
);
|
||||
}
|
||||
|
||||
// Create tables + content-synced FTS5 with auto-sync triggers
|
||||
db.execute_batch(
|
||||
"
|
||||
CREATE TABLE IF NOT EXISTS meta (
|
||||
@@ -280,7 +278,6 @@ impl SessionSearchIndex {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove a session document from the index.
|
||||
pub fn delete_doc(&self, session_id: &str) -> Result<(), rusqlite::Error> {
|
||||
self.db.execute(
|
||||
"DELETE FROM session_docs WHERE session_id = ?1",
|
||||
@@ -340,7 +337,6 @@ impl SessionSearchIndex {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read a value from the `meta` key-value table.
|
||||
pub fn get_meta(&self, key: &str) -> Result<Option<String>, rusqlite::Error> {
|
||||
self.db
|
||||
.query_row(
|
||||
|
||||
@@ -13,10 +13,9 @@ use super::search_fts::SessionSearchIndex;
|
||||
const META_KEY_LAST_BOOTSTRAP: &str = "last_bootstrap_at";
|
||||
|
||||
/// Read `last_bootstrap_at` from the sqlite meta table, preserving read
|
||||
/// failures, so callers
|
||||
/// can tell "marker genuinely absent" apart from "could not read the DB"
|
||||
/// (transient busy/locked/I/O). A missing DB file is a true absence, not an
|
||||
/// error.
|
||||
/// failures, so callers can tell "marker genuinely absent" apart from "could
|
||||
/// not read the DB" (transient busy/locked/I/O). A missing DB file is a true
|
||||
/// absence, not an error.
|
||||
pub fn try_read_last_bootstrap_at(db_path: &Path) -> Result<Option<i64>, String> {
|
||||
if !db_path.exists() {
|
||||
return Ok(None);
|
||||
@@ -38,8 +37,6 @@ pub fn write_last_bootstrap_at(db_path: &Path) -> io::Result<()> {
|
||||
.map_err(|e| io::Error::other(e.to_string()))
|
||||
}
|
||||
|
||||
// Tests
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -49,13 +46,10 @@ mod tests {
|
||||
let tmp = tempfile::TempDir::new().unwrap();
|
||||
let db_path = tmp.path().join("session_search.sqlite");
|
||||
|
||||
// Before writing, should be None
|
||||
assert_eq!(try_read_last_bootstrap_at(&db_path).unwrap(), None);
|
||||
|
||||
// Create DB and write timestamp
|
||||
write_last_bootstrap_at(&db_path).unwrap();
|
||||
|
||||
// Should now have a reasonable timestamp
|
||||
let ts = try_read_last_bootstrap_at(&db_path).unwrap().unwrap();
|
||||
let now = chrono::Utc::now().timestamp();
|
||||
assert!(
|
||||
|
||||
Reference in new issue
Block a user