{-# Language NoMonomorphismRestriction #-}
{-# Language OverloadedStrings #-}
-- module Tests where
import Control.Monad
import Data.Either
import Data.List
import Data.Maybe
import Data.Text (Text)
import Data.XML.Pickle
import Data.XML.Types
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck
import Test.QuickCheck
import qualified Data.Text as Text
-- import Data.Monoid
-- import Test.QuickCheck
-- import Test.Framework
-- import Test.Framework.Providers.QuickCheck2
main =
defaultMain $ testGroup "all"
[ testProperty "prop_showXpPrim" prop_showXpPrim
, testProperty "prop_xpUnit" prop_xpUnit
, testProperty "prop_xpAttribute" prop_xpAttribute
, testProperty "prop_xpElemVerbatimUnpickle" prop_xpElemVerbatimUnpickle
, testProperty "prop_xpElemVerbatimPickle" prop_xpElemVerbatimPickle
, testProperty "prop_xpElemTextPickle" prop_xpElemTextPickle
, testProperty "prop_xpElemBlankUnpickleBlanks"
prop_xpElemBlankUnpickleBlanks
, testProperty "prop_xpElemBlankUnpickleNonBlanks"
prop_xpElemBlankUnpickleNonBlanks
, testProperty "prop_xpElemBlankPickle" prop_xpElemBlankPickle
, testProperty "prop_xpElemExistsPickleTrue" prop_xpElemExistsPickleTrue
, testProperty "prop_xpElemExistsPickleFalse" prop_xpElemExistsPickleFalse
, testProperty "prop_xpElemExistsUnpickleBlanks"
prop_xpElemExistsUnpickleBlanks
, testProperty "prop_xpElemExistsUnpickleNonBlanks"
prop_xpElemExistsUnpickleNonBlanks
, testProperty "prop_xpBool" prop_xpBool
, testProperty "prop_flattenContent" prop_flattenContent
, testCase "xpBoolUnpickleTrue" $ assertBool "" xpBoolUnpickleTrue
, testCase "xpBoolUnpickleFalse" $ assertBool "" xpBoolUnpickleFalse
, testCase "xpBoolUnpickle1" $ assertBool "" xpBoolUnpickle1
, testCase "xpBoolUnpickle0" $ assertBool "" xpBoolUnpickle0
, testCase "xpBoolPickleTrue" $ assertBool "" xpBoolPickleTrue
, testCase "xpBoolPickleFalse" $ assertBool "" xpBoolPickleFalse
]
-- Generates an arbitrary XML name. Namespaces (not part of XML 1.0) are not
-- generated.
instance Arbitrary Name where
arbitrary = do
localName <- xmlString
prefix <- oneof [return $ Nothing, xmlString >>= return . Just]
return Name { nameLocalName = localName
, nameNamespace = Nothing
, namePrefix = prefix }
where
xmlString =
-- We want to include a suitable set of non-ASCII characters here.
let range = ['A'..'Z'] ++ ['a'..'z'] ++ ['0'..'9'] ++ "_" in do
char <- elements range
string <- listOf $ elements $ "-." ++ range
return . Text.pack $ char:string
-- We're using the printable ASCII characters. The commented list includes the
-- printable characters of the ISO 8859-1 extension.
instance Arbitrary Content where
arbitrary = do
string <- listOf $ elements ['\0032'..'\0126'] -- ['\0160'..'\0255']
return $ ContentText $ Text.pack string
-- Generator for XML elements.
instance Arbitrary Element where
arbitrary = do
name <- arbitrary
attributes <- arbitrary
nodes <- arbitrary
return Element { elementName = name
, elementAttributes = attributes
, elementNodes = nodes }
-- Generator for nodes. We resize child elements to avoid infinite structures.
instance Arbitrary Node where
arbitrary = oneof [ sized (\n -> resize (n `div` 2) $ do
element <- arbitrary
return $ NodeElement element)
, arbitrary >>= \content -> return $ NodeContent content
, arbitrary >>= \comment -> return $ NodeComment comment
]
-- Our `Text' generator is simply a wrapper around the `String' generator.
instance Arbitrary Text where
arbitrary = liftM Text.pack arbitrary
-- Generator for an XML element without attributes and with child nodes that
-- are considered empty (see `emptyNodeGen').
emptyElementGen :: Gen Element
emptyElementGen = do
name <- arbitrary
nodes <- listOf emptyNodeGen
return Element { elementName = name
, elementAttributes = []
, elementNodes = nodes }
-- Generator for child nodes that are considered empty: "" strings and comments.
emptyNodeGen :: Gen Node
emptyNodeGen = oneof [ return $ NodeContent (ContentText "")
, arbitrary >>= \comment -> return $ NodeComment comment ]
-- Generator for XML elements with only text.
elementWithTextGen :: Gen Element
elementWithTextGen = do
name <- arbitrary
nodes <- listOf nodeWithTextGen
return Element { elementName = name
, elementAttributes = []
, elementNodes = nodes }
-- Generator for XML nodes with only text content and comments.
nodeWithTextGen :: Gen Node
nodeWithTextGen = do
content0 <- arbitrary
content1 <- arbitrary
oneof [return $ NodeContent content0, return $ NodeComment content1]
-- Verify that the pickled show value is the same as the show value itself.
prop_showXpPrim :: Integer -> Bool
prop_showXpPrim a = Text.unpack (pickle xpPrim a) == show a
-- Verify that `xpUnit' never fails.
prop_xpUnit :: [Integer] -> Bool
prop_xpUnit as = (case unpickle xpUnit as of
Right () -> True
_ -> False) && null (pickle xpUnit ())
-- Given a list of attributes, every attribute should be present upon unpickle.
prop_xpAttribute :: [Attribute] -> Bool
prop_xpAttribute attributes =
let result = map (\(n, c) -> let t = contentToText c
in unpickle (xpAttribute_ n t) attributes)
attributes
in length result == length (rights (result))
-- Provided a list of Nodes containing at least one Element, we verify that
-- xpElemVerbatim always finds the first element.
prop_xpElemVerbatimUnpickle :: [Node] -> Property
prop_xpElemVerbatimUnpickle ns =
let ne = find (\n -> case n of NodeElement _ -> True; _ -> False) ns
in isJust ne ==> let NodeElement e = fromJust ne
in case unpickle xpElemVerbatim ns of
Right e_ -> e == e_
Left _ -> False
-- Verify that xpElemVerbatim pickling an element returns only that element.
prop_xpElemVerbatimPickle :: Element -> Bool
prop_xpElemVerbatimPickle e = pickle xpElemVerbatim e == [NodeElement e]
-- Verify that xpElemText pickling text content returns that content properly.
prop_xpElemTextPickle :: Name -> Text -> Bool
prop_xpElemTextPickle n t =
let nodes = pickle (xpElemText n) t
in nodes == [ NodeElement
Element { elementName = n
, elementAttributes = []
, elementNodes = [NodeContent (ContentText t)]
} ]
-- Pickling of blanks should work.
prop_xpElemBlankUnpickleBlanks :: Property
prop_xpElemBlankUnpickleBlanks =
forAll (listOf $ oneof [emptyNodeGen, arbitrary]) $ \nodes ->
let blanks = filter (isEmpty . unwrapElement) (filterNodeElements nodes)
in all (\e -> case unpickle (xpElemBlank (elementName e)) nodes of
Right () -> True
r -> False) (filterMapNodeElements blanks)
-- Pickling of non-blanks should not work.
prop_xpElemBlankUnpickleNonBlanks :: [Node] -> Bool
prop_xpElemBlankUnpickleNonBlanks nodes =
let nonBlanks = filter (not . isEmpty . unwrapElement)
(filterNodeElements nodes)
in all (\e -> case unpickle (xpElemBlank (elementName e)) nodes of
Right () -> False
_ -> True) (filterMapNodeElements nonBlanks)
-- Return true if element has no attributes, and has no non-comment nodes.
isEmpty :: Element -> Bool
isEmpty Element { elementAttributes = [], elementNodes = [] } = True
isEmpty Element { elementAttributes = [], elementNodes = ns }
| all (\n -> case n of
NodeComment _ -> True
NodeContent (ContentText "") -> True
NodeContent (ContentEntity _) ->
error "Content entities not supported!"
_ -> False) ns = True
isEmpty _ = False
-- Verify that `xpElemText' pickling text content returns that content
-- properly.
prop_xpElemBlankPickle :: Name -> Bool
prop_xpElemBlankPickle n =
let nodes = pickle (xpElemBlank n) ()
in nodes == [ NodeElement
Element { elementName = n
, elementAttributes = []
, elementNodes = []
} ]
-- Verify that `xpElemExists' always generates an empty `NodeElement' with the
-- correct name when the provided parameter is `True'.
prop_xpElemExistsPickleTrue :: Name -> Bool
prop_xpElemExistsPickleTrue n =
pickle (xpElemExists n) True == [NodeElement Element { elementName = n
, elementAttributes = []
, elementNodes = [] }]
-- Verify that xpElemExists always generates an empty list when the provided
-- parameter is `False'.
prop_xpElemExistsPickleFalse :: Name -> Bool
prop_xpElemExistsPickleFalse n = pickle (xpElemExists n) False == []
-- Pickling of empty elements should work.
-- Verifies that all generated elements are actually found, that no error is
-- generated if the element is empty, and that an error is generated if the
-- element is not empty.
prop_xpElemExistsUnpickleBlanks :: Property
prop_xpElemExistsUnpickleBlanks =
forAll (listOf $ oneof [emptyNodeGen, arbitrary]) $ \nodes ->
let blanks = filter (isEmpty . unwrapElement) (filterNodeElements nodes)
in all (\e -> case unpickle (xpElemExists (elementName e)) nodes of
Right True -> True
_ -> False) (filterMapNodeElements blanks)
-- Pickling of non-empty elements should not work.
prop_xpElemExistsUnpickleNonBlanks :: [Node] -> Bool
prop_xpElemExistsUnpickleNonBlanks nodes =
let nonBlanks = filter (not . isEmpty . unwrapElement)
(filterNodeElements nodes)
in all (\e -> case unpickle (xpElemExists (elementName e)) nodes of
Right _ -> False
_ -> True) (filterMapNodeElements nonBlanks)
-- Remove all Node values that are not NodeElement values, and convert the
-- remaining values to Element values.
filterMapNodeElements :: [Node] -> [Element]
filterMapNodeElements = map unwrapElement . filterNodeElements
-- Remove node children that are not elements.
filterNodeElements :: [Node] -> [Node]
filterNodeElements = filter (\n -> case n of NodeElement _ -> True; _ -> False)
-- Access inner `Element' values, and fail if something else is encountered.
unwrapElement :: Node -> Element
unwrapElement (NodeElement e) = e
unwrapElement _ = error "unwrapElement: Non-element Node value encountered."
-- xpBool should unpickle to `True' for "true".
xpBoolUnpickleTrue :: Bool
xpBoolUnpickleTrue = case unpickle xpBool "true" of
Right True -> True
_ -> False
-- xpBool should unpickle to `False' for "false".
xpBoolUnpickleFalse :: Bool
xpBoolUnpickleFalse = case unpickle xpBool "false" of
Right False -> True
_ -> False
-- xpBool should unpickle to `True' for "1".
xpBoolUnpickle1 :: Bool
xpBoolUnpickle1 = case unpickle xpBool "1" of
Right True -> True
_ -> False
-- xpBool should unpickle to `False' for "0".
xpBoolUnpickle0 :: Bool
xpBoolUnpickle0 = case unpickle xpBool "0" of
Right False -> True
_ -> False
-- xpBool should pickle to "true" for `True'.
xpBoolPickleTrue :: Bool
xpBoolPickleTrue = pickle xpBool True == "true"
-- `xpBool' should pickle to "false" for `False'.
xpBoolPickleFalse :: Bool
xpBoolPickleFalse = pickle xpBool False == "false"
-- `xpBool' should fail for any string that is not "true", "1", "false" or "0".
prop_xpBool :: Text -> Property
prop_xpBool t = and [ t /= "true"
, t /= "1"
, t /= "false"
, t /= "0" ] ==> case unpickle xpBool t of
Right _ -> False
Left _ -> True
-- Verify that arbitrarily splitting up `ContentText' values does not change
-- the resulting list of nodes.
prop_flattenContent :: [(Node, Content)] -> Property
prop_flattenContent ncs =
hasContent (map fst ncs) ==>
let combined = map (\(n, c) -> case (n, c) of
(NodeContent (ContentText t), ContentText t')->
NodeContent (ContentText (Text.concat [t, t']))
(NodeContent (ContentEntity _), _) ->
error "Content entities not supported!"
_ -> n) ncs
split = concat $
map (\(n, c) -> case (n, c) of
(NodeContent (ContentText t), ContentText t') ->
[ NodeContent (ContentText t)
, NodeContent (ContentText t') ]
(NodeContent _, _) ->
error "Content entities not supported!"
_ -> [n]) ncs
in (flattenContent combined) == (flattenContent split)
where
hasContent :: [Node] -> Bool
hasContent [] = False
hasContent ((NodeContent _):_) = True
hasContent (_:ns) = hasContent ns
-- Get the `Text' value based on a list of the `Content' values.
contentToText :: [Content] -> Text
contentToText = Text.concat . map contentToText_
-- Unwrap `ContentText' values. Fail on `ContentEntity' values, as they are
-- not supported.
contentToText_ :: Content -> Text
contentToText_ (ContentText t) = t
contentToText_ (ContentEntity _) = error "Content entities not supported!"
-- escape :: Text -> Text
-- escape = Text.replace "&" "&" . Text.replace "'" "'" .
-- Text.replace ">" ">" . Text.replace "<" "<" .
-- Text.replace "\"" """
-- Given a list of nodes which has elements containing only character data and
-- comments (yet has at least a CDATA length of 1), and verifies that the
-- content of those elements can be accessed properly.
-- prop_xpElemTextUnpickle :: [Element] -> Property
-- prop_xpElemTextUnpickle elems = undefined
-- main = flip defaultMainWithOpts (mempty {ropt_color_mode = Just ColorNever}) $
-- [ testGroup "xpUnit" [ testProperty "xpUnit1" testxpUnit1
-- , testProperty "xpUnit2" testxpUnit2
-- ]
-- , testGroup "xpId" [ testProperty "xpId" testxpId]
-- , testGroup "combinators" [ testProperty "xpIsolate" testxpIsolate
-- , testProperty "xpIsolatePair" testxpIsolatePair
-- , testProperty "pair leniency" testPairLenient
-- ]
-- ]
-- testxpUnit1 xs = pickle xpUnit () == ([] :: [Int])
-- where types = xs :: Int
-- testxpUnit2 xs = unpickle xpUnit xs == Right ()
-- where types = xs :: [Int]
-- testxpId xs = null xs .||. (unpickle xpId xs == Right xs)
-- where types = xs :: [Int]
-- testxpIsolate xs = null xs .||.
-- (unpickle (xpIsolate xpId) xs == Right (take 1 xs))
-- where types = xs :: [Int]
-- testxpIsolatePair xs = null xs .||.
-- (unpickle (xpPair (xpIsolate xpId)
-- (xpId)
-- ) xs == Right (take 1 xs, drop 1 xs))
-- where types = xs :: [Int]
-- testPairLenient xs = unpickle (xpId <++> xpUnit) xs == Right (xs, ())
-- where types = xs :: [Int]
-- el x = Element "foo" [("bar",(return . ContentText $ "blub" `Text.append` x))] []
-- el' xs = Element "parent" [] $ flip map xs (\x -> NodeElement $ el (Text.pack $ show x))
-- doc = el' [1..5]
-- testDoc = xpRoot $ xpElem "parent" xpUnit (xpAll $ xpElem "foo" xpId xpUnit)