diff options
author | James Crayne <jim.crayne@gmail.com> | 2019-09-28 13:43:29 -0400 |
---|---|---|
committer | Joe Crayne <joe@jerkface.net> | 2020-01-01 19:27:53 -0500 |
commit | 11987749fc6e6d3e53ea737d46d5ab13a16faeb8 (patch) | |
tree | 5716463275c2d3e902889db619908ded2a73971c /dht/bittorrent/tests/Network | |
parent | add2c76bced51fde5e9917e7449ef52be70faf87 (diff) |
Factor out some new libraries
word64-map:
Data.Word64Map
network-addr:
Network.Address
tox-crypto:
Crypto.Tox
lifted-concurrent:
Control.Concurrent.Lifted.Instrument
Control.Concurrent.Async.Lifted.Instrument
psq-wrap:
Data.Wrapper.PSQInt
Data.Wrapper.PSQ
minmax-psq:
Data.MinMaxPSQ
tasks:
Control.Concurrent.Tasks
kad:
Network.Kademlia
Network.Kademlia.Bootstrap
Network.Kademlia.Routing
Network.Kademlia.CommonAPI
Network.Kademlia.Persistence
Network.Kademlia.Search
Diffstat (limited to 'dht/bittorrent/tests/Network')
27 files changed, 2208 insertions, 0 deletions
diff --git a/dht/bittorrent/tests/Network/BitTorrent/Client/HandleSpec.hs b/dht/bittorrent/tests/Network/BitTorrent/Client/HandleSpec.hs new file mode 100644 index 00000000..d51bab02 --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/Client/HandleSpec.hs | |||
@@ -0,0 +1,19 @@ | |||
1 | module Network.BitTorrent.Client.HandleSpec (spec) where | ||
2 | import Data.Default | ||
3 | import Test.Hspec | ||
4 | |||
5 | import Data.Torrent | ||
6 | import Network.BitTorrent.Client | ||
7 | import Network.BitTorrent.Client.Handle | ||
8 | |||
9 | data_dir :: FilePath | ||
10 | data_dir = "data" | ||
11 | |||
12 | spec :: Spec | ||
13 | spec = do | ||
14 | describe "openMagnet" $ do | ||
15 | it "should add new infohash to index" $ do | ||
16 | simpleClient $ do | ||
17 | _ <- openMagnet data_dir (nullMagnet def) | ||
18 | _ <- getHandle def | ||
19 | return () | ||
diff --git a/dht/bittorrent/tests/Network/BitTorrent/CoreSpec.hs b/dht/bittorrent/tests/Network/BitTorrent/CoreSpec.hs new file mode 100644 index 00000000..e9b17a42 --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/CoreSpec.hs | |||
@@ -0,0 +1,309 @@ | |||
1 | {-# LANGUAGE FlexibleInstances #-} | ||
2 | {-# OPTIONS_GHC -fno-warn-orphans #-} | ||
3 | module Network.BitTorrent.CoreSpec (spec) where | ||
4 | import Control.Applicative | ||
5 | import Data.BEncode as BE | ||
6 | import Data.ByteString.Lazy as BL | ||
7 | #if MIN_VERSION_iproute(1,7,4) | ||
8 | import Data.IP hiding (fromSockAddr) | ||
9 | #else | ||
10 | import Data.IP | ||
11 | #endif | ||
12 | import Data.Serialize as S | ||
13 | import Data.String | ||
14 | import Data.Text.Encoding as T | ||
15 | import Data.Word | ||
16 | import Network | ||
17 | import Test.Hspec | ||
18 | import Test.QuickCheck | ||
19 | import Test.QuickCheck.Instances () | ||
20 | |||
21 | import Network.BitTorrent.Address | ||
22 | |||
23 | |||
24 | instance Arbitrary IPv4 where | ||
25 | arbitrary = do | ||
26 | a <- choose (0, 255) | ||
27 | b <- choose (0, 255) | ||
28 | c <- choose (0, 255) | ||
29 | d <- choose (0, 255) | ||
30 | return $ toIPv4 [a, b, c, d] | ||
31 | |||
32 | instance Arbitrary IPv6 where | ||
33 | arbitrary = do | ||
34 | a <- choose (0, fromIntegral (maxBound :: Word16)) | ||
35 | b <- choose (0, fromIntegral (maxBound :: Word16)) | ||
36 | c <- choose (0, fromIntegral (maxBound :: Word16)) | ||
37 | d <- choose (0, fromIntegral (maxBound :: Word16)) | ||
38 | e <- choose (0, fromIntegral (maxBound :: Word16)) | ||
39 | f <- choose (0, fromIntegral (maxBound :: Word16)) | ||
40 | g <- choose (0, fromIntegral (maxBound :: Word16)) | ||
41 | h <- choose (0, fromIntegral (maxBound :: Word16)) | ||
42 | return $ toIPv6 [a, b, c, d, e, f, g, h] | ||
43 | |||
44 | instance Arbitrary IP where | ||
45 | arbitrary = frequency | ||
46 | [ (1, IPv4 <$> arbitrary) | ||
47 | , (1, IPv6 <$> arbitrary) | ||
48 | ] | ||
49 | |||
50 | instance Arbitrary PortNumber where | ||
51 | arbitrary = fromIntegral <$> (arbitrary :: Gen Word16) | ||
52 | |||
53 | instance Arbitrary PeerId where | ||
54 | arbitrary = oneof | ||
55 | [ azureusStyle defaultClientId defaultVersionNumber | ||
56 | <$> (T.encodeUtf8 <$> arbitrary) | ||
57 | , shadowStyle 'X' defaultVersionNumber | ||
58 | <$> (T.encodeUtf8 <$> arbitrary) | ||
59 | ] | ||
60 | |||
61 | instance Arbitrary a => Arbitrary (PeerAddr a) where | ||
62 | arbitrary = PeerAddr <$> arbitrary <*> arbitrary <*> arbitrary | ||
63 | |||
64 | instance Arbitrary NodeId where | ||
65 | arbitrary = fromString <$> vector 20 | ||
66 | |||
67 | instance Arbitrary a => Arbitrary (NodeAddr a) where | ||
68 | arbitrary = NodeAddr <$> arbitrary <*> arbitrary | ||
69 | |||
70 | instance Arbitrary a => Arbitrary (NodeInfo a) where | ||
71 | arbitrary = NodeInfo <$> arbitrary <*> arbitrary | ||
72 | |||
73 | spec :: Spec | ||
74 | spec = do | ||
75 | describe "PeerId" $ do | ||
76 | it "properly bencoded" $ do | ||
77 | BE.decode "20:01234567890123456789" | ||
78 | `shouldBe` Right ("01234567890123456789" :: PeerId) | ||
79 | |||
80 | describe "PortNumber" $ do | ||
81 | it "properly serialized" $ do | ||
82 | S.decode "\x1\x2" `shouldBe` Right (258 :: PortNumber) | ||
83 | S.encode (258 :: PortNumber) `shouldBe` "\x1\x2" | ||
84 | |||
85 | it "properly bencoded" $ do | ||
86 | BE.decode "i80e" `shouldBe` Right (80 :: PortNumber) | ||
87 | |||
88 | it "fail if port number is invalid" $ do | ||
89 | (BE.decode "i-10e" :: BE.Result PortNumber) | ||
90 | `shouldBe` | ||
91 | Left "fromBEncode: unable to decode PortNumber: -10" | ||
92 | |||
93 | (BE.decode "i70000e" :: BE.Result PortNumber) | ||
94 | `shouldBe` | ||
95 | Left "fromBEncode: unable to decode PortNumber: 70000" | ||
96 | |||
97 | describe "Peer IPv4" $ do | ||
98 | it "properly serialized" $ do | ||
99 | S.decode "\x1\x2\x3\x4" `shouldBe` Right (toIPv4 [1, 2, 3, 4]) | ||
100 | S.encode (toIPv4 [1, 2, 3, 4]) `shouldBe` "\x1\x2\x3\x4" | ||
101 | |||
102 | it "properly serialized (iso)" $ property $ \ ip -> do | ||
103 | S.decode (S.encode ip) `shouldBe` Right (ip :: IPv4) | ||
104 | |||
105 | it "properly bencoded" $ do | ||
106 | BE.decode "11:168.192.0.1" `shouldBe` Right (toIPv4 [168, 192, 0, 1]) | ||
107 | BE.encode (toIPv4 [168, 192, 0, 1]) `shouldBe` "11:168.192.0.1" | ||
108 | |||
109 | it "properly bencoded (iso)" $ property $ \ ip -> | ||
110 | BE.decode (BL.toStrict (BE.encode ip)) `shouldBe` Right (ip :: IPv4) | ||
111 | |||
112 | it "fail gracefully on invalid strings" $ do | ||
113 | BE.decode "3:1.1" `shouldBe` | ||
114 | (Left "fromBEncode: unable to decode IP: 1.1" :: BE.Result IPv4) | ||
115 | |||
116 | it "fail gracefully on invalid bencode" $ do | ||
117 | BE.decode "i10e" `shouldBe` | ||
118 | (Left "fromBEncode: unable to decode IP: addr should be a bstring" | ||
119 | :: BE.Result IPv4) | ||
120 | |||
121 | describe "Peer IPv6" $ do | ||
122 | it "properly serialized" $ do | ||
123 | S.decode "\x1\x2\x3\x4\x5\x6\x7\x8\x9\xa\xb\xc\xd\xe\xf\x10" | ||
124 | `shouldBe` | ||
125 | Right ("102:304:506:708:90a:b0c:d0e:f10" :: IPv6) | ||
126 | |||
127 | S.encode ("102:304:506:708:90a:b0c:d0e:f10" :: IPv6) | ||
128 | `shouldBe` | ||
129 | "\x1\x2\x3\x4\x5\x6\x7\x8\x9\xa\xb\xc\xd\xe\xf\x10" | ||
130 | |||
131 | it "properly serialized (iso)" $ property $ \ ip -> | ||
132 | S.decode (S.encode ip) `shouldBe` Right (ip :: IPv6) | ||
133 | |||
134 | it "properly bencoded" $ do | ||
135 | BE.decode "3:::1" `shouldBe` Right (toIPv6 [0, 0, 0, 0, 0, 0, 0, 1]) | ||
136 | BE.encode (toIPv6 [0, 0, 0, 0, 0, 0, 0, 1]) `shouldBe` | ||
137 | "23:00:00:00:00:00:00:00:01" | ||
138 | |||
139 | BE.decode "23:00:00:00:00:00:00:00:01" | ||
140 | `shouldBe` | ||
141 | Right (toIPv6 [0, 0, 0, 0, 0, 0, 0, 1]) | ||
142 | |||
143 | it "properly bencoded iso" $ property $ \ ip -> | ||
144 | BE.decode (BL.toStrict (BE.encode ip)) `shouldBe` Right (ip :: IPv4) | ||
145 | |||
146 | it "fail gracefully on invalid strings" $ do | ||
147 | BE.decode "4:g::1" `shouldBe` | ||
148 | (Left "fromBEncode: unable to decode IP: g::1" :: BE.Result IPv6) | ||
149 | |||
150 | it "fail gracefully on invalid bencode" $ do | ||
151 | BE.decode "i10e" `shouldBe` | ||
152 | (Left "fromBEncode: unable to decode IP: addr should be a bstring" | ||
153 | :: BE.Result IPv6) | ||
154 | |||
155 | |||
156 | describe "Peer IP" $ do | ||
157 | it "properly serialized IPv6" $ do | ||
158 | S.decode "\x1\x2\x3\x4\x5\x6\x7\x8\x9\xa\xb\xc\xd\xe\xf\x10" | ||
159 | `shouldBe` | ||
160 | Right ("102:304:506:708:90a:b0c:d0e:f10" :: IP) | ||
161 | |||
162 | S.encode ("102:304:506:708:90a:b0c:d0e:f10" :: IP) | ||
163 | `shouldBe` | ||
164 | "\x1\x2\x3\x4\x5\x6\x7\x8\x9\xa\xb\xc\xd\xe\xf\x10" | ||
165 | |||
166 | it "properly serialized (iso) IPv6" $ property $ \ ip -> | ||
167 | S.decode (S.encode ip) `shouldBe` Right (ip :: IP) | ||
168 | |||
169 | it "properly serialized IPv4" $ do | ||
170 | S.decode "\x1\x2\x3\x4" `shouldBe` Right (IPv4 $ toIPv4 [1, 2, 3, 4]) | ||
171 | S.encode (toIPv4 [1, 2, 3, 4]) `shouldBe` "\x1\x2\x3\x4" | ||
172 | |||
173 | it "properly serialized (iso) IPv4" $ property $ \ ip -> do | ||
174 | S.decode (S.encode ip) `shouldBe` Right (ip :: IP) | ||
175 | |||
176 | it "properly bencoded" $ do | ||
177 | BE.decode "11:168.192.0.1" `shouldBe` | ||
178 | Right (IPv4 (toIPv4 [168, 192, 0, 1])) | ||
179 | |||
180 | BE.decode "3:::1" `shouldBe` Right | ||
181 | (IPv6 (toIPv6 [0, 0, 0, 0, 0, 0, 0, 1])) | ||
182 | |||
183 | BE.encode (IPv6 (toIPv6 [0, 0, 0, 0, 0, 0, 0, 1])) `shouldBe` | ||
184 | "23:00:00:00:00:00:00:00:01" | ||
185 | |||
186 | BE.decode "23:00:00:00:00:00:00:00:01" | ||
187 | `shouldBe` | ||
188 | Right (IPv6 (toIPv6 [0, 0, 0, 0, 0, 0, 0, 1])) | ||
189 | |||
190 | it "properly bencoded iso" $ property $ \ ip -> | ||
191 | BE.decode (BL.toStrict (BE.encode ip)) `shouldBe` Right (ip :: IP) | ||
192 | |||
193 | it "fail gracefully on invalid strings" $ do | ||
194 | BE.decode "4:g::1" `shouldBe` | ||
195 | (Left "fromBEncode: unable to decode IP: g::1" :: BE.Result IP) | ||
196 | |||
197 | it "fail gracefully on invalid bencode" $ do | ||
198 | BE.decode "i10e" `shouldBe` | ||
199 | (Left "fromBEncode: unable to decode IP: addr should be a bstring" | ||
200 | :: BE.Result IP) | ||
201 | |||
202 | describe "PeerAddr" $ do | ||
203 | it "IsString" $ do | ||
204 | ("127.0.0.1:80" :: PeerAddr IP) | ||
205 | `shouldBe` PeerAddr Nothing "127.0.0.1" 80 | ||
206 | |||
207 | ("127.0.0.1:80" :: PeerAddr IPv4) | ||
208 | `shouldBe` PeerAddr Nothing "127.0.0.1" 80 | ||
209 | |||
210 | ("[::1]:80" :: PeerAddr IP) | ||
211 | `shouldBe` PeerAddr Nothing "::1" 80 | ||
212 | |||
213 | ("[::1]:80" :: PeerAddr IPv6) | ||
214 | `shouldBe` PeerAddr Nothing "::1" 80 | ||
215 | |||
216 | it "properly bencoded (iso)" $ property $ \ addr -> | ||
217 | BE.decode (BL.toStrict (BE.encode addr)) | ||
218 | `shouldBe` Right (addr :: PeerAddr IP) | ||
219 | |||
220 | |||
221 | it "properly bencoded (ipv4)" $ do | ||
222 | BE.decode "d2:ip11:168.192.0.1\ | ||
223 | \7:peer id20:01234567890123456789\ | ||
224 | \4:porti6881e\ | ||
225 | \e" | ||
226 | `shouldBe` | ||
227 | Right (PeerAddr (Just "01234567890123456789") | ||
228 | (IPv4 (toIPv4 [168, 192, 0, 1])) | ||
229 | 6881) | ||
230 | |||
231 | it "properly bencoded (ipv6)" $ do | ||
232 | BE.decode "d2:ip3:::1\ | ||
233 | \7:peer id20:01234567890123456789\ | ||
234 | \4:porti6881e\ | ||
235 | \e" | ||
236 | `shouldBe` | ||
237 | Right (PeerAddr (Just "01234567890123456789") | ||
238 | (IPv6 (toIPv6 [0, 0, 0, 0, 0, 0, 0, 1])) | ||
239 | 6881) | ||
240 | |||
241 | it "peer id is optional" $ do | ||
242 | BE.decode "d2:ip11:168.192.0.1\ | ||
243 | \4:porti6881e\ | ||
244 | \e" | ||
245 | `shouldBe` | ||
246 | Right (PeerAddr Nothing (IPv4 (toIPv4 [168, 192, 0, 1])) 6881) | ||
247 | |||
248 | it "has sock addr for both ipv4 and ipv6" $ do | ||
249 | show (peerSockAddr "128.0.0.1:80") `shouldBe` "128.0.0.1:80" | ||
250 | show (peerSockAddr "[::1]:8080" ) `shouldBe` "[::1]:8080" | ||
251 | |||
252 | describe "NodeId" $ do | ||
253 | it "properly serialized" $ do | ||
254 | S.decode "mnopqrstuvwxyz123456" | ||
255 | `shouldBe` Right ("mnopqrstuvwxyz123456" :: NodeId) | ||
256 | |||
257 | S.encode ("mnopqrstuvwxyz123456" :: NodeId) | ||
258 | `shouldBe` "mnopqrstuvwxyz123456" | ||
259 | |||
260 | it "properly serialized (iso)" $ property $ \ nid -> | ||
261 | S.decode (S.encode nid) `shouldBe` | ||
262 | Right (nid :: NodeId) | ||
263 | |||
264 | describe "NodeAddr" $ do | ||
265 | it "properly serialized" $ do | ||
266 | S.decode "\127\0\0\1\1\2" `shouldBe` | ||
267 | Right ("127.0.0.1:258" :: NodeAddr IPv4) | ||
268 | |||
269 | it "properly serialized (iso)" $ property $ \ nid -> | ||
270 | S.decode (S.encode nid) `shouldBe` | ||
271 | Right (nid :: NodeAddr IPv4) | ||
272 | |||
273 | describe "NodeInfo" $ do | ||
274 | it "properly serialized" $ do | ||
275 | S.decode "mnopqrstuvwxyz123456\ | ||
276 | \\127\0\0\1\1\2" `shouldBe` Right | ||
277 | (NodeInfo "mnopqrstuvwxyz123456" "127.0.0.1:258" :: NodeInfo IPv4) | ||
278 | |||
279 | it "properly serialized (iso)" $ property $ \ nid -> | ||
280 | S.decode (S.encode nid) `shouldBe` | ||
281 | Right (nid :: NodeInfo IPv4) | ||
282 | |||
283 | -- see <http://bittorrent.org/beps/bep_0020.html> | ||
284 | describe "Fingerprint" $ do | ||
285 | it "decode mainline encoded peer id" $ do | ||
286 | fingerprint "M4-3-6--xxxxxxxxxxxx" `shouldBe` "Mainline-4.3.6" | ||
287 | fingerprint "M4-20-8-xxxxxxxxxxxx" `shouldBe` "Mainline-4.20.8" | ||
288 | |||
289 | it "decode azureus encoded peer id" $ do | ||
290 | fingerprint "-AZ2060-xxxxxxxxxxxx" `shouldBe` "Azureus-2060" | ||
291 | fingerprint "-BS0000-xxxxxxxxxxxx" `shouldBe` "BTSlave-0" | ||
292 | |||
293 | it "decode Shad0w style peer id" $ do | ||
294 | fingerprint "S58B-----xxxxxxxxxxx" `shouldBe` "Shadow-5.8.11" | ||
295 | fingerprint "T58B-----xxxxxxxxxxx" `shouldBe` "BitTornado-5.8.11" | ||
296 | |||
297 | it "decode bitcomet style peer id" $ do | ||
298 | fingerprint "exbc01xxxxxxxxxxxxxx" `shouldBe` "BitComet-48.49" | ||
299 | fingerprint "FUTB01xxxxxxxxxxxxxx" `shouldBe` "BitComet-48.49" | ||
300 | fingerprint "exbc01LORDxxxxxxxxxx" `shouldBe` "BitLord-48.49" | ||
301 | |||
302 | it "decode opera style peer id" $ do | ||
303 | fingerprint "OP0123xxxxxxxxxxxxxx" `shouldBe` "Opera-123" | ||
304 | |||
305 | it "decode ML donkey style peer id" $ do | ||
306 | fingerprint "-ML2.7.2-xxxxxxxxxxx" `shouldBe` "MLdonkey-0" | ||
307 | |||
308 | -- TODO XBT, Bits on Wheels, Queen Bee, BitTyrant, TorrenTopia, | ||
309 | -- BitSpirit, Rufus, G3 Torrent, FlashGet | ||
diff --git a/dht/bittorrent/tests/Network/BitTorrent/DHT/MessageSpec.hs b/dht/bittorrent/tests/Network/BitTorrent/DHT/MessageSpec.hs new file mode 100644 index 00000000..6f3c7489 --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/DHT/MessageSpec.hs | |||
@@ -0,0 +1,221 @@ | |||
1 | {-# LANGUAGE RecordWildCards #-} | ||
2 | module Network.BitTorrent.DHT.MessageSpec (spec) where | ||
3 | import Control.Monad.Reader | ||
4 | import Control.Monad.Logger | ||
5 | import Control.Concurrent | ||
6 | import Data.BEncode as BE | ||
7 | import Data.ByteString.Lazy as BL | ||
8 | import Data.Default | ||
9 | import Data.List as L | ||
10 | import Data.Maybe | ||
11 | import Network.BitTorrent.Address | ||
12 | import Network.BitTorrent.DHT.Message | ||
13 | import qualified Network.KRPC as KRPC (def) | ||
14 | import Network.KRPC hiding (def) | ||
15 | import Network.Socket (PortNumber) | ||
16 | import Test.Hspec | ||
17 | import Test.QuickCheck | ||
18 | import System.Timeout | ||
19 | |||
20 | import Data.TorrentSpec () | ||
21 | import Network.BitTorrent.CoreSpec () | ||
22 | import Network.BitTorrent.DHT.TokenSpec () | ||
23 | |||
24 | -- Arbitrary queries and responses. | ||
25 | instance Arbitrary Ping where arbitrary = pure Ping | ||
26 | instance Arbitrary FindNode where arbitrary = FindNode <$> arbitrary | ||
27 | instance Arbitrary ip => Arbitrary (NodeFound ip) where arbitrary = NodeFound <$> arbitrary | ||
28 | instance Arbitrary GetPeers where arbitrary = GetPeers <$> arbitrary | ||
29 | instance Arbitrary ip => Arbitrary (GotPeers ip) where arbitrary = GotPeers <$> arbitrary <*> arbitrary | ||
30 | instance Arbitrary Announce where arbitrary = Announce <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary | ||
31 | instance Arbitrary Announced where arbitrary = pure Announced | ||
32 | instance Arbitrary x => Arbitrary (Query x) where arbitrary = Query <$> arbitrary <*> arbitrary <*> arbitrary | ||
33 | instance Arbitrary x => Arbitrary (Response x) where arbitrary = Response <$> arbitrary <*> arbitrary | ||
34 | |||
35 | instance MonadLogger IO where | ||
36 | monadLoggerLog _ _ _ _ = return () | ||
37 | |||
38 | remoteAddr :: SockAddr | ||
39 | remoteAddr = SockAddrInet 6881 (256 * 256 * 256 + 127) | ||
40 | |||
41 | thisAddr :: SockAddr | ||
42 | thisAddr = SockAddrInet 60000 (256 * 256 * 256 + 127) | ||
43 | |||
44 | thisPort :: PortNumber | ||
45 | thisPort = 60001 | ||
46 | |||
47 | rpc :: ReaderT (Manager IO) IO a -> IO a | ||
48 | rpc action = do | ||
49 | withManager KRPC.def thisAddr [] $ runReaderT $ do | ||
50 | listen | ||
51 | action | ||
52 | |||
53 | isQueryError :: QueryFailure -> Bool | ||
54 | isQueryError _ = True | ||
55 | |||
56 | prop_bencode :: Eq a => Show a => BEncode a => a -> Expectation | ||
57 | prop_bencode x = BE.decode (BL.toStrict (BE.encode x)) `shouldBe` Right x | ||
58 | |||
59 | retry :: Int -> IO (Maybe a) -> IO (Maybe a) | ||
60 | retry 0 _ = return Nothing | ||
61 | retry n a = do | ||
62 | res <- a | ||
63 | case res of | ||
64 | Just _ -> return res | ||
65 | Nothing -> threadDelay (100 * 1000) >> retry (n-1) a | ||
66 | |||
67 | spec :: Spec | ||
68 | spec = do | ||
69 | context ("you need running DHT node at " ++ show remoteAddr) $ do | ||
70 | it "is running" $ do | ||
71 | running <- retry 5 $ timeout (100 * 1000) $ do | ||
72 | nid <- genNodeId | ||
73 | Response _remoteAddr Ping <- | ||
74 | rpc (query remoteAddr (Query nid False Ping)) | ||
75 | return () | ||
76 | running `shouldSatisfy` isJust | ||
77 | |||
78 | describe "ping" $ do | ||
79 | it "properly bencoded" $ do | ||
80 | BE.decode "d2:id20:abcdefghij0123456789e" | ||
81 | `shouldBe` Right (Query "abcdefghij0123456789" False Ping) | ||
82 | |||
83 | BE.encode (Query "abcdefghij0123456789" False Ping) | ||
84 | `shouldBe` "d2:id20:abcdefghij0123456789e" | ||
85 | |||
86 | BE.decode "d2:id20:mnopqrstuvwxyz123456e" | ||
87 | `shouldBe` Right (Response "mnopqrstuvwxyz123456" Ping) | ||
88 | |||
89 | BE.encode (Response "mnopqrstuvwxyz123456" Ping) | ||
90 | `shouldBe` "d2:id20:mnopqrstuvwxyz123456e" | ||
91 | |||
92 | it "properly bencoded (iso)" $ property $ \ nid -> do | ||
93 | prop_bencode (Query nid False Ping) | ||
94 | prop_bencode (Response nid Ping) | ||
95 | |||
96 | it "does compatible with existing DHT" $ do | ||
97 | nid <- genNodeId | ||
98 | Response _remoteAddr Ping <- rpc (query remoteAddr (Query nid False Ping)) | ||
99 | return () | ||
100 | |||
101 | describe "find_node" $ do | ||
102 | it "properly bencoded" $ do | ||
103 | BE.decode "d2:id20:abcdefghij0123456789\ | ||
104 | \6:target20:mnopqrstuvwxyz123456e" | ||
105 | `shouldBe` Right (Query "abcdefghij0123456789" False | ||
106 | (FindNode "mnopqrstuvwxyz123456")) | ||
107 | |||
108 | BE.encode (Query "abcdefghij0123456789" False | ||
109 | (FindNode "mnopqrstuvwxyz123456")) | ||
110 | `shouldBe` | ||
111 | "d2:id20:abcdefghij01234567896:target20:mnopqrstuvwxyz123456e" | ||
112 | |||
113 | let naddr = "127.0.0.1:258" :: NodeAddr IPv4 | ||
114 | let nid = "0123456789abcdefghij" | ||
115 | let nid' = "mnopqrstuvwxyz123456" | ||
116 | BE.decode "d2:id20:0123456789abcdefghij\ | ||
117 | \5:nodes26:mnopqrstuvwxyz123456\127\0\0\1\1\2\ | ||
118 | \e" | ||
119 | `shouldBe` Right (Response nid (NodeFound [NodeInfo nid' naddr])) | ||
120 | |||
121 | it "properly bencoded (iso)" $ property $ \ nid x xs -> do | ||
122 | prop_bencode (Query nid False (FindNode x)) | ||
123 | prop_bencode (Response nid (NodeFound (xs :: [NodeInfo IPv4] ))) | ||
124 | |||
125 | it "does compatible with existing DHT" $ do | ||
126 | nid <- genNodeId | ||
127 | Response _remoteAddr (NodeFound xs) <- rpc $ do | ||
128 | query remoteAddr (Query nid False (FindNode nid)) | ||
129 | L.length (xs :: [NodeInfo IPv4]) `shouldSatisfy` (> 0) | ||
130 | |||
131 | describe "get_peers" $ do | ||
132 | it "properly bencoded" $ do | ||
133 | BE.decode "d2:id20:abcdefghij0123456789\ | ||
134 | \9:info_hash20:mnopqrstuvwxyz123456\ | ||
135 | \e" | ||
136 | `shouldBe` Right (Query "abcdefghij0123456789" False | ||
137 | (GetPeers "mnopqrstuvwxyz123456")) | ||
138 | |||
139 | BE.decode "d2:id20:abcdefghij0123456789\ | ||
140 | \5:token8:aoeusnth\ | ||
141 | \6:valuesl6:\127\0\0\1\1\2\&6:\192\168\1\100\1\2e\ | ||
142 | \e" | ||
143 | `shouldBe` Right (Response "abcdefghij0123456789" | ||
144 | (GotPeers (Right [ "127.0.0.1:258" :: PeerAddr IPv4 | ||
145 | , "192.168.1.100:258" | ||
146 | ]) "aoeusnth")) | ||
147 | |||
148 | BE.decode "d2:id20:abcdefghij0123456789\ | ||
149 | \5:nodes26:mnopqrstuvwxyz123456\127\0\0\1\1\2\ | ||
150 | \5:token8:aoeusnth\ | ||
151 | \e" | ||
152 | `shouldBe` Right (Response "abcdefghij0123456789" | ||
153 | (GotPeers | ||
154 | { peers = Left [NodeInfo "mnopqrstuvwxyz123456" "127.0.0.1:258" | ||
155 | :: NodeInfo IPv4] | ||
156 | , grantedToken = "aoeusnth" | ||
157 | })) | ||
158 | |||
159 | it "properly bencoded (iso)" $ property $ \ nid topic exs token -> do | ||
160 | prop_bencode (Query nid False (GetPeers topic)) | ||
161 | let _ = exs :: Either [NodeInfo IPv4] [PeerAddr IPv4] | ||
162 | let nullPeerId paddr = paddr {peerId = Nothing} | ||
163 | let nullPeerIds = either Left (Right . L.map nullPeerId) | ||
164 | prop_bencode (Response nid (GotPeers (nullPeerIds exs) token)) | ||
165 | |||
166 | it "does compatible with existing DHT" $ do | ||
167 | nid <- genNodeId | ||
168 | Response _remoteId (GotPeers {..}) | ||
169 | <- rpc $ query remoteAddr (Query nid False (GetPeers def)) | ||
170 | let _ = peers :: Either [NodeInfo IPv4] [PeerAddr IPv4] | ||
171 | either L.length L.length peers `shouldSatisfy` (> 0) | ||
172 | |||
173 | describe "announce" $ do | ||
174 | it "properly bencoded" $ do | ||
175 | BE.decode "d2:id20:abcdefghij0123456789\ | ||
176 | \9:info_hash20:mnopqrstuvwxyz123456\ | ||
177 | \4:porti6881e\ | ||
178 | \5:token8:aoeusnth\ | ||
179 | \e" `shouldBe` Right | ||
180 | (Query "abcdefghij0123456789" False | ||
181 | (Announce False "mnopqrstuvwxyz123456" Nothing 6881 "aoeusnth")) | ||
182 | |||
183 | BE.decode "d2:id20:abcdefghij0123456789\ | ||
184 | \12:implied_porti1e\ | ||
185 | \9:info_hash20:mnopqrstuvwxyz123456\ | ||
186 | \4:porti6881e\ | ||
187 | \5:token8:aoeusnth\ | ||
188 | \e" `shouldBe` Right | ||
189 | (Query "abcdefghij0123456789" False | ||
190 | (Announce True "mnopqrstuvwxyz123456" Nothing 6881 "aoeusnth")) | ||
191 | |||
192 | |||
193 | BE.decode "d2:id20:mnopqrstuvwxyz123456e" | ||
194 | `shouldBe` Right | ||
195 | (Response "mnopqrstuvwxyz123456" Announced) | ||
196 | |||
197 | it "properly bencoded (iso)" $ property $ \ nid flag topic port token -> do | ||
198 | prop_bencode (Query nid False (Announce flag topic Nothing port token)) | ||
199 | prop_bencode (Response nid (Announced)) | ||
200 | |||
201 | |||
202 | it "does compatible with existing DHT" $ do | ||
203 | nid <- genNodeId | ||
204 | Response _remoteId Announced <- rpc $ do | ||
205 | Response _ GotPeers {..} <- query remoteAddr (Query nid False (GetPeers def)) | ||
206 | let _ = peers :: Either [NodeInfo IPv4] [PeerAddr IPv4] | ||
207 | query remoteAddr (Query nid False (Announce False def Nothing thisPort grantedToken)) | ||
208 | return () | ||
209 | |||
210 | it "does fail on invalid token" $ do | ||
211 | nid <- genNodeId | ||
212 | (rpc $ do | ||
213 | Response _ GotPeers {..} <- query remoteAddr (Query nid False (GetPeers def)) | ||
214 | let _ = peers :: Either [NodeInfo IPv4] [PeerAddr IPv4] | ||
215 | let invalidToken = "" | ||
216 | let q :: MonadKRPC h m => SockAddr -> Query Announce | ||
217 | -> m (Response Announced) | ||
218 | q = query | ||
219 | q remoteAddr (Query nid False (Announce False def Nothing thisPort invalidToken))) | ||
220 | `shouldThrow` isQueryError | ||
221 | return () | ||
diff --git a/dht/bittorrent/tests/Network/BitTorrent/DHT/QuerySpec.hs b/dht/bittorrent/tests/Network/BitTorrent/DHT/QuerySpec.hs new file mode 100644 index 00000000..93f78263 --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/DHT/QuerySpec.hs | |||
@@ -0,0 +1,105 @@ | |||
1 | {-# LANGUAGE RecordWildCards #-} | ||
2 | module Network.BitTorrent.DHT.QuerySpec (spec) where | ||
3 | import Control.Applicative | ||
4 | import Control.Exception | ||
5 | import Control.Monad.Reader | ||
6 | import Data.Conduit as C | ||
7 | import Data.Conduit.List as CL | ||
8 | import Data.Default | ||
9 | import Data.List as L | ||
10 | import Test.Hspec | ||
11 | |||
12 | import Network.BitTorrent.Address | ||
13 | import Network.BitTorrent.DHT | ||
14 | import Network.BitTorrent.DHT.Session | ||
15 | import Network.BitTorrent.DHT.Query | ||
16 | |||
17 | import Network.BitTorrent.DHT.TestData | ||
18 | |||
19 | |||
20 | myAddr :: NodeAddr IPv4 | ||
21 | myAddr = "0.0.0.0:0" | ||
22 | |||
23 | nullLogger :: LogFun | ||
24 | nullLogger _ _ _ _ = return () | ||
25 | |||
26 | --simpleLogger :: LogFun | ||
27 | --simpleLogger _ t _ _ = print t | ||
28 | |||
29 | simpleDHT :: [NodeHandler IPv4] -> DHT IPv4 a -> IO a | ||
30 | simpleDHT hs m = | ||
31 | bracket (newNode hs def myAddr nullLogger Nothing) closeNode $ \ node -> | ||
32 | runDHT node m | ||
33 | |||
34 | getBootInfo :: IO (NodeInfo IPv4) | ||
35 | getBootInfo = do | ||
36 | startAddr <- resolveHostName (L.head defaultBootstrapNodes) | ||
37 | simpleDHT [] $ fmap fst (pingQ startAddr) | ||
38 | |||
39 | spec :: Spec | ||
40 | spec = parallel $ do | ||
41 | describe "environment" $ do | ||
42 | describe "test node" $ do | ||
43 | it "is alive" $ do | ||
44 | _ <- getBootInfo | ||
45 | return () | ||
46 | |||
47 | describe "handlers" $ do | ||
48 | it "" $ pendingWith "need to setup 2 DHT locally" | ||
49 | |||
50 | describe "basic queries" $ do | ||
51 | it "ping" $ do | ||
52 | _ <- getBootInfo | ||
53 | return () | ||
54 | |||
55 | it "findNode" $ do | ||
56 | startInfo <- getBootInfo | ||
57 | _ <- simpleDHT [] $ do | ||
58 | nid <- myNodeIdAccordingTo (read "8.8.8.8:6881") | ||
59 | findNodeQ nid startInfo | ||
60 | return () | ||
61 | |||
62 | it "getPeers" $ do | ||
63 | startInfo <- getBootInfo | ||
64 | peers <- simpleDHT [] $ do | ||
65 | nid <- myNodeIdAccordingTo (read "8.8.8.8:6881") | ||
66 | |||
67 | -- we should not run getPeers query on boot node, because | ||
68 | -- it may not support it | ||
69 | Right infos <- findNodeQ nid startInfo | ||
70 | |||
71 | when (L.null infos) $ | ||
72 | error "boot node malfunction" | ||
73 | |||
74 | -- at least one node should reply | ||
75 | queryParallel $ do | ||
76 | getPeersQ (entryHash (L.head testTorrents)) <$> infos | ||
77 | |||
78 | peers `shouldSatisfy` (not . L.null) | ||
79 | |||
80 | it "announce" $ do | ||
81 | bootNode <- getBootInfo | ||
82 | _ <- simpleDHT [] $ do | ||
83 | let ih = entryHash (L.head testTorrents) | ||
84 | Right nodes <- findNodeQ ih bootNode | ||
85 | |||
86 | when (L.null nodes) $ | ||
87 | error "boot node malfunction" | ||
88 | |||
89 | queryParallel $ do | ||
90 | announceQ ih (nodePort myAddr) <$> nodes | ||
91 | |||
92 | return () | ||
93 | |||
94 | describe "iterative queries" $ do | ||
95 | forM_ testTorrents $ \ TestEntry {..} -> do | ||
96 | context entryName $ do | ||
97 | |||
98 | it "get at least 10 unique peers for each infohash" $ do | ||
99 | bootNode <- getBootInfo | ||
100 | peers <- simpleDHT [] $ do | ||
101 | Right startNodes <- findNodeQ entryHash bootNode | ||
102 | sourceList [startNodes] $= | ||
103 | search entryHash (getPeersQ entryHash) $= | ||
104 | CL.concat $$ CL.take 10 | ||
105 | L.length peers `shouldBe` 10 | ||
diff --git a/dht/bittorrent/tests/Network/BitTorrent/DHT/RoutingSpec.hs b/dht/bittorrent/tests/Network/BitTorrent/DHT/RoutingSpec.hs new file mode 100644 index 00000000..07a906ba --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/DHT/RoutingSpec.hs | |||
@@ -0,0 +1,77 @@ | |||
1 | {-# LANGUAGE ScopedTypeVariables #-} | ||
2 | {-# LANGUAGE FlexibleContexts #-} | ||
3 | module Network.BitTorrent.DHT.RoutingSpec (spec) where | ||
4 | import Control.Applicative | ||
5 | import Control.Monad.State | ||
6 | import Data.Default | ||
7 | import Data.List as L | ||
8 | import Data.Maybe | ||
9 | import Test.Hspec | ||
10 | import Test.QuickCheck | ||
11 | |||
12 | import Network.BitTorrent.Address | ||
13 | import Network.BitTorrent.DHT.Routing as T | ||
14 | |||
15 | import Network.BitTorrent.CoreSpec hiding (spec) | ||
16 | |||
17 | |||
18 | type Network ip = [NodeAddr ip] | ||
19 | |||
20 | data Env ip = Env | ||
21 | { currentTime :: Timestamp | ||
22 | , network :: Network ip | ||
23 | } deriving Show | ||
24 | |||
25 | type Simulation ip = State (Env ip) | ||
26 | |||
27 | runSimulation :: Eq ip => Env ip -> Routing ip a -> Maybe a | ||
28 | runSimulation e m = evalState (runRouting ping closest timestamp m) e | ||
29 | where | ||
30 | ping addr = gets (L.elem addr . network) | ||
31 | closest nid = error "runSimulation" | ||
32 | timestamp = gets currentTime | ||
33 | |||
34 | instance Arbitrary ip => Arbitrary (Env ip) where | ||
35 | arbitrary = Env <$> arbitrary <*> (vector nodeCount) | ||
36 | where | ||
37 | nodeCount = 1000 | ||
38 | |||
39 | instance (Arbitrary ip, Eq ip) => Arbitrary (Table ip) where | ||
40 | arbitrary = do | ||
41 | thisId <- arbitrary | ||
42 | bucketN <- choose (1, 20) | ||
43 | let table = nullTable thisId bucketN | ||
44 | |||
45 | -- nodeN <- (`mod` bucketN) <$> arbitrary | ||
46 | -- nodes <- vector nodeN | ||
47 | |||
48 | node <- arbitrary | ||
49 | mt <- do | ||
50 | env <- arbitrary | ||
51 | return $ runSimulation env $ do | ||
52 | (_,t') <- T.insert (currentTime env) (TryInsert node) table | ||
53 | return t' :: Routing ip (Table ip) | ||
54 | --(foldM (flip fillTable) table nodes) | ||
55 | return (fromJust mt) | ||
56 | -- where | ||
57 | -- fillTable x t = do | ||
58 | -- t' <- T.insert x t | ||
59 | -- return $ if T.full t' then t else t' | ||
60 | |||
61 | spec :: Spec | ||
62 | spec = do | ||
63 | describe "size" $ do | ||
64 | it "null table is empty" $ do | ||
65 | T.size (nullTable def 2 :: Table IPv4) `shouldBe` 0 | ||
66 | |||
67 | it "the same node never appear in different buckets" $ property $ \ t -> do | ||
68 | let xss = T.toList (t :: Table Int) | ||
69 | let justOnce x = L.length (L.filter (L.elem x) xss) == 1 | ||
70 | L.all justOnce (L.concat xss) | ||
71 | |||
72 | it "insert is idemponent" $ property $ \ (e :: Env Int) n t -> do | ||
73 | let ins :: NodeInfo Int -> Table Int -> Routing Int (Table Int) | ||
74 | ins n t = snd <$> T.insert (currentTime e) (TryInsert n) t | ||
75 | let t1 = runSimulation e (ins n t) | ||
76 | let t2 = runSimulation e (ins n t >>= ins n) | ||
77 | t1 `shouldBe` t2 | ||
diff --git a/dht/bittorrent/tests/Network/BitTorrent/DHT/SessionSpec.hs b/dht/bittorrent/tests/Network/BitTorrent/DHT/SessionSpec.hs new file mode 100644 index 00000000..32e4c158 --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/DHT/SessionSpec.hs | |||
@@ -0,0 +1,110 @@ | |||
1 | {-# LANGUAGE ScopedTypeVariables #-} | ||
2 | module Network.BitTorrent.DHT.SessionSpec (spec) where | ||
3 | import Control.Applicative | ||
4 | import Control.Concurrent | ||
5 | import Control.Exception | ||
6 | import Control.Monad.Reader | ||
7 | import Control.Monad.Trans.Resource | ||
8 | import Data.Conduit.Lazy | ||
9 | import Data.Default | ||
10 | import Data.List as L | ||
11 | import Test.Hspec | ||
12 | import Test.QuickCheck | ||
13 | |||
14 | import Network.BitTorrent.Address | ||
15 | import Network.BitTorrent.DHT | ||
16 | import Network.BitTorrent.DHT.Message | ||
17 | import Network.BitTorrent.DHT.Session | ||
18 | import Network.BitTorrent.DHT.Query | ||
19 | |||
20 | import Data.TorrentSpec () | ||
21 | import Network.BitTorrent.CoreSpec () | ||
22 | import Network.BitTorrent.DHT.TokenSpec () | ||
23 | |||
24 | |||
25 | myAddr :: NodeAddr IPv4 | ||
26 | myAddr = "127.0.0.1:60000" | ||
27 | |||
28 | simpleDHT :: DHT IPv4 a -> IO a | ||
29 | simpleDHT m = | ||
30 | bracket (newNode defaultHandlers def myAddr nullLogger Nothing) closeNode $ \ node -> | ||
31 | runDHT node m | ||
32 | |||
33 | isRight :: Either a b -> Bool | ||
34 | isRight (Left _) = False | ||
35 | isRight (Right _) = True | ||
36 | |||
37 | isLeft :: Either a b -> Bool | ||
38 | isLeft = not . isRight | ||
39 | |||
40 | nullLogger :: LogFun | ||
41 | nullLogger _ _ _ _ = return () | ||
42 | |||
43 | spec :: Spec | ||
44 | spec = do | ||
45 | describe "session" $ do | ||
46 | it "is active until closeNode called" $ do | ||
47 | node <- newNode [] def myAddr nullLogger Nothing | ||
48 | runDHT node monadActive `shouldReturn` True | ||
49 | runDHT node monadActive `shouldReturn` True | ||
50 | closeNode node | ||
51 | runDHT node monadActive `shouldReturn` False | ||
52 | |||
53 | describe "tokens" $ do | ||
54 | it "should not complain about valid token" $ | ||
55 | property $ \ (addrs :: [NodeAddr IPv4]) -> do | ||
56 | isOks <- simpleDHT $ do | ||
57 | forM addrs $ \ addr -> do | ||
58 | token <- grantToken addr | ||
59 | checkToken addr token | ||
60 | L.and isOks `shouldBe` True | ||
61 | |||
62 | it "should complain about invalid token" $ | ||
63 | property $ \ (addr :: NodeAddr IPv4) token -> do | ||
64 | isOk <- simpleDHT (checkToken addr token) | ||
65 | isOk `shouldBe` False | ||
66 | |||
67 | describe "routing table" $ | ||
68 | it "accept any node entry when table is empty" $ | ||
69 | property $ \ (nid :: NodeId) -> do | ||
70 | let info = NodeInfo nid myAddr | ||
71 | closest <- simpleDHT $ do | ||
72 | _ <- insertNode info Nothing | ||
73 | liftIO $ yield | ||
74 | getClosest nid | ||
75 | closest `shouldSatisfy` L.elem info | ||
76 | |||
77 | describe "peer storage" $ do | ||
78 | it "should return nodes, if there are no peers" $ property $ \ ih -> do | ||
79 | res <- simpleDHT $ do getPeerList ih | ||
80 | res `shouldSatisfy` isLeft | ||
81 | |||
82 | it "should return peers, if any" $ property $ \ ih addr -> do | ||
83 | res <- simpleDHT $ do | ||
84 | insertPeer ih addr | ||
85 | getPeerList ih | ||
86 | res `shouldSatisfy` isRight | ||
87 | |||
88 | describe "topic storage" $ do | ||
89 | it "should not grow indefinitely" $ do | ||
90 | pending | ||
91 | |||
92 | describe "messaging" $ do | ||
93 | describe "queryNode" $ do | ||
94 | it "should always ping this node" $ do | ||
95 | (rid, tid) <- simpleDHT $ do | ||
96 | (remoteId, Ping) <- queryNode myAddr Ping | ||
97 | thisId <- myNodeIdAccordingTo (read "8.8.8.8:6881") | ||
98 | return (remoteId, thisId) | ||
99 | rid `shouldBe` tid | ||
100 | |||
101 | describe "queryParallel" $ do | ||
102 | it "should handle parallel requests" $ do | ||
103 | (nid, resps) <- simpleDHT $ do | ||
104 | me <- myNodeIdAccordingTo (read "8.8.8.8:6881") | ||
105 | ( (,) me ) <$> queryParallel (L.replicate 100 $ queryNode myAddr Ping) | ||
106 | resps `shouldSatisfy` L.all (== (nid, Ping)) | ||
107 | |||
108 | describe "(<@>) operator" $ do | ||
109 | it "" $ | ||
110 | pending | ||
diff --git a/dht/bittorrent/tests/Network/BitTorrent/DHT/TestData.hs b/dht/bittorrent/tests/Network/BitTorrent/DHT/TestData.hs new file mode 100644 index 00000000..e9473cbb --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/DHT/TestData.hs | |||
@@ -0,0 +1,45 @@ | |||
1 | module Network.BitTorrent.DHT.TestData | ||
2 | ( TestEntry (..) | ||
3 | , testTorrents | ||
4 | ) where | ||
5 | |||
6 | import Data.Torrent | ||
7 | |||
8 | data TestEntry = TestEntry | ||
9 | { entryName :: String | ||
10 | , entryHash :: InfoHash | ||
11 | , entryPeers :: Int -- ^ approximate number of peers, may change with time | ||
12 | } | ||
13 | |||
14 | testTorrents :: [TestEntry] | ||
15 | testTorrents = | ||
16 | [ TestEntry | ||
17 | { entryName = "Automate with Arduino, Android..." | ||
18 | , entryHash = "8c0433e541dc5d1cfc095799cef171cd4eb586f7" | ||
19 | , entryPeers = 300 | ||
20 | } | ||
21 | |||
22 | , TestEntry | ||
23 | { entryName = "Beginning Programming with Java For Dummies" | ||
24 | , entryHash = "fd8967721731cc16c8b203a03e49ce839cecf184" | ||
25 | , entryPeers = 200 | ||
26 | } | ||
27 | |||
28 | , TestEntry | ||
29 | { entryName = "The C Programming Language" | ||
30 | , entryHash = "146d13f090e50e97091dbbe5b37678dd1471cfad" | ||
31 | , entryPeers = 100 | ||
32 | } | ||
33 | |||
34 | , TestEntry | ||
35 | { entryName = "The C++ Programming Language" | ||
36 | , entryHash = "8e8e8e6319031a22cff26d895afe050085c84a7f" | ||
37 | , entryPeers = 50 | ||
38 | } | ||
39 | |||
40 | , TestEntry | ||
41 | { entryName = "Game and Graphics Programming for iOS..." | ||
42 | , entryHash = "703d0595b727fccbfaa3d03be25f57347ccfd6de" | ||
43 | , entryPeers = 30 | ||
44 | } | ||
45 | ] | ||
diff --git a/dht/bittorrent/tests/Network/BitTorrent/DHT/TokenSpec.hs b/dht/bittorrent/tests/Network/BitTorrent/DHT/TokenSpec.hs new file mode 100644 index 00000000..a45d2212 --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/DHT/TokenSpec.hs | |||
@@ -0,0 +1,42 @@ | |||
1 | {-# LANGUAGE ScopedTypeVariables #-} | ||
2 | {-# OPTIONS_GHC -fno-warn-orphans #-} | ||
3 | module Network.BitTorrent.DHT.TokenSpec (spec) where | ||
4 | import Control.Applicative | ||
5 | import Data.List as L | ||
6 | import Data.String | ||
7 | import Test.Hspec | ||
8 | import Test.QuickCheck | ||
9 | |||
10 | import Network.BitTorrent.Address | ||
11 | import Network.BitTorrent.CoreSpec () | ||
12 | import Network.BitTorrent.DHT.Token as T | ||
13 | |||
14 | |||
15 | instance Arbitrary Token where | ||
16 | arbitrary = fromString <$> arbitrary | ||
17 | |||
18 | instance Arbitrary TokenMap where | ||
19 | arbitrary = tokens <$> arbitrary | ||
20 | |||
21 | repeatN :: Int -> (a -> a) -> (a -> a) | ||
22 | repeatN n f = L.foldr (.) id $ L.replicate n f | ||
23 | |||
24 | spec :: Spec | ||
25 | spec = do | ||
26 | describe "Token" $ do | ||
27 | return () | ||
28 | |||
29 | describe "TokenMap" $ do | ||
30 | it "is keeping any granted token in current session" $ | ||
31 | property $ \ (addr :: NodeAddr IPv4) m -> | ||
32 | T.member addr (T.lookup addr m) m | ||
33 | |||
34 | it "is keeping any granted token in next session" $ | ||
35 | property $ \ (addr :: NodeAddr IPv4) m -> | ||
36 | T.member addr (T.lookup addr m) (T.update m) | ||
37 | |||
38 | -- can fail with some small probability | ||
39 | it "is rejecting any outdated tokens" $ | ||
40 | property $ \ (addr :: NodeAddr IPv4) m k -> not $ | ||
41 | let n = min 100 (abs k + 2) in | ||
42 | T.member addr (T.lookup addr m) (repeatN n T.update m) \ No newline at end of file | ||
diff --git a/dht/bittorrent/tests/Network/BitTorrent/DHTSpec.hs b/dht/bittorrent/tests/Network/BitTorrent/DHTSpec.hs new file mode 100644 index 00000000..77160eb5 --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/DHTSpec.hs | |||
@@ -0,0 +1,60 @@ | |||
1 | module Network.BitTorrent.DHTSpec (spec) where | ||
2 | import Control.Exception | ||
3 | import Control.Monad | ||
4 | import Data.Default | ||
5 | import Data.List as L | ||
6 | import Test.Hspec | ||
7 | import System.Timeout | ||
8 | |||
9 | import Data.Torrent | ||
10 | import Network.BitTorrent.DHT | ||
11 | |||
12 | |||
13 | partialBootstrapTimeout :: Int | ||
14 | partialBootstrapTimeout = 10 * 1000000 | ||
15 | |||
16 | opts :: Options | ||
17 | opts = def { optBucketCount = 1 } | ||
18 | |||
19 | -- NOTE to shorten test cases run time include only "good" infohashes | ||
20 | -- with many nodes | ||
21 | existingInfoHashes :: [InfoHash] | ||
22 | existingInfoHashes = | ||
23 | [ | ||
24 | ] | ||
25 | |||
26 | -- TODO use Test.Hspec.parallel | ||
27 | |||
28 | spec :: Spec | ||
29 | spec = do | ||
30 | describe "bootstrapping" $ do | ||
31 | it "should resolve all default bootstrap nodes" $ do | ||
32 | nodes <- forM defaultBootstrapNodes resolveHostName | ||
33 | _ <- evaluate nodes | ||
34 | return () | ||
35 | |||
36 | it "partial bootstrapping should finish in less than 10 seconds" $ do | ||
37 | node <- resolveHostName (L.head defaultBootstrapNodes) | ||
38 | res <- timeout partialBootstrapTimeout $ do | ||
39 | dht opts def fullLogging $ do | ||
40 | bootstrap Nothing [node] | ||
41 | isBootstrapped | ||
42 | res `shouldBe` Just True | ||
43 | |||
44 | describe "initialization" $ do | ||
45 | it "should be bootstrapped after restore process" $ do | ||
46 | pending | ||
47 | |||
48 | describe "lookup" $ do | ||
49 | describe "for any existing infohash" $ do | ||
50 | forM_ existingInfoHashes $ \ ih -> do | ||
51 | context (show ih) $ do | ||
52 | it "should find peers" $ do | ||
53 | pending | ||
54 | |||
55 | describe "insert" $ do | ||
56 | it "should return this peer if announced" $ do | ||
57 | pending | ||
58 | |||
59 | describe "delete" $ do | ||
60 | return () | ||
diff --git a/dht/bittorrent/tests/Network/BitTorrent/Exchange/BitfieldSpec.hs b/dht/bittorrent/tests/Network/BitTorrent/Exchange/BitfieldSpec.hs new file mode 100644 index 00000000..1ba772f6 --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/Exchange/BitfieldSpec.hs | |||
@@ -0,0 +1,14 @@ | |||
1 | {-# OPTIONS -fno-warn-orphans #-} | ||
2 | module Network.BitTorrent.Exchange.BitfieldSpec (spec) where | ||
3 | import Control.Applicative | ||
4 | import Data.ByteString.Arbitrary | ||
5 | import Test.Hspec | ||
6 | import Test.QuickCheck | ||
7 | |||
8 | import Network.BitTorrent.Exchange.Bitfield | ||
9 | |||
10 | instance Arbitrary Bitfield where | ||
11 | arbitrary = fromBitmap . fromABS <$> arbitrary | ||
12 | |||
13 | spec :: Spec | ||
14 | spec = return () | ||
diff --git a/dht/bittorrent/tests/Network/BitTorrent/Exchange/BlockSpec.hs b/dht/bittorrent/tests/Network/BitTorrent/Exchange/BlockSpec.hs new file mode 100644 index 00000000..2dc8e0b8 --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/Exchange/BlockSpec.hs | |||
@@ -0,0 +1,35 @@ | |||
1 | module Network.BitTorrent.Exchange.BlockSpec (spec) where | ||
2 | import Control.Applicative | ||
3 | import Control.Exception | ||
4 | import Data.Maybe | ||
5 | import Test.Hspec | ||
6 | import Test.QuickCheck | ||
7 | import Test.QuickCheck.Instances () | ||
8 | |||
9 | import Network.BitTorrent.Exchange.Block as Block | ||
10 | |||
11 | |||
12 | instance Arbitrary a => Arbitrary (Block a) where | ||
13 | arbitrary = Block <$> arbitrary <*> arbitrary <*> arbitrary | ||
14 | |||
15 | instance Arbitrary BlockIx where | ||
16 | arbitrary = BlockIx <$> arbitrary <*> arbitrary <*> arbitrary | ||
17 | |||
18 | instance Arbitrary Bucket where | ||
19 | arbitrary = do | ||
20 | s <- arbitrary `suchThat` (> 0) | ||
21 | chunks <- arbitrary | ||
22 | return $ Block.fromList s chunks | ||
23 | |||
24 | isSomeException :: SomeException -> Bool | ||
25 | isSomeException = const True | ||
26 | |||
27 | spec :: Spec | ||
28 | spec = do | ||
29 | describe "empty" $ do | ||
30 | it "should fail on bad size" $ do | ||
31 | evaluate (Block.empty (-1)) `shouldThrow` isSomeException | ||
32 | |||
33 | describe "toPiece" $ do | ||
34 | it "render to piece when it is full" $ property $ \ bkt -> | ||
35 | full bkt == isJust (toPiece bkt) \ No newline at end of file | ||
diff --git a/dht/bittorrent/tests/Network/BitTorrent/Exchange/ConnectionSpec.hs b/dht/bittorrent/tests/Network/BitTorrent/Exchange/ConnectionSpec.hs new file mode 100644 index 00000000..d654cda1 --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/Exchange/ConnectionSpec.hs | |||
@@ -0,0 +1,58 @@ | |||
1 | {-# LANGUAGE RecordWildCards #-} | ||
2 | {-# OPTIONS_GHC -fno-warn-orphans #-} | ||
3 | module Network.BitTorrent.Exchange.ConnectionSpec (spec) where | ||
4 | import Control.Applicative | ||
5 | import Control.Monad.Trans | ||
6 | import Data.Default | ||
7 | import Test.Hspec | ||
8 | import Test.QuickCheck | ||
9 | |||
10 | import Data.Torrent | ||
11 | import Network.BitTorrent.Address | ||
12 | import Network.BitTorrent.Exchange.Connection | ||
13 | import Network.BitTorrent.Exchange.Message | ||
14 | |||
15 | import Config | ||
16 | import Network.BitTorrent.Exchange.MessageSpec () | ||
17 | |||
18 | nullSession :: InfoHash -> PeerId -> SessionLink () | ||
19 | nullSession ih pid = SessionLink ih pid Nothing Nothing () | ||
20 | |||
21 | instance Arbitrary Options where | ||
22 | arbitrary = return def | ||
23 | |||
24 | instance Arbitrary ConnectionPrefs where | ||
25 | arbitrary = ConnectionPrefs <$> arbitrary <*> pure def | ||
26 | <*> arbitrary <*> arbitrary | ||
27 | |||
28 | withWire :: ConnectionPrefs -> Wire () () -> IO () | ||
29 | withWire prefs wire = | ||
30 | withRemote $ \ ClientOpts {..} -> do | ||
31 | pid <- genPeerId | ||
32 | t <- getTestTorrent | ||
33 | let ih = idInfoHash (tInfoDict t) | ||
34 | let cfg = ConnectionConfig prefs (nullSession ih pid) (wire) | ||
35 | let addr = PeerAddr Nothing "127.0.0.1" peerPort | ||
36 | connectWire addr cfg | ||
37 | |||
38 | spec :: Spec | ||
39 | spec = do | ||
40 | describe "connectWire" $ do | ||
41 | it "can establish connection with all possible preferences" $ | ||
42 | property $ \ prefs -> do | ||
43 | withWire prefs (return ()) | ||
44 | |||
45 | it "must not connect with invalid topic" $ do | ||
46 | pending | ||
47 | |||
48 | describe "acceptWire" $ do | ||
49 | it "" $ do | ||
50 | pending | ||
51 | |||
52 | describe "messaging" $ do | ||
53 | it "first message is bitfield" $ do | ||
54 | withWire def $ do | ||
55 | msg <- recvMessage | ||
56 | let isBitfield (Available (Bitfield _)) = True | ||
57 | isBitfield _ = False | ||
58 | liftIO $ msg `shouldSatisfy` isBitfield | ||
diff --git a/dht/bittorrent/tests/Network/BitTorrent/Exchange/DownloadSpec.hs b/dht/bittorrent/tests/Network/BitTorrent/Exchange/DownloadSpec.hs new file mode 100644 index 00000000..d46f2034 --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/Exchange/DownloadSpec.hs | |||
@@ -0,0 +1,59 @@ | |||
1 | {-# LANGUAGE RecordWildCards #-} | ||
2 | module Network.BitTorrent.Exchange.DownloadSpec (spec) where | ||
3 | import Control.Concurrent | ||
4 | import Data.ByteString as BS | ||
5 | import Data.ByteString.Lazy as BL | ||
6 | import Test.Hspec | ||
7 | import Test.QuickCheck | ||
8 | |||
9 | import Data.BEncode as BE | ||
10 | import Data.Torrent as Torrent | ||
11 | import Network.BitTorrent.Address | ||
12 | import Network.BitTorrent.Exchange.Download | ||
13 | import Network.BitTorrent.Exchange.Message | ||
14 | |||
15 | import Config | ||
16 | import Network.BitTorrent.CoreSpec () | ||
17 | |||
18 | |||
19 | placeholderAddr :: PeerAddr IP | ||
20 | placeholderAddr = "0.0.0.0:0" | ||
21 | |||
22 | chunkBy :: Int -> BS.ByteString -> [BS.ByteString] | ||
23 | chunkBy s bs | ||
24 | | BS.null bs = [] | ||
25 | | otherwise = BS.take s bs : chunkBy s (BS.drop s bs) | ||
26 | |||
27 | withUpdates :: Updates s a -> IO a | ||
28 | withUpdates m = do | ||
29 | Torrent {..} <- getTestTorrent | ||
30 | let infoDictLen = fromIntegral $ BL.length $ BE.encode tInfoDict | ||
31 | --mvar <- newMVar (nullStatus infoDictLen) | ||
32 | --runUpdates mvar placeholderAddr m | ||
33 | undefined | ||
34 | |||
35 | simulateFetch :: InfoDict -> Updates s (Maybe InfoDict) | ||
36 | simulateFetch dict = undefined | ||
37 | |||
38 | spec :: Spec | ||
39 | spec = do | ||
40 | describe "scheduleBlock" $ do | ||
41 | it "never schedule the same index twice" $ do | ||
42 | pending | ||
43 | |||
44 | describe "resetPending" $ do | ||
45 | it "" $ do | ||
46 | pending | ||
47 | |||
48 | describe "cancelPending" $ do | ||
49 | it "must not throw an exception if cancel the same piece twice" $ do | ||
50 | pending | ||
51 | |||
52 | describe "pushBlock" $ do | ||
53 | it "assemble infodict from chunks" $ do | ||
54 | Torrent {..} <- getTestTorrent | ||
55 | mdict <- withUpdates $ simulateFetch tInfoDict | ||
56 | mdict `shouldBe` Just tInfoDict | ||
57 | |||
58 | it "must throw an exception if block if not requested" $ do | ||
59 | pending \ No newline at end of file | ||
diff --git a/dht/bittorrent/tests/Network/BitTorrent/Exchange/MessageSpec.hs b/dht/bittorrent/tests/Network/BitTorrent/Exchange/MessageSpec.hs new file mode 100644 index 00000000..d615b1ff --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/Exchange/MessageSpec.hs | |||
@@ -0,0 +1,102 @@ | |||
1 | {-# OPTIONS_GHC -fno-warn-orphans #-} | ||
2 | module Network.BitTorrent.Exchange.MessageSpec (spec) where | ||
3 | import Control.Applicative | ||
4 | import Control.Exception | ||
5 | import Data.ByteString as BS | ||
6 | import Data.List as L | ||
7 | import Data.Set as S | ||
8 | import Data.Serialize as S | ||
9 | import Data.String | ||
10 | import Test.Hspec | ||
11 | import Test.QuickCheck | ||
12 | |||
13 | import Data.TorrentSpec () | ||
14 | import Network.BitTorrent.Exchange.BitfieldSpec () | ||
15 | import Network.BitTorrent.CoreSpec () | ||
16 | import Network.BitTorrent.Address () | ||
17 | import Network.BitTorrent.Exchange.BlockSpec () | ||
18 | import Network.BitTorrent.Exchange.Message | ||
19 | |||
20 | instance Arbitrary Extension where | ||
21 | arbitrary = elements [minBound .. maxBound] | ||
22 | |||
23 | instance Arbitrary Caps where | ||
24 | arbitrary = toCaps <$> arbitrary | ||
25 | |||
26 | instance Arbitrary ExtendedExtension where | ||
27 | arbitrary = elements [minBound .. maxBound] | ||
28 | |||
29 | instance Arbitrary ExtendedCaps where | ||
30 | arbitrary = toCaps <$> arbitrary | ||
31 | |||
32 | instance Arbitrary ProtocolName where | ||
33 | arbitrary = fromString <$> (arbitrary `suchThat` ((200 <) . L.length)) | ||
34 | |||
35 | instance Arbitrary Handshake where | ||
36 | arbitrary = Handshake <$> arbitrary <*> arbitrary | ||
37 | <*> arbitrary <*> arbitrary | ||
38 | |||
39 | instance Arbitrary StatusUpdate where | ||
40 | arbitrary = frequency | ||
41 | [ (1, Choking <$> arbitrary) | ||
42 | , (1, Interested <$> arbitrary) | ||
43 | ] | ||
44 | |||
45 | instance Arbitrary Available where | ||
46 | arbitrary = frequency | ||
47 | [ (1, Have <$> arbitrary) | ||
48 | , (1, Bitfield <$> arbitrary) | ||
49 | ] | ||
50 | |||
51 | instance Arbitrary Transfer where | ||
52 | arbitrary = frequency | ||
53 | [ (1, Request <$> arbitrary) | ||
54 | , (1, Piece <$> arbitrary) | ||
55 | , (1, Cancel <$> arbitrary) | ||
56 | ] | ||
57 | |||
58 | instance Arbitrary FastMessage where | ||
59 | arbitrary = frequency | ||
60 | [ (1, pure HaveAll) | ||
61 | , (1, pure HaveNone) | ||
62 | , (1, SuggestPiece <$> arbitrary) | ||
63 | , (1, RejectRequest <$> arbitrary) | ||
64 | , (1, AllowedFast <$> arbitrary) | ||
65 | ] | ||
66 | |||
67 | instance Arbitrary Message where | ||
68 | arbitrary = frequency | ||
69 | [ (1, pure KeepAlive) | ||
70 | , (1, Status <$> arbitrary) | ||
71 | , (1, Available <$> arbitrary) | ||
72 | , (1, Transfer <$> arbitrary) | ||
73 | , (1, Fast <$> arbitrary) | ||
74 | ] | ||
75 | |||
76 | -- TODO test extension protocol | ||
77 | |||
78 | spec :: Spec | ||
79 | spec = do | ||
80 | describe "Caps" $ do | ||
81 | it "set-like container" $ property $ \ exts -> | ||
82 | L.all (`allowed` (toCaps exts :: Caps)) exts | ||
83 | |||
84 | it "preserve items" $ property $ \ extSet -> | ||
85 | S.fromList (fromCaps (toCaps (S.toList extSet) :: Caps)) | ||
86 | `shouldBe` extSet | ||
87 | |||
88 | describe "ByteStats" $ do | ||
89 | it "preserve size" $ property $ \ msg -> | ||
90 | byteLength (stats msg) `shouldBe` | ||
91 | fromIntegral (BS.length (S.encode (msg :: Message))) | ||
92 | |||
93 | describe "ProtocolName" $ do | ||
94 | it "fail to construct invalid string" $ do | ||
95 | let str = L.replicate 500 'x' | ||
96 | evaluate (fromString str :: ProtocolName) | ||
97 | `shouldThrow` | ||
98 | errorCall ("fromString: ProtocolName too long: " ++ str) | ||
99 | |||
100 | describe "Handshake" $ do | ||
101 | it "properly serialized" $ property $ \ hs -> | ||
102 | S.decode (S.encode hs ) `shouldBe` Right (hs :: Handshake) | ||
diff --git a/dht/bittorrent/tests/Network/BitTorrent/Exchange/SessionSpec.hs b/dht/bittorrent/tests/Network/BitTorrent/Exchange/SessionSpec.hs new file mode 100644 index 00000000..bf5b95a1 --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/Exchange/SessionSpec.hs | |||
@@ -0,0 +1,64 @@ | |||
1 | {-# LANGUAGE RecordWildCards #-} | ||
2 | module Network.BitTorrent.Exchange.SessionSpec (spec) where | ||
3 | import Test.Hspec | ||
4 | |||
5 | import Data.Torrent | ||
6 | import Network.BitTorrent.Address | ||
7 | import Network.BitTorrent.Exchange.Session | ||
8 | |||
9 | import Config | ||
10 | |||
11 | |||
12 | nullLogger :: LogFun | ||
13 | nullLogger _ _ x _ = print x | ||
14 | |||
15 | simpleSession :: InfoDict -> (Session -> IO ()) -> IO () | ||
16 | simpleSession dict action = do | ||
17 | withRemoteAddr $ \ addr -> do | ||
18 | myAddr <- getMyAddr | ||
19 | ses <- newSession nullLogger myAddr "" (Right dict) | ||
20 | connect addr ses | ||
21 | action ses | ||
22 | closeSession ses | ||
23 | |||
24 | spec :: Spec | ||
25 | spec = do | ||
26 | describe "construction" $ do | ||
27 | describe "newSession" $ do | ||
28 | it "" $ do | ||
29 | pending | ||
30 | |||
31 | describe "closeSession" $ do | ||
32 | it "" $ do | ||
33 | pending | ||
34 | |||
35 | describe "connection set" $ do | ||
36 | describe "connect" $ do | ||
37 | it "" $ do | ||
38 | pending | ||
39 | |||
40 | describe "establish" $ do | ||
41 | it "" $ do | ||
42 | pending | ||
43 | |||
44 | describe "exchange" $ do | ||
45 | describe "metadata" $ do | ||
46 | it "should fetch info dictionary" $ do | ||
47 | Torrent {..} <- getTestTorrent | ||
48 | simpleSession tInfoDict $ \ ses -> do | ||
49 | dict <- waitMetadata ses | ||
50 | dict `shouldBe` tInfoDict | ||
51 | |||
52 | it "should serve info dictionary" $ do | ||
53 | pending | ||
54 | |||
55 | describe "content" $ do | ||
56 | it "should fetch torrent content" $ do | ||
57 | Torrent {..} <- getTestTorrent | ||
58 | simpleSession tInfoDict $ \ ses -> do | ||
59 | pending | ||
60 | -- st <- waitData ses | ||
61 | -- verifyStorage st (idPieceInfo tInfoDict) | ||
62 | |||
63 | it "should serve torrent content" $ do | ||
64 | pending | ||
diff --git a/dht/bittorrent/tests/Network/BitTorrent/Internal/CacheSpec.hs b/dht/bittorrent/tests/Network/BitTorrent/Internal/CacheSpec.hs new file mode 100644 index 00000000..337e7add --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/Internal/CacheSpec.hs | |||
@@ -0,0 +1,7 @@ | |||
1 | module Network.BitTorrent.Internal.CacheSpec (spec) where | ||
2 | import Test.Hspec | ||
3 | |||
4 | spec :: Spec | ||
5 | spec = do | ||
6 | describe "Cached" $ do | ||
7 | return () | ||
diff --git a/dht/bittorrent/tests/Network/BitTorrent/Internal/ProgressSpec.hs b/dht/bittorrent/tests/Network/BitTorrent/Internal/ProgressSpec.hs new file mode 100644 index 00000000..acbfd84c --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/Internal/ProgressSpec.hs | |||
@@ -0,0 +1,13 @@ | |||
1 | {-# OPTIONS -fno-warn-orphans #-} | ||
2 | module Network.BitTorrent.Internal.ProgressSpec (spec) where | ||
3 | import Control.Applicative | ||
4 | import Test.Hspec | ||
5 | import Test.QuickCheck | ||
6 | import Network.BitTorrent.Internal.Progress | ||
7 | |||
8 | |||
9 | instance Arbitrary Progress where | ||
10 | arbitrary = Progress <$> arbitrary <*> arbitrary <*> arbitrary | ||
11 | |||
12 | spec :: Spec | ||
13 | spec = return () | ||
diff --git a/dht/bittorrent/tests/Network/BitTorrent/Tracker/ListSpec.hs b/dht/bittorrent/tests/Network/BitTorrent/Tracker/ListSpec.hs new file mode 100644 index 00000000..bba9d0e2 --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/Tracker/ListSpec.hs | |||
@@ -0,0 +1,40 @@ | |||
1 | module Network.BitTorrent.Tracker.ListSpec (spec) where | ||
2 | import Control.Exception | ||
3 | import Data.Default | ||
4 | import Data.Foldable as F | ||
5 | import Data.List as L | ||
6 | import Data.Maybe | ||
7 | import Network.URI | ||
8 | import Test.Hspec | ||
9 | |||
10 | import Data.Torrent | ||
11 | import Network.BitTorrent.Tracker.List | ||
12 | import Network.BitTorrent.Tracker.RPC | ||
13 | |||
14 | |||
15 | uris :: [URI] | ||
16 | uris = fmap (fromJust . parseURI . renderURI) [1..10 :: Int] | ||
17 | where | ||
18 | renderURI n = "http://" ++ show n ++ ".org" | ||
19 | |||
20 | list :: TrackerList () | ||
21 | list = trackerList def { tAnnounceList = Just [uris] } | ||
22 | |||
23 | spec :: Spec | ||
24 | spec = do | ||
25 | describe "TrackerList" $ do | ||
26 | it "shuffleTiers (may fail with very small probability)" $ do | ||
27 | list' <- shuffleTiers list | ||
28 | list' `shouldSatisfy` (/= list) | ||
29 | |||
30 | it "traverseAll" $ do | ||
31 | xs <- traverseAll (\ (uri, _) -> if uri == L.last uris | ||
32 | then throwIO (GenericException "") | ||
33 | else return ()) list | ||
34 | return () | ||
35 | |||
36 | it "traverseTiers" $ do | ||
37 | xs' <- traverseTiers (\ (uri, _) -> if uri == L.last uris then return () | ||
38 | else throwIO (GenericException "")) list | ||
39 | |||
40 | return () | ||
diff --git a/dht/bittorrent/tests/Network/BitTorrent/Tracker/MessageSpec.hs b/dht/bittorrent/tests/Network/BitTorrent/Tracker/MessageSpec.hs new file mode 100644 index 00000000..29854d58 --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/Tracker/MessageSpec.hs | |||
@@ -0,0 +1,173 @@ | |||
1 | {-# LANGUAGE RecordWildCards #-} | ||
2 | {-# LANGUAGE ViewPatterns #-} | ||
3 | {-# LANGUAGE FlexibleInstances #-} | ||
4 | {-# OPTIONS -fno-warn-orphans #-} | ||
5 | module Network.BitTorrent.Tracker.MessageSpec | ||
6 | ( spec | ||
7 | , arbitrarySample | ||
8 | ) where | ||
9 | |||
10 | import Control.Applicative | ||
11 | import Control.Exception | ||
12 | import Data.BEncode as BE | ||
13 | import Data.ByteString.Lazy as BL | ||
14 | import Data.List as L | ||
15 | import Data.Maybe | ||
16 | import Test.Hspec | ||
17 | import Test.QuickCheck | ||
18 | |||
19 | import Data.TorrentSpec () | ||
20 | import Network.BitTorrent.Internal.ProgressSpec () | ||
21 | import Network.BitTorrent.Address () | ||
22 | import Network.BitTorrent.Address () | ||
23 | |||
24 | import Network.BitTorrent.Tracker.Message as Message | ||
25 | import Network.BitTorrent.Address | ||
26 | |||
27 | |||
28 | --prop_bencode :: Eq a => BEncode a => a -> Bool | ||
29 | --prop_bencode a = BE.decode (BL.toStrict (BE.encode a)) == return a | ||
30 | |||
31 | --prop_urlencode :: Eq a => URLDecoded a => URLEncoded a => a -> Bool | ||
32 | --prop_urlencode a = urlDecode (T.pack (urlEncode a)) == a | ||
33 | |||
34 | instance Arbitrary AnnounceEvent where | ||
35 | arbitrary = elements [minBound..maxBound] | ||
36 | |||
37 | instance Arbitrary AnnounceQuery where | ||
38 | arbitrary = AnnounceQuery | ||
39 | <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary | ||
40 | <*> arbitrary <*> arbitrary <*> arbitrary | ||
41 | |||
42 | instance Arbitrary (PeerList IP) where | ||
43 | arbitrary = frequency | ||
44 | [ (1, (PeerList . maybeToList) <$> arbitrary) | ||
45 | , (1, (CompactPeerList . maybeToList . fmap zeroPeerId) <$> arbitrary) | ||
46 | ] | ||
47 | |||
48 | shrink ( PeerList xs) = PeerList <$> shrink xs | ||
49 | shrink (CompactPeerList xs) = CompactPeerList <$> shrink xs | ||
50 | |||
51 | instance Arbitrary AnnounceInfo where | ||
52 | arbitrary = AnnounceInfo | ||
53 | <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary | ||
54 | <*> arbitrary <*> arbitrary | ||
55 | |||
56 | arbitrarySample :: Arbitrary a => IO a | ||
57 | arbitrarySample = L.head <$> sample' arbitrary | ||
58 | |||
59 | zeroPeerId :: PeerAddr a -> PeerAddr a | ||
60 | zeroPeerId addr = addr { peerId = Nothing } | ||
61 | |||
62 | spec :: Spec | ||
63 | spec = do | ||
64 | describe "AnnounceQuery" $ do | ||
65 | it "properly url encoded" $ property $ \ q -> | ||
66 | parseAnnounceQuery (renderAnnounceQuery q) | ||
67 | `shouldBe` Right q | ||
68 | |||
69 | describe "PeerList" $ do | ||
70 | context "Non compact" $ do | ||
71 | it "properly encoded (both ipv4 and ipv6)" $ do | ||
72 | BE.decode "ld2:ip7:1.2.3.44:porti80eed2:ip3:::14:porti8080eee" | ||
73 | `shouldBe` Right | ||
74 | (PeerList ["1.2.3.4:80", "[::1]:8080"] :: PeerList IPv4) | ||
75 | |||
76 | it "properly encoded (iso)" $ property $ \ xs -> | ||
77 | BE.decode (BL.toStrict (BE.encode (PeerList xs :: PeerList IPv4))) | ||
78 | `shouldBe` Right (PeerList xs :: PeerList IPv4) | ||
79 | |||
80 | context "Compact" $ do | ||
81 | it "properly encodes (ipv4)" $ do | ||
82 | BE.decode "12:\x1\x2\x3\x4\x1\x2\x9\x8\x7\x6\x1\x2" | ||
83 | `shouldBe` Right | ||
84 | (CompactPeerList ["1.2.3.4:258", "9.8.7.6:258"] :: PeerList IPv4) | ||
85 | |||
86 | it "properly encodes (ipv6)" $ do | ||
87 | BE.decode "18:\x1\x2\x3\x4\x5\x6\x7\x8\x1\x2\x3\x4\x5\x6\x7\x8\x1\x2" | ||
88 | `shouldBe` Right | ||
89 | (CompactPeerList ["[102:304:506:708:102:304:506:708]:258"] | ||
90 | :: PeerList IPv6) | ||
91 | |||
92 | it "properly encoded (ipv4, iso)" $ | ||
93 | property $ \ (fmap zeroPeerId -> xs) -> | ||
94 | BE.decode (BL.toStrict (BE.encode (CompactPeerList xs))) | ||
95 | `shouldBe` Right (CompactPeerList xs :: PeerList IPv4) | ||
96 | |||
97 | it "properly encoded (ipv6, iso)" $ | ||
98 | property $ \ (fmap zeroPeerId -> xs) -> | ||
99 | BE.decode (BL.toStrict (BE.encode (CompactPeerList xs))) | ||
100 | `shouldBe` Right (CompactPeerList xs :: PeerList IPv6) | ||
101 | |||
102 | describe "AnnounceInfo" $ do | ||
103 | it "parses minimal sample" $ do | ||
104 | "d8:intervali0e5:peerslee" | ||
105 | `shouldBe` | ||
106 | AnnounceInfo Nothing Nothing 0 Nothing (PeerList []) Nothing | ||
107 | |||
108 | it "parses optional fields" $ do | ||
109 | "d8:completei1e\ | ||
110 | \10:incompletei2e\ | ||
111 | \8:intervali3e\ | ||
112 | \12:min intervali4e\ | ||
113 | \5:peersle\ | ||
114 | \15:warning message3:str\ | ||
115 | \e" | ||
116 | `shouldBe` | ||
117 | AnnounceInfo (Just 1) (Just 2) 3 (Just 4) (PeerList []) (Just "str") | ||
118 | |||
119 | it "parses failed response" $ do | ||
120 | "d14:failure reason10:any reasone" | ||
121 | `shouldBe` | ||
122 | Message.Failure "any reason" | ||
123 | |||
124 | it "fail if no peer list present" $ do | ||
125 | evaluate ("d8:intervali0ee" :: AnnounceInfo) | ||
126 | `shouldThrow` | ||
127 | errorCall "fromString: unable to decode AnnounceInfo: \ | ||
128 | \required field `peers' not found" | ||
129 | |||
130 | it "parses `peer' list" $ do -- TODO | ||
131 | "d8:intervali0e\ | ||
132 | \5:peersl\ | ||
133 | \d2:ip7:1.2.3.4\ | ||
134 | \4:porti80e\ | ||
135 | \e\ | ||
136 | \d2:ip3:::1\ | ||
137 | \4:porti80e\ | ||
138 | \e\ | ||
139 | \e\ | ||
140 | \e" `shouldBe` | ||
141 | let xs = PeerList ["1.2.3.4:80", "[::1]:80"] in | ||
142 | AnnounceInfo Nothing Nothing 0 Nothing xs Nothing | ||
143 | |||
144 | it "parses `peers6' list" $ do | ||
145 | "d8:intervali0e\ | ||
146 | \5:peers0:\ | ||
147 | \6:peers60:\ | ||
148 | \e" `shouldBe` | ||
149 | AnnounceInfo Nothing Nothing 0 Nothing (CompactPeerList []) Nothing | ||
150 | |||
151 | it "fails on invalid combinations of the peer lists" $ do | ||
152 | BE.decode "d8:intervali0e\ | ||
153 | \5:peers0:\ | ||
154 | \6:peers6le\ | ||
155 | \e" | ||
156 | `shouldBe` (Left | ||
157 | "PeerList: the `peers6' field value should contain \ | ||
158 | \*compact* peer list" :: BE.Result AnnounceInfo) | ||
159 | |||
160 | BE.decode "d8:intervali0e\ | ||
161 | \5:peersle\ | ||
162 | \6:peers60:\ | ||
163 | \e" | ||
164 | `shouldBe` (Left | ||
165 | "PeerList: non-compact peer list provided, \ | ||
166 | \but the `peers6' field present" :: BE.Result AnnounceInfo) | ||
167 | |||
168 | it "properly bencoded (iso)" $ property $ \ info -> | ||
169 | BE.decode (BL.toStrict (BE.encode info)) | ||
170 | `shouldBe` Right (info :: AnnounceInfo) | ||
171 | |||
172 | describe "Scrape" $ do | ||
173 | return () | ||
diff --git a/dht/bittorrent/tests/Network/BitTorrent/Tracker/RPC/HTTPSpec.hs b/dht/bittorrent/tests/Network/BitTorrent/Tracker/RPC/HTTPSpec.hs new file mode 100644 index 00000000..e928f917 --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/Tracker/RPC/HTTPSpec.hs | |||
@@ -0,0 +1,95 @@ | |||
1 | {-# LANGUAGE RecordWildCards #-} | ||
2 | module Network.BitTorrent.Tracker.RPC.HTTPSpec (spec) where | ||
3 | import Control.Monad | ||
4 | import Data.Default | ||
5 | import Data.List as L | ||
6 | import Test.Hspec | ||
7 | |||
8 | import Network.BitTorrent.Internal.Progress | ||
9 | import Network.BitTorrent.Tracker.Message as Message | ||
10 | import Network.BitTorrent.Tracker.RPC.HTTP | ||
11 | |||
12 | import Network.BitTorrent.Tracker.TestData | ||
13 | import Network.BitTorrent.Tracker.MessageSpec hiding (spec) | ||
14 | |||
15 | |||
16 | validateInfo :: AnnounceQuery -> AnnounceInfo -> Expectation | ||
17 | validateInfo _ (Message.Failure reason) = do | ||
18 | error $ "validateInfo: " ++ show reason | ||
19 | validateInfo AnnounceQuery {..} AnnounceInfo {..} = do | ||
20 | return () | ||
21 | -- case respComplete <|> respIncomplete of | ||
22 | -- Nothing -> return () | ||
23 | -- Just n -> n `shouldBe` L.length (getPeerList respPeers) | ||
24 | |||
25 | isUnrecognizedScheme :: RpcException -> Bool | ||
26 | isUnrecognizedScheme (RequestFailed _) = True | ||
27 | isUnrecognizedScheme _ = False | ||
28 | |||
29 | isNotResponding :: RpcException -> Bool | ||
30 | isNotResponding (RequestFailed _) = True | ||
31 | isNotResponding _ = False | ||
32 | |||
33 | spec :: Spec | ||
34 | spec = parallel $ do | ||
35 | describe "Manager" $ do | ||
36 | describe "newManager" $ do | ||
37 | it "" $ pending | ||
38 | |||
39 | describe "closeManager" $ do | ||
40 | it "" $ pending | ||
41 | |||
42 | describe "withManager" $ do | ||
43 | it "" $ pending | ||
44 | |||
45 | describe "RPC" $ do | ||
46 | describe "announce" $ do | ||
47 | it "must fail on bad uri scheme" $ do | ||
48 | withManager def $ \ mgr -> do | ||
49 | q <- arbitrarySample | ||
50 | announce mgr "magnet://foo.bar" q | ||
51 | `shouldThrow` isUnrecognizedScheme | ||
52 | |||
53 | describe "scrape" $ do | ||
54 | it "must fail on bad uri scheme" $ do | ||
55 | withManager def $ \ mgr -> do | ||
56 | scrape mgr "magnet://foo.bar" [] | ||
57 | `shouldThrow` isUnrecognizedScheme | ||
58 | |||
59 | forM_ (L.filter isHttpTracker trackers) $ \ TrackerEntry {..} -> | ||
60 | context trackerName $ do | ||
61 | |||
62 | describe "announce" $ do | ||
63 | if tryAnnounce | ||
64 | then do | ||
65 | it "have valid response" $ do | ||
66 | withManager def $ \ mgr -> do | ||
67 | -- q <- arbitrarySample | ||
68 | let ih = maybe def L.head hashList | ||
69 | let q = AnnounceQuery ih "-HS0003-203534.37420" 6000 | ||
70 | (Progress 0 0 0) Nothing Nothing (Just Started) | ||
71 | info <- announce mgr trackerURI q | ||
72 | validateInfo q info | ||
73 | else do | ||
74 | it "should fail with RequestFailed" $ do | ||
75 | withManager def $ \ mgr -> do | ||
76 | q <- arbitrarySample | ||
77 | announce mgr trackerURI q | ||
78 | `shouldThrow` isNotResponding | ||
79 | |||
80 | describe "scrape" $ do | ||
81 | if tryScraping | ||
82 | then do | ||
83 | it "have valid response" $ do | ||
84 | withManager def $ \ mgr -> do | ||
85 | xs <- scrape mgr trackerURI [def] | ||
86 | L.length xs `shouldSatisfy` (>= 1) | ||
87 | else do | ||
88 | it "should fail with ScrapelessTracker" $ do | ||
89 | pending | ||
90 | |||
91 | when (not tryAnnounce) $ do | ||
92 | it "should fail with RequestFailed" $ do | ||
93 | withManager def $ \ mgr -> do | ||
94 | scrape mgr trackerURI [def] | ||
95 | `shouldThrow` isNotResponding | ||
diff --git a/dht/bittorrent/tests/Network/BitTorrent/Tracker/RPC/UDPSpec.hs b/dht/bittorrent/tests/Network/BitTorrent/Tracker/RPC/UDPSpec.hs new file mode 100644 index 00000000..73acb3fa --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/Tracker/RPC/UDPSpec.hs | |||
@@ -0,0 +1,144 @@ | |||
1 | {-# LANGUAGE RecordWildCards #-} | ||
2 | module Network.BitTorrent.Tracker.RPC.UDPSpec (spec, rpcOpts) where | ||
3 | import Control.Concurrent | ||
4 | import Control.Concurrent.Async | ||
5 | import Control.Exception | ||
6 | import Control.Monad | ||
7 | import Data.Default | ||
8 | import Data.List as L | ||
9 | import Data.Maybe | ||
10 | import Test.Hspec | ||
11 | |||
12 | import Network.BitTorrent.Address | ||
13 | import Network.BitTorrent.Tracker.Message as Message | ||
14 | |||
15 | import Network.BitTorrent.Tracker.TestData | ||
16 | import Network.BitTorrent.Tracker.MessageSpec hiding (spec) | ||
17 | import Network.BitTorrent.Tracker.RPC.UDP | ||
18 | |||
19 | |||
20 | validateInfo :: AnnounceQuery -> AnnounceInfo -> Expectation | ||
21 | validateInfo _ Message.Failure {} = error "validateInfo: failure" | ||
22 | validateInfo AnnounceQuery {..} AnnounceInfo {..} = do | ||
23 | respComplete `shouldSatisfy` isJust | ||
24 | respIncomplete `shouldSatisfy` isJust | ||
25 | respMinInterval `shouldSatisfy` isNothing | ||
26 | respWarning `shouldSatisfy` isNothing | ||
27 | peerList `shouldSatisfy` L.all (isNothing . peerId) | ||
28 | where | ||
29 | peerList = getPeerList respPeers | ||
30 | |||
31 | -- | Number of concurrent calls. | ||
32 | rpcCount :: Int | ||
33 | rpcCount = 100 | ||
34 | |||
35 | rpcOpts :: Options | ||
36 | rpcOpts = def | ||
37 | { optMinTimeout = 1 | ||
38 | , optMaxTimeout = 10 | ||
39 | } | ||
40 | |||
41 | isTimeoutExpired :: RpcException -> Bool | ||
42 | isTimeoutExpired (TimeoutExpired _) = True | ||
43 | isTimeoutExpired _ = False | ||
44 | |||
45 | isSomeException :: SomeException -> Bool | ||
46 | isSomeException _ = True | ||
47 | |||
48 | isIOException :: IOException -> Bool | ||
49 | isIOException _ = True | ||
50 | |||
51 | spec :: Spec | ||
52 | spec = parallel $ do | ||
53 | describe "newManager" $ do | ||
54 | it "should throw exception on zero optMaxPacketSize" $ do | ||
55 | let opts = def { optMaxPacketSize = 0 } | ||
56 | newManager opts `shouldThrow` isSomeException | ||
57 | |||
58 | it "should throw exception on zero optMinTimout" $ do | ||
59 | let opts = def { optMinTimeout = 0 } | ||
60 | newManager opts `shouldThrow` isSomeException | ||
61 | |||
62 | it "should throw exception on zero optMaxTimeout" $ do | ||
63 | let opts = def { optMaxTimeout = 0 } | ||
64 | newManager opts `shouldThrow` isSomeException | ||
65 | |||
66 | it "should throw exception on maxTimeout < minTimeout" $ do | ||
67 | let opts = def { optMinTimeout = 2, optMaxTimeout = 1 } | ||
68 | newManager opts `shouldThrow` isSomeException | ||
69 | |||
70 | it "should throw exception on zero optMultiplier" $ do | ||
71 | let opts = def { optMultiplier = 0 } | ||
72 | newManager opts `shouldThrow` isSomeException | ||
73 | |||
74 | describe "closeManager" $ do | ||
75 | it "unblock rpc calls" $ do | ||
76 | mgr <- newManager rpcOpts | ||
77 | _ <- forkIO $ do | ||
78 | threadDelay 10000000 | ||
79 | closeManager mgr | ||
80 | q <- arbitrarySample | ||
81 | announce mgr (trackerURI badTracker) q `shouldThrow` (== ManagerClosed) | ||
82 | |||
83 | it "announce throw exception after manager closed" $ do | ||
84 | mgr <- newManager rpcOpts | ||
85 | closeManager mgr | ||
86 | q <- arbitrarySample | ||
87 | announce mgr (trackerURI badTracker) q `shouldThrow` isIOException | ||
88 | |||
89 | it "scrape throw exception after manager closed" $ do | ||
90 | mgr <- newManager rpcOpts | ||
91 | closeManager mgr | ||
92 | scrape mgr (trackerURI badTracker) [def] `shouldThrow` isIOException | ||
93 | |||
94 | describe "withManager" $ do | ||
95 | it "closesManager at exit" $ do | ||
96 | mgr <- withManager rpcOpts return | ||
97 | scrape mgr (trackerURI badTracker) [def] `shouldThrow` isSomeException | ||
98 | |||
99 | describe "RPC" $ do | ||
100 | describe "announce" $ do | ||
101 | it "must fail on bad scheme" $ do | ||
102 | withManager rpcOpts $ \ mgr -> do | ||
103 | q <- arbitrarySample | ||
104 | announce mgr "magnet://a.com" q | ||
105 | `shouldThrow` (== UnrecognizedScheme "magnet:") | ||
106 | |||
107 | describe "scrape" $ do | ||
108 | it "must fail on bad scheme" $ do | ||
109 | withManager rpcOpts $ \ mgr -> do | ||
110 | scrape mgr "magnet://a.com" [] | ||
111 | `shouldThrow` (== UnrecognizedScheme "magnet:") | ||
112 | |||
113 | forM_ (L.filter isUdpTracker trackers) $ \ TrackerEntry {..} -> | ||
114 | context trackerName $ do | ||
115 | |||
116 | describe "announce" $ do | ||
117 | if tryAnnounce then do | ||
118 | it "have valid response" $ do | ||
119 | withManager rpcOpts $ \ mgr -> do | ||
120 | q <- arbitrarySample | ||
121 | announce mgr trackerURI q >>= validateInfo q | ||
122 | else do | ||
123 | it "should throw TimeoutExpired" $ do | ||
124 | withManager rpcOpts $ \ mgr -> do | ||
125 | q <- arbitrarySample | ||
126 | announce mgr trackerURI q `shouldThrow` isTimeoutExpired | ||
127 | |||
128 | describe "scrape" $ do | ||
129 | if tryScraping then do | ||
130 | it "have valid response" $ do | ||
131 | withManager rpcOpts $ \ mgr -> do | ||
132 | xs <- scrape mgr trackerURI [def] | ||
133 | L.length xs `shouldSatisfy` (>= 1) | ||
134 | else do | ||
135 | it "should throw TimeoutExpired" $ do | ||
136 | withManager rpcOpts $ \ mgr -> do | ||
137 | scrape mgr trackerURI [def] `shouldThrow` isTimeoutExpired | ||
138 | |||
139 | describe "Manager" $ do | ||
140 | when tryScraping $ do | ||
141 | it "should handle arbitrary intermixed concurrent queries" $ do | ||
142 | withManager rpcOpts $ \ mgr -> do | ||
143 | _ <- mapConcurrently (\ _ -> scrape mgr trackerURI [def]) [1..rpcCount] | ||
144 | return () | ||
diff --git a/dht/bittorrent/tests/Network/BitTorrent/Tracker/RPCSpec.hs b/dht/bittorrent/tests/Network/BitTorrent/Tracker/RPCSpec.hs new file mode 100644 index 00000000..dfc13a1e --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/Tracker/RPCSpec.hs | |||
@@ -0,0 +1,79 @@ | |||
1 | {-# LANGUAGE RecordWildCards #-} | ||
2 | {-# OPTIONS_GHC -fno-warn-orphans #-} | ||
3 | module Network.BitTorrent.Tracker.RPCSpec (spec) where | ||
4 | import Control.Applicative | ||
5 | import Control.Monad | ||
6 | import Data.Default | ||
7 | import Data.List as L | ||
8 | import Test.Hspec | ||
9 | import Test.QuickCheck | ||
10 | |||
11 | import Network.BitTorrent.Tracker.RPC as RPC | ||
12 | |||
13 | import Network.BitTorrent.Tracker.TestData | ||
14 | import Network.BitTorrent.Tracker.MessageSpec hiding (spec) | ||
15 | import qualified Network.BitTorrent.Tracker.RPC.UDPSpec as UDP (rpcOpts) | ||
16 | |||
17 | |||
18 | instance Arbitrary SAnnounceQuery where | ||
19 | arbitrary = SAnnounceQuery <$> arbitrary <*> arbitrary | ||
20 | <*> arbitrary <*> arbitrary | ||
21 | |||
22 | rpcOpts :: Options | ||
23 | rpcOpts = def | ||
24 | { optUdpRPC = UDP.rpcOpts | ||
25 | } | ||
26 | |||
27 | matchUnrecognizedScheme :: String -> RpcException -> Bool | ||
28 | matchUnrecognizedScheme x (UnrecognizedScheme scheme) = x == scheme | ||
29 | matchUnrecognizedScheme _ _ = False | ||
30 | |||
31 | spec :: Spec | ||
32 | spec = parallel $ do | ||
33 | describe "Manager" $ do | ||
34 | describe "newManager" $ do | ||
35 | it "" $ pending | ||
36 | |||
37 | describe "closeManager" $ do | ||
38 | it "" $ pending | ||
39 | |||
40 | describe "withManager" $ do | ||
41 | it "" $ pending | ||
42 | |||
43 | describe "RPC" $ do | ||
44 | describe "announce" $ do | ||
45 | it "must fail on bad uri scheme" $ do | ||
46 | withManager rpcOpts def $ \ mgr -> do | ||
47 | q <- arbitrarySample | ||
48 | announce mgr "magnet://foo.bar" q | ||
49 | `shouldThrow` matchUnrecognizedScheme "magnet:" | ||
50 | |||
51 | describe "scrape" $ do | ||
52 | it "must fail on bad uri scheme" $ do | ||
53 | withManager rpcOpts def $ \ mgr -> do | ||
54 | scrape mgr "magnet://foo.bar" [] | ||
55 | `shouldThrow` matchUnrecognizedScheme "magnet:" | ||
56 | |||
57 | forM_ trackers $ \ TrackerEntry {..} -> | ||
58 | context trackerName $ do | ||
59 | |||
60 | describe "announce" $ do | ||
61 | if tryAnnounce then do | ||
62 | it "have valid response" $ do | ||
63 | withManager rpcOpts def $ \ mgr -> do | ||
64 | q <- arbitrarySample | ||
65 | _ <- announce mgr trackerURI q | ||
66 | return () | ||
67 | else do | ||
68 | it "should throw exception" $ do | ||
69 | pending | ||
70 | |||
71 | describe "scrape" $ do | ||
72 | if tryScraping then do | ||
73 | it "have valid response" $ do | ||
74 | withManager rpcOpts def $ \ mgr -> do | ||
75 | xs <- scrape mgr trackerURI [def] | ||
76 | L.length xs `shouldSatisfy` (>= 1) | ||
77 | else do | ||
78 | it "should throw exception" $ do | ||
79 | pending | ||
diff --git a/dht/bittorrent/tests/Network/BitTorrent/Tracker/SessionSpec.hs b/dht/bittorrent/tests/Network/BitTorrent/Tracker/SessionSpec.hs new file mode 100644 index 00000000..72936ee7 --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/Tracker/SessionSpec.hs | |||
@@ -0,0 +1,61 @@ | |||
1 | module Network.BitTorrent.Tracker.SessionSpec (spec) where | ||
2 | import Control.Monad | ||
3 | import Data.Default | ||
4 | import Data.List as L | ||
5 | import Test.Hspec | ||
6 | |||
7 | import Data.Torrent | ||
8 | import Network.BitTorrent.Tracker.Message | ||
9 | import Network.BitTorrent.Tracker.List | ||
10 | import Network.BitTorrent.Tracker.RPC | ||
11 | import Network.BitTorrent.Tracker.Session | ||
12 | |||
13 | import Config | ||
14 | |||
15 | testSession :: Bool -> (Manager -> Session -> IO ()) -> IO () | ||
16 | testSession runEmpty action = do | ||
17 | t <- getTestTorrent | ||
18 | withManager def def $ \ m -> do | ||
19 | withSession m (idInfoHash (tInfoDict t)) (trackerList t) $ \ s -> | ||
20 | action m s | ||
21 | |||
22 | when runEmpty $ do | ||
23 | withSession m (idInfoHash (tInfoDict t)) def $ \ s -> | ||
24 | action m s | ||
25 | |||
26 | spec :: Spec | ||
27 | spec = do | ||
28 | describe "Session" $ do | ||
29 | it "start new session in paused state" $ do | ||
30 | testSession True $ \ _ s -> do | ||
31 | status <- getStatus s | ||
32 | status `shouldBe` Paused | ||
33 | |||
34 | describe "Query" $ do | ||
35 | it "change status after notify" $ do | ||
36 | testSession True $ \ m s -> do | ||
37 | notify m s Started | ||
38 | status <- getStatus s | ||
39 | status `shouldBe` Running | ||
40 | |||
41 | notify m s Stopped | ||
42 | stopped <- getStatus s | ||
43 | stopped `shouldBe` Paused | ||
44 | |||
45 | it "completed event do not change status" $ do | ||
46 | testSession True $ \ m s -> do | ||
47 | notify m s Completed | ||
48 | status <- getStatus s | ||
49 | status `shouldBe` Paused | ||
50 | |||
51 | testSession True $ \ m s -> do | ||
52 | notify m s Started | ||
53 | notify m s Completed | ||
54 | status <- getStatus s | ||
55 | status `shouldBe` Running | ||
56 | |||
57 | it "return non-empty list of peers" $ do | ||
58 | testSession False $ \ m s -> do | ||
59 | notify m s Started | ||
60 | peers <- askPeers m s | ||
61 | peers `shouldSatisfy` (not . L.null) | ||
diff --git a/dht/bittorrent/tests/Network/BitTorrent/Tracker/TestData.hs b/dht/bittorrent/tests/Network/BitTorrent/Tracker/TestData.hs new file mode 100644 index 00000000..b95e2df4 --- /dev/null +++ b/dht/bittorrent/tests/Network/BitTorrent/Tracker/TestData.hs | |||
@@ -0,0 +1,93 @@ | |||
1 | {-# LANGUAGE RecordWildCards #-} | ||
2 | {-# OPTIONS_GHC -fno-warn-orphans #-} | ||
3 | module Network.BitTorrent.Tracker.TestData | ||
4 | ( TrackerEntry (..) | ||
5 | , isUdpTracker | ||
6 | , isHttpTracker | ||
7 | , trackers | ||
8 | , badTracker | ||
9 | ) where | ||
10 | |||
11 | import Data.Maybe | ||
12 | import Data.String | ||
13 | import Network.URI | ||
14 | |||
15 | import Data.Torrent | ||
16 | |||
17 | |||
18 | data TrackerEntry = TrackerEntry | ||
19 | { -- | May be used to show tracker name in test suite report. | ||
20 | trackerName :: String | ||
21 | |||
22 | -- | Announce uri of the tracker. | ||
23 | , trackerURI :: URI | ||
24 | |||
25 | -- | Some trackers abadoned, so don't even try to announce. | ||
26 | , tryAnnounce :: Bool | ||
27 | |||
28 | -- | Some trackers do not support scraping, so we should not even | ||
29 | -- try to scrape them. | ||
30 | , tryScraping :: Bool | ||
31 | |||
32 | -- | Some trackers allow | ||
33 | , hashList :: Maybe [InfoHash] | ||
34 | } | ||
35 | |||
36 | isUdpTracker :: TrackerEntry -> Bool | ||
37 | isUdpTracker TrackerEntry {..} = uriScheme trackerURI == "udp:" | ||
38 | |||
39 | isHttpTracker :: TrackerEntry -> Bool | ||
40 | isHttpTracker TrackerEntry {..} = uriScheme trackerURI == "http:" | ||
41 | || uriScheme trackerURI == "https:" | ||
42 | |||
43 | instance IsString URI where | ||
44 | fromString str = fromMaybe err $ parseURI str | ||
45 | where | ||
46 | err = error $ "fromString: bad URI " ++ show str | ||
47 | |||
48 | trackerEntry :: URI -> TrackerEntry | ||
49 | trackerEntry uri = TrackerEntry | ||
50 | { trackerName = maybe "<unknown>" uriRegName (uriAuthority uri) | ||
51 | , trackerURI = uri | ||
52 | , tryAnnounce = False | ||
53 | , tryScraping = False | ||
54 | , hashList = Nothing | ||
55 | } | ||
56 | |||
57 | announceOnly :: String -> URI -> TrackerEntry | ||
58 | announceOnly name uri = (trackerEntry uri) | ||
59 | { trackerName = name | ||
60 | , tryAnnounce = True | ||
61 | } | ||
62 | |||
63 | announceScrape :: String -> URI -> TrackerEntry | ||
64 | announceScrape name uri = (announceOnly name uri) | ||
65 | { tryScraping = True | ||
66 | } | ||
67 | |||
68 | notWorking :: String -> URI -> TrackerEntry | ||
69 | notWorking name uri = (trackerEntry uri) | ||
70 | { trackerName = name | ||
71 | } | ||
72 | |||
73 | trackers :: [TrackerEntry] | ||
74 | trackers = | ||
75 | [ (announceOnly "LinuxTracker" | ||
76 | "http://linuxtracker.org:2710/00000000000000000000000000000000/announce") | ||
77 | { hashList = Just ["1c82a95b9e02bf3db4183da072ad3ef656aacf0e"] -- debian 7 | ||
78 | } | ||
79 | |||
80 | , (announceScrape "Arch" "http://tracker.archlinux.org:6969/announce") | ||
81 | { hashList = Just ["bc9ae647a3e6c3636de58535dd3f6360ce9f4621"] | ||
82 | } | ||
83 | |||
84 | , notWorking "rarbg" "udp://9.rarbg.com:2710/announce" | ||
85 | |||
86 | , announceScrape "OpenBitTorrent" "udp://tracker.openbittorrent.com:80/announce" | ||
87 | , announceScrape "PublicBT" "udp://tracker.publicbt.com:80/announce" | ||
88 | , notWorking "OpenBitTorrent" "http://tracker.openbittorrent.com:80/announce" | ||
89 | , notWorking "PublicBT" "http://tracker.publicbt.com:80/announce" | ||
90 | ] | ||
91 | |||
92 | badTracker :: TrackerEntry | ||
93 | badTracker = notWorking "rarbg" "udp://9.rarbg.com:2710/announce" \ No newline at end of file | ||
diff --git a/dht/bittorrent/tests/Network/KRPC/MessageSpec.hs b/dht/bittorrent/tests/Network/KRPC/MessageSpec.hs new file mode 100644 index 00000000..498ef679 --- /dev/null +++ b/dht/bittorrent/tests/Network/KRPC/MessageSpec.hs | |||
@@ -0,0 +1,72 @@ | |||
1 | {-# LANGUAGE OverloadedStrings #-} | ||
2 | {-# OPTIONS_GHC -fno-warn-orphans #-} | ||
3 | module Network.KRPC.MessageSpec (spec) where | ||
4 | import Control.Applicative | ||
5 | import Data.ByteString.Lazy as BL | ||
6 | import Test.Hspec | ||
7 | import Test.QuickCheck | ||
8 | import Test.QuickCheck.Instances () | ||
9 | |||
10 | import Data.BEncode as BE | ||
11 | import Network.KRPC.Message | ||
12 | |||
13 | instance Arbitrary ErrorCode where | ||
14 | arbitrary = arbitraryBoundedEnum | ||
15 | |||
16 | instance Arbitrary KError where | ||
17 | arbitrary = KError <$> arbitrary <*> arbitrary <*> arbitrary | ||
18 | |||
19 | instance Arbitrary KQuery where | ||
20 | arbitrary = KQuery <$> pure (BInteger 0) <*> arbitrary <*> arbitrary | ||
21 | |||
22 | instance Arbitrary KResponse where | ||
23 | -- TODO: Abitrary instance for ReflectedIP | ||
24 | arbitrary = KResponse <$> pure (BList []) <*> arbitrary <*> pure Nothing | ||
25 | |||
26 | instance Arbitrary KMessage where | ||
27 | arbitrary = frequency | ||
28 | [ (1, Q <$> arbitrary) | ||
29 | , (1, R <$> arbitrary) | ||
30 | , (1, E <$> arbitrary) | ||
31 | ] | ||
32 | |||
33 | spec :: Spec | ||
34 | spec = do | ||
35 | describe "error message" $ do | ||
36 | it "properly bencoded (iso)" $ property $ \ ke -> | ||
37 | BE.decode (BL.toStrict (BE.encode ke)) `shouldBe` Right (ke :: KError) | ||
38 | |||
39 | it "properly bencoded" $ do | ||
40 | BE.decode "d1:eli201e23:A Generic Error Ocurrede1:t2:aa1:y1:ee" | ||
41 | `shouldBe` Right (KError GenericError "A Generic Error Ocurred" "aa") | ||
42 | |||
43 | BE.decode "d1:eli202e22:A Server Error Ocurrede1:t2:bb1:y1:ee" | ||
44 | `shouldBe` Right (KError ServerError "A Server Error Ocurred" "bb") | ||
45 | |||
46 | BE.decode "d1:eli203e24:A Protocol Error Ocurrede1:t2:cc1:y1:ee" | ||
47 | `shouldBe` Right (KError ProtocolError "A Protocol Error Ocurred" "cc") | ||
48 | |||
49 | BE.decode "d1:eli204e30:Attempt to call unknown methode1:t2:dd1:y1:ee" | ||
50 | `shouldBe` Right | ||
51 | (KError MethodUnknown "Attempt to call unknown method" "dd") | ||
52 | |||
53 | describe "query message" $ do | ||
54 | it "properly bencoded (iso)" $ property $ \ kq -> | ||
55 | BE.decode (BL.toStrict (BE.encode kq)) `shouldBe` Right (kq :: KQuery) | ||
56 | |||
57 | it "properly bencoded" $ do | ||
58 | BE.decode "d1:ale1:q4:ping1:t2:aa1:y1:qe" `shouldBe` | ||
59 | Right (KQuery (BList []) "ping" "aa") | ||
60 | |||
61 | |||
62 | describe "response message" $ do | ||
63 | it "properly bencoded (iso)" $ property $ \ kr -> | ||
64 | BE.decode (BL.toStrict (BE.encode kr)) `shouldBe` Right (kr :: KResponse) | ||
65 | |||
66 | it "properly bencoded" $ do | ||
67 | BE.decode "d1:rle1:t2:aa1:y1:re" `shouldBe` | ||
68 | Right (KResponse (BList []) "aa" Nothing) | ||
69 | |||
70 | describe "generic message" $ do | ||
71 | it "properly bencoded (iso)" $ property $ \ km -> | ||
72 | BE.decode (BL.toStrict (BE.encode km)) `shouldBe` Right (km :: KMessage) | ||
diff --git a/dht/bittorrent/tests/Network/KRPC/MethodSpec.hs b/dht/bittorrent/tests/Network/KRPC/MethodSpec.hs new file mode 100644 index 00000000..c1c58282 --- /dev/null +++ b/dht/bittorrent/tests/Network/KRPC/MethodSpec.hs | |||
@@ -0,0 +1,52 @@ | |||
1 | {-# LANGUAGE MultiParamTypeClasses #-} | ||
2 | {-# LANGUAGE FlexibleInstances #-} | ||
3 | {-# LANGUAGE OverloadedStrings #-} | ||
4 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
5 | {-# LANGUAGE DeriveDataTypeable #-} | ||
6 | {-# OPTIONS_GHC -fno-warn-orphans #-} | ||
7 | module Network.KRPC.MethodSpec where | ||
8 | import Control.Applicative | ||
9 | import Data.BEncode | ||
10 | import Data.ByteString as BS | ||
11 | import Data.Typeable | ||
12 | import Network.KRPC | ||
13 | import Test.Hspec | ||
14 | |||
15 | |||
16 | data Ping = Ping | ||
17 | deriving (Show, Eq, Typeable) | ||
18 | |||
19 | instance BEncode Ping where | ||
20 | toBEncode Ping = toBEncode () | ||
21 | fromBEncode b = Ping <$ (fromBEncode b :: Result ()) | ||
22 | |||
23 | instance KRPC Ping Ping | ||
24 | |||
25 | ping :: Monad h => Handler h | ||
26 | ping = handler $ \ _ Ping -> return Ping | ||
27 | |||
28 | newtype Echo a = Echo a | ||
29 | deriving (Show, Eq, BEncode, Typeable) | ||
30 | |||
31 | echo :: Monad h => Handler h | ||
32 | echo = handler $ \ _ (Echo a) -> return (Echo (a :: ByteString)) | ||
33 | |||
34 | instance (Typeable a, BEncode a) => KRPC (Echo a) (Echo a) | ||
35 | |||
36 | spec :: Spec | ||
37 | spec = do | ||
38 | describe "ping method" $ do | ||
39 | it "name is ping" $ do | ||
40 | (method :: Method Ping Ping) `shouldBe` "ping" | ||
41 | |||
42 | it "has pretty Show instance" $ do | ||
43 | show (method :: Method Ping Ping) `shouldBe` "ping :: Ping -> Ping" | ||
44 | |||
45 | describe "echo method" $ do | ||
46 | it "is overloadable" $ do | ||
47 | (method :: Method (Echo Int ) (Echo Int )) `shouldBe` "echo int" | ||
48 | (method :: Method (Echo Bool) (Echo Bool)) `shouldBe` "echo bool" | ||
49 | |||
50 | it "has pretty Show instance" $ do | ||
51 | show (method :: Method (Echo Int) (Echo Int)) | ||
52 | `shouldBe` "echo int :: Echo Int -> Echo Int" \ No newline at end of file | ||
diff --git a/dht/bittorrent/tests/Network/KRPCSpec.hs b/dht/bittorrent/tests/Network/KRPCSpec.hs new file mode 100644 index 00000000..eabcc817 --- /dev/null +++ b/dht/bittorrent/tests/Network/KRPCSpec.hs | |||
@@ -0,0 +1,59 @@ | |||
1 | {-# LANGUAGE OverloadedStrings #-} | ||
2 | {-# OPTIONS_GHC -fno-warn-orphans #-} | ||
3 | module Network.KRPCSpec (spec) where | ||
4 | import Control.Monad.Logger | ||
5 | import Control.Monad.Reader | ||
6 | import Network.KRPC | ||
7 | import Network.KRPC.MethodSpec hiding (spec) | ||
8 | import Test.Hspec | ||
9 | |||
10 | servAddr :: SockAddr | ||
11 | servAddr = SockAddrInet 6000 (256 * 256 * 256 + 127) | ||
12 | |||
13 | handlers :: [Handler IO] | ||
14 | handlers = | ||
15 | [ handler $ \ _ Ping -> return Ping | ||
16 | , handler $ \ _ (Echo a) -> return (Echo (a :: Bool)) | ||
17 | , handler $ \ _ (Echo a) -> return (Echo (a :: Int)) | ||
18 | ] | ||
19 | |||
20 | instance MonadLogger IO where | ||
21 | monadLoggerLog _ _ _ _ = return () | ||
22 | |||
23 | opts :: Options | ||
24 | opts = def { optQueryTimeout = 1 } | ||
25 | |||
26 | spec :: Spec | ||
27 | spec = do | ||
28 | let qr :: MonadKRPC h m => SockAddr -> Echo Int -> m (Echo Int) | ||
29 | qr = query | ||
30 | |||
31 | describe "manager" $ do | ||
32 | it "is active until closeManager called" $ do | ||
33 | m <- newManager opts servAddr [] | ||
34 | isActive m `shouldReturn` True | ||
35 | closeManager m | ||
36 | isActive m `shouldReturn` False | ||
37 | |||
38 | describe "query" $ do | ||
39 | it "run handlers" $ do | ||
40 | let int = 0xabcd :: Int | ||
41 | (withManager opts servAddr handlers $ runReaderT $ do | ||
42 | listen | ||
43 | query servAddr (Echo int)) | ||
44 | `shouldReturn` Echo int | ||
45 | |||
46 | it "count transactions properly" $ do | ||
47 | (withManager opts servAddr handlers $ runReaderT $ do | ||
48 | listen | ||
49 | _ <- qr servAddr (Echo 0xabcd) | ||
50 | _ <- qr servAddr (Echo 0xabcd) | ||
51 | getQueryCount | ||
52 | ) | ||
53 | `shouldReturn` 2 | ||
54 | |||
55 | it "throw timeout exception" $ do | ||
56 | (withManager opts servAddr handlers $ runReaderT $ do | ||
57 | qr servAddr (Echo 0xabcd) | ||
58 | ) | ||
59 | `shouldThrow` (== TimeoutExpired) | ||