dhall-1.25.0: tests/Dhall/Test/Dhall.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Dhall.Test.Dhall where
import Control.Exception (SomeException, try)
import Data.Text (Text)
import Dhall (Inject, Interpret)
import Dhall.Core (Expr(..))
import GHC.Generics (Generic)
import Numeric.Natural (Natural)
import Test.Tasty
import Test.Tasty.HUnit
import qualified Dhall
import qualified Dhall.Core
import qualified Dhall.Import
import qualified Dhall.Map
import qualified Dhall.Parser
tests :: TestTree
tests =
testGroup "Input"
[ shouldShowDetailedTypeError
, shouldHandleBothUnionLiterals
, shouldHaveWorkingGenericAuto
, shouldHandleUnionsCorrectly
, shouldTreatAConstructorStoringUnitAsEmptyAlternative
]
data MyType = MyType { foo :: String , bar :: Natural }
wrongDhallType :: Dhall.Type MyType
wrongDhallType = Dhall.Type { .. }
where expected =
Dhall.Core.Record
( Dhall.Map.fromList
[ ( "bar", Dhall.Core.Natural)
, ( "foo", Dhall.Core.Text )
]
)
extract expr = Dhall.typeError expected expr
shouldShowDetailedTypeError :: TestTree
shouldShowDetailedTypeError = testCase "detailed TypeError" $ do
inputEx :: Either SomeException MyType <-
try ( Dhall.input wrongDhallType "{ bar = 0, foo = \"foo\" }")
let expectedMsg =
"\ESC[1;31mError\ESC[0m: Invalid Dhall.Type \n\
\ \n\
\Every Type must provide an extract function that succeeds if an expression \n\
\matches the expected type. You provided a Type that disobeys this contract \n\
\ \n\
\The Type provided has the expected dhall type: \n\
\ \n\
\↳ { bar : Natural, foo : Text }\n\
\ \n\
\and it couldn't extract a value from the well-typed expression: \n\
\ \n\
\↳ { bar = 0, foo = \"foo\" }\n\
\ \n"
let assertMsg = "The exception message did not match the expected output"
case inputEx of
Left ex -> assertEqual assertMsg expectedMsg (show ex)
Right _ -> fail "The extraction using a wrong type succeded"
-- https://github.com/dhall-lang/dhall-haskell/issues/915
shouldHandleBothUnionLiterals :: TestTree
shouldHandleBothUnionLiterals = testCase "Marshal union literals" $ do
let example :: Dhall.Type Bool
example = Dhall.union (Dhall.constructor "Test" Dhall.bool)
_ <- Dhall.input example "< Test : Bool >.Test True"
_ <- Dhall.input example "< Test = True >"
return ()
shouldTreatAConstructorStoringUnitAsEmptyAlternative :: TestTree
shouldTreatAConstructorStoringUnitAsEmptyAlternative = testCase "Handle unit constructors" $ do
let exampleType :: Dhall.Type ()
exampleType = Dhall.union (Dhall.constructor "A" Dhall.unit)
() <- Dhall.input exampleType "< A >.A"
let exampleInputType :: Dhall.InputType ()
exampleInputType = Dhall.inputUnion (Dhall.inputConstructor "A")
Dhall.embed exampleInputType () @=? Field (Union (Dhall.Map.singleton "A" Nothing)) "A"
data CompilerFlavor3 =
GHC3 | GHCJS3 | Helium3
deriving (Generic, Show, Eq)
data CompilerFlavor2 =
GHC2 | GHCJS2
deriving (Generic, Show, Eq)
-- https://github.com/dhall-lang/dhall-haskell/issues/926
shouldHaveWorkingGenericAuto :: TestTree
shouldHaveWorkingGenericAuto = testGroup "genericAuto"
[ testCase "works for a three-constructor enum" $ do
compiler <- Dhall.input Dhall.genericAuto "< GHC3 | GHCJS3 | Helium3 >.GHC3"
assertEqual "genericAuto didn't give us what we wanted" GHC3 compiler
, testCase "works for a two-constructor enum" $ do
compiler <- Dhall.input Dhall.genericAuto "< GHC2 | GHCJS2 >.GHC2"
assertEqual "genericAuto didn't give us what we wanted" GHC2 compiler
]
data NonEmptyUnion = N0 Bool | N1 Natural | N2 Text
deriving (Eq, Generic, Inject, Interpret, Show)
data Enum = E0 | E1 | E2
deriving (Eq, Generic, Inject, Interpret, Show)
data Mixed = M0 Bool | M1 | M2 ()
deriving (Eq, Generic, Inject, Interpret, Show)
deriving instance Interpret ()
shouldHandleUnionsCorrectly :: TestTree
shouldHandleUnionsCorrectly =
testGroup "Handle union literals"
[ "λ(x : < N0 : { _1 : Bool } | N1 : { _1 : Natural } | N2 : { _1 : Text } >) → x"
`shouldPassThrough` [ N0 True, N1 5, N2 "ABC" ]
, "λ(x : < E0 | E1 | E2 >) → x"
`shouldPassThrough` [ E0, E1, E2 ]
, "λ(x : < M0 : { _1 : Bool } | M1 | M2 : { _1 : {} } >) → x"
`shouldPassThrough` [ M0 True, M1, M2 () ]
, "(< N0 : { _1 : Bool } | N1 : { _1 : Natural } | N2 : { _1 : Text } >).N0 { _1 = True }"
`shouldMarshalInto` N0 True
, "(< N0 : { _1 : Bool } | N1 : { _1 : Natural } | N2 : { _1 : Text } >).N1 { _1 = 5 }"
`shouldMarshalInto` N1 5
, "(< N0 : { _1 : Bool } | N1 : { _1 : Natural } | N2 : { _1 : Text } >).N2 { _1 = \"ABC\" }"
`shouldMarshalInto` N2 "ABC"
, "< N0 = { _1 = True } | N1 : { _1 : Natural } | N2 : { _1 : Text } >"
`shouldMarshalInto` N0 True
, "< N0 : { _1 : Bool } | N1 = { _1 = 5 } | N2 : { _1 : Text } >"
`shouldMarshalInto` N1 5
, "< N0 : { _1 : Bool } | N1 : { _1 : Natural } | N2 = { _1 = \"ABC\" } >"
`shouldMarshalInto` N2 "ABC"
, "(< E0 | E1 | E2>).E0" `shouldMarshalInto` E0
, "(< E0 | E1 | E2>).E1" `shouldMarshalInto` E1
, "(< E0 | E1 | E2>).E2" `shouldMarshalInto` E2
, "(< M0 : { _1 : Bool } | M1 | M2 : { _1 : {} } >).M0 { _1 = True }"
`shouldMarshalInto` M0 True
, "(< M0 : { _1 : Bool } | M1 | M2 : { _1 : {} } >).M1"
`shouldMarshalInto` M1
, "(< M0 : { _1 : Bool } | M1 | M2 : { _1 : {} } >).M2 { _1 = {=} }"
`shouldMarshalInto` M2 ()
, "< M0 = { _1 = True } | M1 | M2 : { _1 : {} } >"
`shouldMarshalInto` M0 True
, N0 True
`shouldInjectInto`
"(< N0 : { _1 : Bool } | N1 : { _1 : Natural } | N2 : { _1 : Text } >).N0 { _1 = True }"
, N1 5
`shouldInjectInto`
"(< N0 : { _1 : Bool } | N1 : { _1 : Natural } | N2 : { _1 : Text } >).N1 { _1 = 5 }"
, N2 "ABC"
`shouldInjectInto`
"(< N0 : { _1 : Bool } | N1 : { _1 : Natural } | N2 : { _1 : Text } >).N2 { _1 = \"ABC\" }"
, E0 `shouldInjectInto` "< E0 | E1 | E2 >.E0"
, E1 `shouldInjectInto` "< E0 | E1 | E2 >.E1"
, E2 `shouldInjectInto` "< E0 | E1 | E2 >.E2"
, M0 True `shouldInjectInto` "(< M0 : { _1 : Bool } | M1 | M2 : { _1 : {} } >).M0 { _1 = True }"
, M1 `shouldInjectInto` "(< M0 : { _1 : Bool } | M1 | M2 : { _1 : {} } >).M1"
, M2 () `shouldInjectInto` "(< M0 : { _1 : Bool } | M1 | M2 : { _1 : {} } >).M2 { _1 = {=} }"
]
where
code `shouldPassThrough` values = testCase "Pass through" $ do
f <- Dhall.input Dhall.auto code
values @=? map f values
code `shouldMarshalInto` expectedValue = testCase "Marshal" $ do
actualValue <- Dhall.input Dhall.auto code
expectedValue @=? actualValue
value `shouldInjectInto` expectedCode = testCase "Inject" $ do
parsedExpression <- Dhall.Core.throws (Dhall.Parser.exprFromText "(test)" expectedCode)
resolvedExpression <- Dhall.Import.assertNoImports parsedExpression
Dhall.Core.denote resolvedExpression @=? Dhall.embed Dhall.inject value