10 changed files with 409 additions and 11 deletions
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
{-# LANGUAGE OverloadedStrings #-} |
||||
module Main where |
||||
|
||||
import Criterion.Main |
||||
import Network.Xmpp.Types |
||||
|
||||
bench_jidFromTexts = whnf (\(a,b,c) -> jidFromTexts a b c) |
||||
( Just "+\227\161[\\3\8260\&4" |
||||
, "\242|8e3\EOTrf6\DLEp\\\a" |
||||
, Just ")\211\226") |
||||
|
||||
main = do defaultMain [bench "jidFromTexts 2" bench_jidFromTexts] |
||||
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
module Main where |
||||
|
||||
import Test.Tasty |
||||
|
||||
import Tests.Parsers |
||||
import Tests.Picklers |
||||
|
||||
main :: IO () |
||||
main = defaultMain $ testGroup "root" [ parserTests |
||||
, picklerTests |
||||
] |
||||
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
module Tests.Arbitrary where |
||||
|
||||
import Tests.Arbitrary.Xml () |
||||
import Tests.Arbitrary.Xmpp () |
||||
|
||||
-- $derive makeArbitrary IQRequestType |
||||
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
module Tests.Arbitrary.Common where |
||||
|
||||
import Data.Char |
||||
import qualified Data.Text as Text |
||||
import Test.QuickCheck |
||||
import Test.QuickCheck.Instances () |
||||
|
||||
shrinkText1 :: Text.Text -> [Text.Text] |
||||
shrinkText1 txt = filter (not . Text.null) $ shrink txt |
||||
|
||||
shrinkTextMaybe :: Maybe Text.Text -> [Maybe Text.Text] |
||||
shrinkTextMaybe mbtxt = filter (\mb -> mb /= Just (Text.empty)) $ shrink mbtxt |
||||
|
||||
genText1 :: Gen Text.Text |
||||
genText1 = Text.pack `fmap` string1 |
||||
where |
||||
string1 = listOf1 arbitrary `suchThat` (not . all isSpace) |
||||
@ -0,0 +1,85 @@
@@ -0,0 +1,85 @@
|
||||
module Tests.Arbitrary.Xml where |
||||
|
||||
import Control.Applicative ((<$>), (<*>)) |
||||
import Test.QuickCheck |
||||
import Test.QuickCheck.Instances() |
||||
-- import Data.DeriveTH |
||||
import qualified Data.Text as Text |
||||
import Data.XML.Types |
||||
import Tests.Arbitrary.Common |
||||
import Text.CharRanges |
||||
|
||||
|
||||
selectFromRange :: Range -> Gen Char |
||||
selectFromRange (Single a) = return a |
||||
selectFromRange (Range a b) = choose (a, b) |
||||
|
||||
nameStartChar :: [Range] |
||||
nameStartChar = |
||||
[ -- Single ':' |
||||
Single '_' |
||||
, Range 'A' 'Z' |
||||
, Range 'a' 'z' |
||||
, Range '\xC0' '\xD6' |
||||
, Range '\xD8' '\xF6' |
||||
, Range '\xF8' '\x2FF' |
||||
, Range '\x370' '\x37D' |
||||
, Range '\x37F' '\x1FFF' |
||||
, Range '\x200C' '\x200D' |
||||
, Range '\x2070' '\x218F' |
||||
, Range '\x2C00' '\x2FEF' |
||||
, Range '\x3001' '\xD7FF' |
||||
, Range '\xF900' '\xFDCF' |
||||
, Range '\xFDF0' '\xFFFD' |
||||
, Range '\x10000' '\xEFFFF' |
||||
] |
||||
|
||||
nameChar :: [Range] |
||||
nameChar = |
||||
Single '-' |
||||
: Single '.' |
||||
: Single '\xB7' |
||||
: Range '0' '9' |
||||
: Range '\x0300' '\x036F' |
||||
: Range '\x203F' '\x2040' |
||||
: nameStartChar |
||||
|
||||
|
||||
genNCName :: Gen Text.Text |
||||
genNCName = do |
||||
sc <- elements nameStartChar >>= selectFromRange |
||||
ncs <- listOf $ elements nameChar >>= selectFromRange |
||||
return . Text.pack $ sc:ncs |
||||
|
||||
-- | Cap the size of child elements. |
||||
slow :: Gen a -> Gen a |
||||
slow g = sized $ \n -> resize (min 5 (n `div` 4)) g |
||||
|
||||
instance Arbitrary Name where |
||||
arbitrary = Name <$> genNCName <*> genMaybe genNCName <*> genMaybe genNCName |
||||
where |
||||
genMaybe g = oneof [return Nothing, Just <$> g] |
||||
shrink (Name a b c) = [ Name a' b c | a' <- shrinkText1 a] |
||||
++[ Name a b' c | b' <- shrinkTextMaybe b] |
||||
++[ Name a b c' | c' <- shrinkTextMaybe c] |
||||
|
||||
instance Arbitrary Content where |
||||
arbitrary = ContentText <$> arbitrary |
||||
shrink (ContentText txt) = ContentText <$> shrinkText1 txt |
||||
shrink _ = [] |
||||
|
||||
|
||||
instance Arbitrary Node where |
||||
arbitrary = oneof [ NodeElement <$> arbitrary |
||||
, NodeContent <$> arbitrary |
||||
] |
||||
shrink (NodeElement e) = NodeElement <$> shrink e |
||||
shrink (NodeContent c) = NodeContent <$> shrink c |
||||
shrink _ = [] |
||||
|
||||
instance Arbitrary Element where |
||||
arbitrary = Element <$> arbitrary <*> slow arbitrary <*> slow arbitrary |
||||
shrink (Element a b c) = |
||||
[ Element a' b c | a' <- shrink a] |
||||
++[ Element a b' c | b' <- shrink b] |
||||
++[ Element a b c' | c' <- shrink c] |
||||
@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
|
||||
{-# LANGUAGE TemplateHaskell #-} |
||||
module Tests.Arbitrary.Xmpp where |
||||
|
||||
import Control.Applicative ((<$>), (<*>)) |
||||
import Data.Maybe |
||||
import qualified Data.Text as Text |
||||
import Network.Xmpp.Types |
||||
import Test.QuickCheck |
||||
import Test.QuickCheck.Instances() |
||||
import qualified Text.CharRanges as Ranges |
||||
import qualified Text.StringPrep as SP |
||||
import qualified Text.StringPrep.Profiles as SP |
||||
|
||||
import Tests.Arbitrary.Common |
||||
import Tests.Arbitrary.Xml () |
||||
|
||||
import Data.Derive.Arbitrary |
||||
import Data.DeriveTH |
||||
|
||||
|
||||
instance Arbitrary Jid where |
||||
arbitrary = do |
||||
Just jid <- tryJid `suchThat` isJust |
||||
return jid |
||||
where |
||||
tryJid = jidFromTexts <$> maybeGen (genString nodeprepProfile) |
||||
<*> genString (SP.namePrepProfile False) |
||||
<*> maybeGen (genString resourceprepProfile) |
||||
maybeGen g = oneof [ return Nothing |
||||
, Just <$> g |
||||
] |
||||
genString profile = Text.pack . take 1024 <$> listOf1 genChar |
||||
where |
||||
genChar = arbitrary `suchThat` (not . isProhibited) |
||||
prohibited = Ranges.toSet $ concat (SP.prohibited profile) |
||||
isProhibited x = Ranges.member x prohibited |
||||
|| x `elem` "@/" |
||||
|
||||
shrink (Jid lp dp rp) = [ Jid lp' dp rp | lp' <- shrinkTextMaybe lp] |
||||
++ [ Jid lp dp' rp | dp' <- shrinkText1 dp] |
||||
++ [ Jid lp dp rp' | rp' <- shrinkTextMaybe rp] |
||||
|
||||
|
||||
string :: SP.StringPrepProfile -> Gen [Char] |
||||
string profile = take 1024 <$> listOf1 genChar |
||||
where |
||||
genChar = arbitrary `suchThat` (not . isProhibited) |
||||
prohibited = Ranges.toSet $ concat (SP.prohibited profile) |
||||
isProhibited x = Ranges.member x prohibited |
||||
|| x `elem` "@/" |
||||
|
||||
instance Arbitrary LangTag where |
||||
arbitrary = LangTag <$> genTag <*> listOf genTag |
||||
where genTag = fmap Text.pack . listOf1 . elements $ ['a'..'z'] ++ ['A'..'Z'] |
||||
shrink (LangTag lt lts) = [LangTag lt' lts | lt' <- shrinkText1 lt] ++ |
||||
[LangTag lt lts' | lts' <- filter (not . Text.null) |
||||
<$> shrink lts] |
||||
|
||||
|
||||
instance Arbitrary StanzaError where |
||||
arbitrary = StanzaError <$> arbitrary |
||||
<*> arbitrary |
||||
<*> oneof [ return Nothing |
||||
, Just <$> ((,) <$> arbitrary <*> genText1) |
||||
] |
||||
<*> arbitrary |
||||
|
||||
-- Auto-derive trivial instances |
||||
concat <$> mapM (derive makeArbitrary) [ ''StanzaErrorType |
||||
, ''StanzaErrorCondition |
||||
, ''IQRequestType |
||||
, ''IQRequest |
||||
, ''IQResult |
||||
, ''IQError |
||||
, ''MessageType |
||||
, ''Message |
||||
, ''MessageError |
||||
, ''PresenceType |
||||
, ''Presence |
||||
, ''PresenceError |
||||
, ''Stanza |
||||
|
||||
, ''SaslError |
||||
, ''SaslFailure |
||||
, ''StreamErrorCondition |
||||
, ''StreamErrorInfo |
||||
-- , ''HandshakeFailed |
||||
-- , ''XmppTlsError |
||||
-- , ''AuthFailure |
||||
, ''Version |
||||
, ''ConnectionState |
||||
, ''TlsBehaviour |
||||
] |
||||
@ -0,0 +1,57 @@
@@ -0,0 +1,57 @@
|
||||
{-# LANGUAGE NoMonomorphismRestriction #-} |
||||
{-# LANGUAGE OverloadedStrings #-} |
||||
{-# LANGUAGE TemplateHaskell #-} |
||||
|
||||
module Tests.Parsers where |
||||
|
||||
import Control.Applicative ((<$>)) |
||||
import Network.Xmpp.Types |
||||
import Test.Hspec |
||||
import Test.Tasty.QuickCheck |
||||
import Test.Tasty |
||||
import Test.Tasty.Hspec |
||||
import Test.Tasty.TH |
||||
|
||||
import Tests.Arbitrary () |
||||
|
||||
case_JidFromText :: Spec |
||||
case_JidFromText = describe "jidFromText" $ do |
||||
it "parses a full JID" $ jidFromText "foo@bar.com/quux" |
||||
`shouldBe` Just (Jid (Just "foo") |
||||
"bar.com" |
||||
(Just "quux")) |
||||
it "parses a bare JID" $ jidFromText "foo@bar.com" |
||||
`shouldBe` Just (Jid (Just "foo") |
||||
"bar.com" |
||||
Nothing) |
||||
it "parses a domain" $ jidFromText "bar.com" |
||||
`shouldBe` Just (Jid Nothing |
||||
"bar.com" |
||||
Nothing) |
||||
it "parses domain with resource" $ jidFromText "bar.com/quux" |
||||
`shouldBe` Just (Jid Nothing |
||||
"bar.com" |
||||
(Just "quux")) |
||||
it "rejects multiple '@'" $ shouldReject "foo@bar@baz" |
||||
it "rejects multiple '/'" $ shouldReject "foo/bar/baz" |
||||
it "rejects multiple '/' after '@'" $ shouldReject "quux@foo/bar/baz" |
||||
it "rejects '@' after '/'" $ shouldReject "foo/bar@baz" |
||||
it "rejects empty local part" $ shouldReject "@bar/baz" |
||||
it "rejects empty resource part" $ shouldReject "foo@bar/" |
||||
it "rejects empty domain part" $ shouldReject "foo@/baz" |
||||
where shouldReject jid = jidFromText jid `shouldBe` Nothing |
||||
|
||||
prop_jidFromText_rightInverse :: Jid -> Bool |
||||
prop_jidFromText_rightInverse j = let jidText = jidToText j in |
||||
(jidToText <$> jidFromText jidText) == Just jidText |
||||
|
||||
prop_jidFromText_leftInverse :: Jid -> Bool |
||||
prop_jidFromText_leftInverse jid = (jidFromText $ jidToText jid) == Just jid |
||||
|
||||
|
||||
case_LangTagParser :: Spec |
||||
case_LangTagParser = describe "langTagFromText" $ |
||||
it "has some properties" $ pendingWith "Check requirements" |
||||
|
||||
parserTests :: TestTree |
||||
parserTests = $testGroupGenerator |
||||
@ -0,0 +1,95 @@
@@ -0,0 +1,95 @@
|
||||
{-# LANGUAGE TemplateHaskell #-} |
||||
{-# LANGUAGE OverloadedStrings #-} |
||||
module Tests.Picklers where |
||||
|
||||
import Tests.Arbitrary () |
||||
import Data.XML.Pickle |
||||
import Network.Xmpp.Marshal |
||||
import Network.Xmpp.Types |
||||
import Test.Tasty |
||||
import Test.Tasty.TH |
||||
import Test.Tasty.QuickCheck |
||||
|
||||
import Data.XML.Types |
||||
|
||||
testPicklerInvertible :: Eq a => PU t a -> a -> Bool |
||||
testPicklerInvertible p = \x -> case unpickle p (pickle p x) of |
||||
Left _ -> False |
||||
Right x' -> x == x' |
||||
|
||||
testPickler p x = case unpickle p (pickle p x) of |
||||
Left e -> putStrLn $ ppUnpickleError e |
||||
Right r -> putStrLn "OK." |
||||
|
||||
prop_errorConditionPicklerInvertible :: StanzaErrorCondition -> Bool |
||||
prop_errorConditionPicklerInvertible = testPicklerInvertible xpErrorCondition |
||||
|
||||
prop_stanzaErrorPicklerInvertible :: StanzaError -> Bool |
||||
prop_stanzaErrorPicklerInvertible = testPicklerInvertible xpStanzaError |
||||
|
||||
prop_messagePicklerInvertible :: Message -> Bool |
||||
prop_messagePicklerInvertible = testPicklerInvertible xpMessage |
||||
|
||||
prop_messageErrorPicklerInvertible :: MessageError -> Bool |
||||
prop_messageErrorPicklerInvertible = testPicklerInvertible xpMessageError |
||||
|
||||
prop_presencePicklerInvertible :: Presence -> Bool |
||||
prop_presencePicklerInvertible = testPicklerInvertible xpPresence |
||||
|
||||
prop_presenceErrorPicklerInvertible :: PresenceError -> Bool |
||||
prop_presenceErrorPicklerInvertible = testPicklerInvertible xpPresenceError |
||||
|
||||
prop_iqRequestPicklerInvertible :: IQRequest -> Bool |
||||
prop_iqRequestPicklerInvertible = testPicklerInvertible xpIQRequest |
||||
|
||||
prop_iqResultPicklerInvertible :: IQResult -> Bool |
||||
prop_iqResultPicklerInvertible = testPicklerInvertible xpIQResult |
||||
|
||||
prop_iqErrorPicklerInvertible :: IQError -> Bool |
||||
prop_iqErrorPicklerInvertible = testPicklerInvertible xpIQError |
||||
|
||||
prop_langTagPicklerInvertible :: Maybe LangTag -> Bool |
||||
prop_langTagPicklerInvertible = testPicklerInvertible xpLangTag |
||||
|
||||
prop_langPicklerInvertible :: LangTag -> Bool |
||||
prop_langPicklerInvertible = testPicklerInvertible xpLang |
||||
|
||||
picklerTests :: TestTree |
||||
picklerTests = $testGroupGenerator |
||||
|
||||
bad1 = StanzaError { stanzaErrorType = Cancel |
||||
, stanzaErrorCondition = Forbidden |
||||
, stanzaErrorText = Just $ (Just $ LangTag "v" [], "") |
||||
, stanzaErrorApplicationSpecificCondition = |
||||
Just (Element {elementName = |
||||
Name { nameLocalName = "\231" |
||||
, nameNamespace = Nothing |
||||
, namePrefix = Nothing} |
||||
, elementAttributes = [] |
||||
, elementNodes = [] |
||||
}) |
||||
} |
||||
|
||||
bad2StanzaError = StanzaError { stanzaErrorType = Continue |
||||
, stanzaErrorCondition = NotAllowed |
||||
, stanzaErrorText = Just (Just $ parseLangTag "W-o","\f") |
||||
, stanzaErrorApplicationSpecificCondition = |
||||
Just (Element {elementName = |
||||
Name { nameLocalName = "\8204" |
||||
, nameNamespace = Nothing |
||||
, namePrefix = Just "\8417A"} |
||||
, elementAttributes = [] |
||||
, elementNodes = []})} |
||||
|
||||
bad2 = MessageError { messageErrorID = Just "" |
||||
, messageErrorFrom = Just $ parseJid "a@y/\177" |
||||
, messageErrorTo = Just $ parseJid "\250@7" |
||||
, messageErrorLangTag = Nothing |
||||
, messageErrorStanzaError = bad2StanzaError |
||||
, messageErrorPayload = |
||||
[Element {elementName = |
||||
Name { nameLocalName = "\12226C" |
||||
, nameNamespace = Nothing |
||||
, namePrefix = Nothing} |
||||
, elementAttributes = [] |
||||
, elementNodes = []}]} |
||||
Loading…
Reference in new issue