||| basic test framework using the TAP output format (https://testanything.org) module TAP import public TAP.Options import public Control.Monad.Either import Data.String import Data.List import Data.List.Elem import Data.SnocList import Control.Monad.Reader import Control.Monad.State import Control.ANSI import System import Text.PrettyPrint.Prettyprinter %default total ||| extra info attached to a result (positive or negative). the TAP spec allows ||| any YAML, but this is what you get for now public export Info : Type Info = List (String, String) ||| result of running a test. or not doing so, in the case of `Skip` and `Todo` private data Result = Tried Bool Info | Skip String | Todo String private record TestBase where constructor MakeTest label : String run : IO Result ||| render an `Info` value as a YAML document, including the `---`/`...` ||| delimiters. returns nothing at all if the `Info` is empty export toLines : Info -> List String toLines [] = [] toLines xs = "---" :: concatMap toLines1 xs <+> ["..."] where toLines1 : (String, String) -> List String toLines1 (k, v) = let vs = lines v in if length vs == 1 then ["\{k}: \{v}"] else "\{k}: |" :: map (indent 2) vs ||| represent a value (e.g. an error message) as an `Info` for including in the ||| output. public export interface ToInfo e where toInfo : e -> Info ||| represent a value as a string value in an `Info`. public export interface ToValue e where toValue : e -> String ||| an info of `()` prints nothing export ToInfo () where toInfo () = [] export (ToValue a, Foldable t) => ToInfo (t (String, a)) where toInfo = map (mapSnd toValue) . toList export ToValue String where toValue = id export ToValue (Doc a) where toValue = show . align ||| a test or group of tests export data Test = One TestBase | Group String (List Test) | Note String ||| is this a real test or just a note? export isRealTest : Test -> Bool isRealTest (One _) = True isRealTest (Group _ _) = True isRealTest (Note _) = False private result : ToInfo a => Bool -> a -> IO Result result ok = pure . Tried ok . toInfo ||| promote a lazy value to an IO action that will run it private lazyToIO : Lazy a -> IO a lazyToIO val = primIO $ \w => MkIORes (force val) w ||| a test that can do some IO before returning `Left` for a failure or `Right` ||| for a success export testIO : (ToInfo e, ToInfo a) => String -> EitherT e IO a -> Test testIO label act = One $ MakeTest label $ do case !(runEitherT act) of Right val => result True val Left err => result False err ||| a pure test that returns `Left` for a failure or `Right` for a success export test : (ToInfo e, ToInfo a) => String -> Lazy (Either e a) -> Test test label val = testIO label $ MkEitherT $ lazyToIO val ||| a todo with a reason given, e.g. ||| `todo "" "