1 {-# LANGUAGE TemplateHaskell #-}
3 {-| Unittest helpers for Haskell components
9 Copyright (C) 2011, 2012 Google Inc.
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
16 This program is distributed in the hope that it will be useful, but
17 WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
28 module Test.Ganeti.TestHelper
33 import Control.Applicative
34 import Data.List (stripPrefix, isPrefixOf)
35 import Data.Maybe (fromMaybe)
37 import Test.Framework.Providers.HUnit
38 import Test.Framework.Providers.QuickCheck2
39 import Test.HUnit (Assertion)
40 import Test.QuickCheck
41 import Language.Haskell.TH
43 -- | Test property prefix.
47 -- | Test case prefix.
51 -- | Tries to drop a prefix from a string.
52 simplifyName :: String -> String -> String
53 simplifyName pfx string = fromMaybe string (stripPrefix pfx string)
55 -- | Builds a test from a QuickCheck property.
56 runProp :: Testable prop => String -> prop -> Test
57 runProp = testProperty . simplifyName propPrefix
59 -- | Builds a test for a HUnit test case.
60 runCase :: String -> Assertion -> Test
61 runCase = testCase . simplifyName casePrefix
63 -- | Runs the correct test provider for a given test, based on its
64 -- name (not very nice, but...).
67 let str = nameBase name
69 strE = litE (StringL str)
71 _ | propPrefix `isPrefixOf` str -> [| runProp $strE $nameE |]
72 | casePrefix `isPrefixOf` str -> [| runCase $strE $nameE |]
73 | otherwise -> fail $ "Unsupported test function name '" ++ str ++ "'"
75 -- | Convert slashes in a name to underscores.
76 mapSlashes :: String -> String
77 mapSlashes = map (\c -> if c == '/' then '_' else c)
79 -- | Builds a test suite.
80 testSuite :: String -> [Name] -> Q [Dec]
81 testSuite tsname tdef = do
82 let fullname = mkName $ "test" ++ mapSlashes tsname
83 tests <- mapM run tdef
84 sigtype <- [t| (String, [Test]) |]
85 return [ SigD fullname sigtype
86 , ValD (VarP fullname) (NormalB (TupE [LitE (StringL tsname),
90 -- | Builds an arbitrary value for a given constructor. This doesn't
91 -- use the actual types of the fields, since we expect arbitrary
92 -- instances for all of the types anyway, we only care about the
94 mkConsArbitrary :: (Name, [a]) -> Exp
95 mkConsArbitrary (name, types) =
96 let infix_arb a = InfixE (Just a) (VarE '(<*>)) (Just (VarE 'arbitrary))
97 constr = AppE (VarE 'pure) (ConE name)
98 in foldl (\a _ -> infix_arb a) constr types
100 -- | Extracts the name and the types from a constructor.
101 conInfo :: Con -> (Name, [Type])
102 conInfo (NormalC name t) = (name, map snd t)
103 conInfo (RecC name t) = (name, map (\(_, _, x) -> x) t)
104 conInfo (InfixC t1 name t2) = (name, [snd t1, snd t2])
105 conInfo (ForallC _ _ subcon) = conInfo subcon
107 -- | Builds an arbitrary instance for a regular data type (i.e. not Bounded).
108 mkRegularArbitrary :: Name -> [Con] -> Q [Dec]
109 mkRegularArbitrary name cons = do
111 [] -> fail "Can't make Arbitrary instance for an empty data type"
112 [x] -> return $ mkConsArbitrary (conInfo x)
113 xs -> appE (varE 'oneof) $
114 listE (map (return . mkConsArbitrary . conInfo) xs)
115 return [InstanceD [] (AppT (ConT ''Arbitrary) (ConT name))
116 [ValD (VarP 'arbitrary) (NormalB expr) []]]
118 -- | Builds a default Arbitrary instance for a type. This requires
119 -- that all members are of types that already have Arbitrary
120 -- instances, and that the arbitrary instances are well behaved
121 -- (w.r.t. recursive data structures, or similar concerns). In that
122 -- sense, this is not appropriate for all data types, just those that
123 -- are simple but very repetitive or have many simple fields.
124 genArbitrary :: Name -> Q [Dec]
125 genArbitrary name = do
128 TyConI (DataD _ _ _ cons _) ->
129 mkRegularArbitrary name cons
130 TyConI (NewtypeD _ _ _ con _) ->
131 mkRegularArbitrary name [con]
132 TyConI (TySynD _ _ (ConT tn)) -> genArbitrary tn
133 _ -> fail $ "Invalid type in call to genArbitrary for " ++ show name
134 ++ ", type " ++ show r