summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bittorrent.cabal6
-rw-r--r--res/testfilebin0 -> 8192 bytes
-rw-r--r--res/testfile.torrent1
-rw-r--r--src/Network/BitTorrent/Exchange/Wire.hs40
-rw-r--r--tests/Main.hs285
-rw-r--r--tests/Spec.hs2
6 files changed, 55 insertions, 279 deletions
diff --git a/bittorrent.cabal b/bittorrent.cabal
index e987a4be..39972550 100644
--- a/bittorrent.cabal
+++ b/bittorrent.cabal
@@ -157,8 +157,9 @@ test-suite spec
157 default-extensions: OverloadedStrings 157 default-extensions: OverloadedStrings
158 type: exitcode-stdio-1.0 158 type: exitcode-stdio-1.0
159 hs-source-dirs: tests 159 hs-source-dirs: tests
160 main-is: Spec.hs 160 main-is: Main.hs
161 other-modules: Data.Torrent.BitfieldSpec 161 other-modules: Spec
162 Data.Torrent.BitfieldSpec
162 Data.Torrent.InfoHashSpec 163 Data.Torrent.InfoHashSpec
163 Data.Torrent.LayoutSpec 164 Data.Torrent.LayoutSpec
164 Data.Torrent.MagnetSpec 165 Data.Torrent.MagnetSpec
@@ -201,6 +202,7 @@ test-suite spec
201 , quickcheck-instances 202 , quickcheck-instances
202 , http-types 203 , http-types
203 , bencoding 204 , bencoding
205 , process
204 , bittorrent 206 , bittorrent
205 ghc-options: -Wall -fno-warn-orphans 207 ghc-options: -Wall -fno-warn-orphans
206 208
diff --git a/res/testfile b/res/testfile
new file mode 100644
index 00000000..8e984818
--- /dev/null
+++ b/res/testfile
Binary files differ
diff --git a/res/testfile.torrent b/res/testfile.torrent
new file mode 100644
index 00000000..a03f7780
--- /dev/null
+++ b/res/testfile.torrent
@@ -0,0 +1 @@
d8:announce44:udp://tracker.openbittorrent.com:80/announce10:created by26:Enhanced-CTorrent/dnh3.3.213:creation datei1387753787e4:infod6:lengthi8192e4:name8:testfile12:piece lengthi262144e6:pieces20:œd•Dú—uÈÔtÝ®aÿöK³2ee \ No newline at end of file
diff --git a/src/Network/BitTorrent/Exchange/Wire.hs b/src/Network/BitTorrent/Exchange/Wire.hs
index 109f6551..c0658961 100644
--- a/src/Network/BitTorrent/Exchange/Wire.hs
+++ b/src/Network/BitTorrent/Exchange/Wire.hs
@@ -432,8 +432,9 @@ data Connection = Connection
432 -- future. 432 -- future.
433 connProtocol :: !ProtocolName 433 connProtocol :: !ProtocolName
434 434
435 -- | A set of enabled extensions. This value used to check if a 435 -- | Set of enabled core extensions, i.e. the pre BEP10 extension
436 -- message is allowed to be sent or received. 436 -- mecahnism. This value is used to check if a message is allowed to be sent
437 -- or received.
437 , connCaps :: !Caps 438 , connCaps :: !Caps
438 439
439 -- | /Both/ peers handshaked with this infohash. A connection can 440 -- | /Both/ peers handshaked with this infohash. A connection can
@@ -450,9 +451,9 @@ data Connection = Connection
450 -- | 451 -- |
451 , connOptions :: !Options 452 , connOptions :: !Options
452 453
453 -- | If @not (allowed ExtExtended connCaps)@ then this set is 454 -- | If @not (allowed ExtExtended connCaps)@ then this set is always
454 -- always empty. Otherwise it has extension protocol 'MessageId' 455 -- empty. Otherwise it has the BEP10 extension protocol mandated mapping of
455 -- map. 456 -- 'MessageId' to the message type for the remote peer.
456 , connExtCaps :: !(IORef ExtendedCaps) 457 , connExtCaps :: !(IORef ExtendedCaps)
457 458
458 -- | Current extended handshake information from the remote peer 459 -- | Current extended handshake information from the remote peer
@@ -632,9 +633,9 @@ rehandshake caps = undefined
632reconnect :: Wire () 633reconnect :: Wire ()
633reconnect = undefined 634reconnect = undefined
634 635
635-- | Initiate 'Wire' connection and handshake with a peer. This 636-- | Initiate 'Wire' connection and handshake with a peer. This function will
636-- function will also do extension protocol handshake if 'ExtExtended' 637-- also do the BEP10 extension protocol handshake if 'ExtExtended' is enabled on
637-- is enabled on both sides. 638-- both sides.
638-- 639--
639-- This function can throw 'WireFailure' exception. 640-- This function can throw 'WireFailure' exception.
640-- 641--
@@ -643,20 +644,19 @@ connectWire hs addr extCaps wire =
643 bracket (connectToPeer addr) close $ \ sock -> do 644 bracket (connectToPeer addr) close $ \ sock -> do
644 hs' <- initiateHandshake sock hs 645 hs' <- initiateHandshake sock hs
645 646
646 unless (def == hsProtocol hs') $ do 647 Prelude.mapM_ (\(t,e) -> unless t $ throwIO $ ProtocolError e) [
647 throwIO $ ProtocolError $ InvalidProtocol (hsProtocol hs') 648 (def == hsProtocol hs'
648 649 , InvalidProtocol $ hsProtocol hs'),
649 unless (hsProtocol hs == hsProtocol hs') $ do 650 (hsProtocol hs == hsProtocol hs'
650 throwIO $ ProtocolError $ UnexpectedProtocol (hsProtocol hs') 651 , UnexpectedProtocol $ hsProtocol hs'),
651 652 (hsInfoHash hs == hsInfoHash hs'
652 unless (hsInfoHash hs == hsInfoHash hs') $ do 653 , UnexpectedTopic $ hsInfoHash hs'),
653 throwIO $ ProtocolError $ UnexpectedTopic (hsInfoHash hs') 654 (hsPeerId hs' == fromMaybe (hsPeerId hs') (peerId addr)
654 655 , UnexpectedPeerId $ hsPeerId hs')
655 unless (hsPeerId hs' == fromMaybe (hsPeerId hs') (peerId addr)) $ do 656 ]
656 throwIO $ ProtocolError $ UnexpectedPeerId (hsPeerId hs')
657 657
658 let caps = hsReserved hs <> hsReserved hs' 658 let caps = hsReserved hs <> hsReserved hs'
659 let wire' = if ExtExtended `allowed` caps 659 wire' = if ExtExtended `allowed` caps
660 then extendedHandshake extCaps >> wire 660 then extendedHandshake extCaps >> wire
661 else wire 661 else wire
662 662
diff --git a/tests/Main.hs b/tests/Main.hs
index 3c58e2ed..439e9c4e 100644
--- a/tests/Main.hs
+++ b/tests/Main.hs
@@ -1,260 +1,33 @@
1-- | 1module Main where
2-- Copyright : (c) Sam Truzjan 2013 2
3-- License : BSD3 3import Spec
4-- Maintainer : pxqr.sta@gmail.com 4import System.Exit
5-- Stability : experimental 5import System.Environment
6-- Portability : portable 6import System.Process
7-- 7import Control.Exception
8-- Do not add other (than this) test suites without need. Do not use 8import Data.List
9-- linux-specific paths, use 'filepath' and 'directory' machinery.
10--
11{-# LANGUAGE StandaloneDeriving #-}
12{-# LANGUAGE GeneralizedNewtypeDeriving #-}
13{-# LANGUAGE OverloadedStrings #-}
14module Main (main) where
15
16import Control.Applicative hiding (empty)
17import Data.ByteString (ByteString)
18import qualified Data.ByteString as B
19import qualified Data.ByteString.Lazy as Lazy
20import Data.List as L
21import Data.Maybe 9import Data.Maybe
22import Data.Word
23import Data.Serialize as S
24import Data.Text as T
25
26import Network
27import Network.URI
28
29import Test.QuickCheck as QC
30import Test.Framework as Framework (Test, defaultMain)
31import Test.Framework.Providers.QuickCheck2 (testProperty)
32
33import Data.Aeson as JSON
34import Data.BEncode as BE
35import Data.Torrent.Block
36import Data.Torrent.Bitfield as BF
37import Data.Torrent.Metainfo
38import Network.BitTorrent.Exchange.Protocol
39import Network.BitTorrent.Tracker
40import Network.BitTorrent.Tracker.Protocol
41import Network.BitTorrent.Tracker.HTTP
42import Network.BitTorrent.Peer
43
44-- import Debug.Trace
45
46
47data T a = T
48
49
50prop_properBEncode :: Show a => BEncodable a => Eq a => T a -> a -> Bool
51prop_properBEncode _ expected = actual == Right expected
52 where
53 actual = decoded $ Lazy.toStrict $ encoded expected
54
55instance Arbitrary URI where
56 arbitrary = pure $ fromJust
57 $ parseURI "http://exsample.com:80/123365_asd"
58
59instance Arbitrary Text where
60 arbitrary = T.pack <$> arbitrary
61
62{-
63{-----------------------------------------------------------------------
64 MemMap
65-----------------------------------------------------------------------}
66
67tmpdir :: FilePath
68tmpdir = "tmp"
69
70boundaryTest :: Assertion
71boundaryTest = do
72 f <- mallocTo (Fixed.interval 0 1) Fixed.empty
73 f <- mallocTo (Fixed.interval 1 2) f
74 writeElem f 0 (1 :: Word8)
75 writeElem f 1 (2 :: Word8)
76 bs <- readBytes (Fixed.interval 0 2) f
77 "\x1\x2" @=? bs
78
79mmapSingle :: Assertion
80mmapSingle = do
81 f <- mmapTo (tmpdir </> "single.test") (10, 5) 5 Fixed.empty
82 writeBytes (Fixed.interval 5 5) "abcde" f
83 bs <- readBytes (Fixed.interval 5 5) f
84 "abcde" @=? bs
85
86coalesceTest :: Assertion
87coalesceTest = do
88 f <- mmapTo (tmpdir </> "a.test") (0, 1) 10 Fixed.empty
89 f <- mmapTo (tmpdir </> "bc.test") (0, 2) 12 f
90 f <- mmapTo (tmpdir </> "c.test") (0, 1) 13 f
91 writeBytes (Fixed.interval 10 4) "abcd" f
92 bs <- readBytes (Fixed.interval 10 4) f
93 "abcd" @=? bs
94-}
95
96{-----------------------------------------------------------------------
97 Bitfield
98-----------------------------------------------------------------------}
99-- other properties are tested in IntervalSet
100
101prop_completenessRange :: Bitfield -> Bool
102prop_completenessRange bf = 0 <= c && c <= 1
103 where
104 c = completeness bf
105
106prop_minMax :: Bitfield -> Bool
107prop_minMax bf
108 | BF.null bf = True
109 | otherwise = BF.findMin bf <= BF.findMax bf
110
111prop_rarestInRange :: [Bitfield] -> Bool
112prop_rarestInRange xs = case rarest xs of
113 Just r -> 0 <= r
114 && r < totalCount (maximumBy (comparing totalCount) xs)
115 Nothing -> True
116
117{- this one should give pretty good coverage -}
118prop_differenceDeMorgan :: Bitfield -> Bitfield -> Bitfield -> Bool
119prop_differenceDeMorgan a b c =
120 (a `BF.difference` (b `BF.intersection` c))
121 == ((a `BF.difference` b) `BF.union` (a `BF.difference` c))
122 &&
123 (a `BF.difference` (b `BF.union` c))
124 == ((a `BF.difference` b) `BF.intersection` (a `BF.difference` c))
125
126 {-
127 [ -- mem map
128 testCase "boudary" boundaryTest
129 , testCase "single" mmapSingle
130 , testCase "coalesce" coalesceTest
131 ]
132
133instance Arbitrary Bitfield where
134 arbitrary = mkBitfield <$> (succ . min 1000 <$> positive)
135 <*> arbitrary
136
137 testProperty "completeness range" prop_completenessRange
138 , testProperty "rarest in range" prop_rarestInRange
139 , testProperty "min less that max" prop_minMax
140 , testProperty "difference de morgan" prop_differenceDeMorgan
141-}
142{-----------------------------------------------------------------------
143 Handshake
144-----------------------------------------------------------------------}
145
146instance Arbitrary PeerId where
147 arbitrary = azureusStyle <$> pure defaultClientId
148 <*> arbitrary
149 <*> arbitrary
150
151instance Arbitrary InfoHash where
152 arbitrary = (hash . B.pack) <$> arbitrary
153
154instance Arbitrary Handshake where
155 arbitrary = defaultHandshake <$> arbitrary <*> arbitrary
156
157prop_cerealEncoding :: (Serialize a, Eq a) => T a -> [a] -> Bool
158prop_cerealEncoding _ msgs = S.decode (S.encode msgs) == Right msgs
159
160{-----------------------------------------------------------------------
161 Tracker/Scrape
162-----------------------------------------------------------------------}
163
164instance Arbitrary ScrapeInfo where
165 arbitrary = ScrapeInfo <$> arbitrary <*> arbitrary
166 <*> arbitrary <*> arbitrary
167
168-- | Note that in 6 esample we intensionally do not agree with
169-- specification, because taking in account '/' in query parameter
170-- seems to be meaningless. (And thats because other clients do not
171-- chunk uri by parts) Moreover in practice there should be no
172-- difference. (I think so)
173--
174test_scrape_url :: [Framework.Test]
175test_scrape_url = L.zipWith mkTest [1 :: Int ..] (check `L.map` tests)
176 where
177 check (iu, ou) = (parseURI iu >>= (`scrapeURL` [])
178 >>= return . show) == ou
179 tests =
180 [ ( "http://example.com/announce"
181 , Just "http://example.com/scrape")
182 , ( "http://example.com/x/announce"
183 , Just "http://example.com/x/scrape")
184 , ( "http://example.com/announce.php"
185 , Just "http://example.com/scrape.php")
186 , ( "http://example.com/a" , Nothing)
187 , ( "http://example.com/announce?x2%0644"
188 , Just "http://example.com/scrape?x2%0644")
189 , ( "http://example.com/announce?x=2/4"
190 , Just "http://example.com/scrape?x=2/4")
191-- , ("http://example.com/announce?x=2/4" , Nothing) -- by specs
192 , ("http://example.com/x%064announce" , Nothing)
193 ]
194
195 mkTest i = testProperty ("scrape test #" ++ show i)
196
197{-----------------------------------------------------------------------
198 P2P/message
199-----------------------------------------------------------------------}
200
201positive :: Gen Int
202positive = fromIntegral <$> (arbitrary :: Gen Word32)
203
204instance Arbitrary ByteString where
205 arbitrary = B.pack <$> arbitrary
206
207instance Arbitrary Lazy.ByteString where
208 arbitrary = Lazy.pack <$> arbitrary
209
210instance Arbitrary BlockIx where
211 arbitrary = BlockIx <$> positive <*> positive <*> positive
212
213instance Arbitrary Block where
214 arbitrary = Block <$> positive <*> positive <*> arbitrary
215
216instance Arbitrary PortNumber where
217 arbitrary = fromIntegral <$> (arbitrary :: Gen Word16)
218
219instance Arbitrary Message where
220 arbitrary = oneof
221 [ pure KeepAlive
222 , pure Choke
223 , pure Unchoke
224 , pure Interested
225 , pure NotInterested
226 , Have <$> positive
227 , pure $ Bitfield $ BF.haveNone 0
228 , Request <$> arbitrary
229 , Piece <$> arbitrary
230 , Cancel <$> arbitrary
231 , Port <$> arbitrary
232 ]
233-- todo add all messages
234
235prop_messageEncoding :: Message -> Bool
236prop_messageEncoding msg @ (Bitfield bf)
237 = case S.decode (S.encode msg) of
238 Right (Bitfield bf') -> bf == adjustSize (totalCount bf) bf'
239 _ -> False
240prop_messageEncoding msg
241 = S.decode (S.encode msg) == Right msg
242
243{-----------------------------------------------------------------------
244 Main
245-----------------------------------------------------------------------}
246 10
247allTests :: [Framework.Test] 11clients :: [(String, String)]
248allTests = 12clients = [
249 [ -- handshake module 13 ("rtorrent","rtorrent -p 51234-51234 res/testfile.torrent") ]
250 testProperty "handshake encoding" $
251 prop_cerealEncoding (T :: T Handshake)
252 , testProperty "message encoding" prop_messageEncoding
253 ] ++ test_scrape_url
254 ++
255 [ testProperty "scrape bencode" $
256 prop_properBEncode (T :: T ScrapeInfo)
257 ]
258 14
259main :: IO () 15main :: IO ()
260main = defaultMain allTests 16main = do
17 args <- getArgs
18 let cmd' = do
19 cl <- listToMaybe $ reverse
20 $ map (tail . dropWhile (/='='))
21 $ filter (isPrefixOf "--bittorrent-client=") args
22 cmd <- (++) "screen -dm -S bittorrent-testsuite " <$> lookup cl clients
23 return cmd
24 case cmd' of
25 Just cmd -> do _ <- system "screen -S bittorrent-testsuite -X quit"
26 createProcess (shell cmd) >> return ()
27 Nothing -> return ()
28
29 let args' = (filter (not . isPrefixOf "--bittorrent-client=") args)
30 code <- catch (withArgs args' hspecMain >> return ExitSuccess) return
31
32 _ <- system "screen -S bittorrent-testsuite -X quit"
33 exitWith code >> return ()
diff --git a/tests/Spec.hs b/tests/Spec.hs
index 52ef578f..8d2b5139 100644
--- a/tests/Spec.hs
+++ b/tests/Spec.hs
@@ -1 +1 @@
{-# OPTIONS_GHC -F -pgmF hspec-discover #-} \ No newline at end of file {-# OPTIONS_GHC -F -pgmF hspec-discover -optF --hook-main #-}