|
| 1 | +use std::path::Path; |
| 2 | + |
| 3 | +/// Characters that act as command separators in shell commands. |
| 4 | +const SHELL_SEPARATORS: &[char] = &['|', ';', '&', '(', ')']; |
| 5 | + |
| 6 | +/// Split a command string into tokens on whitespace and shell operators, |
| 7 | +/// extracting the file name from each token (stripping directory paths). |
| 8 | +fn tokenize(command: &str) -> impl Iterator<Item = &str> + '_ { |
| 9 | + command |
| 10 | + .split(|c: char| c.is_whitespace() || SHELL_SEPARATORS.contains(&c)) |
| 11 | + .filter(|t| !t.is_empty()) |
| 12 | + .filter_map(|token| Path::new(token).file_name()?.to_str()) |
| 13 | +} |
| 14 | + |
| 15 | +/// Check if a command string contains any of the given executable names. |
| 16 | +/// |
| 17 | +/// Splits the command into tokens on whitespace and shell operators, then checks |
| 18 | +/// for exact matches on the file name component. This is strictly better than |
| 19 | +/// `command.contains("java")` which would false-positive on "javascript". |
| 20 | +pub fn command_has_executable(command: &str, names: &[&str]) -> bool { |
| 21 | + tokenize(command).any(|token| names.contains(&token)) |
| 22 | +} |
| 23 | + |
| 24 | +#[cfg(test)] |
| 25 | +mod tests { |
| 26 | + use super::*; |
| 27 | + use rstest::rstest; |
| 28 | + |
| 29 | + #[rstest] |
| 30 | + #[case("java -jar bench.jar", &["java"])] |
| 31 | + #[case("/usr/bin/java -jar bench.jar", &["java"])] |
| 32 | + #[case("FOO=bar java -jar bench.jar", &["java"])] |
| 33 | + #[case("cd /app && gradle bench", &["gradle"])] |
| 34 | + #[case("cat file | python script.py", &["python"])] |
| 35 | + #[case("sudo java -jar bench.jar", &["java"])] |
| 36 | + #[case("(cd /app && java -jar bench.jar)", &["java"])] |
| 37 | + #[case("setup.sh; java -jar bench.jar", &["java"])] |
| 38 | + #[case("try_first || java -jar bench.jar", &["java"])] |
| 39 | + #[case("cargo codspeed bench\npytest tests/ --codspeed", &["cargo"])] |
| 40 | + #[case("mvn test", &["gradle", "java", "maven", "mvn"])] |
| 41 | + #[case("./java -jar bench.jar", &["java"])] |
| 42 | + fn matches(#[case] command: &str, #[case] names: &[&str]) { |
| 43 | + assert!(command_has_executable(command, names)); |
| 44 | + } |
| 45 | + |
| 46 | + #[rstest] |
| 47 | + #[case("javascript-runtime run", &["java"])] |
| 48 | + #[case("/home/user/javascript/run.sh", &["java"])] |
| 49 | + #[case("scargoship build", &["cargo"])] |
| 50 | + #[case("node index.js", &["gradle", "java", "maven", "mvn"])] |
| 51 | + fn does_not_match(#[case] command: &str, #[case] names: &[&str]) { |
| 52 | + assert!(!command_has_executable(command, names)); |
| 53 | + } |
| 54 | +} |
0 commit comments