summaryrefslogtreecommitdiff
path: root/subplotlib/src/steplibrary/files.rs
diff options
context:
space:
mode:
Diffstat (limited to 'subplotlib/src/steplibrary/files.rs')
-rw-r--r--subplotlib/src/steplibrary/files.rs129
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)?;