// 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(¬hing, |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" ); }