;;; The Geesh Shell Interpreter ;;; Copyright 2018 Timothy Sample ;;; ;;; This file is part of Geesh. ;;; ;;; Geesh is free software: you can redistribute it and/or modify ;;; it under the terms of the GNU General Public License as published by ;;; the Free Software Foundation, either version 3 of the License, or ;;; (at your option) any later version. ;;; ;;; Geesh is distributed in the hope that it will be useful, ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;;; GNU General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with Geesh. If not, see . (define-module (test-parser) #:use-module (geesh parser) #:use-module (srfi srfi-64) #:use-module (tests automake)) ;;; Commentary: ;;; ;;; Tests for the parser module. ;;; ;;; Code: (define (parse str) (call-with-input-string str read-sh)) (test-begin "reader") ;; Commands and lists (test-equal "Parses simple command" '( "echo" "foo") (parse "echo foo")) (test-equal "Parses command with keywords as arguments" '( "echo" "{" "!" "for" "}") (parse "echo { ! for }")) (test-equal "Parses command lists" '( ( "echo" "foo") ( "echo" "bar")) (parse "echo foo; echo bar")) (test-equal "Parses asynchronous command lists" '( ( ( "echo" "foo")) ( ( "echo" "bar"))) (parse "echo foo& echo bar&")) (test-equal "Parses mixed command lists" '( ( ( "echo" "foo")) ( "echo" "bar")) (parse "echo foo& echo bar")) (test-equal "Parses commands with assignments" '( (("FOO" "bar")) "echo" ( "FOO")) (parse "FOO=bar echo $FOO")) (test-equal "Parses commands with default redirects" '( ((> 1 "bar")) ( "echo" "foo")) (parse "echo foo > bar")) (test-equal "Parses commands with specific redirects" '( ((< 5 "bar")) ( "echo" "foo")) (parse "echo foo 5< bar")) (test-equal "Parses commands with dup redirects" '( ((>& 1 "3")) ( "exec")) (parse "exec >&3")) (test-equal "Parses commands with close redirects" '( ((<& 3 "-")) ( "exec")) (parse "exec 3<&-")) (test-equal "Parses redirects without a command" '( ((>& 2 "1")) #f) (parse "2>&1")) (test-equal "Parses command with prefix redirect and no arguments" '( ((< 0 "bar")) ( "cat")) (parse " ("FOO" "bar")) (parse "FOO=bar")) (test-equal "Parses multiple assignments" '( ("FOO" "bar") ("BAZ" "quux")) (parse "FOO=bar BAZ=quux")) ;; Boolean expressions (test-equal "Parses disjunctions" '( ( "echo" "foo") ( "echo" "bar")) (parse "echo foo || echo bar")) (test-equal "Parses conjunctions" '( ( "echo" "foo") ( "echo" "bar")) (parse "echo foo && echo bar")) (test-equal "Parses conjunction than disjunction" '( ( ( "echo" "foo") ( "echo" "bar")) ( "echo" "baz")) (parse "echo foo && echo bar || echo baz")) (test-equal "Parses disjunction than conjunction" '( ( ( "echo" "foo") ( "echo" "bar")) ( "echo" "baz")) (parse "echo foo || echo bar && echo baz")) (test-equal "Parses negations" '( ( "echo" "foo")) (parse "! echo foo")) (test-equal "Parses negated pipelines" '( ( ( "echo" "foo") ( "echo" "bar"))) (parse "! echo foo | echo bar")) ;; Pipelines (test-equal "Parses pipelines" '( ( "cat" "foo.txt") ( "grep" "bar")) (parse "cat foo.txt | grep bar")) ;; Brace groups and subshells (test-equal "Parses brace groups" '( ( "echo" "foo") ( "echo" "bar")) (parse "{ echo foo echo bar; }")) (test-equal "Parses subshells" '( ( ( "echo" "foo") ( "echo" "bar"))) (parse "(echo foo; echo bar)")) ;; Here documents (test-equal "Parses one here-document in a complete command" '( ((<< 0 ( "foo\n"))) ( "cat")) (parse "cat < ( ((<< 0 ( "foo\n"))) ( "cat")) ( ((<< 0 ( "bar\n"))) ( "cat"))) (parse "cat < ( ((<< 0 ( "foo\n"))) ( "cat"))) (parse "(cat < ( ( ((<< 0 ( "foo\n"))) ( "cat")) ( ((<< 0 ( "bar\n"))) ( "cat")))) (parse "(cat < ( ( ((<< 0 ( "foo\n"))) ( "cat"))) ( ((<< 0 ( "bar\n"))) ( "cat"))) (parse "(cat < ( ( ((<< 0 ( "foo\n"))) ( "cat")) ( ((<< 0 ( "bar\n"))) ( "cat")))) (parse "(\ncat < ((<< 0 ( "foo\n"))) ( "cat")) (parse "cat <<-eof\n\tfoo\n\teof")) ;; For loops (test-equal "Parses for loops over parameters without seperator" '( ("x" ( "@")) ( "echo" ( "x"))) (parse "for x do echo $x; done")) (test-equal "Parses for loops over parameters with seperator" '( ("x" ( "@")) ( "echo" ( "x"))) (parse "for x; do echo $x; done")) (test-equal "Parses for loops over parameters with \"in\"" '( ("x" ( "@")) ( "echo" ( "x"))) (parse "for x in; do echo $x; done")) (test-equal "Parses for loops over word lists" '( ("x" ("foo" "bar" "baz")) ( "echo" ( "x"))) (parse "for x in foo bar baz; do echo $x; done")) ;; Case statements (test-equal "Parses case statements with final seperator" '( ( "foo") (("bar") ( "echo" "bar"))) (parse "case $foo in bar) echo bar ;; esac")) (test-equal "Parses case statements without final seperator" '( ( "foo") (("bar") ( "echo" "bar"))) (parse "case $foo in bar) echo bar; esac")) (test-equal "Parses empty case statements" '( ( "foo")) (parse "case $foo in esac")) (test-equal "Parses case statements with empty case item" '( ( "foo") (("bar") #f)) (parse "case $foo in bar) esac")) (test-equal "Parses case statements with multiple case items" '( ( "foo") (("bar") ( "echo" "bar")) (("baz") ( "echo" "baz"))) (parse "case $foo in bar) echo bar ;; baz) echo baz; esac")) (test-equal "Parses case statements with compound patterns" '( ( "foo") (("bar" "baz") ( "echo" ( "bar or baz")))) (parse "case $foo in bar | baz) echo 'bar or baz' ;; esac")) ;; If statements (test-equal "Parses one-branch if statements" '( (( "[" ( "foo") "=" "bar" "]") ( "echo" "bar"))) (parse "if [ $foo = bar ]; then echo bar; fi")) (test-equal "Parses two-branch if statements" '( (( "[" ( "foo") "=" "bar" "]") ( "echo" "bar")) ( ( "echo" "baz"))) (parse "if [ $foo = bar ]; then echo bar; else echo baz; fi")) (test-equal "Parses multi-branch if statements" '( (( "[" ( "foo") "=" "bar" "]") ( "echo" "bar")) (( "[" ( "foo") "=" "baz" "]") ( "echo" "baz")) ( ( "echo" "quux"))) (parse "if [ $foo = bar ] then echo bar elif [ $foo = baz ] then echo baz else echo quux fi")) ;; While and until loops (test-equal "Parses while loops" '( ( "is-foo-time") ( "foo")) (parse "while is-foo-time; do foo; done")) (test-equal "Parses until loops" '( ( "is-no-longer-foo-time") ( "foo")) (parse "until is-no-longer-foo-time; do foo; done")) ;; Functions (test-equal "Parses functions" '( "foo" ( "echo" "foo")) (parse "foo() { echo foo; }")) ;; Nested commands (test-equal "Parses bracketed command substitions" '( "echo" ( ( "foo")) ( ( "bar"))) (parse "echo $(foo) $(bar)")) (test-equal "Parses nested bracketed command substitions" '( "echo" ( ( "foo" ( ( "bar"))))) (parse "echo $(foo $(bar))")) (test-equal "Parses empty bracketed command substitions" '( "echo" ()) (parse "echo $()")) (test-equal "Parses multiline bracketed command substitions" '( "echo" ( ( "foo") ( "bar"))) (parse "echo $(foo bar)")) (test-equal "Parses backquoted command substitions" '( "echo" ( ( "foo")) ( ( "bar"))) (parse "echo `foo` `bar`")) (test-equal "Parses nested backquoted command substitions" '( "echo" ( ( "foo" ( ( "bar"))))) (parse "echo `foo \\`bar\\``")) (test-equal "Parses empty backquoted command substitions" '( "echo" ()) (parse "echo ``")) (test-equal "Parses multiline backquoted command substitions" '( "echo" ( ( "foo") ( "bar"))) (parse "echo `foo bar`")) ;; Other tests (test-assert "Returns EOF on EOF" (eof-object? (parse ""))) (test-equal "Parses one statement at a time" '(( "echo" "foo") ( "echo" "bar")) (call-with-input-string "echo foo echo bar" (lambda (port) (list (read-sh port) (read-sh port))))) (test-equal "Reads all commands" '(( "echo" "foo") ( "echo" "bar")) (call-with-input-string "echo foo echo bar" read-sh-all)) (test-equal "Reads all commands and returns a list for one command" '(( "echo" "foo")) (call-with-input-string "echo foo" read-sh-all)) (test-equal "Reads all commands and returns a list for no commands" '() (call-with-input-string "" read-sh-all)) (test-end)