| {-# LANGUAGE TemplateHaskell #-} |
| |
| {-| Unittest helpers for TemplateHaskell components. |
| |
| -} |
| |
| {- |
| |
| Copyright (C) 2011, 2012, 2013 Google Inc. |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program; if not, write to the Free Software |
| Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301, USA. |
| |
| -} |
| |
| module Test.Ganeti.TestHelper |
| ( testSuite |
| , genArbitrary |
| ) where |
| |
| import Control.Applicative |
| import Data.List (stripPrefix, isPrefixOf) |
| import Data.Maybe (fromMaybe) |
| import Test.Framework |
| import Test.Framework.Providers.HUnit |
| import Test.Framework.Providers.QuickCheck2 |
| import Test.HUnit (Assertion) |
| import Test.QuickCheck |
| import Language.Haskell.TH |
| |
| -- | Test property prefix. |
| propPrefix :: String |
| propPrefix = "prop_" |
| |
| -- | Test case prefix. |
| casePrefix :: String |
| casePrefix = "case_" |
| |
| -- | Test case prefix without underscore. |
| case2Pfx :: String |
| case2Pfx = "case" |
| |
| -- | Tries to drop a prefix from a string. |
| simplifyName :: String -> String -> String |
| simplifyName pfx string = fromMaybe string (stripPrefix pfx string) |
| |
| -- | Builds a test from a QuickCheck property. |
| runProp :: Testable prop => String -> prop -> Test |
| runProp = testProperty . simplifyName propPrefix |
| |
| -- | Builds a test for a HUnit test case. |
| runCase :: String -> Assertion -> Test |
| runCase = testCase . simplifyName casePrefix |
| |
| -- | Runs the correct test provider for a given test, based on its |
| -- name (not very nice, but...). |
| run :: Name -> Q Exp |
| run name = |
| let str = nameBase name |
| nameE = varE name |
| strE = litE (StringL str) |
| in case () of |
| _ | propPrefix `isPrefixOf` str -> [| runProp $strE $nameE |] |
| | casePrefix `isPrefixOf` str -> [| runCase $strE $nameE |] |
| | case2Pfx `isPrefixOf` str -> |
| [| (testCase . simplifyName case2Pfx) $strE $nameE |] |
| | otherwise -> fail $ "Unsupported test function name '" ++ str ++ "'" |
| |
| -- | Convert slashes in a name to underscores. |
| mapSlashes :: String -> String |
| mapSlashes = map (\c -> if c == '/' then '_' else c) |
| |
| -- | Builds a test suite. |
| testSuite :: String -> [Name] -> Q [Dec] |
| testSuite tsname tdef = do |
| let fullname = mkName $ "test" ++ mapSlashes tsname |
| tests <- mapM run tdef |
| sigtype <- [t| Test |] |
| body <- [| testGroup $(litE $ stringL tsname) $(return $ ListE tests) |] |
| return [ SigD fullname sigtype |
| , ValD (VarP fullname) (NormalB body) [] |
| ] |
| |
| -- | Builds an arbitrary value for a given constructor. This doesn't |
| -- use the actual types of the fields, since we expect arbitrary |
| -- instances for all of the types anyway, we only care about the |
| -- number of fields. |
| mkConsArbitrary :: (Name, [a]) -> Exp |
| mkConsArbitrary (name, types) = |
| let infix_arb a = InfixE (Just a) (VarE '(<*>)) (Just (VarE 'arbitrary)) |
| constr = AppE (VarE 'pure) (ConE name) |
| in foldl (\a _ -> infix_arb a) constr types |
| |
| -- | Extracts the name and the types from a constructor. |
| conInfo :: Con -> (Name, [Type]) |
| conInfo (NormalC name t) = (name, map snd t) |
| conInfo (RecC name t) = (name, map (\(_, _, x) -> x) t) |
| conInfo (InfixC t1 name t2) = (name, [snd t1, snd t2]) |
| conInfo (ForallC _ _ subcon) = conInfo subcon |
| |
| -- | Builds an arbitrary instance for a regular data type (i.e. not Bounded). |
| mkRegularArbitrary :: Name -> [Con] -> Q [Dec] |
| mkRegularArbitrary name cons = do |
| expr <- case cons of |
| [] -> fail "Can't make Arbitrary instance for an empty data type" |
| [x] -> return $ mkConsArbitrary (conInfo x) |
| xs -> appE (varE 'oneof) $ |
| listE (map (return . mkConsArbitrary . conInfo) xs) |
| return [InstanceD [] (AppT (ConT ''Arbitrary) (ConT name)) |
| [ValD (VarP 'arbitrary) (NormalB expr) []]] |
| |
| -- | Builds a default Arbitrary instance for a type. This requires |
| -- that all members are of types that already have Arbitrary |
| -- instances, and that the arbitrary instances are well behaved |
| -- (w.r.t. recursive data structures, or similar concerns). In that |
| -- sense, this is not appropriate for all data types, just those that |
| -- are simple but very repetitive or have many simple fields. |
| genArbitrary :: Name -> Q [Dec] |
| genArbitrary name = do |
| r <- reify name |
| case r of |
| TyConI (DataD _ _ _ cons _) -> |
| mkRegularArbitrary name cons |
| TyConI (NewtypeD _ _ _ con _) -> |
| mkRegularArbitrary name [con] |
| TyConI (TySynD _ _ (ConT tn)) -> genArbitrary tn |
| _ -> fail $ "Invalid type in call to genArbitrary for " ++ show name |
| ++ ", type " ++ show r |