Skip to main content

clitest_lib/
lib.rs

1//! This crate provides the core functionality for the `clitest` crate as a library.
2
3pub mod command;
4pub mod output;
5pub mod parser;
6pub mod script;
7pub mod term;
8pub mod util;
9
10use std::path::Path;
11
12use script::{ScriptFile, ScriptOutput, ScriptRunArgs, ScriptRunError};
13
14/// Error returned by [`try_run_captured`] and [`try_run_file_captured`].
15pub struct RunError {
16    pub error: String,
17    pub output: String,
18}
19
20impl std::fmt::Display for RunError {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        write!(f, "{}", self.error)
23    }
24}
25
26impl std::fmt::Debug for RunError {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        write!(f, "{}", self.error)
29    }
30}
31
32fn make_args() -> ScriptRunArgs {
33    ScriptRunArgs {
34        quiet: true,
35        no_color: true,
36        simplified_output: true,
37        ..Default::default()
38    }
39}
40
41fn execute(parsed: &script::Script, output: ScriptOutput) -> Result<(), ScriptRunError> {
42    parsed.run_with_args(make_args(), output)
43}
44
45/// Parse and run a clitest script string. Output goes to stdout. Panics on failure.
46pub fn run(script: &str) {
47    let file = ScriptFile::new("<inline>");
48    let parsed =
49        parser::parse_script(file, script).unwrap_or_else(|e| panic!("clitest parse error: {e}"));
50    let output = ScriptOutput::no_color();
51    execute(&parsed, output).unwrap_or_else(|e| panic!("clitest failed: {e}"));
52}
53
54/// Parse and run a clitest script string. Returns captured output. Panics on failure.
55pub fn run_captured(script: &str) -> String {
56    match try_run_captured(script) {
57        Ok(output) => output,
58        Err(e) => panic!("clitest failed: {}\n\nOutput:\n{}", e.error, e.output),
59    }
60}
61
62/// Parse and run a clitest script string. Returns `Ok(output)` on success,
63/// or `Err(RunError)` with both the error message and captured output on failure.
64pub fn try_run_captured(script: &str) -> Result<String, RunError> {
65    let file = ScriptFile::new("<inline>");
66    let parsed = match parser::parse_script(file, script) {
67        Ok(s) => s,
68        Err(e) => {
69            return Err(RunError {
70                error: e.to_string(),
71                output: String::new(),
72            });
73        }
74    };
75    let output = ScriptOutput::quiet(true);
76    match execute(&parsed, output.clone()) {
77        Ok(()) => Ok(output.take_buffer()),
78        Err(e) => Err(RunError {
79            error: e.to_string(),
80            output: output.take_buffer(),
81        }),
82    }
83}
84
85/// Parse and run a clitest script file. Output goes to stdout. Panics on failure.
86pub fn run_file(path: impl AsRef<Path>) {
87    let file = ScriptFile::new(path);
88    let parsed = parser::parse_script_file(None, file)
89        .unwrap_or_else(|e| panic!("clitest parse error: {:?}", e));
90    let output = ScriptOutput::no_color();
91    execute(&parsed, output).unwrap_or_else(|e| panic!("clitest failed: {e}"));
92}
93
94/// Parse and run a clitest script file. Returns captured output. Panics on failure.
95pub fn run_file_captured(path: impl AsRef<Path>) -> String {
96    match try_run_file_captured(path) {
97        Ok(output) => output,
98        Err(e) => panic!("clitest failed: {}\n\nOutput:\n{}", e.error, e.output),
99    }
100}
101
102/// Parse and run a clitest script file. Returns `Ok(output)` on success,
103/// or `Err(RunError)` with the error message and captured output on failure.
104pub fn try_run_file_captured(path: impl AsRef<Path>) -> Result<String, RunError> {
105    let file = ScriptFile::new(path);
106    let parsed = match parser::parse_script_file(None, file) {
107        Ok(s) => s,
108        Err(e) => {
109            let msg = e
110                .iter()
111                .map(|e| e.to_string())
112                .collect::<Vec<_>>()
113                .join("\n");
114            return Err(RunError {
115                error: msg,
116                output: String::new(),
117            });
118        }
119    };
120    let output = ScriptOutput::quiet(true);
121    match execute(&parsed, output.clone()) {
122        Ok(()) => Ok(output.take_buffer()),
123        Err(e) => Err(RunError {
124            error: e.to_string(),
125            output: output.take_buffer(),
126        }),
127    }
128}
129
130/// Generate `#[test]` functions from inline clitest scripts.
131///
132/// The `#!/usr/bin/env clitest --v0` header is automatically prepended.
133///
134/// ```rust
135/// use clitest_lib::clitest;
136///
137/// clitest!(my_test, r#"
138/// $ echo hello
139/// ! hello
140/// "#);
141/// ```
142#[macro_export]
143macro_rules! clitest {
144    ($name:ident, $script:expr) => {
145        #[test]
146        fn $name() {
147            $crate::run(&format!("#!/usr/bin/env clitest --v0\n{}", $script));
148        }
149    };
150}