{-# LANGUAGE OverloadedStrings #-} module Main ( main ) where import Network.IRC import Data.ByteString (ByteString, append, pack) import Data.Word (Word8) import Data.Char (ord) import Control.Applicative ((<$>), (<*>), liftA) import Test.HUnit import Test.QuickCheck import Test.Framework as TF (defaultMain, testGroup, Test) import Test.Framework.Providers.HUnit import Test.Framework.Providers.QuickCheck2 (testProperty) -- --------------------------------------------------------- -- Helpful Wrappers -- An identifier starts with a letter, and consists of interspersed numbers -- and special characters newtype Identifier = Identifier { unIdentifier :: ByteString } deriving (Read,Show,Eq) instance Arbitrary Identifier where arbitrary = do l <- letter ls <- sized $ \n -> loop n return $ Identifier (pack (l:ls)) where loop n | n <= 0 = return [] | otherwise = do i <- identifier is <- loop (n-1) return (i:is) -- A hostname is a string that starts and ends with an identifier, and has -- periods peppered in the middle. newtype Host = Host { unHost :: ByteString } instance Arbitrary Host where arbitrary = do l <- identifier ls <- sized $ \n -> loop n js <- sized $ \n -> loop n e <- identifier return $ Host (pack (l:ls ++ (w8 '.':js) ++ [e])) where loop n | n <= 0 = return [] | otherwise = do i <- host is <- loop (n-1) return (i:is) w8 :: Char -> Word8 w8 = fromIntegral . ord letter :: Gen Word8 letter = frequency [ (50, choose (w8 'a', w8 'z')) , (50, choose (w8 'A', w8 'Z')) ] digit :: Gen Word8 digit = choose (w8 '0', w8 '9') special :: Gen Word8 special = elements [w8 '_', w8 '-'] identifier :: Gen Word8 identifier = frequency [ (50, letter) , (30, digit) , (10, special) ] host :: Gen Word8 host = frequency [ (90, identifier) , (20, return (w8 '.')) ] -- --------------------------------------------------------- -- IRC Types newtype Cmd = Cmd { unCmd :: ByteString } deriving (Read,Show,Eq) instance Arbitrary Cmd where arbitrary = let c = (replyTable !!) <$> choose (0, length replyTable - 1) in Cmd . fst <$> c instance Arbitrary Prefix where arbitrary = oneof [ NickName <$> fmap unIdentifier arbitrary <*> fmap (liftA unIdentifier) arbitrary <*> fmap (liftA unIdentifier) arbitrary , Server <$> fmap unHost arbitrary ] instance Arbitrary Message where arbitrary = let params = map unIdentifier <$> sized vector cmd = unCmd <$> arbitrary in Message <$> arbitrary <*> cmd <*> params -- --------------------------------------------------------- -- Properties prop_encodeDecode :: Message -> Bool prop_encodeDecode msg = (decode . appendCRLF . encode $ msg) == Just msg where appendCRLF bs = append bs (pack [w8 '\r', w8 '\n']) properties :: TF.Test properties = testGroup "QuickCheck Network.IRC" [ testProperty "encodeDecode" prop_encodeDecode ] -- --------------------------------------------------------- -- Unit Tests unitTests :: TF.Test unitTests = testGroup "HUnit tests Network.IRC" [ -- Decoding tests testCase "PRIVMSG foo :bar baz" ( decode "PRIVMSG foo :bar baz" @=? Just (Message Nothing "PRIVMSG" ["foo", "bar baz"])) , testCase ":foo.bar NOTICE baz baz :baz baz" ( decode ":foo.bar NOTICE baz baz :baz baz" @=? Just (Message (Just (Server "foo.bar")) "NOTICE" ["baz", "baz", "baz baz"])) , testCase ":foo.bar 001 baz baz :baz baz" ( decode ":foo.bar 001 baz baz :baz baz" @=? Just (Message (Just (Server "foo.bar")) "001" ["baz", "baz", "baz baz"])) , testCase ":foo!bar@baz PRIVMSG #foo :bar baz" ( decode ":foo!bar@baz PRIVMSG #foo :bar baz" @=? Just (Message (Just (NickName "foo" (Just "bar") (Just "baz"))) "PRIVMSG" ["#foo", "bar baz"])) , testCase ":foo@baz PRIVMSG #foo :bar baz" ( decode ":foo@baz PRIVMSG #foo :bar baz" @=? Just (Message (Just (NickName "foo" Nothing (Just "baz"))) "PRIVMSG" ["#foo", "bar baz"])) , testCase ":foo!bar PRIVMSG #foo :bar baz" ( decode ":foo!bar PRIVMSG #foo :bar baz" @=? Just (Message (Just (NickName "foo" (Just "bar") Nothing)) "PRIVMSG" ["#foo", "bar baz"])) , testCase ":foo PRIVMSG #foo :bar baz" ( decode ":foo PRIVMSG #foo :bar baz" @=? Just (Message (Just (NickName "foo" Nothing Nothing)) "PRIVMSG" ["#foo", "bar baz"])) -- Decoding tests -- Initial colon encoding tests , testCase "Message Nothing \"PRIVMSG\" [\"#foo\", \":bar bas\"]" ( encode (Message Nothing "PRIVMSG" ["#foo", ":bar bas"]) @?= "PRIVMSG #foo ::bar bas") , testCase "Message Nothing \"PRIVMSG\" [\"#foo\", \":bar\"]" ( encode (Message Nothing "PRIVMSG" ["#foo", ":bar"]) @?= "PRIVMSG #foo ::bar") -- Corrected case , testCase ":talon.nl.eu.SwiftIRC.net 332 foo #bar :\n" ( decode ":talon.nl.eu.SwiftIRC.net 332 foo #bar :\n" @?= Just (Message (Just $ Server "talon.nl.eu.SwiftIRC.net") "332" ["foo","#bar",""])) ] -- --------------------------------------------------------- -- Test List tests :: [TF.Test] tests = [ properties , unitTests ] main :: IO () main = defaultMain tests