dhall-1.27.0: tests/Dhall/Test/Parser.hs
{-# LANGUAGE OverloadedStrings #-}
module Dhall.Test.Parser where
import Data.Monoid ((<>))
import Data.Text (Text)
import Data.Void (Void)
import Dhall.Core (Binding(..), Expr(..), Import, Var(..))
import Prelude hiding (FilePath)
import Test.Tasty (TestTree)
import Turtle (FilePath, (</>))
import qualified Codec.CBOR.Read as CBOR
import qualified Codec.CBOR.Term as CBOR
import qualified Codec.Serialise as Serialise
import qualified Control.Monad as Monad
import qualified Data.Bifunctor as Bifunctor
import qualified Data.ByteString as ByteString
import qualified Data.ByteString.Lazy as ByteString.Lazy
import qualified Data.Text as Text
import qualified Data.Text.IO as Text.IO
import qualified Data.Text.Encoding as Text.Encoding
import qualified Dhall.Binary as Binary
import qualified Dhall.Core as Core
import qualified Dhall.Parser as Parser
import qualified Dhall.Test.Util as Test.Util
import qualified Test.Tasty as Tasty
import qualified Test.Tasty.HUnit as Tasty.HUnit
import qualified Turtle
parseDirectory :: FilePath
parseDirectory = "./dhall-lang/tests/parser"
binaryDecodeDirectory :: FilePath
binaryDecodeDirectory = "./dhall-lang/tests/binary-decode"
getTests :: IO TestTree
getTests = do
let successFiles = Turtle.lstree (parseDirectory </> "success")
successTests <- do
Test.Util.discover (Turtle.chars <* "A.dhall") shouldParse successFiles
let failureFiles = Turtle.lstree (parseDirectory </> "failure")
failureTests <- do
Test.Util.discover (Turtle.chars <> ".dhall") shouldNotParse failureFiles
let binaryDecodeSuccessFiles =
Turtle.lstree (binaryDecodeDirectory </> "success")
binaryDecodeSuccessTests <- do
Test.Util.discover (Turtle.chars <* "A.dhallb") shouldDecode binaryDecodeSuccessFiles
let binaryDecodeFailureFiles = Turtle.lstree (binaryDecodeDirectory </> "failure")
binaryDecodeFailureTests <- do
Test.Util.discover (Turtle.chars <* ".dhallb") shouldNotDecode binaryDecodeFailureFiles
let testTree =
Tasty.testGroup "parser tests"
[ successTests
, failureTests
, internalTests
, binaryDecodeSuccessTests
, binaryDecodeFailureTests
]
return testTree
internalTests :: TestTree
internalTests =
Tasty.testGroup "internal"
[ notesInLetInLet ]
notesInLetInLet :: TestTree
notesInLetInLet = do
Tasty.HUnit.testCase "Notes in let-in-let" $ do
let code = "let x = 0 let y = 1 in let z = 2 in x"
expression <- Core.throws (Parser.exprFromText mempty code)
let simplifyNotes = Bifunctor.first Parser.srcText
let expected =
(Note code
(Let
(Binding
(Just " ")
"x"
(Just " ")
Nothing
(Just " ")
(Note "0 " (NaturalLit 0)))
-- This 'Let' isn't wrapped in a 'Note'!
(Let
(Binding
(Just " ")
"y"
(Just " ")
Nothing
(Just " ")
(Note "1 " (NaturalLit 1))
)
(Note "let z = 2 in x"
(Let
(Binding
(Just " ")
"z"
(Just " ")
Nothing
(Just " ")
(Note "2 " (NaturalLit 2))
)
(Note "x"
(Var (V "x" 0))))))))
let msg = "Unexpected parse result"
Tasty.HUnit.assertEqual msg expected (simplifyNotes expression)
shouldParse :: Text -> TestTree
shouldParse path = do
let expectedFailures =
-- This is a bug created by a parsing performance
-- improvement
[ parseDirectory </> "success/unit/MergeParenAnnotation" ]
let pathString = Text.unpack path
Test.Util.testCase path expectedFailures $ do
text <- Text.IO.readFile (pathString <> "A.dhall")
encoded <- ByteString.Lazy.readFile (pathString <> "B.dhallb")
expression <- Core.throws (Parser.exprFromText mempty text)
let term = Binary.encodeExpression expression
let bytes = Serialise.serialise term
Monad.unless (encoded == bytes) $ do
("", expected) <- Core.throws (CBOR.deserialiseFromBytes CBOR.decodeTerm encoded)
let message = "The expected CBOR representation doesn't match the actual one\n"
++ "expected: " ++ show expected ++ "\n but got: " ++ show term
++ "\n expr: " ++ show expression
Tasty.HUnit.assertFailure message
shouldNotParse :: Text -> TestTree
shouldNotParse path = do
let expectedFailures =
[ -- These two unexpected successes are due to not correctly
-- requiring non-empty whitespace after the `:` in a type
-- annotation
parseDirectory </> "failure/unit/ImportEnvWrongEscape.dhall"
-- Other spacing related unexpected successes:
, parseDirectory </> "failure/spacing/AnnotationNoSpace.dhall"
, parseDirectory </> "failure/spacing/ApplicationNoSpace1.dhall"
, parseDirectory </> "failure/spacing/ApplicationNoSpace2.dhall"
, parseDirectory </> "failure/spacing/AssertNoSpace.dhall"
, parseDirectory </> "failure/spacing/ForallNoSpace.dhall"
, parseDirectory </> "failure/spacing/ImportAltNoSpace.dhall"
, parseDirectory </> "failure/spacing/ImportHashedNoSpace.dhall"
, parseDirectory </> "failure/spacing/LambdaNoSpace.dhall"
, parseDirectory </> "failure/spacing/ListLitEmptyNoSpace.dhall"
, parseDirectory </> "failure/spacing/MergeAnnotationNoSpace3.dhall"
, parseDirectory </> "failure/spacing/MergeNoSpace2.dhall"
, parseDirectory </> "failure/spacing/NaturalPlusNoSpace.dhall"
, parseDirectory </> "failure/spacing/RecordTypeNoSpace.dhall"
, parseDirectory </> "failure/spacing/ToMapAnnotNoSpace.dhall"
, parseDirectory </> "failure/spacing/UnionTypeNoSpace.dhall"
, parseDirectory </> "failure/ImportHeadersExteriorHash.dhall"
-- For parsing performance reasons the implementation
-- treats a missing type annotation on an empty list as
-- as a type-checking failure instead of a parse failure,
-- but this might be fixable.
, parseDirectory </> "failure/unit/ListLitEmptyMissingAnnotation.dhall"
, parseDirectory </> "failure/unit/ListLitEmptyAnnotation.dhall"
-- The same performance improvements also broke the
-- precedence of parsing empty list literals
, parseDirectory </> "failure/unit/ListLitEmptyPrecedence.dhall"
]
let pathString = Text.unpack path
Test.Util.testCase path expectedFailures (do
bytes <- ByteString.readFile pathString
case Text.Encoding.decodeUtf8' bytes of
Left _ -> return ()
Right text -> do
case Parser.exprFromText mempty text of
Left _ -> return ()
Right _ -> Tasty.HUnit.assertFailure "Unexpected successful parse" )
shouldDecode :: Text -> TestTree
shouldDecode pathText = do
let expectedFailures = []
let pathString = Text.unpack pathText
Test.Util.testCase pathText expectedFailures (do
bytes <- ByteString.Lazy.readFile (pathString <> "A.dhallb")
term <- Core.throws (Serialise.deserialiseOrFail bytes)
decodedExpression <- Core.throws (Binary.decodeExpression term)
text <- Text.IO.readFile (pathString <> "B.dhall")
parsedExpression <- Core.throws (Parser.exprFromText mempty text)
let strippedExpression :: Expr Void Import
strippedExpression = Core.denote parsedExpression
let message =
"The decoded expression didn't match the parsed expression"
Tasty.HUnit.assertEqual message decodedExpression strippedExpression )
shouldNotDecode :: Text -> TestTree
shouldNotDecode pathText = do
let expectedFailures = []
let pathString = Text.unpack pathText
Test.Util.testCase pathText expectedFailures (do
bytes <- ByteString.Lazy.readFile (pathString <> ".dhallb")
term <- Core.throws (Serialise.deserialiseOrFail bytes)
case Binary.decodeExpression term :: Either Binary.DecodingFailure (Expr Void Import) of
Left _ -> return ()
Right _ -> Tasty.HUnit.assertFailure "Unexpected successful decode" )