{-# LANGUAGE DeriveDataTypeable , DataKinds , RankNTypes , GADTs , PolyKinds , TypeInType , ScopedTypeVariables , FlexibleContexts #-} {-# OPTIONS_GHC -Wall -fwarn-tabs #-} module Tests.TestTools ( module Tests.TestTools , MapleOptions(..) , defaultMapleOptions) where import Language.Hakaru.Types.Sing import Language.Hakaru.Parser.Parser (parseHakaru) import Language.Hakaru.Parser.SymbolResolve (resolveAST) import Language.Hakaru.Command (parseAndInferWithMode', Source(..), fileSource, noFileSource) import Language.Hakaru.Syntax.ABT import Language.Hakaru.Syntax.AST import Language.Hakaru.Syntax.TypeCheck import Language.Hakaru.Syntax.AST.Eq (alphaEq) import Language.Hakaru.Syntax.AST.Transforms (expandTransformations ,expandTransformationsWith ,allTransformationsWithMOpts) import Language.Hakaru.Syntax.IClasses (TypeEq(..), jmEq1) import Language.Hakaru.Pretty.Concrete import Language.Hakaru.Simplify import Language.Hakaru.Maple (MapleOptions(..), defaultMapleOptions) import Language.Hakaru.Syntax.AST.Eq() import Text.PrettyPrint (Doc) import Data.Maybe (isJust) import Data.List import Data.Kind import qualified Data.Text as T import qualified Data.Text.Utf8 as IO import Data.Typeable (Typeable) import Control.Exception import Control.Monad import Test.HUnit data TestException = TestSimplifyException String SomeException deriving Typeable instance Exception TestException instance Show TestException where show (TestSimplifyException prettyHakaru e) = show e ++ "\nwhile simplifying Hakaru:\n" ++ prettyHakaru -- assert that we get a result and that no error is thrown assertResult :: [a] -> Assertion assertResult s = assertBool "no result" $ not $ null s assertJust :: Maybe a -> Assertion assertJust = assertBool "expected Just but got Nothing" . isJust handleException :: String -> SomeException -> IO a handleException t e = throw (TestSimplifyException t e) testS :: (ABT Term abt) => String -> abt '[] a -> Assertion testS p x = do _ <- simplify (expandTransformations x) `catch` handleException (p ++ ": simplify failed") return () testStriv :: TrivialABT Term '[] a -> Assertion testStriv = testS "" testSS1 :: (ABT Term abt) => String -> abt '[] a -- | Expected -> abt '[] a -- | To simplify -> Assertion testSS1 = testSS1WithOpts defaultMapleOptions testSS1WithOpts :: (ABT Term abt) => MapleOptions () -> String -> abt '[] a -- | Expected -> abt '[] a -- | To simplify -> Assertion testSS1WithOpts o nm t' t = simplifyWithOpts o (expandTransformations t) >>= \p -> assertAlphaEq nm p t' -- Assert that all the given Hakaru programs simplify to the given one testSS :: (ABT Term abt) => String -> [(abt '[] a)] -> abt '[] a -> Test testSS nm ts t' = test $ map (testSS1 nm t') (t':ts) testSStriv :: [(TrivialABT Term '[] a)] -> TrivialABT Term '[] a -> Test testSStriv = testSS "" -- | Assert that the given programs are equal after expanding transformations. -- By convention, the first program is taken to be the expected output, but -- this function is symmetric. testET :: (ABT Term abt) => MapleOptions () -> String -> abt '[] a -> abt '[] a -> Assertion testET opts nm t0 t1 = let et = expandTransformationsWith (allTransformationsWithMOpts opts) in mapM et [t0, t1] >>= \[t0', t1'] -> assertAlphaEq nm t0' t1' assertAlphaEq :: (ABT Term abt) => String -> abt '[] a -> abt '[] a -> Assertion assertAlphaEq preface a b = unless (alphaEq a b) (assertFailure $ mismatchMessage pretty preface a b) mismatchMessage :: forall q (k :: q -> Type) . (forall a . k a -> Doc) -> String -> forall a b . k a -> k b -> String mismatchMessage k preface a b = msg where msg = concat [ p , "expected:\n" , show (k b) , "\nbut got:\n" , show (k a) ] p = if null preface then "" else preface ++ "\n" testWithConcreteImport :: (ABT Term abt) => Source -> TypeCheckMode -> (forall a. Sing a -> abt '[] a -> Assertion) -> Assertion testWithConcreteImport s mode k = either (assertFailure . T.unpack) (\(TypedAST typ ast) -> k typ ast) =<< parseAndInferWithMode' s mode testWithConcrete :: (ABT Term abt) => T.Text -> TypeCheckMode -> (forall a. Sing a -> abt '[] a -> Assertion) -> Assertion testWithConcrete t m k = testWithConcreteImport (noFileSource t) m k -- Like testWithConcrete, but for many programs testWithConcreteMany :: forall abt. (ABT Term abt) => FilePath -> [FilePath] -> TypeCheckMode -> (forall a . Sing a -> abt '[] a -> abt '[] a -> Assertion) -> Test testWithConcreteMany t ts mode k = test $ map (mkT t) (t:ts) where mkT :: FilePath -> FilePath -> Assertion mkT t0' t1' = mapM (\t -> fileSource t <$> IO.readFile t) [t0', t1'] >>= \[t0,t1] -> testWithConcreteImport t0 mode $ \t0ty (t0p :: abt '[] x0) -> testWithConcreteImport t1 mode $ \t1ty (t1p :: abt '[] x1) -> case jmEq1 t0ty t1ty of Just Refl -> k t0ty t0p t1p Nothing -> assertFailure $ concat [ "Files don't have same type (" , T.unpack (source t0), " :: ", prettyTypeS t0ty , ", " , T.unpack (source t1), " :: ", prettyTypeS t1ty ] testWithConcrete' :: T.Text -> TypeCheckMode -> (forall a. Sing a -> TrivialABT Term '[] a -> Assertion) -> Assertion testWithConcrete' = testWithConcrete testWithConcreteMany' :: FilePath -> [FilePath] -> TypeCheckMode -> (forall a . Sing a -> TrivialABT Term '[] a -> TrivialABT Term '[] a -> Assertion) -> Test testWithConcreteMany' = testWithConcreteMany -- Like testSStriv but for many concrete files testConcreteFilesMany :: [FilePath] -> FilePath -> Test testConcreteFilesMany = testConcreteFilesManyWithOpts defaultMapleOptions -- TODO: Should there be a variant with options for each program? testConcreteFilesManyWithOpts :: MapleOptions () -> [FilePath] -> FilePath -> Test testConcreteFilesManyWithOpts o fs f = testWithConcreteMany' f fs LaxMode $ \_ -> testSS1WithOpts o "" testConcreteFilesET :: MapleOptions () -> [FilePath] -> FilePath -> Test testConcreteFilesET o fs f = testWithConcreteMany' f fs LaxMode $ \_ -> testET o "" -- Like testSStriv but for two concrete files testConcreteFiles :: FilePath -> FilePath -> Test testConcreteFiles f1 f2 = testConcreteFilesMany [f1] f2 -- Like testStriv but for a concrete file. testConcreteFile :: FilePath -> Assertion testConcreteFile f = IO.readFile f >>= \t -> testWithConcreteImport (fileSource f t) LaxMode $ \_ -> testStriv ignore :: a -> Assertion ignore _ = assertFailure "ignored" -- ignoring a test reports as a failure -- Runs a single test from a list of tests given its index runTestI :: Test -> Int -> IO Counts runTestI (TestList ts) i = runTestTT $ ts !! i runTestI (TestCase _) _ = error "expecting a TestList, but got a TestCase" runTestI (TestLabel _ _) _ = error "expecting a TestList, but got a TestLabel" hasLab :: String -> Test -> Bool hasLab l (TestLabel lab _) = lab == l hasLab _ _ = False -- Runs a single test from a TestList given its label runTestN :: Test -> String -> IO Counts runTestN (TestList ts) l = case find (hasLab l) ts of Just t -> runTestTT t Nothing -> error $ "no test with label " ++ l runTestN (TestCase _) _ = error "expecting a TestList, but got a TestCase" runTestN (TestLabel _ _) _ = error "expecting a TestList, but got a TestLabel"