#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use std::path::PathBuf;
use proptest::prelude::*;
use rippy_cli::analyzer::Analyzer;
use rippy_cli::config::{Config, ConfigFormat};
use rippy_cli::parser::BashParser;
use rippy_cli::pattern::Pattern;
use rippy_cli::payload::Payload;
fn fresh_analyzer() -> Analyzer {
Analyzer::new(
Config::empty(),
false,
PathBuf::from("/tmp"),
false,
)
.expect("Analyzer::new with empty config and /tmp cwd is infallible")
}
fn cmdsub_heredoc_strategy() -> impl Strategy<Value = String> {
let delim = prop_oneof![Just("EOF"), Just("END"), Just("FOO")];
(delim, any::<bool>(), "[\\s\\S]{0,512}").prop_map(|(d, quoted, body)| {
let opener = if quoted {
format!("<<'{d}'")
} else {
format!("<<{d}")
};
format!("$(cat {opener}\n{body}\n{d}\n)")
})
}
fn nested_substitution_strategy() -> impl Strategy<Value = String> {
let leaf = prop_oneof![
Just("echo hi".to_string()),
Just("ls".to_string()),
Just("pwd".to_string()),
Just("cat file".to_string()),
];
leaf.prop_recursive(
4,
32,
1,
|inner| {
prop_oneof![
inner.clone().prop_map(|s| format!("$({s})")),
inner.clone().prop_map(|s| format!("`{s}`")),
inner.prop_map(|s| format!("<({s})")),
]
},
)
}
fn escaped_backtick_strategy() -> impl Strategy<Value = String> {
"[`\\\\\"'a ]{0,64}".prop_map(String::from)
}
proptest! {
#![proptest_config(ProptestConfig {
cases: 256,
max_shrink_iters: 256,
.. ProptestConfig::default()
})]
#[test]
fn payload_parse_no_panic_on_strings(s in "[\\s\\S]{0,4096}") {
let _ = Payload::parse(&s, None);
}
#[test]
fn payload_parse_no_panic_on_random_bytes(
bytes in proptest::collection::vec(any::<u8>(), 0..4096)
) {
if let Ok(s) = std::str::from_utf8(&bytes) {
let _ = Payload::parse(s, None);
}
}
#[test]
fn parser_and_analyzer_no_panic(source in "[\\s\\S]{0,4096}") {
let mut parser = BashParser::new().expect("BashParser::new is infallible");
let _ = parser.parse(&source);
let mut analyzer = fresh_analyzer();
let _ = analyzer.analyze(&source);
}
#[test]
fn pattern_matches_no_panic_and_literal_self_match(
pat in "[\\s\\S]{0,256}",
input in "[\\s\\S]{0,1024}",
) {
let pattern = Pattern::new(&pat);
let _ = pattern.matches(&input);
if !pat.is_empty() && !pat.chars().any(|c| matches!(c, '*' | '?' | '[' | '|')) {
let exact = Pattern::new(&format!("{pat}|"));
prop_assert!(
exact.matches(&pat),
"literal exact-match pattern {pat:?} failed to self-match",
);
}
}
#[test]
fn config_load_from_str_no_panic(s in "[\\s\\S]{0,4096}") {
let _ = Config::load_from_str(&s, ConfigFormat::Toml);
let _ = Config::load_from_str(&s, ConfigFormat::Lines);
}
#[test]
fn cmdsub_with_heredoc_no_panic(source in cmdsub_heredoc_strategy()) {
let mut parser = BashParser::new().expect("BashParser::new is infallible");
let _ = parser.parse(&source);
let mut analyzer = fresh_analyzer();
let _ = analyzer.analyze(&source);
}
#[test]
fn nested_substitutions_no_panic(source in nested_substitution_strategy()) {
let mut parser = BashParser::new().expect("BashParser::new is infallible");
let _ = parser.parse(&source);
let mut analyzer = fresh_analyzer();
let _ = analyzer.analyze(&source);
}
#[test]
fn escaped_backtick_no_panic(source in escaped_backtick_strategy()) {
let mut parser = BashParser::new().expect("BashParser::new is infallible");
let _ = parser.parse(&source);
let mut analyzer = fresh_analyzer();
let _ = analyzer.analyze(&source);
}
}