]> Sergey Matveev's repositories - btrtrc.git/blob - client_test.go
Finally make Torrent.torrent private
[btrtrc.git] / client_test.go
1 package torrent
2
3 import (
4         "encoding/binary"
5         "fmt"
6         "io"
7         "io/ioutil"
8         "log"
9         "net"
10         "os"
11         "path/filepath"
12         "sync"
13         "testing"
14         "time"
15
16         _ "github.com/anacrolix/envpprof"
17         "github.com/anacrolix/missinggo"
18         . "github.com/anacrolix/missinggo"
19         "github.com/anacrolix/missinggo/filecache"
20         "github.com/anacrolix/utp"
21         "github.com/bradfitz/iter"
22         "github.com/stretchr/testify/assert"
23         "github.com/stretchr/testify/require"
24
25         "github.com/anacrolix/torrent/bencode"
26         "github.com/anacrolix/torrent/data/pieceStore"
27         "github.com/anacrolix/torrent/data/pieceStore/dataBackend/fileCache"
28         "github.com/anacrolix/torrent/dht"
29         "github.com/anacrolix/torrent/internal/testutil"
30         "github.com/anacrolix/torrent/iplist"
31         "github.com/anacrolix/torrent/metainfo"
32 )
33
34 func init() {
35         log.SetFlags(log.LstdFlags | log.Llongfile)
36 }
37
38 var TestingConfig = Config{
39         ListenAddr:           "localhost:0",
40         NoDHT:                true,
41         DisableTrackers:      true,
42         NoDefaultBlocklist:   true,
43         DisableMetainfoCache: true,
44         DataDir:              filepath.Join(os.TempDir(), "anacrolix"),
45         DHTConfig: dht.ServerConfig{
46                 NoDefaultBootstrap: true,
47         },
48 }
49
50 func TestClientDefault(t *testing.T) {
51         cl, err := NewClient(&TestingConfig)
52         if err != nil {
53                 t.Fatal(err)
54         }
55         cl.Close()
56 }
57
58 func TestAddDropTorrent(t *testing.T) {
59         cl, err := NewClient(&TestingConfig)
60         if err != nil {
61                 t.Fatal(err)
62         }
63         defer cl.Close()
64         dir, mi := testutil.GreetingTestTorrent()
65         defer os.RemoveAll(dir)
66         tt, new, err := cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
67         if err != nil {
68                 t.Fatal(err)
69         }
70         if !new {
71                 t.FailNow()
72         }
73         tt.Drop()
74 }
75
76 func TestAddTorrentNoSupportedTrackerSchemes(t *testing.T) {
77         t.SkipNow()
78 }
79
80 func TestAddTorrentNoUsableURLs(t *testing.T) {
81         t.SkipNow()
82 }
83
84 func TestAddPeersToUnknownTorrent(t *testing.T) {
85         t.SkipNow()
86 }
87
88 func TestPieceHashSize(t *testing.T) {
89         if pieceHash.Size() != 20 {
90                 t.FailNow()
91         }
92 }
93
94 func TestTorrentInitialState(t *testing.T) {
95         dir, mi := testutil.GreetingTestTorrent()
96         defer os.RemoveAll(dir)
97         tor, err := newTorrent(func() (ih InfoHash) {
98                 missinggo.CopyExact(ih[:], mi.Info.Hash)
99                 return
100         }())
101         if err != nil {
102                 t.Fatal(err)
103         }
104         tor.chunkSize = 2
105         err = tor.setMetadata(&mi.Info.Info, mi.Info.Bytes)
106         if err != nil {
107                 t.Fatal(err)
108         }
109         if len(tor.Pieces) != 3 {
110                 t.Fatal("wrong number of pieces")
111         }
112         tor.pendAllChunkSpecs(0)
113         assert.EqualValues(t, 3, tor.pieceNumPendingChunks(0))
114         assert.EqualValues(t, chunkSpec{4, 1}, chunkIndexSpec(2, tor.pieceLength(0), tor.chunkSize))
115 }
116
117 func TestUnmarshalPEXMsg(t *testing.T) {
118         var m peerExchangeMessage
119         if err := bencode.Unmarshal([]byte("d5:added12:\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0ce"), &m); err != nil {
120                 t.Fatal(err)
121         }
122         if len(m.Added) != 2 {
123                 t.FailNow()
124         }
125         if m.Added[0].Port != 0x506 {
126                 t.FailNow()
127         }
128 }
129
130 func TestReducedDialTimeout(t *testing.T) {
131         for _, _case := range []struct {
132                 Max             time.Duration
133                 HalfOpenLimit   int
134                 PendingPeers    int
135                 ExpectedReduced time.Duration
136         }{
137                 {nominalDialTimeout, 40, 0, nominalDialTimeout},
138                 {nominalDialTimeout, 40, 1, nominalDialTimeout},
139                 {nominalDialTimeout, 40, 39, nominalDialTimeout},
140                 {nominalDialTimeout, 40, 40, nominalDialTimeout / 2},
141                 {nominalDialTimeout, 40, 80, nominalDialTimeout / 3},
142                 {nominalDialTimeout, 40, 4000, nominalDialTimeout / 101},
143         } {
144                 reduced := reducedDialTimeout(_case.Max, _case.HalfOpenLimit, _case.PendingPeers)
145                 expected := _case.ExpectedReduced
146                 if expected < minDialTimeout {
147                         expected = minDialTimeout
148                 }
149                 if reduced != expected {
150                         t.Fatalf("expected %s, got %s", _case.ExpectedReduced, reduced)
151                 }
152         }
153 }
154
155 func TestUTPRawConn(t *testing.T) {
156         l, err := utp.NewSocket("udp", "")
157         if err != nil {
158                 t.Fatal(err)
159         }
160         defer l.Close()
161         go func() {
162                 for {
163                         _, err := l.Accept()
164                         if err != nil {
165                                 break
166                         }
167                 }
168         }()
169         // Connect a UTP peer to see if the RawConn will still work.
170         s, _ := utp.NewSocket("udp", "")
171         defer s.Close()
172         utpPeer, err := s.Dial(fmt.Sprintf("localhost:%d", missinggo.AddrPort(l.Addr())))
173         if err != nil {
174                 t.Fatalf("error dialing utp listener: %s", err)
175         }
176         defer utpPeer.Close()
177         peer, err := net.ListenPacket("udp", ":0")
178         if err != nil {
179                 t.Fatal(err)
180         }
181         defer peer.Close()
182
183         msgsReceived := 0
184         // How many messages to send. I've set this to double the channel buffer
185         // size in the raw packetConn.
186         const N = 200
187         readerStopped := make(chan struct{})
188         // The reader goroutine.
189         go func() {
190                 defer close(readerStopped)
191                 b := make([]byte, 500)
192                 for i := 0; i < N; i++ {
193                         n, _, err := l.ReadFrom(b)
194                         if err != nil {
195                                 t.Fatalf("error reading from raw conn: %s", err)
196                         }
197                         msgsReceived++
198                         var d int
199                         fmt.Sscan(string(b[:n]), &d)
200                         if d != i {
201                                 log.Printf("got wrong number: expected %d, got %d", i, d)
202                         }
203                 }
204         }()
205         udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("localhost:%d", missinggo.AddrPort(l.Addr())))
206         if err != nil {
207                 t.Fatal(err)
208         }
209         for i := 0; i < N; i++ {
210                 _, err := peer.WriteTo([]byte(fmt.Sprintf("%d", i)), udpAddr)
211                 if err != nil {
212                         t.Fatal(err)
213                 }
214                 time.Sleep(time.Microsecond)
215         }
216         select {
217         case <-readerStopped:
218         case <-time.After(time.Second):
219                 t.Fatal("reader timed out")
220         }
221         if msgsReceived != N {
222                 t.Fatalf("messages received: %d", msgsReceived)
223         }
224 }
225
226 func TestTwoClientsArbitraryPorts(t *testing.T) {
227         for i := 0; i < 2; i++ {
228                 cl, err := NewClient(&TestingConfig)
229                 if err != nil {
230                         t.Fatal(err)
231                 }
232                 defer cl.Close()
233         }
234 }
235
236 func TestAddDropManyTorrents(t *testing.T) {
237         cl, _ := NewClient(&TestingConfig)
238         defer cl.Close()
239         for i := range iter.N(1000) {
240                 var spec TorrentSpec
241                 binary.PutVarint(spec.InfoHash[:], int64(i))
242                 tt, new, err := cl.AddTorrentSpec(&spec)
243                 if err != nil {
244                         t.Error(err)
245                 }
246                 if !new {
247                         t.FailNow()
248                 }
249                 defer tt.Drop()
250         }
251 }
252
253 func TestClientTransfer(t *testing.T) {
254         greetingTempDir, mi := testutil.GreetingTestTorrent()
255         defer os.RemoveAll(greetingTempDir)
256         cfg := TestingConfig
257         cfg.Seed = true
258         cfg.DataDir = greetingTempDir
259         seeder, err := NewClient(&cfg)
260         if err != nil {
261                 t.Fatal(err)
262         }
263         defer seeder.Close()
264         seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
265         leecherDataDir, err := ioutil.TempDir("", "")
266         if err != nil {
267                 t.Fatal(err)
268         }
269         defer os.RemoveAll(leecherDataDir)
270         // cfg.TorrentDataOpener = func(info *metainfo.Info) (data.Data, error) {
271         //      return blob.TorrentData(info, leecherDataDir), nil
272         // }
273         // blobStore := blob.NewStore(leecherDataDir)
274         // cfg.TorrentDataOpener = func(info *metainfo.Info) Data {
275         //      return blobStore.OpenTorrent(info)
276         // }
277         cfg.TorrentDataOpener = func() TorrentDataOpener {
278                 fc, err := filecache.NewCache(leecherDataDir)
279                 require.NoError(t, err)
280                 store := pieceStore.New(fileCacheDataBackend.New(fc))
281                 return func(mi *metainfo.Info) Data {
282                         return store.OpenTorrentData(mi)
283                 }
284         }()
285         leecher, _ := NewClient(&cfg)
286         defer leecher.Close()
287         leecherGreeting, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
288                 ret = TorrentSpecFromMetaInfo(mi)
289                 ret.ChunkSize = 2
290                 return
291         }())
292         // TODO: The piece state publishing is kinda jammed in here until I have a
293         // more thorough test.
294         go func() {
295                 s := leecherGreeting.torrent.pieceStateChanges.Subscribe()
296                 defer s.Close()
297                 for i := range s.Values {
298                         log.Print(i)
299                 }
300                 log.Print("finished")
301         }()
302         leecherGreeting.AddPeers([]Peer{
303                 Peer{
304                         IP:   missinggo.AddrIP(seeder.ListenAddr()),
305                         Port: missinggo.AddrPort(seeder.ListenAddr()),
306                 },
307         })
308         r := leecherGreeting.NewReader()
309         defer r.Close()
310         _greeting, err := ioutil.ReadAll(r)
311         if err != nil {
312                 t.Fatalf("%q %s", string(_greeting), err)
313         }
314         greeting := string(_greeting)
315         if greeting != testutil.GreetingFileContents {
316                 t.Fatal(":(")
317         }
318 }
319
320 // Check that after completing leeching, a leecher transitions to a seeding
321 // correctly. Connected in a chain like so: Seeder <-> Leecher <-> LeecherLeecher.
322 func TestSeedAfterDownloading(t *testing.T) {
323         greetingTempDir, mi := testutil.GreetingTestTorrent()
324         defer os.RemoveAll(greetingTempDir)
325         cfg := TestingConfig
326         cfg.Seed = true
327         cfg.DataDir = greetingTempDir
328         seeder, err := NewClient(&cfg)
329         defer seeder.Close()
330         seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
331         cfg.DataDir, err = ioutil.TempDir("", "")
332         require.NoError(t, err)
333         defer os.RemoveAll(cfg.DataDir)
334         leecher, _ := NewClient(&cfg)
335         defer leecher.Close()
336         cfg.Seed = false
337         cfg.TorrentDataOpener = nil
338         cfg.DataDir, err = ioutil.TempDir("", "")
339         require.NoError(t, err)
340         defer os.RemoveAll(cfg.DataDir)
341         leecherLeecher, _ := NewClient(&cfg)
342         defer leecherLeecher.Close()
343         leecherGreeting, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
344                 ret = TorrentSpecFromMetaInfo(mi)
345                 ret.ChunkSize = 2
346                 return
347         }())
348         llg, _, _ := leecherLeecher.AddTorrentSpec(func() (ret *TorrentSpec) {
349                 ret = TorrentSpecFromMetaInfo(mi)
350                 ret.ChunkSize = 3
351                 return
352         }())
353         // Simultaneously DownloadAll in Leecher, and read the contents
354         // consecutively in LeecherLeecher. This non-deterministically triggered a
355         // case where the leecher wouldn't unchoke the LeecherLeecher.
356         var wg sync.WaitGroup
357         wg.Add(1)
358         go func() {
359                 defer wg.Done()
360                 r := llg.NewReader()
361                 defer r.Close()
362                 b, err := ioutil.ReadAll(r)
363                 require.NoError(t, err)
364                 require.EqualValues(t, testutil.GreetingFileContents, b)
365         }()
366         leecherGreeting.AddPeers([]Peer{
367                 Peer{
368                         IP:   missinggo.AddrIP(seeder.ListenAddr()),
369                         Port: missinggo.AddrPort(seeder.ListenAddr()),
370                 },
371                 Peer{
372                         IP:   missinggo.AddrIP(leecherLeecher.ListenAddr()),
373                         Port: missinggo.AddrPort(leecherLeecher.ListenAddr()),
374                 },
375         })
376         wg.Add(1)
377         go func() {
378                 defer wg.Done()
379                 leecherGreeting.DownloadAll()
380                 leecher.WaitAll()
381         }()
382         wg.Wait()
383 }
384
385 func TestReadaheadPieces(t *testing.T) {
386         for _, case_ := range []struct {
387                 readaheadBytes, pieceLength int64
388                 readaheadPieces             int
389         }{
390                 {5 * 1024 * 1024, 256 * 1024, 19},
391                 {5 * 1024 * 1024, 5 * 1024 * 1024, 1},
392                 {5*1024*1024 - 1, 5 * 1024 * 1024, 1},
393                 {5 * 1024 * 1024, 5*1024*1024 - 1, 2},
394                 {0, 5 * 1024 * 1024, 0},
395                 {5 * 1024 * 1024, 1048576, 4},
396         } {
397                 pieces := readaheadPieces(case_.readaheadBytes, case_.pieceLength)
398                 assert.Equal(t, case_.readaheadPieces, pieces, "%v", case_)
399         }
400 }
401
402 func TestMergingTrackersByAddingSpecs(t *testing.T) {
403         cl, _ := NewClient(&TestingConfig)
404         defer cl.Close()
405         spec := TorrentSpec{}
406         T, new, _ := cl.AddTorrentSpec(&spec)
407         if !new {
408                 t.FailNow()
409         }
410         spec.Trackers = [][]string{{"http://a"}, {"udp://b"}}
411         _, new, _ = cl.AddTorrentSpec(&spec)
412         if new {
413                 t.FailNow()
414         }
415         assert.EqualValues(t, T.torrent.Trackers[0][0].URL(), "http://a")
416         assert.EqualValues(t, T.torrent.Trackers[1][0].URL(), "udp://b")
417 }
418
419 type badData struct{}
420
421 func (me badData) Close() {}
422
423 func (me badData) WriteAt(b []byte, off int64) (int, error) {
424         return 0, nil
425 }
426
427 func (me badData) WriteSectionTo(w io.Writer, off, n int64) (int64, error) {
428         return 0, nil
429 }
430
431 func (me badData) PieceComplete(piece int) bool {
432         return true
433 }
434
435 func (me badData) PieceCompleted(piece int) error {
436         return nil
437 }
438
439 func (me badData) ReadAt(b []byte, off int64) (n int, err error) {
440         if off >= 5 {
441                 err = io.EOF
442                 return
443         }
444         n = copy(b, []byte("hello")[off:])
445         return
446 }
447
448 // We read from a piece which is marked completed, but is missing data.
449 func TestCompletedPieceWrongSize(t *testing.T) {
450         cfg := TestingConfig
451         cfg.TorrentDataOpener = func(*metainfo.Info) Data {
452                 return badData{}
453         }
454         cl, _ := NewClient(&cfg)
455         defer cl.Close()
456         tt, new, err := cl.AddTorrentSpec(&TorrentSpec{
457                 Info: &metainfo.InfoEx{
458                         Info: metainfo.Info{
459                                 PieceLength: 15,
460                                 Pieces:      make([]byte, 20),
461                                 Files: []metainfo.FileInfo{
462                                         metainfo.FileInfo{Path: []string{"greeting"}, Length: 13},
463                                 },
464                         },
465                 },
466         })
467         if err != nil {
468                 t.Fatal(err)
469         }
470         if !new {
471                 t.Fatal("expected new")
472         }
473         r := tt.NewReader()
474         defer r.Close()
475         b := make([]byte, 20)
476         n, err := io.ReadFull(r, b)
477         if n != 5 || err != io.ErrUnexpectedEOF {
478                 t.Fatal(n, err)
479         }
480         defer tt.Drop()
481 }
482
483 func BenchmarkAddLargeTorrent(b *testing.B) {
484         cfg := TestingConfig
485         cfg.DisableTCP = true
486         cfg.DisableUTP = true
487         cfg.ListenAddr = "redonk"
488         cl, _ := NewClient(&cfg)
489         defer cl.Close()
490         for range iter.N(b.N) {
491                 t, err := cl.AddTorrentFromFile("testdata/bootstrap.dat.torrent")
492                 if err != nil {
493                         b.Fatal(err)
494                 }
495                 t.Drop()
496         }
497 }
498
499 func TestResponsive(t *testing.T) {
500         seederDataDir, mi := testutil.GreetingTestTorrent()
501         defer os.RemoveAll(seederDataDir)
502         cfg := TestingConfig
503         cfg.Seed = true
504         cfg.DataDir = seederDataDir
505         seeder, err := NewClient(&cfg)
506         require.Nil(t, err)
507         defer seeder.Close()
508         seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
509         leecherDataDir, err := ioutil.TempDir("", "")
510         require.Nil(t, err)
511         defer os.RemoveAll(leecherDataDir)
512         cfg = TestingConfig
513         cfg.DataDir = leecherDataDir
514         leecher, err := NewClient(&cfg)
515         require.Nil(t, err)
516         defer leecher.Close()
517         leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
518                 ret = TorrentSpecFromMetaInfo(mi)
519                 ret.ChunkSize = 2
520                 return
521         }())
522         leecherTorrent.AddPeers([]Peer{
523                 Peer{
524                         IP:   missinggo.AddrIP(seeder.ListenAddr()),
525                         Port: missinggo.AddrPort(seeder.ListenAddr()),
526                 },
527         })
528         reader := leecherTorrent.NewReader()
529         reader.SetReadahead(0)
530         reader.SetResponsive()
531         b := make([]byte, 2)
532         _, err = reader.ReadAt(b, 3)
533         assert.Nil(t, err)
534         assert.EqualValues(t, "lo", string(b))
535         n, err := reader.ReadAt(b, 11)
536         assert.Nil(t, err)
537         assert.EqualValues(t, 2, n)
538         assert.EqualValues(t, "d\n", string(b))
539 }
540
541 func TestTorrentDroppedDuringResponsiveRead(t *testing.T) {
542         seederDataDir, mi := testutil.GreetingTestTorrent()
543         defer os.RemoveAll(seederDataDir)
544         cfg := TestingConfig
545         cfg.Seed = true
546         cfg.DataDir = seederDataDir
547         seeder, err := NewClient(&cfg)
548         require.Nil(t, err)
549         defer seeder.Close()
550         seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
551         leecherDataDir, err := ioutil.TempDir("", "")
552         require.Nil(t, err)
553         defer os.RemoveAll(leecherDataDir)
554         cfg = TestingConfig
555         cfg.DataDir = leecherDataDir
556         leecher, err := NewClient(&cfg)
557         require.Nil(t, err)
558         defer leecher.Close()
559         leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
560                 ret = TorrentSpecFromMetaInfo(mi)
561                 ret.ChunkSize = 2
562                 return
563         }())
564         leecherTorrent.AddPeers([]Peer{
565                 Peer{
566                         IP:   missinggo.AddrIP(seeder.ListenAddr()),
567                         Port: missinggo.AddrPort(seeder.ListenAddr()),
568                 },
569         })
570         reader := leecherTorrent.NewReader()
571         reader.SetReadahead(0)
572         reader.SetResponsive()
573         b := make([]byte, 2)
574         _, err = reader.ReadAt(b, 3)
575         assert.Nil(t, err)
576         assert.EqualValues(t, "lo", string(b))
577         go leecherTorrent.Drop()
578         n, err := reader.ReadAt(b, 11)
579         assert.EqualError(t, err, "torrent closed")
580         assert.EqualValues(t, 0, n)
581 }
582
583 func TestDHTInheritBlocklist(t *testing.T) {
584         ipl := iplist.New(nil)
585         require.NotNil(t, ipl)
586         cfg := TestingConfig
587         cfg.IPBlocklist = ipl
588         cfg.NoDHT = false
589         cl, err := NewClient(&cfg)
590         require.NoError(t, err)
591         defer cl.Close()
592         require.Equal(t, ipl, cl.DHT().IPBlocklist())
593 }
594
595 // Check that stuff is merged in subsequent AddTorrentSpec for the same
596 // infohash.
597 func TestAddTorrentSpecMerging(t *testing.T) {
598         cl, err := NewClient(&TestingConfig)
599         require.NoError(t, err)
600         defer cl.Close()
601         dir, mi := testutil.GreetingTestTorrent()
602         defer os.RemoveAll(dir)
603         var ts TorrentSpec
604         missinggo.CopyExact(&ts.InfoHash, mi.Info.Hash)
605         tt, new, err := cl.AddTorrentSpec(&ts)
606         require.NoError(t, err)
607         require.True(t, new)
608         require.Nil(t, tt.Info())
609         _, new, err = cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
610         require.NoError(t, err)
611         require.False(t, new)
612         require.NotNil(t, tt.Info())
613 }
614
615 // Check that torrent Info is obtained from the metainfo file cache.
616 func TestAddTorrentMetainfoInCache(t *testing.T) {
617         cfg := TestingConfig
618         cfg.DisableMetainfoCache = false
619         cfg.ConfigDir, _ = ioutil.TempDir(os.TempDir(), "")
620         defer os.RemoveAll(cfg.ConfigDir)
621         cl, err := NewClient(&cfg)
622         require.NoError(t, err)
623         defer cl.Close()
624         dir, mi := testutil.GreetingTestTorrent()
625         defer os.RemoveAll(dir)
626         tt, new, err := cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
627         require.NoError(t, err)
628         require.True(t, new)
629         require.NotNil(t, tt.Info())
630         _, err = os.Stat(filepath.Join(cfg.ConfigDir, "torrents", fmt.Sprintf("%x.torrent", mi.Info.Hash)))
631         require.NoError(t, err)
632         // Contains only the infohash.
633         var ts TorrentSpec
634         missinggo.CopyExact(&ts.InfoHash, mi.Info.Hash)
635         _, ok := cl.Torrent(ts.InfoHash)
636         require.True(t, ok)
637         tt.Drop()
638         _, ok = cl.Torrent(ts.InfoHash)
639         require.False(t, ok)
640         tt, new, err = cl.AddTorrentSpec(&ts)
641         require.NoError(t, err)
642         require.True(t, new)
643         // Obtained from the metainfo cache.
644         require.NotNil(t, tt.Info())
645 }
646
647 func TestTorrentDroppedBeforeGotInfo(t *testing.T) {
648         dir, mi := testutil.GreetingTestTorrent()
649         os.RemoveAll(dir)
650         cl, _ := NewClient(&TestingConfig)
651         defer cl.Close()
652         var ts TorrentSpec
653         CopyExact(&ts.InfoHash, mi.Info.Hash)
654         tt, _, _ := cl.AddTorrentSpec(&ts)
655         tt.Drop()
656         assert.EqualValues(t, 0, len(cl.Torrents()))
657         select {
658         case <-tt.GotInfo():
659                 t.FailNow()
660         default:
661         }
662 }