// This is an example step library. // Our step function uses the subplotlib Runcmd context // in order that it can access the stdout of the recently // executed command. use subplotlib::steplibrary::runcmd::Runcmd; // All step functions are annotated with the #[step] attribute // which is implemented in the subplotlib-derive crate. // This marks the function as a step function and automatically // creates the step builders and other code needed to be able to // use this step in a scenario. #[step] // The first argument to a step function is always the context with // which the step is run. If the step is simple (needs only one // context) then this will be a borrow (or mut borrow) of the context // type. If the step uses multiple kinds of context then instead there // will be some number of #[context(TypeName)] attributes, and the step // function will take a ScenarioContext as its context argument. // The rest of the arguments to the step function are the captures from the // binding for the step. fn count_lines_in_stdout(context: &Runcmd, count: usize) { let stdout_count = context.stdout_as_string().lines().count(); if stdout_count != count { // Step functions can use the throw!() macro to raise a // step error. This will be reported as the reason the // scenario fails. throw!(format!( "Incorrect number of lines, got {stdout_count} expected {count}", )); } } // While this step function could just use Runcmd again, we use ScenarioContext // in order to demonstrate how to access other contexts from a generic step // function in Rust #[step] #[context(Runcmd)] fn stderr_contains_two_things(context: &ScenarioContext, what: &str, other: &str) { // To keep lifetimes sane, you access other contexts by using closures which // take the context as an argument. let stderr_has_both = context.with( |runcmd: &Runcmd| { // In here, `runcmd` is the context we need let stderr = runcmd.stderr_as_string(); // Since we could fail in here, we're actually returning a Result object Ok(stderr.contains(what) && stderr.contains(other)) }, // This `false` means that we will not run the closure if the scenario is // currently failing. This is more useful in cleanup functions since it // allows you to decide if you want to ignore the failure mode in order to // clean up more effectively. false, )?; if !stderr_has_both { throw!(format!( "Stderr does not contain both of {what:?} and {other:?}", )) } }