diff options
Diffstat (limited to 'bittorrent/tests/Network/BitTorrent/DHT')
-rw-r--r-- | bittorrent/tests/Network/BitTorrent/DHT/MessageSpec.hs | 221 | ||||
-rw-r--r-- | bittorrent/tests/Network/BitTorrent/DHT/QuerySpec.hs | 105 | ||||
-rw-r--r-- | bittorrent/tests/Network/BitTorrent/DHT/RoutingSpec.hs | 77 | ||||
-rw-r--r-- | bittorrent/tests/Network/BitTorrent/DHT/SessionSpec.hs | 110 | ||||
-rw-r--r-- | bittorrent/tests/Network/BitTorrent/DHT/TestData.hs | 45 | ||||
-rw-r--r-- | bittorrent/tests/Network/BitTorrent/DHT/TokenSpec.hs | 42 |
6 files changed, 600 insertions, 0 deletions
diff --git a/bittorrent/tests/Network/BitTorrent/DHT/MessageSpec.hs b/bittorrent/tests/Network/BitTorrent/DHT/MessageSpec.hs new file mode 100644 index 00000000..6f3c7489 --- /dev/null +++ b/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/bittorrent/tests/Network/BitTorrent/DHT/QuerySpec.hs b/bittorrent/tests/Network/BitTorrent/DHT/QuerySpec.hs new file mode 100644 index 00000000..93f78263 --- /dev/null +++ b/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/bittorrent/tests/Network/BitTorrent/DHT/RoutingSpec.hs b/bittorrent/tests/Network/BitTorrent/DHT/RoutingSpec.hs new file mode 100644 index 00000000..07a906ba --- /dev/null +++ b/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/bittorrent/tests/Network/BitTorrent/DHT/SessionSpec.hs b/bittorrent/tests/Network/BitTorrent/DHT/SessionSpec.hs new file mode 100644 index 00000000..32e4c158 --- /dev/null +++ b/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/bittorrent/tests/Network/BitTorrent/DHT/TestData.hs b/bittorrent/tests/Network/BitTorrent/DHT/TestData.hs new file mode 100644 index 00000000..e9473cbb --- /dev/null +++ b/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/bittorrent/tests/Network/BitTorrent/DHT/TokenSpec.hs b/bittorrent/tests/Network/BitTorrent/DHT/TokenSpec.hs new file mode 100644 index 00000000..a45d2212 --- /dev/null +++ b/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 | ||