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