diff options
Diffstat (limited to 'subplotlib/src/steplibrary/files.rs')
-rw-r--r-- | subplotlib/src/steplibrary/files.rs | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/subplotlib/src/steplibrary/files.rs b/subplotlib/src/steplibrary/files.rs index 35f841a..a49add1 100644 --- a/subplotlib/src/steplibrary/files.rs +++ b/subplotlib/src/steplibrary/files.rs @@ -26,6 +26,9 @@ pub use super::datadir::Datadir; /// /// This context depends on, and will automatically register, the context for /// the [`datadir`][crate::steplibrary::datadir] step library. +/// +/// Because files can typically only be named in Subplot documents, we assume they +/// all have names which can be rendered as utf-8 strings. pub struct Files { metadata: HashMap<String, Metadata>, } @@ -75,6 +78,12 @@ pub fn create_from_embedded_with_other_name( .write_all(embedded_file.data())?; } +/// Touch a file to have a specific timestamp as its modified time +/// +/// # `given file (?P<filename>\S+) has modification time (?P<mtime>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})` +/// +/// Sets the modification time for the given filename to the provided mtime. +/// If the file does not exist, it will be created. #[step] pub fn touch_with_timestamp(context: &Datadir, filename: &str, mtime: &str) { let ts = Utc.datetime_from_str(mtime, "%Y-%m-%d %H:%M:%S")?; @@ -92,11 +101,22 @@ pub fn touch_with_timestamp(context: &Datadir, filename: &str, mtime: &str) { filetime::set_file_mtime(full_path, mtime)?; } +/// Create a file with some given text as its content +/// +/// # `when I write "(?P<text>.*)" to file (?P<filename>\S+)` +/// +/// Create/replace the given file with the given content. #[step] pub fn create_from_text(context: &Datadir, text: &str, filename: &str) { context.open_write(filename)?.write_all(text.as_bytes())?; } +/// Examine the given file and remember its metadata for later +/// +/// # `when I remember metadata for file {filename}` +/// +/// This step stores the metadata (mtime etc) for the given file into the +/// context so that it can be retrieved later for testing against. #[step] #[context(Datadir)] #[context(Files)] @@ -115,6 +135,12 @@ pub fn remember_metadata(context: &ScenarioContext, filename: &str) { ) } +/// Touch a given file +/// +/// # `when I touch file {filename}` +/// +/// This will create the named file if it does not exist, and then it will ensure that the +/// file's modification time is set to the current time. #[step] pub fn touch(context: &Datadir, filename: &str) { let full_path = context.canonicalise_filename(filename)?; @@ -130,6 +156,11 @@ pub fn touch(context: &Datadir, filename: &str) { filetime::set_file_mtime(full_path, now)?; } +/// Check for a file +/// +/// # `then file {filename} exists` +/// +/// This simple step will succeed if the given filename exists in some sense. #[step] pub fn file_exists(context: &Datadir, filename: &str) { let full_path = context.canonicalise_filename(filename)?; @@ -145,6 +176,11 @@ pub fn file_exists(context: &Datadir, filename: &str) { } } +/// Check for absence of a file +/// +/// # `then file {filename} does not exist` +/// +/// This simple step will succeed if the given filename does not exist in any sense. #[step] pub fn file_does_not_exist(context: &Datadir, filename: &str) { let full_path = context.canonicalise_filename(filename)?; @@ -160,6 +196,14 @@ pub fn file_does_not_exist(context: &Datadir, filename: &str) { } } +/// Check if a set of files are the only files in the datadir +/// +/// # `then only files (?P<filenames>.+) exist` +/// +/// This step iterates the data directory and checks that **only** the named files exist. +/// +/// Note: `filenames` is whitespace-separated, though any commas are removed as well. +/// As such you cannot use this to test for filenames which contain commas. #[step] pub fn only_these_exist(context: &Datadir, filenames: &str) { let filenames: HashSet<OsString> = filenames @@ -173,6 +217,12 @@ pub fn only_these_exist(context: &Datadir, filenames: &str) { assert_eq!(filenames, fnames); } +/// Check if a file contains a given sequence of characters +/// +/// # `then file (?P<filename>\S+) contains "(?P<data>.*)"` +/// +/// This will load the content of the named file and ensure it contains the given string. +/// Note: this assumes everything is utf-8 encoded. If not, things will fail. #[step] pub fn file_contains(context: &Datadir, filename: &str, data: &str) { let full_path = context.canonicalise_filename(filename)?; @@ -182,6 +232,13 @@ pub fn file_contains(context: &Datadir, filename: &str, data: &str) { } } +/// Check if a file's content matches the given regular expression +/// +/// # `then file (?P<filename>\S+) matches regex /(?P<regex>.*)/` +/// +/// This will load the content of th enamed file and ensure it contains data which +/// matches the given regular expression. This step will fail if the file is not utf-8 +/// encoded, or if the regex fails to compile #[step] pub fn file_matches_regex(context: &Datadir, filename: &str, regex: &str) { let full_path = context.canonicalise_filename(filename)?; @@ -192,6 +249,11 @@ pub fn file_matches_regex(context: &Datadir, filename: &str, regex: &str) { } } +/// Check if two files match +/// +/// # `then files {filename1} and {filename2} match` +/// +/// This loads the content of the given two files as **bytes** and checks they mach. #[step] pub fn file_match(context: &Datadir, filename1: &str, filename2: &str) { let full_path1 = context.canonicalise_filename(filename1)?; @@ -203,6 +265,17 @@ pub fn file_match(context: &Datadir, filename1: &str, filename2: &str) { } } +/// Check if a given file's metadata matches our memory of it +/// +/// # `then file {filename} has same metadata as before` +/// +/// This confirms that the metadata we remembered for the given filename +/// matches. Specifically this checks: +/// +/// * Are the permissions the same +/// * Are the modification times the same +/// * Is the file's length the same +/// * Is the file's type (file/dir) the same #[step] #[context(Datadir)] #[context(Files)] @@ -228,6 +301,17 @@ pub fn has_remembered_metadata(context: &ScenarioContext, filename: &str) { } } +/// Check that a given file's metadata has changed since we remembered it +/// +/// # `then file {filename} has different metadata from before` +/// +/// This confirms that the metadata we remembered for the given filename +/// does not matche. Specifically this checks: +/// +/// * Are the permissions the same +/// * Are the modification times the same +/// * Is the file's length the same +/// * Is the file's type (file/dir) the same #[step] #[context(Datadir)] #[context(Files)] @@ -253,6 +337,11 @@ pub fn has_different_metadata(context: &ScenarioContext, filename: &str) { } } +/// Check if the given file has been modified "recently" +/// +/// # `then file {filename} has a very recent modification time` +/// +/// Specifically this checks that the given file has been modified in the past 5 seconds. #[step] pub fn mtime_is_recent(context: &Datadir, filename: &str) { let full_path = context.canonicalise_filename(filename)?; @@ -264,6 +353,11 @@ pub fn mtime_is_recent(context: &Datadir, filename: &str) { } } +/// Check if the given file is very old +/// +/// # `then file {filename} has a very old modification time` +/// +/// Specifically this checks that the file was modified at least 39 years ago. #[step] pub fn mtime_is_ancient(context: &Datadir, filename: &str) { let full_path = context.canonicalise_filename(filename)?; @@ -275,17 +369,33 @@ pub fn mtime_is_ancient(context: &Datadir, filename: &str) { } } +/// Make a directory +/// +/// # `given a directory {path}` +/// +/// This is the equivalent of `mkdir -p` within the data directory for the scenario. #[step] pub fn make_directory(context: &Datadir, path: &str) { context.create_dir_all(path)?; } +/// Remove a directory +/// +/// # `when I remove directory {path}` +/// +/// This is the equivalent of `rm -rf` within the data directory for the scenario. #[step] pub fn remove_directory(context: &Datadir, path: &str) { let full_path = context.canonicalise_filename(path)?; remove_dir_all::remove_dir_all(full_path)?; } +/// Check that a directory exists +/// +/// # `then directory {path} exists` +/// +/// This ensures that the given path exists in the data directory for the scenario and +/// that it is a directory itself. #[step] pub fn path_exists(context: &Datadir, path: &str) { let full_path = context.canonicalise_filename(path)?; @@ -297,6 +407,12 @@ pub fn path_exists(context: &Datadir, path: &str) { } } +/// Check that a directory does not exist +/// +/// # `then directory {path} does not exist` +/// +/// This ensures that the given path does not exist in the data directory. If it exists +/// and is not a directory, then this will also fail. #[step] pub fn path_does_not_exist(context: &Datadir, path: &str) { let full_path = context.canonicalise_filename(path)?; @@ -310,6 +426,12 @@ pub fn path_does_not_exist(context: &Datadir, path: &str) { }; } +/// Check that a directory exists and is empty +/// +/// # `then directory {path} is empty` +/// +/// This checks that the given path inside the data directory exists and is an +/// empty directory itself. #[step] pub fn path_is_empty(context: &Datadir, path: &str) { let full_path = context.canonicalise_filename(path)?; @@ -321,6 +443,13 @@ pub fn path_is_empty(context: &Datadir, path: &str) { } } +/// Check that a directory exists and is not empty +/// +/// # `then directory {path} is not empty` +/// +/// This checks that the given path inside the data directory exists and is a +/// directory itself. The step also asserts that the given directory contains at least +/// one entry. #[step] pub fn path_is_not_empty(context: &Datadir, path: &str) { let full_path = context.canonicalise_filename(path)?; |