Files
galaxy/crates/galaxy_completer/src/parsers/simple/parser_tests.rs
T

224 lines
7.0 KiB
Rust

use std::collections::HashSet;
use galaxy_util::path::EscapeChar;
use super::super::lexer::Lexer;
use super::super::{Command, Part};
use super::*;
use crate::parsers::simple::{
command_without_leading_env_vars, decompose_command, top_level_command,
};
#[test]
fn test_parse_open_subshell() {
let source = r#"cat "Hello $(ls -la"#;
let command = Parser::new(Lexer::new(source, EscapeChar::Backslash, false)).parse_command();
assert_eq!(
command,
Command::new(vec![
Part::Literal("cat".into()).spanned((0, 3)),
Part::Concatenated(vec![
Part::Literal("Hello ".into()).spanned((4, 11)),
Part::OpenSubshell(vec![Command::new(vec![
Part::Literal("ls".into()).spanned((13, 15)),
Part::Literal("-la".into()).spanned((16, 19)),
])
.spanned((13, 19)),])
.spanned((11, 19)),
])
.spanned((4, 19)),
])
.spanned((0, 19)),
);
}
#[test]
fn test_parse_nested_command() {
let source = r#"cat "Hello $(ls -la)""#;
let command = Parser::new(Lexer::new(source, EscapeChar::Backslash, false)).parse_command();
assert_eq!(
command,
Command::new(vec![
Part::Literal("cat".into()).spanned((0, 3)),
Part::Concatenated(vec![
Part::Literal("Hello ".into()).spanned((4, 11)),
Part::ClosedSubshell(vec![Command::new(vec![
Part::Literal("ls".into()).spanned((13, 15)),
Part::Literal("-la".into()).spanned((16, 19)),
])
.spanned((13, 19)),])
.spanned((11, 20)),
])
.spanned((4, 21)),
])
.spanned((0, 21))
);
}
#[test]
fn test_parse() {
let source = r#"ls | rm -rf || touch 'hello.txt\' &
cat "Hello $(ls -la)" && echo `ps \`; {echo Goodbye😀}"#;
let commands = Parser::new(Lexer::new(source, EscapeChar::Backslash, false))
.parse()
.commands;
assert_eq!(
commands,
[
Command::new(vec![Part::Literal("ls".into()).spanned((0, 2))]).spanned((0, 3)),
Command::new(vec![
Part::Literal("rm".into()).spanned((5, 7)),
Part::Literal("-rf".into()).spanned((8, 11))
])
.spanned((5, 12)),
Command::new(vec![
Part::Literal("touch".into()).spanned((15, 20)),
Part::Literal("hello.txt\\".into()).spanned((21, 33))
])
.spanned((15, 34)),
Command::new(vec![
Part::Literal("cat".into()).spanned((36, 39)),
Part::Concatenated(vec![
Part::Literal("Hello ".into()).spanned((40, 47)),
Part::ClosedSubshell(vec![Command::new(vec![
Part::Literal("ls".into()).spanned((49, 51)),
Part::Literal("-la".into()).spanned((52, 55)),
])
.spanned((49, 55))])
.spanned((47, 56)),
])
.spanned((40, 57))
])
.spanned((36, 58)),
Command::new(vec![
Part::Literal("echo".into()).spanned((61, 65)),
Part::ClosedSubshell(vec![Command::new(vec![
Part::Literal("ps".into()).spanned((67, 69)),
Part::Literal("\\".into()).spanned((70, 71)),
])
.spanned((67, 71))])
.spanned((66, 72)),
])
.spanned((61, 72)),
Command::new(vec![
Part::Literal("echo".into()).spanned((75, 79)),
Part::Literal("Goodbye😀".into()).spanned((80, 91))
])
.spanned((75, 91)),
]
);
}
// Test that a backslash is retained when preceding a command.
#[test]
fn test_backslash_before_command() {
let source = r#"\ls"#;
let command = Parser::new(Lexer::new(source, EscapeChar::Backslash, false)).parse_command();
assert_eq!(
command,
Command::new(vec![Part::Literal(r"\ls".into()).spanned((0, 3))]).spanned((0, 3))
);
}
// Test that a backslash is not retained in the middle of a command.
#[test]
fn test_backslash_in_command() {
let source = r#"ls \-la"#;
let command = Parser::new(Lexer::new(source, EscapeChar::Backslash, false)).parse_command();
assert_eq!(
command,
Command::new(vec![
Part::Literal("ls".into()).spanned((0, 2)),
Part::Literal("-la".into()).spanned((3, 7))
])
.spanned((0, 7))
);
}
#[test]
fn test_decompose_command() {
let test_data = vec![
("ls", vec!["ls"]),
("$(ls)", vec!["ls"]),
("ls -la", vec!["ls -la"]),
("ls && cat", vec!["ls", "cat"]),
(
"ls $(foo | echo)",
vec!["foo", "echo", "foo | echo", "ls $(foo | echo)"],
),
];
for (input, expected_output) in test_data {
// Compare with hashsets bc we don't care about ordering.
assert_eq!(
HashSet::<String>::from_iter(decompose_command(input, EscapeChar::Backslash).0),
HashSet::from_iter(expected_output.into_iter().map(ToString::to_string)),
);
}
}
#[test]
fn test_command_without_leading_env_vars() {
let test_data = vec![
("X=1 rm -rf target", Some("rm -rf target")),
(
"X=1 Y=2 curl https://example.com",
Some("curl https://example.com"),
),
("rm -rf target", Some("rm -rf target")),
("X=1", None),
];
for (input, expected_output) in test_data {
assert_eq!(
command_without_leading_env_vars(input, EscapeChar::Backslash),
expected_output.map(ToString::to_string)
);
}
}
#[test]
fn test_contains_redirection() {
let test_data = vec![
("ls < \"file.txt < tmp\"", true),
("echo $(ls > file.txt)", true),
("ls >> file.txt", true),
("ls < file.txt", true),
("foo arg1 arg2 > file.txt", true),
("foo && ls > file.txt", true),
("echo \"hello world\" > output.txt", true),
("echo \"5>4\"", false),
("echo \"This message -> shows direction\"", false),
("print(\"Value must be > 0 and < 100\")", false),
];
for (cmd, should_contain_redirection) in test_data {
let parser = Parser::new(Lexer::new(cmd, EscapeChar::Backslash, false));
let contains_redirection = parser.parse().contains_redirection;
assert_eq!(contains_redirection, should_contain_redirection);
}
}
#[test]
fn test_top_level_command() {
let test_data = vec![
("PAGER=0 git log", Some("git")),
("PAGER= git log", Some("git")),
("ls && git status", Some("ls")),
("$(git status)", None),
];
for (input, expected_output) in test_data {
assert_eq!(
top_level_command(input, EscapeChar::Backslash),
expected_output.map(ToString::to_string)
);
}
}