diff options
author | Daniel Silverstone <dsilvers@digital-scurf.org> | 2021-12-27 14:29:28 +0000 |
---|---|---|
committer | Daniel Silverstone <dsilvers@digital-scurf.org> | 2022-01-01 10:40:57 +0000 |
commit | 0fd065044076888285f42e0105f07a0c924b2819 (patch) | |
tree | 307c0a3358f7c57faff9445d2cfc5cacd0b1961b | |
parent | d1f336e141c3533c5e4dda049f03dfe168f99470 (diff) | |
download | subplot-0fd065044076888285f42e0105f07a0c924b2819.tar.gz |
subplotlib: Run subplot.md in Rust too
Signed-off-by: Daniel Silverstone <dsilvers@digital-scurf.org>
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | subplot.md | 2 | ||||
-rw-r--r-- | subplot.yaml | 31 | ||||
-rw-r--r-- | subplotlib/Cargo.toml | 7 | ||||
-rw-r--r-- | subplotlib/build.rs | 3 | ||||
-rw-r--r-- | subplotlib/subplot-rust-support.rs | 219 | ||||
-rw-r--r-- | subplotlib/tests/subplot.rs | 1 |
7 files changed, 261 insertions, 3 deletions
@@ -1159,6 +1159,7 @@ dependencies = [ "lazy_static", "regex", "remove_dir_all 0.7.0", + "serde_json", "shell-words", "state", "subplot-build", @@ -10,6 +10,8 @@ impls: - subplot.py - lib/files.py - lib/runcmd.py + rust: + - subplotlib/subplot-rust-support.rs classes: - json ... diff --git a/subplot.yaml b/subplot.yaml index b865d83..12a9fae 100644 --- a/subplot.yaml +++ b/subplot.yaml @@ -3,60 +3,83 @@ python: function: install_subplot cleanup: uninstall_subplot + rust: + function: install_subplot + cleanup: uninstall_subplot - then: scenario "{name:text}" was run impl: python: function: scenario_was_run + rust: + function: scenario_was_run - then: scenario "{name:text}" was not run impl: python: function: scenario_was_not_run + rust: + function: scenario_was_not_run - then: step "(?P<keyword>given|when|then) (?P<name>.+)" was run impl: python: function: step_was_run + rust: + function: step_was_run regex: true - then: step "(?P<keyword1>given|when|then) (?P<name1>.+)" was run, and then step "(?P<keyword2>given|when|then) (?P<name2>.+)" impl: python: function: step_was_run_and_then + rust: + function: step_was_run_and_then regex: true - then: cleanup for "(?P<keyword1>given|when|then) (?P<name1>.+)" was run, and then for "(?P<keyword2>given|when|then) (?P<name2>.+)" impl: python: function: cleanup_was_run + rust: + function: cleanup_was_run regex: true - then: cleanup for "(?P<keyword>given|when|then) (?P<name>.+)" was not run impl: python: function: cleanup_was_not_run + rust: + function: cleanup_was_not_run regex: true - then: JSON output matches {filename} impl: python: function: json_output_matches_file + rust: + function: json_output_matches_file - then: "{filename} does not end in a newline" impl: python: function: file_ends_in_zero_newlines + rust: + function: file_ends_in_zero_newlines - then: "{filename} ends in one newline" impl: python: function: file_ends_in_one_newline + rust: + function: file_ends_in_one_newline - then: "{filename} ends in two newlines" impl: python: function: file_ends_in_two_newlines + rust: + function: file_ends_in_two_newlines # In order to cope with low granularity filesystems, sometimes we need to wait # for things to happen @@ -64,6 +87,8 @@ impl: python: function: sleep_seconds + rust: + function: sleep_seconds regex: true types: delay: uint @@ -74,13 +99,19 @@ impl: python: function: do_nothing + rust: + function: do_nothing - when: I do the required actions impl: python: function: do_nothing + rust: + function: do_nothing - then: the desired outcome is achieved impl: python: function: do_nothing + rust: + function: do_nothing diff --git a/subplotlib/Cargo.toml b/subplotlib/Cargo.toml index 781a017..ed3fb8a 100644 --- a/subplotlib/Cargo.toml +++ b/subplotlib/Cargo.toml @@ -16,7 +16,7 @@ repository = "https://gitlab.com/subplot/subplot" [dependencies] fehler = "1" -subplotlib-derive = { version="0.1", path = "../subplotlib-derive" } +subplotlib-derive = { version = "0.1", path = "../subplotlib-derive" } lazy_static = "1" base64 = "0.13" state = "0.5" @@ -32,4 +32,7 @@ remove_dir_all = "0.7" [build-dependencies] glob = "0.3" -subplot-build = { version="0.1", path = "../subplot-build" } +subplot-build = { version = "0.1", path = "../subplot-build" } + +[dev-dependencies] +serde_json = "1.0" diff --git a/subplotlib/build.rs b/subplotlib/build.rs index 5f94883..a809a68 100644 --- a/subplotlib/build.rs +++ b/subplotlib/build.rs @@ -17,9 +17,10 @@ use std::path::Path; fn main() { let subplots = glob("*.md").expect("failed to find subplots in subplotlib"); let tests = Path::new("tests"); + let subplots = subplots.chain(Some(Ok("../subplot.md".into()))); for entry in subplots { let entry = entry.expect("failed to get subplot dir entry in subplotlib"); - let mut inc = tests.join(&entry); + let mut inc = tests.join(&entry.file_name().unwrap()); inc.set_extension("rs"); if !inc.exists() { panic!("missing include file: {}", inc.display()); diff --git a/subplotlib/subplot-rust-support.rs b/subplotlib/subplot-rust-support.rs new file mode 100644 index 0000000..19a4ff7 --- /dev/null +++ b/subplotlib/subplot-rust-support.rs @@ -0,0 +1,219 @@ +// Rust support for running subplot-rust.md + +use subplotlib::steplibrary::datadir::Datadir; +use subplotlib::steplibrary::runcmd::{self, Runcmd}; + +use tempfile::TempDir; + +use std::io::{Read, Seek, SeekFrom}; + +#[derive(Default)] +struct SubplotContext { + bin_dir: Option<TempDir>, +} + +impl ContextElement for SubplotContext {} + +#[step] +fn do_nothing(context: &ScenarioContext) { + // Nothing to do here +} + +#[step] +#[context(SubplotContext)] +#[context(Runcmd)] +fn install_subplot(context: &ScenarioContext) { + if let Some(bindir) = std::env::var_os("SUBPLOT_DIR") { + println!("Found SUBPLOT_DIR environment variable, using that"); + context.with_mut( + |rc: &mut Runcmd| { + rc.prepend_to_path(bindir); + Ok(()) + }, + false, + )?; + } else { + let bin_dir = TempDir::new()?; + println!("Creating temporary rundir at {}", bin_dir.path().display()); + + // Since we don't get CARGO_BIN_EXE_subplot when building a subcrate + // we retrieve the path to `subplot` via the assumption that integration + // tests are always located one dir down from the outer crate binaries. + let target_path = std::fs::canonicalize( + std::env::current_exe() + .expect("Cannot determine test exe path") + .parent() + .unwrap() + .join(".."), + ) + .expect("Cannot canonicalise path to binaries"); + + let src_dir = env!("CARGO_MANIFEST_DIR"); + for bin_name in &["subplot"] { + let file_path = bin_dir.path().join(bin_name); + std::fs::write( + &file_path, + format!( + r#" +#!/bin/sh +set -eu +exec '{target_path}/{bin_name}' --resources '{src_dir}/share' "$@" +"#, + target_path = target_path.display(), + bin_name = bin_name, + src_dir = src_dir, + ), + )?; + { + let mut perms = std::fs::metadata(&file_path)?.permissions(); + use std::os::unix::fs::PermissionsExt; + perms.set_mode(perms.mode() | 0o111); // Set executable bit + std::fs::set_permissions(&file_path, perms)?; + } + } + + context.with_mut( + |context: &mut Runcmd| { + context.prepend_to_path(bin_dir.path()); + context.prepend_to_path(target_path); + Ok(()) + }, + false, + )?; + } +} + +#[step] +fn uninstall_subplot(context: &mut SubplotContext) { + context.bin_dir.take(); +} + +#[step] +#[context(Runcmd)] +fn scenario_was_run(context: &ScenarioContext, name: &str) { + let text = format!("\nscenario: {}\n", name); + runcmd::stdout_contains::call(context, &text)?; +} + +#[step] +#[context(Runcmd)] +fn scenario_was_not_run(context: &ScenarioContext, name: &str) { + let text = format!("\nscenario: {}\n", name); + runcmd::stdout_doesnt_contain::call(context, &text)?; +} + +#[step] +#[context(Runcmd)] +fn step_was_run(context: &ScenarioContext, keyword: &str, name: &str) { + let text = format!("\n step: {} {}\n", keyword, name); + runcmd::stdout_contains::call(context, &text)?; +} + +#[step] +#[context(Runcmd)] +fn step_was_run_and_then( + context: &ScenarioContext, + keyword1: &str, + name1: &str, + keyword2: &str, + name2: &str, +) { + let text = format!( + "\n step: {} {}\n step: {} {}", + keyword1, name1, keyword2, name2 + ); + runcmd::stdout_contains::call(context, &text)?; +} + +#[step] +#[context(Runcmd)] +fn cleanup_was_run( + context: &ScenarioContext, + keyword1: &str, + name1: &str, + keyword2: &str, + name2: &str, +) { + let text = format!( + "\n cleanup: {} {}\n cleanup: {} {}\n", + keyword1, name1, keyword2, name2 + ); + runcmd::stdout_contains::call(context, &text)?; +} + +#[step] +#[context(Runcmd)] +fn cleanup_was_not_run(context: &ScenarioContext, keyword: &str, name: &str) { + let text = format!("\n cleanup: {} {}\n", keyword, name); + runcmd::stdout_doesnt_contain::call(context, &text)?; +} + +#[throws(StepError)] +fn end_of_file(context: &Datadir, filename: &str, nbytes: usize) -> Vec<u8> { + let mut fh = context.open_read(filename)?; + fh.seek(SeekFrom::End(-(nbytes as i64)))?; + let mut b = vec![0; nbytes]; + fh.read_exact(&mut b[0..nbytes])?; + b +} + +#[step] +fn file_ends_in_zero_newlines(context: &Datadir, filename: &str) { + let b = end_of_file(context, filename, 1)?; + if b[0] == b'\n' { + throw!(format!("File {} ends in unexpected newline", filename)); + } +} + +#[step] +fn file_ends_in_one_newline(context: &Datadir, filename: &str) { + let b = end_of_file(context, filename, 2)?; + if !(b[0] != b'\n' && b[1] == b'\n') { + throw!(format!( + "File {} does not end in exactly one newline", + filename + )); + } +} + +#[step] +fn file_ends_in_two_newlines(context: &Datadir, filename: &str) { + let b = end_of_file(context, filename, 2)?; + if b[0] != b'\n' || b[1] != b'\n' { + throw!(format!( + "File {} does not end in exactly two newlines", + filename + )); + } +} + +#[step] +fn sleep_seconds(context: &Datadir, delay: u64) { + std::thread::sleep(std::time::Duration::from_secs(delay)); +} + +#[step] +#[context(Datadir)] +#[context(Runcmd)] +fn json_output_matches_file(context: &ScenarioContext, filename: &str) { + let output = context.with(|rc: &Runcmd| Ok(rc.stdout_as_string()), false)?; + let fcontent = context.with( + |dd: &Datadir| { + Ok(std::fs::read_to_string( + dd.canonicalise_filename(filename)?, + )?) + }, + false, + )?; + let output: serde_json::Value = serde_json::from_str(&output)?; + let fcontent: serde_json::Value = serde_json::from_str(&fcontent)?; + println!("########"); + println!("Output:\n{:#}", output); + println!("File:\n{:#}", fcontent); + println!("########"); + assert_eq!( + output, fcontent, + "Command output does not match the content of {}", + filename + ); +} diff --git a/subplotlib/tests/subplot.rs b/subplotlib/tests/subplot.rs new file mode 100644 index 0000000..00accd7 --- /dev/null +++ b/subplotlib/tests/subplot.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/subplot.rs")); |