Rust learning: lessons, notes, and exercise crates

This commit is contained in:
2026-09-06 23:59:08 +07:00
commit 6ddf41aa81
119 changed files with 10873 additions and 0 deletions
+106
View File
@@ -0,0 +1,106 @@
// The 0007 specification, as executable tests. Do not edit this file — make it pass.
// Copy to: tasks/tests/persist.rs
// Run with: cargo test
use std::error::Error;
use std::path::PathBuf;
use std::sync::atomic::{AtomicU32, Ordering};
use tasks::error::TaskError;
use tasks::store::Store;
use tasks::task::{Priority, Status, Task};
// a fresh path per test, inside the OS temp dir — no file is ever left in your crate
fn temp_path() -> PathBuf {
static N: AtomicU32 = AtomicU32::new(0);
let n = N.fetch_add(1, Ordering::Relaxed);
std::env::temp_dir().join(format!("tasks-test-{}-{}.txt", std::process::id(), n))
}
// ---------- a Task survives the trip to text and back ----------
#[test]
fn a_task_becomes_a_line_and_a_line_becomes_a_task() {
let task = Task {
id: 3,
title: String::from("buy milk"),
priority: Priority::High,
status: Status::Done,
};
assert_eq!(task.to_line(), "3|done|high|buy milk");
assert_eq!("3|done|high|buy milk".parse::<Task>().unwrap(), task);
}
#[test]
fn a_title_may_contain_the_separator() {
let line = "1|todo|low|read a|b testing";
let task: Task = line.parse().unwrap();
assert_eq!(task.title, "read a|b testing");
assert_eq!(task.to_line(), line);
}
#[test]
fn a_corrupt_line_names_itself() {
for bad in ["", "nonsense", "1|todo|low", "x|todo|low|t", "1|sleeping|low|t", "1|todo|urgent|t"] {
assert_eq!(
bad.parse::<Task>().unwrap_err(),
TaskError::BadLine(bad.to_string()),
"line {:?} should be reported as a bad line",
bad
);
}
}
// ---------- the store round trip ----------
#[test]
fn every_task_survives_save_then_load() {
let path = temp_path();
let mut store = Store::new();
store.add("buy milk", Priority::High);
let second = store.add("call bank", Priority::Medium);
store.complete(second).unwrap();
store.save(&path).unwrap();
let loaded = Store::load(&path).unwrap();
assert_eq!(loaded.tasks(), store.tasks());
}
#[test]
fn ids_do_not_restart_after_a_reload() {
let path = temp_path();
let mut store = Store::new();
store.add("first", Priority::Low);
store.add("second", Priority::Low);
store.save(&path).unwrap();
let mut loaded = Store::load(&path).unwrap();
assert_eq!(loaded.add("third", Priority::Low), 3);
}
#[test]
fn a_missing_file_is_an_empty_store_not_an_error() {
let path = temp_path(); // never created
let store = Store::load(&path).expect("first run must not fail");
assert!(store.tasks().is_empty());
}
#[test]
fn a_corrupt_file_fails_loudly() {
let path = temp_path();
std::fs::write(&path, "1|todo|low|fine\nrubbish\n").unwrap();
assert_eq!(
Store::load(&path).unwrap_err(),
TaskError::BadLine(String::from("rubbish"))
);
}
// ---------- io::Error becomes TaskError, and keeps its cause ----------
#[test]
fn a_write_that_cannot_happen_is_an_io_error() {
let path = temp_path().join("no-such-dir").join("t.txt");
let err = Store::new().save(&path).unwrap_err();
assert!(matches!(err, TaskError::Io(_)), "got {:?}", err);
assert_eq!(err.to_string(), "cannot read or write the task file");
assert!(err.source().is_some(), "the io::Error must stay reachable");
}