Statistics
| Branch: | Tag: | Revision:

root / htest / Test / Ganeti / TestHelper.hs @ 8492daa3

History | View | Annotate | Download (4.7 kB)

1
{-# LANGUAGE TemplateHaskell #-}
2

    
3
{-| Unittest helpers for Haskell components
4

    
5
-}
6

    
7
{-
8

    
9
Copyright (C) 2011, 2012 Google Inc.
10

    
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.
15

    
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.
20

    
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
24
02110-1301, USA.
25

    
26
-}
27

    
28
module Test.Ganeti.TestHelper
29
  ( testSuite
30
  , genArbitrary
31
  ) where
32

    
33
import Control.Applicative
34
import Data.List (stripPrefix, isPrefixOf)
35
import Data.Maybe (fromMaybe)
36
import Test.Framework
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
42

    
43
-- | Test property prefix.
44
propPrefix :: String
45
propPrefix = "prop_"
46

    
47
-- | Test case prefix.
48
casePrefix :: String
49
casePrefix = "case_"
50

    
51
-- | Tries to drop a prefix from a string.
52
simplifyName :: String -> String -> String
53
simplifyName pfx string = fromMaybe string (stripPrefix pfx string)
54

    
55
-- | Builds a test from a QuickCheck property.
56
runProp :: Testable prop => String -> prop -> Test
57
runProp = testProperty . simplifyName propPrefix
58

    
59
-- | Builds a test for a HUnit test case.
60
runCase :: String -> Assertion -> Test
61
runCase = testCase . simplifyName casePrefix
62

    
63
-- | Runs the correct test provider for a given test, based on its
64
-- name (not very nice, but...).
65
run :: Name -> Q Exp
66
run name =
67
  let str = nameBase name
68
      nameE = varE name
69
      strE = litE (StringL str)
70
  in case () of
71
       _ | propPrefix `isPrefixOf` str -> [| runProp $strE $nameE |]
72
         | casePrefix `isPrefixOf` str -> [| runCase $strE $nameE |]
73
         | otherwise -> fail $ "Unsupported test function name '" ++ str ++ "'"
74

    
75
-- | Convert slashes in a name to underscores.
76
mapSlashes :: String -> String
77
mapSlashes = map (\c -> if c == '/' then '_' else c)
78

    
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),
87
                                                ListE tests])) []
88
         ]
89

    
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
93
-- number of fields.
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
99

    
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
106

    
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
110
  expr <- case cons of
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) []]]
117

    
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
126
  r <- reify name
127
  case r of
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