summaryrefslogtreecommitdiffstats
path: root/src/parse/test_ast.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/parse/test_ast.rs')
-rw-r--r--src/parse/test_ast.rs507
1 files changed, 507 insertions, 0 deletions
diff --git a/src/parse/test_ast.rs b/src/parse/test_ast.rs
new file mode 100644
index 0000000..a1f83dd
--- /dev/null
+++ b/src/parse/test_ast.rs
@@ -0,0 +1,507 @@
+use super::*;
+
+impl From<Pipeline> for Command {
+ fn from(pipeline: Pipeline) -> Self {
+ Self::Pipeline(pipeline)
+ }
+}
+
+macro_rules! cs {
+ ($($commands:expr),*) => {
+ Commands {
+ commands: [$($commands),*]
+ .into_iter()
+ .map(|c| c.into())
+ .collect(),
+ }
+ };
+}
+
+macro_rules! p {
+ ($span:expr, $($exes:expr),*) => {
+ Pipeline {
+ exes: vec![$($exes),*],
+ span: $span,
+ }
+ };
+}
+
+macro_rules! ep {
+ ($($exes:expr),*) => {
+ super::super::Pipeline {
+ exes: vec![$($exes),*],
+ }
+ };
+}
+
+macro_rules! e {
+ ($word:expr) => {
+ Exe {
+ exe: $word,
+ args: vec![],
+ redirects: vec![],
+ }
+ };
+ ($word:expr, $($args:expr),*) => {
+ Exe {
+ exe: $word,
+ args: vec![$($args),*],
+ redirects: vec![],
+ }
+ };
+ ($word:expr ; $($redirects:expr),*) => {
+ Exe {
+ exe: $word,
+ args: vec![],
+ redirects: vec![$($redirects),*],
+ }
+ };
+ ($word:expr, $($args:expr),* ; $($redirects:expr),*) => {
+ Exe {
+ exe: $word,
+ args: vec![$($args),*],
+ redirects: vec![$($redirects),*],
+ }
+ };
+}
+
+macro_rules! ee {
+ ($exe:expr) => {
+ super::super::Exe {
+ exe: std::path::PathBuf::from($exe.to_string()),
+ args: vec![],
+ redirects: vec![],
+ }
+ };
+ ($exe:expr, $($args:expr),*) => {
+ super::super::Exe {
+ exe: std::path::PathBuf::from($exe.to_string()),
+ args: [$($args),*]
+ .into_iter()
+ .map(|s| s.to_string())
+ .collect(),
+ redirects: vec![],
+ }
+ };
+}
+
+macro_rules! r {
+ ($from:literal, $to:expr, $dir:ident) => {
+ Redirect {
+ from: $from,
+ to: $to,
+ dir: super::super::Direction::$dir,
+ }
+ };
+}
+
+macro_rules! w {
+ ($word:literal) => {
+ Word {
+ parts: vec![WordPart::Bareword($word.to_string())],
+ }
+ };
+ ($($word:expr),*) => {
+ Word {
+ parts: vec![$($word),*],
+ }
+ }
+}
+
+macro_rules! wpa {
+ ($($word:expr),*) => {
+ WordPart::Alternation(vec![$($word),*])
+ }
+}
+
+macro_rules! wpv {
+ ($var:literal) => {
+ WordPart::Var($var.to_string())
+ };
+}
+
+macro_rules! wpb {
+ ($bareword:expr) => {
+ WordPart::Bareword($bareword.to_string())
+ };
+}
+
+macro_rules! wpd {
+ ($doublequoted:expr) => {
+ WordPart::DoubleQuoted($doublequoted.to_string())
+ };
+}
+
+macro_rules! wps {
+ ($singlequoted:expr) => {
+ WordPart::SingleQuoted($singlequoted.to_string())
+ };
+}
+
+macro_rules! parse_eq {
+ ($line:literal, $parsed:expr) => {
+ assert_eq!(&Commands::parse($line).unwrap(), &$parsed)
+ };
+}
+
+macro_rules! eval_eq {
+ ($line:literal, $env:expr, $($evaled:expr),*) => {{
+ let ast = Commands::parse($line).unwrap();
+ let mut expected: Vec<super::super::Pipeline>
+ = vec![$($evaled),*];
+ for command in ast.commands {
+ let pipeline = match command {
+ Command::Pipeline(p)
+ | Command::If(p)
+ | Command::While(p) => p,
+ _ => continue,
+ };
+ assert_eq!(
+ pipeline.eval(&$env).await.unwrap(),
+ expected.remove(0)
+ );
+ }
+ }};
+}
+
+macro_rules! deserialize_eq {
+ ($line:literal, $parsed:expr) => {{
+ use serde::de::IntoDeserializer as _;
+ use serde::Deserialize as _;
+ let exe: Result<_, serde::de::value::Error> =
+ Exe::deserialize($line.into_deserializer());
+ assert_eq!(exe.unwrap(), $parsed);
+ }};
+}
+
+macro_rules! eval_fails {
+ ($line:literal, $env:expr) => {{
+ let ast = Commands::parse($line).unwrap();
+ let mut fail = false;
+ for command in ast.commands {
+ let pipeline = match command {
+ Command::Pipeline(p) | Command::If(p) | Command::While(p) => {
+ p
+ }
+ _ => continue,
+ };
+ if pipeline.eval(&$env).await.is_err() {
+ fail = true;
+ }
+ }
+ assert!(fail)
+ }};
+}
+
+#[test]
+fn test_basic() {
+ parse_eq!("foo", cs!(p!((0, 3), e!(w!("foo")))));
+ parse_eq!("foo bar", cs!(p!((0, 7), e!(w!("foo"), w!("bar")))));
+ parse_eq!(
+ "foo bar baz",
+ cs!(p!((0, 11), e!(w!("foo"), w!("bar"), w!("baz"))))
+ );
+ parse_eq!("foo | bar", cs!(p!((0, 9), e!(w!("foo")), e!(w!("bar")))));
+ parse_eq!(
+ "command ls; perl -E 'say foo' | tr a-z A-Z; builtin echo bar",
+ cs!(
+ p!((0, 10), e!(w!("command"), w!("ls"))),
+ p!(
+ (12, 42),
+ e!(w!("perl"), w!("-E"), w!(wps!("say foo"))),
+ e!(w!("tr"), w!("a-z"), w!("A-Z"))
+ ),
+ p!((44, 60), e!(w!("builtin"), w!("echo"), w!("bar")))
+ )
+ );
+
+ // XXX this parse may change in the future
+ let exe = crate::info::current_exe()
+ .unwrap()
+ .into_os_string()
+ .into_string()
+ .unwrap();
+ parse_eq!(
+ "seq 1 5 | (while read line; echo \"line: $line\"; end)",
+ cs!(p!(
+ (0, 52),
+ e!(w!("seq"), w!("1"), w!("5")),
+ e!(
+ w!(wps!(exe)),
+ w!(wps!("-c")),
+ w!(wps!("while read line; echo \"line: $line\"; end"))
+ )
+ ))
+ );
+
+ parse_eq!("foo ''", cs!(p!((0, 6), e!(w!("foo"), w!()))));
+ parse_eq!("foo \"\"", cs!(p!((0, 6), e!(w!("foo"), w!()))));
+}
+
+#[test]
+fn test_whitespace() {
+ parse_eq!(" foo ", cs!(p!((3, 6), e!(w!("foo")))));
+ parse_eq!(
+ " foo # this is a comment",
+ cs!(p!((3, 6), e!(w!("foo"))))
+ );
+ parse_eq!("foo#comment", cs!(p!((0, 3), e!(w!("foo")))));
+ parse_eq!(
+ "foo;bar|baz;quux#comment",
+ cs!(
+ p!((0, 3), e!(w!("foo"))),
+ p!((4, 11), e!(w!("bar")), e!(w!("baz"))),
+ p!((12, 16), e!(w!("quux")))
+ )
+ );
+ parse_eq!(
+ "foo | bar ",
+ cs!(p!((0, 12), e!(w!("foo")), e!(w!("bar"))))
+ );
+ parse_eq!(
+ " abc def ghi |jkl mno| pqr stu; vwxyz # comment",
+ cs!(
+ p!(
+ (2, 36),
+ e!(w!("abc"), w!("def"), w!("ghi")),
+ e!(w!("jkl"), w!("mno")),
+ e!(w!("pqr"), w!("stu"))
+ ),
+ p!((38, 43), e!(w!("vwxyz")))
+ )
+ );
+ parse_eq!(
+ "foo 'bar # baz' \"quux # not a comment\" # comment",
+ cs!(p!(
+ (0, 38),
+ e!(
+ w!("foo"),
+ w!(wps!("bar # baz")),
+ w!(wpd!("quux # not a comment"))
+ )
+ ))
+ );
+}
+
+#[test]
+fn test_redirect() {
+ parse_eq!(
+ "foo > bar",
+ cs!(p!((0, 9), e!(w!("foo") ; r!(1, w!("bar"), Out))))
+ );
+ parse_eq!(
+ "foo <bar",
+ cs!(p!((0, 8), e!(w!("foo") ; r!(0, w!("bar"), In))))
+ );
+ parse_eq!(
+ "foo > /dev/null 2>&1",
+ cs!(p!(
+ (0, 20),
+ e!(
+ w!("foo") ;
+ r!(1, w!("/dev/null"), Out), r!(2, w!("&1"), Out)
+ )
+ ))
+ );
+ parse_eq!(
+ "foo >>bar",
+ cs!(p!((0, 9), e!(w!("foo") ; r!(1, w!("bar"), Append))))
+ );
+ parse_eq!(
+ "foo >> bar",
+ cs!(p!((0, 10), e!(w!("foo") ; r!(1, w!("bar"), Append))))
+ );
+ parse_eq!(
+ "foo > 'bar baz'",
+ cs!(p!((0, 15), e!(w!("foo") ; r!(1, w!(wps!("bar baz")), Out))))
+ );
+}
+
+#[test]
+fn test_escape() {
+ parse_eq!("foo\\ bar", cs!(p!((0, 8), e!(w!("foo bar")))));
+ parse_eq!("'foo\\ bar'", cs!(p!((0, 10), e!(w!(wps!("foo\\ bar"))))));
+ parse_eq!("\"foo\\ bar\"", cs!(p!((0, 10), e!(w!(wpd!("foo bar"))))));
+ parse_eq!("\"foo\\\"bar\"", cs!(p!((0, 10), e!(w!(wpd!("foo\"bar"))))));
+ parse_eq!(
+ "'foo\\'bar\\\\'",
+ cs!(p!((0, 12), e!(w!(wps!("foo'bar\\")))))
+ );
+ parse_eq!(
+ "foo > bar\\ baz",
+ cs!(p!((0, 14), e!(w!("foo") ; r!(1, w!("bar baz"), Out))))
+ );
+}
+
+#[test]
+fn test_parts() {
+ parse_eq!(
+ "echo \"$HOME/bin\"",
+ cs!(p!((0, 16), e!(w!("echo"), w!(wpv!("HOME"), wpd!("/bin")))))
+ );
+ parse_eq!(
+ "echo \"dir: $HOME/bin\"",
+ cs!(p!(
+ (0, 21),
+ e!(w!("echo"), w!(wpd!("dir: "), wpv!("HOME"), wpd!("/bin")))
+ ))
+ );
+ parse_eq!(
+ "echo $HOME/bin",
+ cs!(p!((0, 14), e!(w!("echo"), w!(wpv!("HOME"), wpb!("/bin")))))
+ );
+ parse_eq!(
+ "echo '$HOME/bin'",
+ cs!(p!((0, 16), e!(w!("echo"), w!(wps!("$HOME/bin")))))
+ );
+ parse_eq!(
+ "echo \"foo\"\"bar\"",
+ cs!(p!((0, 15), e!(w!("echo"), w!(wpd!("foo"), wpd!("bar")))))
+ );
+ parse_eq!(
+ "echo $foo$bar$baz",
+ cs!(p!(
+ (0, 17),
+ e!(w!("echo"), w!(wpv!("foo"), wpv!("bar"), wpv!("baz")))
+ ))
+ );
+ parse_eq!(
+ "perl -E'say \"foo\"'",
+ cs!(p!(
+ (0, 18),
+ e!(w!("perl"), w!(wpb!("-E"), wps!("say \"foo\"")))
+ ))
+ );
+}
+
+#[test]
+fn test_alternation() {
+ parse_eq!(
+ "echo {foo,bar}",
+ cs!(p!((0, 14), e!(w!("echo"), w!(wpa!(w!("foo"), w!("bar"))))))
+ );
+ parse_eq!(
+ "echo {foo,bar}.rs",
+ cs!(p!(
+ (0, 17),
+ e!(w!("echo"), w!(wpa!(w!("foo"), w!("bar")), wpb!(".rs")))
+ ))
+ );
+ parse_eq!(
+ "echo {foo,bar,baz}.rs",
+ cs!(p!(
+ (0, 21),
+ e!(
+ w!("echo"),
+ w!(wpa!(w!("foo"), w!("bar"), w!("baz")), wpb!(".rs"))
+ )
+ ))
+ );
+ parse_eq!(
+ "echo {foo,}.rs",
+ cs!(p!(
+ (0, 14),
+ e!(w!("echo"), w!(wpa!(w!("foo"), w!()), wpb!(".rs")))
+ ))
+ );
+ parse_eq!(
+ "echo {foo}",
+ cs!(p!((0, 10), e!(w!("echo"), w!(wpa!(w!("foo"))))))
+ );
+ parse_eq!("echo {}", cs!(p!((0, 7), e!(w!("echo"), w!(wpa!(w!()))))));
+ parse_eq!(
+ "echo {foo,bar}.{rs,c}",
+ cs!(p!(
+ (0, 21),
+ e!(
+ w!("echo"),
+ w!(
+ wpa!(w!("foo"), w!("bar")),
+ wpb!("."),
+ wpa!(w!("rs"), w!("c"))
+ )
+ )
+ ))
+ );
+ parse_eq!(
+ "echo {$foo,\"${HOME}/bin\"}.{'r'\"s\",c}",
+ cs!(p!(
+ (0, 36),
+ e!(
+ w!("echo"),
+ w!(
+ wpa!(w!(wpv!("foo")), w!(wpv!("HOME"), wpd!("/bin"))),
+ wpb!("."),
+ wpa!(w!(wps!("r"), wpd!("s")), w!("c"))
+ )
+ )
+ ))
+ );
+}
+
+#[tokio::main]
+#[test]
+async fn test_eval_alternation() {
+ let mut env = Env::new().unwrap();
+ env.set_var("HOME", "/home/test");
+ env.set_var("foo", "value-of-foo");
+
+ eval_eq!("echo {foo,bar}", env, ep!(ee!("echo", "foo", "bar")));
+ eval_eq!(
+ "echo {foo,bar}.rs",
+ env,
+ ep!(ee!("echo", "foo.rs", "bar.rs"))
+ );
+ eval_eq!(
+ "echo {foo,bar,baz}.rs",
+ env,
+ ep!(ee!("echo", "foo.rs", "bar.rs", "baz.rs"))
+ );
+ eval_eq!("echo {foo,}.rs", env, ep!(ee!("echo", "foo.rs", ".rs")));
+ eval_eq!("echo {foo}", env, ep!(ee!("echo", "foo")));
+ eval_eq!("echo {}", env, ep!(ee!("echo", "")));
+ eval_eq!(
+ "echo {foo,bar}.{rs,c}",
+ env,
+ ep!(ee!("echo", "foo.rs", "foo.c", "bar.rs", "bar.c"))
+ );
+ eval_eq!(
+ "echo {$foo,\"${HOME}/bin\"}.{'r'\"s\",c}",
+ env,
+ ep!(ee!(
+ "echo",
+ "value-of-foo.rs",
+ "value-of-foo.c",
+ "/home/test/bin.rs",
+ "/home/test/bin.c"
+ ))
+ );
+}
+
+#[tokio::main]
+#[test]
+async fn test_eval_glob() {
+ let env = Env::new().unwrap();
+
+ eval_eq!(
+ "echo *.toml",
+ env,
+ ep!(ee!("echo", "Cargo.toml", "deny.toml"))
+ );
+ eval_eq!("echo .*.toml", env, ep!(ee!("echo", ".rustfmt.toml")));
+ eval_eq!(
+ "echo *.{lock,toml}",
+ env,
+ ep!(ee!("echo", "Cargo.lock", "Cargo.toml", "deny.toml"))
+ );
+ eval_eq!("echo foo]", env, ep!(ee!("echo", "foo]")));
+ eval_fails!("echo foo[", env);
+ eval_fails!("echo *.doesnotexist", env);
+ eval_fails!("echo *.{toml,doesnotexist}", env);
+}
+
+#[test]
+fn test_deserialize() {
+ deserialize_eq!("foo", e!(w!("foo")));
+ deserialize_eq!("foo bar baz", e!(w!("foo"), w!("bar"), w!("baz")));
+}