1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
|
{-# LANGUAGE NoImplicitPrelude #-}
-- | Args parser test suite.
module Stack.ArgsSpec
( spec
, argsSpec
, argsInputOutput
, interpreterArgsSpec
) where
import Data.Attoparsec.Args ( EscapingMode (..), parseArgsFromString )
import Data.Attoparsec.Interpreter ( interpreterArgsParser )
import qualified Data.Attoparsec.Text as P
import Data.Text ( pack )
import Prelude ( head )
import Stack.Constants ( stackProgName )
import Stack.Prelude
import Test.Hspec ( Spec, describe, it )
-- | Test spec.
spec :: Spec
spec = do
argsSpec
interpreterArgsSpec
argsSpec :: Spec
argsSpec = forM_ argsInputOutput
(\(input,output) -> it input (parseArgsFromString Escaping input == output))
-- | Fairly comprehensive checks.
argsInputOutput :: [(String, Either String [String])]
argsInputOutput =
[ ("x", Right ["x"])
, ("x y z", Right ["x", "y", "z"])
, ("aaa bbb ccc", Right ["aaa", "bbb", "ccc"])
, (" aaa bbb ccc ", Right ["aaa", "bbb", "ccc"])
, ("aaa\"", Left "unterminated string: endOfInput")
, ("\"", Left "unterminated string: endOfInput")
, ("\"\"", Right [""])
, ("\"aaa", Left "unterminated string: endOfInput")
, ("\"aaa\" bbb ccc \"ddd\"", Right ["aaa", "bbb", "ccc", "ddd"])
, ("\"aa\\\"a\" bbb ccc \"ddd\"", Right ["aa\"a", "bbb", "ccc", "ddd"])
, ("\"aa\\\"a\" bb\\b ccc \"ddd\"", Right ["aa\"a", "bb\\b", "ccc", "ddd"])
, ("\"\" \"\" c", Right ["","","c"])]
interpreterArgsSpec :: Spec
interpreterArgsSpec =
describe "Script interpreter parser" $ do
describe "Success cases" $ do
describe "Line comments" $ do
checkLines ""
checkLines " --x"
checkLines " --x --y"
describe "Literate line comments" $ do
checkLiterateLines ""
checkLiterateLines " --x"
checkLiterateLines " --x --y"
describe "Block comments" $ do
checkBlocks ""
checkBlocks "\n"
checkBlocks " --x"
checkBlocks "\n--x"
checkBlocks " --x --y"
checkBlocks "\n--x\n--y"
checkBlocks "\n\t--x\n\t--y"
describe "Literate block comments" $ do
checkLiterateBlocks "" ""
checkLiterateBlocks "\n>" ""
checkLiterateBlocks " --x" " --x"
checkLiterateBlocks "\n>--x" "--x"
checkLiterateBlocks " --x --y " "--x --y"
checkLiterateBlocks "\n>--x\n>--y" "--x --y"
checkLiterateBlocks "\n>\t--x\n>\t--y" "--x --y"
describe "Failure cases" $ do
checkFailures
describe "Bare directives in literate files" $ do
forM_ (interpreterGenValid lineComment []) $
testAndCheck (acceptFailure True) []
forM_ (interpreterGenValid blockComment []) $
testAndCheck (acceptFailure True) []
where
parse isLiterate s =
P.parseOnly (interpreterArgsParser isLiterate stackProgName) (pack s)
acceptSuccess :: Bool -> String -> String -> Bool
acceptSuccess isLiterate args s = case parse isLiterate s of
Right x | words x == words args -> True
_ -> False
acceptFailure isLiterate _ s = case parse isLiterate s of
Left _ -> True
Right _ -> False
testAndCheck checker out inp = it (show inp) $ checker out inp
checkLines args = forM_
(interpreterGenValid lineComment args)
(testAndCheck (acceptSuccess False) args)
checkLiterateLines args = forM_
(interpreterGenValid literateLineComment args)
(testAndCheck (acceptSuccess True) args)
checkBlocks args = forM_
(interpreterGenValid blockComment args)
(testAndCheck (acceptSuccess False) args)
checkLiterateBlocks inp args = forM_
(interpreterGenValid literateBlockComment inp)
(testAndCheck (acceptSuccess True) args)
checkFailures = forM_
interpreterGenInvalid
(testAndCheck (acceptFailure False) "unused")
-- Generate a set of acceptable inputs for given format and args
interpreterGenValid fmt args = shebang <++> newLine <++> fmt args
interpreterGenInvalid :: [String]
-- Generate a set of Invalid inputs
interpreterGenInvalid =
["-stack\n"] -- random input
-- just the shebang
<|> shebang <++> ["\n"]
-- invalid shebang
<|> blockSpace <++> [head (interpreterGenValid lineComment args)]
-- something between shebang and Stack comment
<|> shebang
<++> newLine
<++> blockSpace
<++> ([head (lineComment args)] <|> [head (blockComment args)])
-- unterminated block comment
-- just chop the closing chars from a valid block comment
<|> shebang
<++> ["\n"]
<++> let c = head (blockComment args)
l = length c - 2
in [assert (drop l c == "-}") (take l c)]
-- nested block comment
<|> shebang
<++> ["\n"]
<++> [head (blockComment "--x {- nested -} --y")]
where
args = " --x --y"
(<++>) = liftA2 (++)
-- Generative grammar for the interpreter comments
shebang = ["#!/usr/bin/env stack"]
newLine = ["\n"] <|> ["\r\n"]
-- A comment may be the last line or followed by something else
postComment = [""] <|> newLine
-- A command starts with zero or more whitespace followed by "stack"
makeComment maker space args =
let makePrefix s = (s <|> [""]) <++> [stackProgName]
in (maker <$> (makePrefix space <++> [args])) <++> postComment
lineSpace = [" "] <|> ["\t"]
lineComment = makeComment makeLine lineSpace
where
makeLine s = "--" ++ s
literateLineComment = makeComment ("> --" ++) lineSpace
blockSpace = lineSpace <|> newLine
blockComment = makeComment makeBlock blockSpace
where
makeBlock s = "{-" ++ s ++ "-}"
literateBlockComment = makeComment
(\s -> "> {-" ++ s ++ "-}")
(lineSpace <|> map (++ ">") newLine)
|