packages feed

candid-0.1: test/SpecTests.hs

{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE UndecidableInstances #-}

module SpecTests (specTests) where

import qualified Data.Text as T
import qualified Data.ByteString.Lazy as BS
import Test.Tasty
import Test.Tasty.HUnit
import System.Environment
import System.Directory
import System.FilePath
import System.IO
import System.Exit
import Data.Traversable
import Language.Haskell.TH
import Language.Haskell.TH.Syntax

import Codec.Candid
import Codec.Candid.TestExports

-- WARNING: Big Template Haskell mess ahead
$(do
  candid_tests <- runIO (lookupEnv "CANDID_TESTS") >>= \case
    Nothing -> do
      runIO $ [] <$ putStrLn "CANDID_TESTS not set, will not run candid spec test"
    Just dir -> do
        files <- runIO $ listDirectory dir
        sequence
          [ do addDependentFile file
               content <- runIO $ readFile file
               case parseCandidTests file content of
                   Left err -> runIO $ do
                     hPutStrLn stderr $ "Failed to parse " ++ file ++ ":"
                     hPutStrLn stderr err
                     exitFailure
                   Right x -> return (name, x)
          | basename <- files
          , let file = dir </> basename
          , Just name <- pure $ T.stripSuffix ".test.did" (T.pack basename)
          -- , name /= "construct" -- for now
          ]
  (decls, testGroups) <- fmap unzip $ for candid_tests $ \(name, testfile) -> do
     (decls, resolve) <- generateCandidDefs (testDefs testfile)
     tests <- traverse (traverse resolve) (testTests testfile)
     testGroup <-
        [| testGroup ("File " ++ $(liftString (T.unpack name))) $(listE
          [ [| testCase name $( case testAssertion of
            CanParse i1 -> [|
                case $(parseInput i1) of
                    Right _  -> return ()
                    Left err -> assertFailure $ "unexpected decoding error:\n" ++ err
                |]
            CannotParse i1 -> [|
                case $(parseInput i1) of
                    Right _ -> assertFailure "unexpected decoding success"
                    Left _  -> return ()
                |]
            ParseEq exp i1 i2 -> [|
                case ($(parseInput i1), $(parseInput i2)) of
                    (Right v1, Right v2) ->
                        if exp then assertBool "values differ" (v1 == v2)
                               else assertBool "values do not differ" (v1 /= v2)
                    (Left err, _) ->
                        assertFailure $ "unexpected decoding error (left arg):\n" ++ err
                    (_, Left err) ->
                        assertFailure $ "unexpected decoding error (right arg):\n" ++ err
                |]
          )|]
          | CandidTest{..} <- tests
          , let name = "[l" ++ show testLine ++ "]" ++
                     case testDesc of
                         Nothing -> ""
                         Just dsc -> " " ++ T.unpack dsc
          , let parseInput (FromBinary blob) =
                  [| decode @ $(candidTypeQ testType) (BS.pack $(lift (BS.unpack blob))) |]
                parseInput (FromTextual txt) =
                  [| parseValues $(liftString (T.unpack txt)) >>= fromCandidVals @ $(candidTypeQ testType) |]
          ])
        |]
     return (decls, testGroup)

  -- no [d| … |] here, it seems
  let n = mkName "specTests"
  d1 <- sigD n [t|TestTree|]
  d2 <- valD (varP n) (normalB [|
        testGroup "Candid spec tests" $(listE (map return testGroups))
       |]) []
  return $ concat decls ++ [d1, d2]
 )