I have a toy program:
$ cat a.hs
main = putStrLn "Toy example"
$ runghc a.hs
Toy example
Let's add some Template Haskell to it:
$ cat b.hs
{-# LANGUAGE TemplateHaskell #-}
id [d|
main = putStrLn "Toy example"
|]
$ runghc b.hs
b.hs:3:0: parse error (possibly incorrect indentation)
Right then, let's fix the indentation:
$ cat c.hs
{-# LANGUAGE TemplateHaskell #-}
id [d|
main = putStrLn "Toy example"
|]
$ runghc c.hs
Toy example
A single space is enough, but I do have to indent both trailing lines.
Can I avoid having to indent most of my module? (My Real Modules have much more than a single line of code.) (And without using { ; ; }
notation?)
I do want all of the module declarations to be captured in the quotation — in normal code I can replace (...)
with $ ...
, is there some equivalent of [d|...|]
that would let me avoid the close brackets and also the indenting?
Or is there some way module A can say that the top-level declarations of any module B that A is imported into are automatically processed by a function A exports?
Notes:
id
— it scans the declarations for variable names that start prop_
, and builds a test suite containing them. Is there some other pure Haskell way I could do this instead, without directly munging source files?b.hs:3:1
— but the behaviour is otherwise identical.If the test suite is for QuickCheck, i advise you to use the new All
module instead:
http://hackage.haskell.org/packages/archive/QuickCheck/2.4.1.1/doc/html/Test-QuickCheck-All.html
It does the same thing except it fetches the names of properties by accessing the file system and parsing the file that the splice resides in (if you are using some other test framework, you can still use the same approach).
If you really want to quote the entire file, you could use a quasi-quoter instead (which does not require indentation). You can easily build your quoter on haskell-src-meta, but i advice against this approach because it will not support some Haskell features and it will probably give poor error messages.
Aggregating test suits is a difficult problem, one could probably extend the name gathering routine to somehow follow imports but it's a lot of work. Here's a workaround:
You can use this modified version of forAllProperties
:
import Test.QuickCheck
import Test.QuickCheck.All
import Language.Haskell.TH
import Data.Char
import Data.List
import Control.Monad
allProperties :: Q Exp -- :: [(String,Property)]
allProperties = do
Loc { loc_filename = filename } <- location
when (filename == "<interactive>") $ error "don't run this interactively"
ls <- runIO (fmap lines (readFile filename))
let prefixes = map (takeWhile (\c -> isAlphaNum c || c == '_') . dropWhile (\c -> isSpace c || c == '>')) ls
idents = nubBy (\x y -> snd x == snd y) (filter (("prop_" `isPrefixOf`) . snd) (zip [1..] prefixes))
quickCheckOne :: (Int, String) -> Q [Exp]
quickCheckOne (l, x) = do
exists <- return False `recover` (reify (mkName x) >> return True)
if exists then sequence [ [| ($(stringE $ x ++ " on " ++ filename ++ ":" ++ show l),
property $(mono (mkName x))) |] ]
else return []
[|$(fmap (ListE . concat) (mapM quickCheckOne idents)) |]
You also need the function runQuickCheckAll
which is not exported from All:
runQuickCheckAll :: [(String, Property)] -> (Property -> IO Result) -> IO Bool
runQuickCheckAll ps qc =
fmap and . forM ps $ \(xs, p) -> do
putStrLn $ "=== " ++ xs ++ " ==="
r <- qc p
return $ case r of
Success { } -> True
Failure { } -> False
NoExpectedFailure { } -> False
In each test module you now define
propsN = $allProperties
where N
is some number or other unique identifier (or you could use the same name and use qualified names in the step below).
In your main test suite you define
props :: [(String,Property)]
props = concat [props1, props2 ... propsN]
If you really want to avoid adding a list member for each module, you could make a TH script that generates this list.
To run all your tests you simply say
runTests = runQuickCheckAll quickCheckResult props
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With