blob: 714c6870bf99ef109a8a3a7f2a92b22bbfb16311 [file] [log] [blame]
% Copyright (C) 2009 John Millikin <jmillikin@gmail.com>
%
% 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 3 of the License, or
% 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, see <http://www.gnu.org/licenses/>.
<<Tests.hs>>=
<<copyright>>
{-# LANGUAGE OverloadedStrings #-}
module Main (tests) where
import Test.QuickCheck
import Test.Framework (Test, testGroup)
import qualified Test.Framework as F
import Test.Framework.Providers.QuickCheck2 (testProperty)
import Control.Arrow ((&&&))
import Control.Monad (replicateM)
import qualified Data.Binary.Get as G
import Data.Char (isPrint)
import Data.List (intercalate, isInfixOf)
import Data.Maybe (fromJust, isJust, isNothing)
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import Data.Word (Word8, Word16, Word32, Word64)
import Data.Int (Int16, Int32, Int64)
import DBus.Address
import DBus.Message.Internal
import DBus.Types
import DBus.Wire
import qualified DBus.Introspection as I
@ \section{Tests}
<<Tests.hs>>=
tests :: [Test]
tests = [<<tests>>
]
main :: IO ()
main = F.defaultMain tests
@ \subsection{Types}
<<tests>>=
testGroup "Types"
[ testGroup "Atomic types"
[<<atom tests>>
]
, testGroup "Container types"
[<<container tests>>
]
]
<<atom tests>>=
<<container tests>>=
@ \subsubsection{Atoms}
<<atom tests>>=
testGroup "Bool" $ commonVariantTests (arbitrary :: Gen Bool)
, testGroup "Word8" $ commonVariantTests (arbitrary :: Gen Word8)
, testGroup "Word16" $ commonVariantTests (arbitrary :: Gen Word16)
, testGroup "Word32" $ commonVariantTests (arbitrary :: Gen Word32)
, testGroup "Word64" $ commonVariantTests (arbitrary :: Gen Word64)
, testGroup "Int16" $ commonVariantTests (arbitrary :: Gen Int16)
, testGroup "Int32" $ commonVariantTests (arbitrary :: Gen Int32)
, testGroup "Int64" $ commonVariantTests (arbitrary :: Gen Int64)
, testGroup "Double" $ commonVariantTests (arbitrary :: Gen Double)
, testGroup "String" $ commonVariantTests (arbitrary :: Gen String) ++
[ testProperty "String -> strict Text"
$ \x -> (fromVariant . toVariant) x == (Just $ T.pack x)
, testProperty "String <- strict Text"
$ \x -> (fromVariant . toVariant) x == (Just $ T.unpack x)
, testProperty "String -> lazy Text"
$ \x -> (fromVariant . toVariant) x == (Just $ TL.pack x)
, testProperty "String <- lazy Text"
$ \x -> (fromVariant . toVariant) x == (Just $ TL.unpack x)
, testProperty "Strict Text -> lazy Text"
$ \x -> (fromVariant . toVariant) x == (Just $ TL.pack . T.unpack $ x)
, testProperty "Strict Text <- lazy Text"
$ \x -> (fromVariant . toVariant) x == (Just $ T.pack . TL.unpack $ x)
]
<<Tests.hs>>=
atomicType :: Gen Type
atomicType = elements
[ DBusBoolean
, DBusByte
, DBusWord16
, DBusWord32
, DBusWord64
, DBusInt16
, DBusInt32
, DBusInt64
, DBusDouble
, DBusString
, DBusObjectPath
, DBusSignature
]
containerType :: Gen Type
containerType = do
c <- choose (0,3) :: Gen Int
case c of
0 -> fmap DBusArray arbitrary
1 -> do
kt <- atomicType
vt <- arbitrary
return $ DBusDictionary kt vt
2 -> fmap DBusStructure $ shrinkingGen arbitrary
3 -> return DBusVariant
instance Arbitrary Type where
arbitrary = oneof [atomicType, containerType]
instance Arbitrary Signature where
arbitrary = clampedSize 255 genSig mkSignature' where
genSig = fmap (TL.concat . map typeCode) arbitrary
<<atom tests>>=
, testGroup "Signature" $ commonVariantTests (arbitrary :: Gen Signature) ++
[ testProperty "Signature identity"
$ \x -> (mkSignature . strSignature) x == Just x
, testProperty "Signature show"
$ \x -> show (strSignature x) `isInfixOf` show x
]
<<Tests.hs>>=
instance Arbitrary ObjectPath where
arbitrary = fmap (mkObjectPath' . TL.pack) path' where
c = ['a'..'z'] ++ ['A'..'Z'] ++ ['0'..'9'] ++ "_"
path = fmap (intercalate "/" . ([] :)) genElements
path' = frequency [(1, return "/"), (9, path)]
genElements = sized' 1 (sized' 1 (elements c))
<<atom tests>>=
, testGroup "ObjectPath" $ commonVariantTests (arbitrary :: Gen ObjectPath) ++
[ testProperty "ObjectPath identity"
$ \x -> (mkObjectPath . strObjectPath) x == Just x
]
<<Tests.hs>>=
instance Arbitrary BusName where
arbitrary = clampedSize 255 (oneof [unique, wellKnown]) mkBusName' where
c = ['a'..'z'] ++ ['A'..'Z'] ++ "_-"
c' = c ++ ['0'..'9']
unique = do
elems' <- sized' 2 $ elems c'
return . TL.pack $ ':' : intercalate "." elems'
wellKnown = do
elems' <- sized' 2 $ elems c
return . TL.pack $ intercalate "." elems'
elems start = do
x <- elements start
xs <- sized' 0 (elements c')
return (x:xs)
<<atom tests>>=
, testGroup "BusName" $ commonVariantTests (arbitrary :: Gen BusName) ++
[ testProperty "BusName identity"
$ \x -> (mkBusName . strBusName) x == Just x
]
<<Tests.hs>>=
instance Arbitrary InterfaceName where
arbitrary = clampedSize 255 genName mkInterfaceName' where
c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"
c' = c ++ ['0'..'9']
genName = fmap (TL.pack . intercalate ".") genElements
genElements = sized' 2 genElement
genElement = do
x <- elements c
xs <- sized' 0 (elements c')
return (x:xs)
<<atom tests>>=
, testGroup "InterfaceName" $ commonVariantTests (arbitrary :: Gen InterfaceName) ++
[ testProperty "InterfaceName identity"
$ \x -> (mkInterfaceName . strInterfaceName) x == Just x
]
<<Tests.hs>>=
instance Arbitrary ErrorName where
arbitrary = fmap (mkErrorName' . strInterfaceName) arbitrary
<<atom tests>>=
, testGroup "ErrorName" $ commonVariantTests (arbitrary :: Gen ErrorName) ++
[ testProperty "ErrorName identity"
$ \x -> (mkErrorName . strErrorName) x == Just x
]
<<Tests.hs>>=
instance Arbitrary MemberName where
arbitrary = clampedSize 255 genName mkMemberName' where
c = ['a'..'z'] ++ ['A'..'Z'] ++ "_"
c' = c ++ ['0'..'9']
genName = do
x <- elements c
xs <- sized' 0 (elements c')
return . TL.pack $ (x:xs)
<<atom tests>>=
, testGroup "MemberName" $ commonVariantTests (arbitrary :: Gen MemberName) ++
[ testProperty "MemberName identity"
$ \x -> (mkMemberName . strMemberName) x == Just x
]
@ \subsubsection{Containers}
@ All variable types must obey these properties.
<<Tests.hs>>=
prop_VariantIdentity gen = testProperty "Variant identity" . forAll gen
$ \x -> (fromVariant . toVariant) x == Just x
prop_VariantEquality gen = testProperty "Variant equality" . forAll gen
$ \x y -> (x == y) == (toVariant x == toVariant y)
@ Since all atomic types are also variable, the Variant properties are
added to the set of common Atom tests.
<<common atom tests>>=
, prop_VariantIdentity gen
, prop_VariantEquality gen
<<Tests.hs>>=
commonVariantTests gen =
[ prop_VariantIdentity gen
, prop_VariantEquality gen
]
<<Tests.hs>>=
genVariant :: Type -> Gen Variant
genVariant DBusBoolean = fmap toVariant (arbitrary :: Gen Bool)
genVariant DBusByte = fmap toVariant (arbitrary :: Gen Word8)
genVariant DBusWord16 = fmap toVariant (arbitrary :: Gen Word16)
genVariant DBusWord32 = fmap toVariant (arbitrary :: Gen Word32)
genVariant DBusWord64 = fmap toVariant (arbitrary :: Gen Word64)
genVariant DBusInt16 = fmap toVariant (arbitrary :: Gen Int16)
genVariant DBusInt32 = fmap toVariant (arbitrary :: Gen Int32)
genVariant DBusInt64 = fmap toVariant (arbitrary :: Gen Int64)
genVariant DBusDouble = fmap toVariant (arbitrary :: Gen Double)
genVariant DBusString = fmap toVariant (arbitrary :: Gen String)
genVariant DBusObjectPath = fmap toVariant (arbitrary :: Gen ObjectPath)
genVariant DBusSignature = fmap toVariant (arbitrary :: Gen Signature)
genVariant (DBusArray _) = fmap toVariant (arbitrary :: Gen Array)
genVariant (DBusDictionary _ _) = fmap toVariant (arbitrary :: Gen Dictionary)
genVariant (DBusStructure _) = fmap toVariant (arbitrary :: Gen Structure)
genVariant DBusVariant = fmap toVariant (arbitrary :: Gen Variant)
instance Arbitrary Variant where
arbitrary = arbitrary >>= genVariant
<<Tests.hs>>=
genAtom :: Type -> Gen Variant
genAtom DBusBoolean = fmap toVariant (arbitrary :: Gen Bool)
genAtom DBusByte = fmap toVariant (arbitrary :: Gen Word8)
genAtom DBusWord16 = fmap toVariant (arbitrary :: Gen Word16)
genAtom DBusWord32 = fmap toVariant (arbitrary :: Gen Word32)
genAtom DBusWord64 = fmap toVariant (arbitrary :: Gen Word64)
genAtom DBusInt16 = fmap toVariant (arbitrary :: Gen Int16)
genAtom DBusInt32 = fmap toVariant (arbitrary :: Gen Int32)
genAtom DBusInt64 = fmap toVariant (arbitrary :: Gen Int64)
genAtom DBusDouble = fmap toVariant (arbitrary :: Gen Double)
genAtom DBusString = fmap toVariant (arbitrary :: Gen String)
genAtom DBusObjectPath = fmap toVariant (arbitrary :: Gen ObjectPath)
genAtom DBusSignature = fmap toVariant (arbitrary :: Gen Signature)
<<container tests>>=
testGroup "Variant" $ commonVariantTests (arbitrary :: Gen Variant)
<<Tests.hs>>=
instance Arbitrary Array where
arbitrary = do
-- Only generate arrays of atomic values, as generating
-- containers randomly almost never results in a valid
-- array.
t <- atomicType
xs <- listOf $ genVariant t
return . fromJust $ arrayFromItems t xs
prop_ArrayHomogeneous vs = isJust array == homogeneousTypes where
array = arrayFromItems firstType vs
homogeneousTypes = all (== firstType) types
types = map variantType vs
firstType = if null types
then DBusByte
else head types
<<container tests>>=
, testGroup "Array" $ commonVariantTests (arbitrary :: Gen Array) ++
[ testProperty "Array identity"
$ \x -> Just x == arrayFromItems (arrayType x) (arrayItems x)
, testProperty "Array homogeneity" prop_ArrayHomogeneous
]
<<Tests.hs>>=
instance Arbitrary Dictionary where
arbitrary = do
-- Only generate dictionaries of atomic values, as generating
-- containers randomly almost never results in a valid
-- dictionary.
kt <- atomicType
vt <- atomicType
ks <- listOf $ genAtom kt
vs <- vectorOf (length ks) $ genVariant vt
return . fromJust $ dictionaryFromItems kt vt $ zip ks vs
prop_DictionaryHomogeneous x = all correctType pairs where
pairs = dictionaryItems x
kType = dictionaryKeyType x
vType = dictionaryValueType x
correctType (k, v) = variantType k == kType &&
variantType v == vType
<<container tests>>=
, testGroup "Dictionary" $ commonVariantTests (arbitrary :: Gen Dictionary) ++
[ testProperty "Dictionary identity"
$ \x -> Just x == dictionaryFromItems
(dictionaryKeyType x)
(dictionaryValueType x)
(dictionaryItems x)
, testProperty "Dictionary homogeneity" prop_DictionaryHomogeneous
, testProperty "Dictionary must have atomic keys"
$ \vt -> forAll containerType $ \kt ->
isNothing (dictionaryFromItems kt vt [])
, testProperty "Dictionary <-> Array conversion"
$ \x -> arrayToDictionary (dictionaryToArray x) == Just x
]
<<Tests.hs>>=
instance Arbitrary Structure where
arbitrary = sized $ \n ->
fmap Structure $ shrinkingGen arbitrary
<<container tests>>=
, testGroup "Structure" $ commonVariantTests (arbitrary :: Gen Structure)
@ \subsection{Addresses}
<<Tests.hs>>=
singleTests :: Testable a => [a] -> [Test]
singleTests ts = singleTests' 1 ts where
singleTests' _ [] = []
singleTests' n (t:ts') = plusOptions (testProperty (name n) t)
: singleTests' (n + 1) ts'
total = length ts
options = F.TestOptions Nothing (Just 1) Nothing Nothing
plusOptions = F.plusTestOptions options
name n = "Test " ++ show n ++ "/" ++ show total
<<tests>>=
, testGroup "Addresses"
[ testProperty "Address identity"
$ \x -> mkAddresses (strAddress x) == Just [x]
, testProperty "Multiple addresses"
$ \x y -> let
joined = TL.concat [strAddress x, ";", strAddress y]
in mkAddresses joined == Just [x, y]
, testProperty "Ignore trailing semicolon"
$ \x -> mkAddresses (TL.append (strAddress x) ";") == Just [x]
, testProperty "Ignore trailing comma"
$ \x -> let
hasParams = not . Map.null . addressParameters $ x
parsed = mkAddresses (TL.append (strAddress x) ",")
in hasParams ==> parsed == Just [x]
, testGroup "Valid addresses" $ singleTests
[ isJust . mkAddresses $ ":"
, isJust . mkAddresses $ "a:"
, isJust . mkAddresses $ "a:b=c"
, isJust . mkAddresses $ "a:;"
, isJust . mkAddresses $ "a:;b:"
, isJust . mkAddresses $ "a:b=c,"
]
, testGroup "Invalid addresses" $ singleTests
[ isNothing . mkAddresses $ ""
, isNothing . mkAddresses $ "a"
, isNothing . mkAddresses $ "a:b"
, isNothing . mkAddresses $ "a:b="
, isNothing . mkAddresses $ "a:,"
]
]
<<Tests.hs>>=
instance Arbitrary Address where
arbitrary = genAddress where
optional = ['0'..'9'] ++ ['a'..'z'] ++ ['A'..'Z'] ++ "-_/\\*."
methodChars = filter (flip notElem ":;") ['!'..'~']
keyChars = filter (flip notElem "=;,") ['!'..'~']
genMethod = sized' 0 $ elements methodChars
genParam = do
key <- genKey
value <- genValue
return . concat $ [key, "=", value]
genKey = sized' 1 $ elements keyChars
genValue = oneof [encodedValue, plainValue]
genHex = elements $ ['0'..'9'] ++ ['a'..'f'] ++ ['A'..'F']
encodedValue = do
x1 <- genHex
x2 <- genHex
return ['%', x1, x2]
plainValue = sized' 1 $ elements optional
genParams = do
params <- sized' 0 genParam
let params' = intercalate "," params
extraComma <- if null params
then return ""
else elements ["", ","]
return $ concat [params', extraComma]
genAddress = do
m <- genMethod
params <- genParams
extraSemicolon <- elements ["", ";"]
let addrStr = concat [m, ":", params, extraSemicolon]
let Just [addr] = mkAddresses $ TL.pack addrStr
return addr
@ \subsection{Messages}
<<Tests.hs>>=
instance Arbitrary Serial where
arbitrary = fmap Serial arbitrary
instance Arbitrary Flag where
arbitrary = elements [NoReplyExpected, NoAutoStart]
instance Arbitrary MethodCall where
arbitrary = do
path <- arbitrary
member <- arbitrary
iface <- arbitrary
dest <- arbitrary
flags <- fmap Set.fromList arbitrary
Structure body <- arbitrary
return $ MethodCall path member iface dest flags body
instance Arbitrary MethodReturn where
arbitrary = do
serial <- arbitrary
dest <- arbitrary
flags <- fmap Set.fromList arbitrary
Structure body <- arbitrary
return $ MethodReturn serial dest flags body
instance Arbitrary Error where
arbitrary = do
name <- arbitrary
serial <- arbitrary
dest <- arbitrary
flags <- fmap Set.fromList arbitrary
Structure body <- arbitrary
return $ Error name serial dest flags body
instance Arbitrary Signal where
arbitrary = do
path <- arbitrary
member <- arbitrary
iface <- arbitrary
dest <- arbitrary
flags <- fmap Set.fromList arbitrary
Structure body <- arbitrary
return $ Signal path member iface dest flags body
@ \subsection{Wire format}
<<Tests.hs>>=
isRight :: Either a b -> Bool
isRight = either (const False) (const True)
prop_Unmarshal :: Endianness -> Variant -> Property
prop_Unmarshal e x = valid ==> unmarshaled == Right [x] where
sig = mkSignature . typeCode . variantType $ x
Just sig' = sig
bytes = runMarshal (marshal x) e
Right bytes' = bytes
valid = isJust sig && isRight bytes
unmarshaled = runUnmarshal (unmarshal sig') e bytes'
prop_MarshalMessage e serial msg expected = valid ==> correct where
bytes = marshalMessage e serial msg
Right bytes' = bytes
getBytes = G.getLazyByteString . fromIntegral
unmarshaled = G.runGet (unmarshalMessage getBytes) bytes'
valid = isRight bytes
correct = unmarshaled == Right expected
prop_WireMethodCall e serial msg = prop_MarshalMessage e serial msg
$ ReceivedMethodCall serial Nothing msg
prop_WireMethodReturn e serial msg = prop_MarshalMessage e serial msg
$ ReceivedMethodReturn serial Nothing msg
prop_WireError e serial msg = prop_MarshalMessage e serial msg
$ ReceivedError serial Nothing msg
prop_WireSignal e serial msg = prop_MarshalMessage e serial msg
$ ReceivedSignal serial Nothing msg
<<tests>>=
, testGroup "Wire format"
[ testProperty "Marshal -> Ummarshal" prop_Unmarshal
, testGroup "Messages"
[ testProperty "Method calls" prop_WireMethodCall
, testProperty "Method returns" prop_WireMethodReturn
, testProperty "Errors" prop_WireError
, testProperty "Signals" prop_WireSignal
]
]
<<Tests.hs>>=
instance Arbitrary Endianness where
arbitrary = elements [LittleEndian, BigEndian]
@ \subsection{Introspection}
<<tests>>=
, testGroup "Introspection"
[ testProperty "Generate -> Parse"
$ \x@(I.Object path _ _) -> let
xml = I.toXML x
Just xml' = xml
parsed = I.fromXML path xml'
in isJust xml ==> I.fromXML path xml' == Just x
]
<<Tests.hs>>=
subObject :: ObjectPath -> Gen I.Object
subObject parentPath = sized $ \n -> resize (min n 4) $ do
let nonRoot = do
x <- arbitrary
case strObjectPath x of
"/" -> nonRoot
x' -> return x'
thisPath <- nonRoot
let path' = case strObjectPath parentPath of
"/" -> thisPath
x -> TL.append x thisPath
let path = mkObjectPath' path'
ifaces <- arbitrary
children <- shrinkingGen . listOf . subObject $ path
return $ I.Object path ifaces children
instance Arbitrary I.Object where
arbitrary = arbitrary >>= subObject
instance Arbitrary I.Interface where
arbitrary = do
name <- arbitrary
methods <- arbitrary
signals <- arbitrary
properties <- arbitrary
return $ I.Interface name methods signals properties
instance Arbitrary I.Method where
arbitrary = do
name <- arbitrary
inParams <- arbitrary
outParams <- arbitrary
return $ I.Method name inParams outParams
instance Arbitrary I.Signal where
arbitrary = do
name <- arbitrary
params <- arbitrary
return $ I.Signal name params
singleType :: Gen Signature
singleType = do
t <- arbitrary
case mkSignature $ typeCode t of
Just x -> return x
Nothing -> singleType
instance Arbitrary I.Parameter where
arbitrary = do
name <- listOf $ arbitrary `suchThat` isPrint
sig <- singleType
return $ I.Parameter (TL.pack name) sig
instance Arbitrary I.Property where
arbitrary = do
name <- listOf $ arbitrary `suchThat` isPrint
sig <- singleType
access <- elements
[[], [I.Read], [I.Write],
[I.Read, I.Write]]
return $ I.Property (TL.pack name) sig access
@ \subsection{Other instances}
<<Tests.hs>>=
iexp :: Integral a => a -> a -> a
iexp x y = floor $ fromIntegral x ** fromIntegral y
instance Arbitrary Word8 where
arbitrary = fmap fromIntegral gen where
gen = choose (0, max') :: Gen Integer
max' = iexp 2 8 - 1
instance Arbitrary Word16 where
arbitrary = fmap fromIntegral gen where
gen = choose (0, max') :: Gen Integer
max' = iexp 2 16 - 1
instance Arbitrary Word32 where
arbitrary = fmap fromIntegral gen where
gen = choose (0, max') :: Gen Integer
max' = iexp 2 32 - 1
instance Arbitrary Word64 where
arbitrary = fmap fromIntegral gen where
gen = choose (0, max') :: Gen Integer
max' = iexp 2 64 - 1
instance Arbitrary Int16 where
arbitrary = fmap fromIntegral gen where
gen = choose (0, max') :: Gen Integer
max' = iexp 2 16 - 1
instance Arbitrary Int32 where
arbitrary = fmap fromIntegral gen where
gen = choose (0, max') :: Gen Integer
max' = iexp 2 32 - 1
instance Arbitrary Int64 where
arbitrary = fmap fromIntegral gen where
gen = choose (0, max') :: Gen Integer
max' = iexp 2 64 - 1
instance Arbitrary T.Text where
arbitrary = fmap T.pack arbitrary
instance Arbitrary TL.Text where
arbitrary = fmap TL.pack arbitrary
sized' :: Int -> Gen a -> Gen [a]
sized' atLeast g = sized $ \n -> do
n' <- choose (atLeast, max atLeast n)
replicateM n' g
clampedSize :: Arbitrary a => Int64 -> Gen TL.Text -> (TL.Text -> a) -> Gen a
clampedSize maxSize gen f = do
s <- gen
if TL.length s > maxSize
then shrinkingGen arbitrary
else return . f $ s
shrinkingGen :: Gen a -> Gen a
shrinkingGen gen = sized $ \n -> if n > 0 then
resize (n `div` 2) gen
else gen