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
+166
View File
@@ -0,0 +1,166 @@
// The 0008 specification, as executable tests. Do not edit this file — make it pass.
// Copy to: tasks/tests/collections.rs
// Run with: cargo test
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::atomic::{AtomicU32, Ordering};
use tasks::stats::tally;
use tasks::store::Store;
use tasks::task::Priority;
// 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-coll-{}-{}.txt", std::process::id(), n))
}
// ---------- tally: one generic function, any element type, any key type ----------
#[test]
fn tally_counts_how_often_each_key_appears() {
let words = ["red", "blue", "red", "green", "red"];
let counts = tally(&words, |word| *word);
assert_eq!(counts[&"red"], 3);
assert_eq!(counts[&"blue"], 1);
assert_eq!(counts.len(), 3);
}
#[test]
fn tally_accepts_a_key_type_that_is_not_the_element_type() {
let words = ["a", "bb", "cc", "ddd"];
let by_length = tally(&words, |word| word.len());
assert_eq!(by_length, HashMap::from([(1, 1), (2, 2), (3, 1)]));
}
#[test]
fn tally_of_nothing_is_an_empty_map() {
let nothing: [u32; 0] = [];
assert!(tally(&nothing, |n| *n).is_empty());
}
// ---------- counting the store, built on that same function ----------
#[test]
fn count_by_priority_counts_every_priority_present() {
let mut store = Store::new();
store.add("buy milk", Priority::High);
store.add("call bank", Priority::High);
store.add("water plants", Priority::Low);
assert_eq!(
store.count_by_priority(),
HashMap::from([(Priority::High, 2), (Priority::Low, 1)])
);
}
#[test]
fn count_by_priority_has_no_entry_for_an_absent_priority() {
let mut store = Store::new();
store.add("buy milk", Priority::High);
let counts = store.count_by_priority();
assert_eq!(counts.len(), 1, "absent priorities must not appear as zero");
assert_eq!(counts.get(&Priority::Low), None);
}
#[test]
fn count_by_priority_of_an_empty_store_is_empty() {
assert!(Store::new().count_by_priority().is_empty());
}
// ---------- selecting titles: filter, map, collect ----------
#[test]
fn titles_with_returns_matching_titles_in_order() {
let mut store = Store::new();
store.add("buy milk", Priority::High);
store.add("water plants", Priority::Low);
store.add("call bank", Priority::High);
assert_eq!(store.titles_with(Priority::High), ["buy milk", "call bank"]);
}
#[test]
fn titles_with_returns_nothing_when_no_task_matches() {
let mut store = Store::new();
store.add("buy milk", Priority::High);
assert!(store.titles_with(Priority::Medium).is_empty());
}
// ---------- clearing out finished work: retain ----------
#[test]
fn remove_completed_drops_done_tasks_and_reports_how_many() {
let mut store = Store::new();
let first = store.add("buy milk", Priority::High);
store.add("water plants", Priority::Low);
let third = store.add("call bank", Priority::Medium);
store.complete(first).unwrap();
store.complete(third).unwrap();
assert_eq!(store.remove_completed(), 2);
assert_eq!(store.titles_with(Priority::Low), ["water plants"]);
assert_eq!(store.tasks().len(), 1);
}
#[test]
fn remove_completed_removes_nothing_when_nothing_is_done() {
let mut store = Store::new();
store.add("buy milk", Priority::High);
store.add("call bank", Priority::Low);
assert_eq!(store.remove_completed(), 0);
assert_eq!(store.tasks().len(), 2);
}
#[test]
fn remove_completed_keeps_the_remaining_ids_unchanged() {
let mut store = Store::new();
let first = store.add("buy milk", Priority::High);
store.add("call bank", Priority::Low);
store.complete(first).unwrap();
store.remove_completed();
assert_eq!(store.tasks()[0].id, 2, "surviving tasks keep their own id");
assert_eq!(
store.add("new one", Priority::Low),
3,
"and the counter is untouched"
);
}
// ---------- load: one collect, and the line rules that come with it ----------
#[test]
fn a_file_without_a_trailing_newline_still_loads() {
let path = temp_path();
std::fs::write(&path, "1|todo|low|fine").unwrap();
let store = Store::load(&path).unwrap();
assert_eq!(store.tasks().len(), 1);
}
#[test]
fn a_blank_line_inside_the_file_is_a_bad_line() {
let path = temp_path();
std::fs::write(&path, "1|todo|low|fine\n\n2|todo|low|also fine\n").unwrap();
let err = Store::load(&path).unwrap_err();
assert_eq!(err.to_string(), "cannot read saved line: ");
}
#[test]
fn one_bad_line_loses_the_whole_load_not_part_of_it() {
let path = temp_path();
std::fs::write(&path, "1|todo|low|fine\nrubbish\n3|todo|low|also fine\n").unwrap();
assert!(Store::load(&path).is_err());
// the file itself is untouched by a failed load — nothing was half-written
assert_eq!(
std::fs::read_to_string(&path).unwrap(),
"1|todo|low|fine\nrubbish\n3|todo|low|also fine\n"
);
}