use std::{collections::HashMap, ffi::OsString, path::MAIN_SEPARATOR}; use notify::event::CreateKind; use watchexec::{ event::{filekind::*, Event, Tag}, paths::summarise_events_to_env, }; #[cfg(unix)] const ENV_SEP: &str = ":"; #[cfg(not(unix))] const ENV_SEP: &str = ";"; fn ospath(path: &str) -> OsString { let root = std::fs::canonicalize(".").unwrap(); if path.is_empty() { root } else { root.join(path) } .into() } fn event(path: &str, kind: FileEventKind) -> Event { Event { tags: vec![ Tag::Path { path: ospath(path).into(), file_type: None, }, Tag::FileEventKind(kind), ], metadata: Default::default(), } } #[test] fn no_events_no_env() { let events = Vec::::new(); assert_eq!(summarise_events_to_env(&events), HashMap::new()); } #[test] fn single_created() { let events = vec![event("file.txt", FileEventKind::Create(CreateKind::File))]; assert_eq!( summarise_events_to_env(&events), HashMap::from([ ("CREATED", OsString::from("file.txt")), ("COMMON", ospath("")), ]) ); } #[test] fn single_meta() { let events = vec![event( "file.txt", FileEventKind::Modify(ModifyKind::Metadata(MetadataKind::Any)), )]; assert_eq!( summarise_events_to_env(&events), HashMap::from([ ("META_CHANGED", OsString::from("file.txt")), ("COMMON", ospath("")), ]) ); } #[test] fn single_removed() { let events = vec![event("file.txt", FileEventKind::Remove(RemoveKind::File))]; assert_eq!( summarise_events_to_env(&events), HashMap::from([ ("REMOVED", OsString::from("file.txt")), ("COMMON", ospath("")), ]) ); } #[test] fn single_renamed() { let events = vec![event( "file.txt", FileEventKind::Modify(ModifyKind::Name(RenameMode::Any)), )]; assert_eq!( summarise_events_to_env(&events), HashMap::from([ ("RENAMED", OsString::from("file.txt")), ("COMMON", ospath("")), ]) ); } #[test] fn single_written() { let events = vec![event( "file.txt", FileEventKind::Modify(ModifyKind::Data(DataChange::Any)), )]; assert_eq!( summarise_events_to_env(&events), HashMap::from([ ("WRITTEN", OsString::from("file.txt")), ("COMMON", ospath("")), ]) ); } #[test] fn single_otherwise() { let events = vec![event("file.txt", FileEventKind::Any)]; assert_eq!( summarise_events_to_env(&events), HashMap::from([ ("OTHERWISE_CHANGED", OsString::from("file.txt")), ("COMMON", ospath("")), ]) ); } #[test] fn all_types_once() { let events = vec![ event("create.txt", FileEventKind::Create(CreateKind::File)), event( "metadata.txt", FileEventKind::Modify(ModifyKind::Metadata(MetadataKind::Any)), ), event("remove.txt", FileEventKind::Remove(RemoveKind::File)), event( "rename.txt", FileEventKind::Modify(ModifyKind::Name(RenameMode::Any)), ), event( "modify.txt", FileEventKind::Modify(ModifyKind::Data(DataChange::Any)), ), event("any.txt", FileEventKind::Any), ]; assert_eq!( summarise_events_to_env(&events), HashMap::from([ ("CREATED", OsString::from("create.txt")), ("META_CHANGED", OsString::from("metadata.txt")), ("REMOVED", OsString::from("remove.txt")), ("RENAMED", OsString::from("rename.txt")), ("WRITTEN", OsString::from("modify.txt")), ("OTHERWISE_CHANGED", OsString::from("any.txt")), ("COMMON", ospath("")), ]) ); } #[test] fn single_type_multipath() { let events = vec![ event("root.txt", FileEventKind::Create(CreateKind::File)), event("sub/folder.txt", FileEventKind::Create(CreateKind::File)), event("dom/folder.txt", FileEventKind::Create(CreateKind::File)), event( "deeper/sub/folder.txt", FileEventKind::Create(CreateKind::File), ), ]; assert_eq!( summarise_events_to_env(&events), HashMap::from([ ( "CREATED", OsString::from( [ format!("deeper{MAIN_SEPARATOR}sub{MAIN_SEPARATOR}folder.txt"), format!("dom{MAIN_SEPARATOR}folder.txt"), "root.txt".to_string(), format!("sub{MAIN_SEPARATOR}folder.txt"), ] .join(ENV_SEP) ) ), ("COMMON", ospath("")), ]) ); } #[test] fn single_type_divergent_paths() { let events = vec![ event("sub/folder.txt", FileEventKind::Create(CreateKind::File)), event("dom/folder.txt", FileEventKind::Create(CreateKind::File)), ]; assert_eq!( summarise_events_to_env(&events), HashMap::from([ ( "CREATED", OsString::from( [ format!("dom{MAIN_SEPARATOR}folder.txt"), format!("sub{MAIN_SEPARATOR}folder.txt"), ] .join(ENV_SEP) ) ), ("COMMON", ospath("")), ]) ); } #[test] fn multitype_multipath() { let events = vec![ event("root.txt", FileEventKind::Create(CreateKind::File)), event("sibling.txt", FileEventKind::Create(CreateKind::Any)), event( "sub/folder.txt", FileEventKind::Modify(ModifyKind::Metadata(MetadataKind::Ownership)), ), event("dom/folder.txt", FileEventKind::Remove(RemoveKind::Folder)), event("deeper/sub/folder.txt", FileEventKind::Other), ]; assert_eq!( summarise_events_to_env(&events), HashMap::from([ ( "CREATED", OsString::from(["root.txt", "sibling.txt"].join(ENV_SEP)), ), ( "META_CHANGED", OsString::from(format!("sub{MAIN_SEPARATOR}folder.txt")) ), ( "REMOVED", OsString::from(format!("dom{MAIN_SEPARATOR}folder.txt")) ), ( "OTHERWISE_CHANGED", OsString::from(format!( "deeper{MAIN_SEPARATOR}sub{MAIN_SEPARATOR}folder.txt" )) ), ("COMMON", ospath("")), ]) ); } #[test] fn multiple_paths_in_one_event() { let events = vec![Event { tags: vec![ Tag::Path { path: ospath("one.txt").into(), file_type: None, }, Tag::Path { path: ospath("two.txt").into(), file_type: None, }, Tag::FileEventKind(FileEventKind::Any), ], metadata: Default::default(), }]; assert_eq!( summarise_events_to_env(&events), HashMap::from([ ( "OTHERWISE_CHANGED", OsString::from(String::new() + "one.txt" + ENV_SEP + "two.txt") ), ("COMMON", ospath("")), ]) ); } #[test] fn mixed_non_paths_events() { let events = vec![ event("one.txt", FileEventKind::Any), Event { tags: vec![Tag::Process(1234)], metadata: Default::default(), }, event("two.txt", FileEventKind::Any), Event { tags: vec![Tag::FileEventKind(FileEventKind::Any)], metadata: Default::default(), }, ]; assert_eq!( summarise_events_to_env(&events), HashMap::from([ ( "OTHERWISE_CHANGED", OsString::from(String::new() + "one.txt" + ENV_SEP + "two.txt") ), ("COMMON", ospath("")), ]) ); } #[test] fn only_non_paths_events() { let events = vec![ Event { tags: vec![Tag::Process(1234)], metadata: Default::default(), }, Event { tags: vec![Tag::FileEventKind(FileEventKind::Any)], metadata: Default::default(), }, ]; assert_eq!(summarise_events_to_env(&events), HashMap::new()); } #[test] fn multipath_is_sorted() { let events = vec![ event("0123.txt", FileEventKind::Any), event("a.txt", FileEventKind::Any), event("b.txt", FileEventKind::Any), event("c.txt", FileEventKind::Any), event("ᄁ.txt", FileEventKind::Any), ]; assert_eq!( summarise_events_to_env(&events), HashMap::from([ ( "OTHERWISE_CHANGED", OsString::from( String::new() + "0123.txt" + ENV_SEP + "a.txt" + ENV_SEP + "b.txt" + ENV_SEP + "c.txt" + ENV_SEP + "ᄁ.txt" ) ), ("COMMON", ospath("")), ]) ); } #[test] fn multipath_is_deduped() { let events = vec![ event("0123.txt", FileEventKind::Any), event("0123.txt", FileEventKind::Any), event("a.txt", FileEventKind::Any), event("a.txt", FileEventKind::Any), event("b.txt", FileEventKind::Any), event("b.txt", FileEventKind::Any), event("c.txt", FileEventKind::Any), event("ᄁ.txt", FileEventKind::Any), event("ᄁ.txt", FileEventKind::Any), ]; assert_eq!( summarise_events_to_env(&events), HashMap::from([ ( "OTHERWISE_CHANGED", OsString::from( String::new() + "0123.txt" + ENV_SEP + "a.txt" + ENV_SEP + "b.txt" + ENV_SEP + "c.txt" + ENV_SEP + "ᄁ.txt" ) ), ("COMMON", ospath("")), ]) ); }