packages feed

zeolite-lang-0.13.0.0: src/Test/Common.hs

{- -----------------------------------------------------------------------------
Copyright 2019-2021 Kevin P. Barry

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
----------------------------------------------------------------------------- -}

-- Author: Kevin P. Barry [ta0kira@gmail.com]

module Test.Common (
  checkDefinesFail,
  checkDefinesSuccess,
  checkEquals,
  checkParseError,
  checkParseMatch,
  checkTypeFail,
  checkTypeSuccess,
  containsAtLeast,
  containsAtMost,
  containsExactly,
  containsNoDuplicates,
  forceParse,
  loadFile,
  parseFilterMap,
  parseTheTest,
  readMulti,
  readSingle,
  readSingleWith,
  runAllTests,
  showParams,
) where

import Control.Monad (when)
import Data.Either
import Data.List
import System.Exit
import System.FilePath
import System.IO
import Text.Regex.TDFA
import qualified Data.Map as Map
import qualified Data.Set as Set

import Base.CompilerError
import Base.CompilerMessage
import Base.TrackedErrors
import Parser.Common
import Parser.TextParser
import Parser.TypeInstance ()
import Types.TypeInstance


runAllTests :: [IO (TrackedErrors ())] -> IO ()
runAllTests ts = do
  results <- sequence ts
  let (es,ps) = partitionEithers $ zipWith numberError ([1..] :: [Int]) results
  mapM_ (\(n,e) -> hPutStr stderr ("Test " ++ show n ++ ": " ++ show e ++ "\n")) es
  hPutStr stderr $ show (length ps) ++ " tests passed + " ++
                   show (length es) ++ " tests failed\n"
  when (not $ null es) exitFailure

numberError :: a -> TrackedErrors b -> Either (a,CompilerMessage) b
numberError n c
  | isCompilerError c = Left (n,getCompilerError c)
  | otherwise        = Right (getCompilerSuccess c)

forceParse :: ParseFromSource a => String -> a
forceParse s = getCompilerSuccess $ runTextParser sourceParser "(string)" s

readSingle :: ParseFromSource a => String -> String -> TrackedErrors a
readSingle  = readSingleWith sourceParser

readSingleWith :: TextParser a -> String -> String -> TrackedErrors a
readSingleWith p = runTextParser (between nullParse endOfDoc p)

readMulti :: ParseFromSource a => String -> String -> TrackedErrors [a]
readMulti f s = runTextParser (between optionalSpace endOfDoc (sepBy sourceParser optionalSpace)) f s

parseFilterMap :: [(String,[String])] -> TrackedErrors ParamFilters
parseFilterMap pa = do
  pa2 <- mapErrorsM parseFilters pa
  return $ Map.fromList pa2
  where
    parseFilters (n,fs) = do
      fs2 <- mapErrorsM (readSingle "(string)") fs
      return (ParamName n,fs2)

parseTheTest :: ParseFromSource a => [(String,[String])] -> [String] -> TrackedErrors ([a],ParamFilters)
parseTheTest pa xs = do
  ts <- mapErrorsM (readSingle "(string)") xs
  pa2 <- parseFilterMap pa
  return (ts,pa2)

showParams :: [(String,[String])] -> String
showParams pa = "[" ++ intercalate "," (concat $ map expand pa) ++ "]" where
  expand (n,ps) = map (\p -> n ++ " " ++ p) ps

checkTypeSuccess :: TypeResolver r => r -> [(String,[String])] -> String -> TrackedErrors ()
checkTypeSuccess r pa x = do
  ([t],pa2) <- parseTheTest pa [x]
  check $ validateGeneralInstance r pa2 t
  where
    prefix = x ++ " " ++ showParams pa
    check x2 = x2 <!! prefix ++ ":"

checkTypeFail :: TypeResolver r => r -> [(String,[String])] -> String -> TrackedErrors ()
checkTypeFail r pa x = do
  ([t],pa2) <- parseTheTest pa [x]
  check $ validateGeneralInstance r pa2 t
  where
    prefix = x ++ " " ++ showParams pa
    check :: TrackedErrors a -> TrackedErrors ()
    check c
      | isCompilerError c = return ()
      | otherwise = compilerErrorM $ prefix ++ ": Expected failure\n"

checkDefinesSuccess :: TypeResolver r => r -> [(String,[String])] -> String -> TrackedErrors ()
checkDefinesSuccess r pa x = do
  ([t],pa2) <- parseTheTest pa [x]
  check $ validateDefinesInstance r pa2 t
  where
    prefix = x ++ " " ++ showParams pa
    check x2 = x2 <!! prefix ++ ":"

checkDefinesFail :: TypeResolver r => r -> [(String,[String])] -> String -> TrackedErrors ()
checkDefinesFail r pa x = do
  ([t],pa2) <- parseTheTest pa [x]
  check $ validateDefinesInstance r pa2 t
  where
    prefix = x ++ " " ++ showParams pa
    check :: TrackedErrors a -> TrackedErrors ()
    check c
      | isCompilerError c = return ()
      | otherwise = compilerErrorM $ prefix ++ ": Expected failure\n"

containsExactly :: (Ord a, Show a) => [a] -> [a] -> TrackedErrors ()
containsExactly actual expected = do
  containsNoDuplicates actual
  containsAtLeast actual expected
  containsAtMost actual expected

containsNoDuplicates :: (Ord a, Show a) => [a] -> TrackedErrors ()
containsNoDuplicates expected =
  (mapErrorsM_ checkSingle $ group $ sort expected) <!! show expected
  where
    checkSingle xa@(x:_:_) =
      compilerErrorM $ "Item " ++ show x ++ " occurs " ++ show (length xa) ++ " times"
    checkSingle _ = return ()

containsAtLeast :: (Ord a, Show a) => [a] -> [a] -> TrackedErrors ()
containsAtLeast actual expected =
  (mapErrorsM_ (checkInActual $ Set.fromList actual) expected) <!!
        show actual ++ " (actual) vs. " ++ show expected ++ " (expected)"
  where
    checkInActual va v =
      if v `Set.member` va
         then return ()
         else compilerErrorM $ "Item " ++ show v ++ " was expected but not present"

containsAtMost :: (Ord a, Show a) => [a] -> [a] -> TrackedErrors ()
containsAtMost actual expected =
  (mapErrorsM_ (checkInExpected $ Set.fromList expected) actual) <!!
        show actual ++ " (actual) vs. " ++ show expected ++ " (expected)"
  where
    checkInExpected va v =
      if v `Set.member` va
         then return ()
         else compilerErrorM $ "Item " ++ show v ++ " is unexpected"

checkEquals :: (Eq a, Show a) => a -> a -> TrackedErrors ()
checkEquals actual expected
  | actual == expected = return ()
  | otherwise = compilerErrorM $ "Expected " ++ show expected ++ " but got " ++ show actual

loadFile :: String -> IO String
loadFile f = readFile ("src" </> "Test" </> f)

checkParseMatch :: Show a => String -> TextParser a -> (a -> Bool) -> IO (TrackedErrors ())
checkParseMatch s p m = return $ do
  let parsed = readSingleWith p "(string)" s
  check parsed
  e <- parsed
  when (not $ m e) $
    compilerErrorM $ "No match in '" ++ s ++ "':\n" ++ show e
  where
    check c
      | isCompilerError c = compilerErrorM $ "Parse '" ++ s ++ "':\n" ++ show (getCompilerError c)
      | otherwise = return ()

checkParseError :: Show a => String -> String -> TextParser a -> IO (TrackedErrors ())
checkParseError s m p = return $ do
  let parsed = readSingleWith p "(string)" s
  check parsed
  where
    check c
      | isCompilerError c = do
          let text = show (getCompilerError c)
          when (not $ text =~ m) $
            compilerErrorM $ "Expected pattern " ++ show m ++ " in error output but got\n" ++ text
      | otherwise =
          compilerErrorM $ "Expected write failure but got\n" ++ show (getCompilerSuccess c)