]> Sergey Matveev's repositories - btrtrc.git/blob - client_test.go
df024899e1d3287ade2ca8cad0b3038b263dd34f
[btrtrc.git] / client_test.go
1 package torrent
2
3 import (
4         "encoding/binary"
5         "errors"
6         "fmt"
7         "io"
8         "io/ioutil"
9         "log"
10         "math/rand"
11         "net"
12         "os"
13         "path/filepath"
14         "strings"
15         "sync"
16         "testing"
17         "time"
18
19         _ "github.com/anacrolix/envpprof"
20         "github.com/anacrolix/missinggo"
21         . "github.com/anacrolix/missinggo"
22         "github.com/anacrolix/utp"
23         "github.com/bradfitz/iter"
24         "github.com/stretchr/testify/assert"
25         "github.com/stretchr/testify/require"
26
27         "github.com/anacrolix/torrent/bencode"
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         "github.com/anacrolix/torrent/storage"
33 )
34
35 func init() {
36         log.SetFlags(log.LstdFlags | log.Llongfile)
37 }
38
39 var TestingConfig = Config{
40         ListenAddr:           "localhost:0",
41         NoDHT:                true,
42         DisableTrackers:      true,
43         NoDefaultBlocklist:   true,
44         DisableMetainfoCache: true,
45         DataDir:              "/dev/null",
46         DHTConfig: dht.ServerConfig{
47                 NoDefaultBootstrap: true,
48         },
49 }
50
51 func TestClientDefault(t *testing.T) {
52         cl, err := NewClient(&TestingConfig)
53         if err != nil {
54                 t.Fatal(err)
55         }
56         cl.Close()
57 }
58
59 func TestAddDropTorrent(t *testing.T) {
60         cl, err := NewClient(&TestingConfig)
61         if err != nil {
62                 t.Fatal(err)
63         }
64         defer cl.Close()
65         dir, mi := testutil.GreetingTestTorrent()
66         defer os.RemoveAll(dir)
67         tt, new, err := cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
68         if err != nil {
69                 t.Fatal(err)
70         }
71         if !new {
72                 t.FailNow()
73         }
74         tt.Drop()
75 }
76
77 func TestAddTorrentNoSupportedTrackerSchemes(t *testing.T) {
78         t.SkipNow()
79 }
80
81 func TestAddTorrentNoUsableURLs(t *testing.T) {
82         t.SkipNow()
83 }
84
85 func TestAddPeersToUnknownTorrent(t *testing.T) {
86         t.SkipNow()
87 }
88
89 func TestPieceHashSize(t *testing.T) {
90         if pieceHash.Size() != 20 {
91                 t.FailNow()
92         }
93 }
94
95 func TestTorrentInitialState(t *testing.T) {
96         dir, mi := testutil.GreetingTestTorrent()
97         defer os.RemoveAll(dir)
98         tor := newTorrent(func() (ih InfoHash) {
99                 missinggo.CopyExact(ih[:], mi.Info.Hash)
100                 return
101         }())
102         tor.chunkSize = 2
103         tor.storage = storage.NewFile(dir)
104         // Needed to lock for asynchronous piece verification.
105         tor.cl = new(Client)
106         err := tor.setMetadata(&mi.Info.Info, mi.Info.Bytes)
107         require.NoError(t, err)
108         require.Len(t, tor.Pieces, 3)
109         tor.pendAllChunkSpecs(0)
110         assert.EqualValues(t, 3, tor.pieceNumPendingChunks(0))
111         assert.EqualValues(t, chunkSpec{4, 1}, chunkIndexSpec(2, tor.pieceLength(0), tor.chunkSize))
112 }
113
114 func TestUnmarshalPEXMsg(t *testing.T) {
115         var m peerExchangeMessage
116         if err := bencode.Unmarshal([]byte("d5:added12:\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0ce"), &m); err != nil {
117                 t.Fatal(err)
118         }
119         if len(m.Added) != 2 {
120                 t.FailNow()
121         }
122         if m.Added[0].Port != 0x506 {
123                 t.FailNow()
124         }
125 }
126
127 func TestReducedDialTimeout(t *testing.T) {
128         for _, _case := range []struct {
129                 Max             time.Duration
130                 HalfOpenLimit   int
131                 PendingPeers    int
132                 ExpectedReduced time.Duration
133         }{
134                 {nominalDialTimeout, 40, 0, nominalDialTimeout},
135                 {nominalDialTimeout, 40, 1, nominalDialTimeout},
136                 {nominalDialTimeout, 40, 39, nominalDialTimeout},
137                 {nominalDialTimeout, 40, 40, nominalDialTimeout / 2},
138                 {nominalDialTimeout, 40, 80, nominalDialTimeout / 3},
139                 {nominalDialTimeout, 40, 4000, nominalDialTimeout / 101},
140         } {
141                 reduced := reducedDialTimeout(_case.Max, _case.HalfOpenLimit, _case.PendingPeers)
142                 expected := _case.ExpectedReduced
143                 if expected < minDialTimeout {
144                         expected = minDialTimeout
145                 }
146                 if reduced != expected {
147                         t.Fatalf("expected %s, got %s", _case.ExpectedReduced, reduced)
148                 }
149         }
150 }
151
152 func TestUTPRawConn(t *testing.T) {
153         l, err := utp.NewSocket("udp", "")
154         if err != nil {
155                 t.Fatal(err)
156         }
157         defer l.Close()
158         go func() {
159                 for {
160                         _, err := l.Accept()
161                         if err != nil {
162                                 break
163                         }
164                 }
165         }()
166         // Connect a UTP peer to see if the RawConn will still work.
167         s, _ := utp.NewSocket("udp", "")
168         defer s.Close()
169         utpPeer, err := s.Dial(fmt.Sprintf("localhost:%d", missinggo.AddrPort(l.Addr())))
170         if err != nil {
171                 t.Fatalf("error dialing utp listener: %s", err)
172         }
173         defer utpPeer.Close()
174         peer, err := net.ListenPacket("udp", ":0")
175         if err != nil {
176                 t.Fatal(err)
177         }
178         defer peer.Close()
179
180         msgsReceived := 0
181         // How many messages to send. I've set this to double the channel buffer
182         // size in the raw packetConn.
183         const N = 200
184         readerStopped := make(chan struct{})
185         // The reader goroutine.
186         go func() {
187                 defer close(readerStopped)
188                 b := make([]byte, 500)
189                 for i := 0; i < N; i++ {
190                         n, _, err := l.ReadFrom(b)
191                         if err != nil {
192                                 t.Fatalf("error reading from raw conn: %s", err)
193                         }
194                         msgsReceived++
195                         var d int
196                         fmt.Sscan(string(b[:n]), &d)
197                         if d != i {
198                                 log.Printf("got wrong number: expected %d, got %d", i, d)
199                         }
200                 }
201         }()
202         udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("localhost:%d", missinggo.AddrPort(l.Addr())))
203         if err != nil {
204                 t.Fatal(err)
205         }
206         for i := 0; i < N; i++ {
207                 _, err := peer.WriteTo([]byte(fmt.Sprintf("%d", i)), udpAddr)
208                 if err != nil {
209                         t.Fatal(err)
210                 }
211                 time.Sleep(time.Microsecond)
212         }
213         select {
214         case <-readerStopped:
215         case <-time.After(time.Second):
216                 t.Fatal("reader timed out")
217         }
218         if msgsReceived != N {
219                 t.Fatalf("messages received: %d", msgsReceived)
220         }
221 }
222
223 func TestTwoClientsArbitraryPorts(t *testing.T) {
224         for i := 0; i < 2; i++ {
225                 cl, err := NewClient(&TestingConfig)
226                 if err != nil {
227                         t.Fatal(err)
228                 }
229                 defer cl.Close()
230         }
231 }
232
233 func TestAddDropManyTorrents(t *testing.T) {
234         cl, err := NewClient(&TestingConfig)
235         require.NoError(t, err)
236         defer cl.Close()
237         for i := range iter.N(1000) {
238                 var spec TorrentSpec
239                 binary.PutVarint(spec.InfoHash[:], int64(i))
240                 tt, new, err := cl.AddTorrentSpec(&spec)
241                 assert.NoError(t, err)
242                 assert.True(t, new)
243                 defer tt.Drop()
244         }
245 }
246
247 func TestClientTransferDefault(t *testing.T) {
248         testClientTransfer(t, testClientTransferParams{
249                 ExportClientStatus: true,
250         })
251 }
252
253 func TestClientTransferSmallCache(t *testing.T) {
254         testClientTransfer(t, testClientTransferParams{
255                 SetLeecherStorageCapacity: true,
256                 // Going below the piece length means it can't complete a piece so
257                 // that it can be hashed.
258                 LeecherStorageCapacity: 5,
259                 SetReadahead:           true,
260                 // Can't readahead too far or the cache will thrash and drop data we
261                 // thought we had.
262                 Readahead:          0,
263                 ExportClientStatus: true,
264         })
265 }
266
267 func TestClientTransferVarious(t *testing.T) {
268         for _, responsive := range []bool{false, true} {
269                 testClientTransfer(t, testClientTransferParams{
270                         Responsive: responsive,
271                 })
272                 for _, readahead := range []int64{-1, 0, 1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14, 15, 20} {
273                         testClientTransfer(t, testClientTransferParams{
274                                 Responsive:   responsive,
275                                 SetReadahead: true,
276                                 Readahead:    readahead,
277                         })
278                 }
279         }
280 }
281
282 type testClientTransferParams struct {
283         Responsive                bool
284         Readahead                 int64
285         SetReadahead              bool
286         ExportClientStatus        bool
287         SetLeecherStorageCapacity bool
288         LeecherStorageCapacity    int64
289 }
290
291 func testClientTransfer(t *testing.T, ps testClientTransferParams) {
292         greetingTempDir, mi := testutil.GreetingTestTorrent()
293         defer os.RemoveAll(greetingTempDir)
294         cfg := TestingConfig
295         cfg.Seed = true
296         cfg.DataDir = greetingTempDir
297         seeder, err := NewClient(&cfg)
298         require.NoError(t, err)
299         defer seeder.Close()
300         if ps.ExportClientStatus {
301                 testutil.ExportStatusWriter(seeder, "s")
302         }
303         _, new, err := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
304         require.NoError(t, err)
305         assert.True(t, new)
306         leecherDataDir, err := ioutil.TempDir("", "")
307         require.NoError(t, err)
308         defer os.RemoveAll(leecherDataDir)
309         // cfg.TorrentDataOpener = func() TorrentDataOpener {
310         //      fc, err := filecache.NewCache(leecherDataDir)
311         //      require.NoError(t, err)
312         //      if ps.SetLeecherStorageCapacity {
313         //              fc.SetCapacity(ps.LeecherStorageCapacity)
314         //      }
315         //      store := pieceStore.New(fileCacheDataBackend.New(fc))
316         //      return func(mi *metainfo.Info) storage.I {
317         //              return store.OpenTorrentData(mi)
318         //      }
319         // }()
320         leecher, err := NewClient(&cfg)
321         require.NoError(t, err)
322         defer leecher.Close()
323         if ps.ExportClientStatus {
324                 testutil.ExportStatusWriter(leecher, "l")
325         }
326         leecherGreeting, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
327                 ret = TorrentSpecFromMetaInfo(mi)
328                 ret.ChunkSize = 2
329                 ret.Storage = storage.NewFile(leecherDataDir)
330                 return
331         }())
332         require.NoError(t, err)
333         assert.True(t, new)
334         leecherGreeting.AddPeers([]Peer{
335                 Peer{
336                         IP:   missinggo.AddrIP(seeder.ListenAddr()),
337                         Port: missinggo.AddrPort(seeder.ListenAddr()),
338                 },
339         })
340         r := leecherGreeting.NewReader()
341         defer r.Close()
342         if ps.Responsive {
343                 r.SetResponsive()
344         }
345         if ps.SetReadahead {
346                 r.SetReadahead(ps.Readahead)
347         }
348         for range iter.N(2) {
349                 pos, err := r.Seek(0, os.SEEK_SET)
350                 assert.NoError(t, err)
351                 assert.EqualValues(t, 0, pos)
352                 _greeting, err := ioutil.ReadAll(r)
353                 assert.NoError(t, err)
354                 assert.EqualValues(t, testutil.GreetingFileContents, _greeting)
355         }
356 }
357
358 // Check that after completing leeching, a leecher transitions to a seeding
359 // correctly. Connected in a chain like so: Seeder <-> Leecher <-> LeecherLeecher.
360 func TestSeedAfterDownloading(t *testing.T) {
361         greetingTempDir, mi := testutil.GreetingTestTorrent()
362         defer os.RemoveAll(greetingTempDir)
363         cfg := TestingConfig
364         cfg.Seed = true
365         cfg.DataDir = greetingTempDir
366         seeder, err := NewClient(&cfg)
367         defer seeder.Close()
368         testutil.ExportStatusWriter(seeder, "s")
369         seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
370         cfg.DataDir, err = ioutil.TempDir("", "")
371         require.NoError(t, err)
372         defer os.RemoveAll(cfg.DataDir)
373         leecher, _ := NewClient(&cfg)
374         defer leecher.Close()
375         testutil.ExportStatusWriter(leecher, "l")
376         cfg.Seed = false
377         // cfg.TorrentDataOpener = nil
378         cfg.DataDir, err = ioutil.TempDir("", "")
379         require.NoError(t, err)
380         defer os.RemoveAll(cfg.DataDir)
381         leecherLeecher, _ := NewClient(&cfg)
382         defer leecherLeecher.Close()
383         testutil.ExportStatusWriter(leecherLeecher, "ll")
384         leecherGreeting, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
385                 ret = TorrentSpecFromMetaInfo(mi)
386                 ret.ChunkSize = 2
387                 return
388         }())
389         llg, _, _ := leecherLeecher.AddTorrentSpec(func() (ret *TorrentSpec) {
390                 ret = TorrentSpecFromMetaInfo(mi)
391                 ret.ChunkSize = 3
392                 return
393         }())
394         // Simultaneously DownloadAll in Leecher, and read the contents
395         // consecutively in LeecherLeecher. This non-deterministically triggered a
396         // case where the leecher wouldn't unchoke the LeecherLeecher.
397         var wg sync.WaitGroup
398         wg.Add(1)
399         go func() {
400                 defer wg.Done()
401                 r := llg.NewReader()
402                 defer r.Close()
403                 b, err := ioutil.ReadAll(r)
404                 require.NoError(t, err)
405                 assert.EqualValues(t, testutil.GreetingFileContents, b)
406         }()
407         leecherGreeting.AddPeers([]Peer{
408                 Peer{
409                         IP:   missinggo.AddrIP(seeder.ListenAddr()),
410                         Port: missinggo.AddrPort(seeder.ListenAddr()),
411                 },
412                 Peer{
413                         IP:   missinggo.AddrIP(leecherLeecher.ListenAddr()),
414                         Port: missinggo.AddrPort(leecherLeecher.ListenAddr()),
415                 },
416         })
417         wg.Add(1)
418         go func() {
419                 defer wg.Done()
420                 leecherGreeting.DownloadAll()
421                 leecher.WaitAll()
422         }()
423         wg.Wait()
424 }
425
426 func TestReadaheadPieces(t *testing.T) {
427         for _, case_ := range []struct {
428                 readaheadBytes, pieceLength int64
429                 readaheadPieces             int
430         }{
431                 {5 * 1024 * 1024, 256 * 1024, 19},
432                 {5 * 1024 * 1024, 5 * 1024 * 1024, 1},
433                 {5*1024*1024 - 1, 5 * 1024 * 1024, 1},
434                 {5 * 1024 * 1024, 5*1024*1024 - 1, 2},
435                 {0, 5 * 1024 * 1024, 0},
436                 {5 * 1024 * 1024, 1048576, 4},
437         } {
438                 pieces := readaheadPieces(case_.readaheadBytes, case_.pieceLength)
439                 assert.Equal(t, case_.readaheadPieces, pieces, "%v", case_)
440         }
441 }
442
443 func TestMergingTrackersByAddingSpecs(t *testing.T) {
444         cl, err := NewClient(&TestingConfig)
445         require.NoError(t, err)
446         defer cl.Close()
447         spec := TorrentSpec{}
448         T, new, _ := cl.AddTorrentSpec(&spec)
449         if !new {
450                 t.FailNow()
451         }
452         spec.Trackers = [][]string{{"http://a"}, {"udp://b"}}
453         _, new, _ = cl.AddTorrentSpec(&spec)
454         if new {
455                 t.FailNow()
456         }
457         assert.EqualValues(t, T.torrent.Trackers[0][0], "http://a")
458         assert.EqualValues(t, T.torrent.Trackers[1][0], "udp://b")
459 }
460
461 type badStorage struct{}
462
463 func (me badStorage) Piece(p metainfo.Piece) storage.Piece {
464         return badStoragePiece{p}
465 }
466
467 type badStoragePiece struct {
468         p metainfo.Piece
469 }
470
471 func (me badStoragePiece) WriteAt(b []byte, off int64) (int, error) {
472         return 0, nil
473 }
474
475 func (me badStoragePiece) GetIsComplete() bool {
476         return true
477 }
478
479 func (me badStoragePiece) MarkComplete() error {
480         return errors.New("psyyyyyyyche")
481 }
482
483 func (me badStoragePiece) randomlyTruncatedDataString() string {
484         return "hello, world\n"[:rand.Intn(14)]
485 }
486
487 func (me badStoragePiece) ReadAt(b []byte, off int64) (n int, err error) {
488         r := strings.NewReader(me.randomlyTruncatedDataString())
489         return r.ReadAt(b, off+me.p.Offset())
490 }
491
492 // We read from a piece which is marked completed, but is missing data.
493 func TestCompletedPieceWrongSize(t *testing.T) {
494         cfg := TestingConfig
495         cfg.DefaultStorage = badStorage{}
496         cl, _ := NewClient(&cfg)
497         defer cl.Close()
498         tt, new, err := cl.AddTorrentSpec(&TorrentSpec{
499                 Info: &metainfo.InfoEx{
500                         Info: metainfo.Info{
501                                 PieceLength: 15,
502                                 Pieces:      make([]byte, 20),
503                                 Files: []metainfo.FileInfo{
504                                         metainfo.FileInfo{Path: []string{"greeting"}, Length: 13},
505                                 },
506                         },
507                 },
508         })
509         require.NoError(t, err)
510         defer tt.Drop()
511         assert.True(t, new)
512         r := tt.NewReader()
513         defer r.Close()
514         b, err := ioutil.ReadAll(r)
515         assert.Len(t, b, 13)
516         assert.NoError(t, err)
517 }
518
519 func BenchmarkAddLargeTorrent(b *testing.B) {
520         cfg := TestingConfig
521         cfg.DisableTCP = true
522         cfg.DisableUTP = true
523         cfg.ListenAddr = "redonk"
524         cl, _ := NewClient(&cfg)
525         defer cl.Close()
526         for range iter.N(b.N) {
527                 t, err := cl.AddTorrentFromFile("testdata/bootstrap.dat.torrent")
528                 if err != nil {
529                         b.Fatal(err)
530                 }
531                 t.Drop()
532         }
533 }
534
535 func TestResponsive(t *testing.T) {
536         seederDataDir, mi := testutil.GreetingTestTorrent()
537         defer os.RemoveAll(seederDataDir)
538         cfg := TestingConfig
539         cfg.Seed = true
540         cfg.DataDir = seederDataDir
541         seeder, err := NewClient(&cfg)
542         require.Nil(t, err)
543         defer seeder.Close()
544         seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
545         leecherDataDir, err := ioutil.TempDir("", "")
546         require.Nil(t, err)
547         defer os.RemoveAll(leecherDataDir)
548         cfg = TestingConfig
549         cfg.DataDir = leecherDataDir
550         leecher, err := NewClient(&cfg)
551         require.Nil(t, err)
552         defer leecher.Close()
553         leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
554                 ret = TorrentSpecFromMetaInfo(mi)
555                 ret.ChunkSize = 2
556                 return
557         }())
558         leecherTorrent.AddPeers([]Peer{
559                 Peer{
560                         IP:   missinggo.AddrIP(seeder.ListenAddr()),
561                         Port: missinggo.AddrPort(seeder.ListenAddr()),
562                 },
563         })
564         reader := leecherTorrent.NewReader()
565         defer reader.Close()
566         reader.SetReadahead(0)
567         reader.SetResponsive()
568         b := make([]byte, 2)
569         _, err = reader.Seek(3, os.SEEK_SET)
570         require.NoError(t, err)
571         _, err = io.ReadFull(reader, b)
572         assert.Nil(t, err)
573         assert.EqualValues(t, "lo", string(b))
574         _, err = reader.Seek(11, os.SEEK_SET)
575         require.NoError(t, err)
576         n, err := io.ReadFull(reader, b)
577         assert.Nil(t, err)
578         assert.EqualValues(t, 2, n)
579         assert.EqualValues(t, "d\n", string(b))
580 }
581
582 func TestTorrentDroppedDuringResponsiveRead(t *testing.T) {
583         seederDataDir, mi := testutil.GreetingTestTorrent()
584         defer os.RemoveAll(seederDataDir)
585         cfg := TestingConfig
586         cfg.Seed = true
587         cfg.DataDir = seederDataDir
588         seeder, err := NewClient(&cfg)
589         require.Nil(t, err)
590         defer seeder.Close()
591         seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
592         leecherDataDir, err := ioutil.TempDir("", "")
593         require.Nil(t, err)
594         defer os.RemoveAll(leecherDataDir)
595         cfg = TestingConfig
596         cfg.DataDir = leecherDataDir
597         leecher, err := NewClient(&cfg)
598         require.Nil(t, err)
599         defer leecher.Close()
600         leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
601                 ret = TorrentSpecFromMetaInfo(mi)
602                 ret.ChunkSize = 2
603                 return
604         }())
605         leecherTorrent.AddPeers([]Peer{
606                 Peer{
607                         IP:   missinggo.AddrIP(seeder.ListenAddr()),
608                         Port: missinggo.AddrPort(seeder.ListenAddr()),
609                 },
610         })
611         reader := leecherTorrent.NewReader()
612         defer reader.Close()
613         reader.SetReadahead(0)
614         reader.SetResponsive()
615         b := make([]byte, 2)
616         _, err = reader.Seek(3, os.SEEK_SET)
617         require.NoError(t, err)
618         _, err = io.ReadFull(reader, b)
619         assert.Nil(t, err)
620         assert.EqualValues(t, "lo", string(b))
621         go leecherTorrent.Drop()
622         _, err = reader.Seek(11, os.SEEK_SET)
623         require.NoError(t, err)
624         n, err := reader.Read(b)
625         assert.EqualError(t, err, "torrent closed")
626         assert.EqualValues(t, 0, n)
627 }
628
629 func TestDHTInheritBlocklist(t *testing.T) {
630         ipl := iplist.New(nil)
631         require.NotNil(t, ipl)
632         cfg := TestingConfig
633         cfg.IPBlocklist = ipl
634         cfg.NoDHT = false
635         cl, err := NewClient(&cfg)
636         require.NoError(t, err)
637         defer cl.Close()
638         require.Equal(t, ipl, cl.DHT().IPBlocklist())
639 }
640
641 // Check that stuff is merged in subsequent AddTorrentSpec for the same
642 // infohash.
643 func TestAddTorrentSpecMerging(t *testing.T) {
644         cl, err := NewClient(&TestingConfig)
645         require.NoError(t, err)
646         defer cl.Close()
647         dir, mi := testutil.GreetingTestTorrent()
648         defer os.RemoveAll(dir)
649         var ts TorrentSpec
650         missinggo.CopyExact(&ts.InfoHash, mi.Info.Hash)
651         tt, new, err := cl.AddTorrentSpec(&ts)
652         require.NoError(t, err)
653         require.True(t, new)
654         require.Nil(t, tt.Info())
655         _, new, err = cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
656         require.NoError(t, err)
657         require.False(t, new)
658         require.NotNil(t, tt.Info())
659 }
660
661 // Check that torrent Info is obtained from the metainfo file cache.
662 func TestAddTorrentMetainfoInCache(t *testing.T) {
663         cfg := TestingConfig
664         cfg.DisableMetainfoCache = false
665         cfg.ConfigDir, _ = ioutil.TempDir(os.TempDir(), "")
666         defer os.RemoveAll(cfg.ConfigDir)
667         cl, err := NewClient(&cfg)
668         require.NoError(t, err)
669         defer cl.Close()
670         dir, mi := testutil.GreetingTestTorrent()
671         defer os.RemoveAll(dir)
672         tt, new, err := cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
673         require.NoError(t, err)
674         require.True(t, new)
675         require.NotNil(t, tt.Info())
676         _, err = os.Stat(filepath.Join(cfg.ConfigDir, "torrents", fmt.Sprintf("%x.torrent", mi.Info.Hash)))
677         require.NoError(t, err)
678         // Contains only the infohash.
679         var ts TorrentSpec
680         missinggo.CopyExact(&ts.InfoHash, mi.Info.Hash)
681         _, ok := cl.Torrent(ts.InfoHash)
682         require.True(t, ok)
683         tt.Drop()
684         _, ok = cl.Torrent(ts.InfoHash)
685         require.False(t, ok)
686         tt, new, err = cl.AddTorrentSpec(&ts)
687         require.NoError(t, err)
688         require.True(t, new)
689         // Obtained from the metainfo cache.
690         require.NotNil(t, tt.Info())
691 }
692
693 func TestTorrentDroppedBeforeGotInfo(t *testing.T) {
694         dir, mi := testutil.GreetingTestTorrent()
695         os.RemoveAll(dir)
696         cl, _ := NewClient(&TestingConfig)
697         defer cl.Close()
698         var ts TorrentSpec
699         CopyExact(&ts.InfoHash, mi.Info.Hash)
700         tt, _, _ := cl.AddTorrentSpec(&ts)
701         tt.Drop()
702         assert.EqualValues(t, 0, len(cl.Torrents()))
703         select {
704         case <-tt.GotInfo():
705                 t.FailNow()
706         default:
707         }
708 }
709
710 // func testAddTorrentPriorPieceCompletion(t *testing.T, alreadyCompleted bool) {
711 //      fileCacheDir, err := ioutil.TempDir("", "")
712 //      require.NoError(t, err)
713 //      defer os.RemoveAll(fileCacheDir)
714 //      fileCache, err := filecache.NewCache(fileCacheDir)
715 //      require.NoError(t, err)
716 //      greetingDataTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
717 //      defer os.RemoveAll(greetingDataTempDir)
718 //      filePieceStore := pieceStore.New(fileCacheDataBackend.New(fileCache))
719 //      greetingData := filePieceStore.OpenTorrentData(&greetingMetainfo.Info.Info)
720 //      written, err := greetingData.WriteAt([]byte(testutil.GreetingFileContents), 0)
721 //      require.Equal(t, len(testutil.GreetingFileContents), written)
722 //      require.NoError(t, err)
723 //      for i := 0; i < greetingMetainfo.Info.NumPieces(); i++ {
724 //              // p := greetingMetainfo.Info.Piece(i)
725 //              if alreadyCompleted {
726 //                      err := greetingData.PieceCompleted(i)
727 //                      assert.NoError(t, err)
728 //              }
729 //      }
730 //      cfg := TestingConfig
731 //      // TODO: Disable network option?
732 //      cfg.DisableTCP = true
733 //      cfg.DisableUTP = true
734 //      // cfg.DefaultStorage = filePieceStore
735 //      cl, err := NewClient(&cfg)
736 //      require.NoError(t, err)
737 //      defer cl.Close()
738 //      tt, err := cl.AddTorrent(greetingMetainfo)
739 //      require.NoError(t, err)
740 //      psrs := tt.PieceStateRuns()
741 //      assert.Len(t, psrs, 1)
742 //      assert.EqualValues(t, 3, psrs[0].Length)
743 //      assert.Equal(t, alreadyCompleted, psrs[0].Complete)
744 //      if alreadyCompleted {
745 //              r := tt.NewReader()
746 //              b, err := ioutil.ReadAll(r)
747 //              assert.NoError(t, err)
748 //              assert.EqualValues(t, testutil.GreetingFileContents, b)
749 //      }
750 // }
751
752 // func TestAddTorrentPiecesAlreadyCompleted(t *testing.T) {
753 //      testAddTorrentPriorPieceCompletion(t, true)
754 // }
755
756 // func TestAddTorrentPiecesNotAlreadyCompleted(t *testing.T) {
757 //      testAddTorrentPriorPieceCompletion(t, false)
758 // }
759
760 func TestAddMetainfoWithNodes(t *testing.T) {
761         cfg := TestingConfig
762         cfg.NoDHT = false
763         // For now, we want to just jam the nodes into the table, without
764         // verifying them first. Also the DHT code doesn't support mixing secure
765         // and insecure nodes if security is enabled (yet).
766         cfg.DHTConfig.NoSecurity = true
767         cl, err := NewClient(&cfg)
768         require.NoError(t, err)
769         defer cl.Close()
770         assert.EqualValues(t, cl.DHT().NumNodes(), 0)
771         tt, err := cl.AddTorrentFromFile("metainfo/testdata/issue_65a.torrent")
772         require.NoError(t, err)
773         assert.Len(t, tt.torrent.Trackers, 5)
774         assert.EqualValues(t, 6, cl.DHT().NumNodes())
775 }
776
777 type testDownloadCancelParams struct {
778         Responsive                bool
779         Readahead                 int64
780         SetReadahead              bool
781         ExportClientStatus        bool
782         SetLeecherStorageCapacity bool
783         LeecherStorageCapacity    int64
784         Cancel                    bool
785 }
786
787 func testDownloadCancel(t *testing.T, ps testDownloadCancelParams) {
788         greetingTempDir, mi := testutil.GreetingTestTorrent()
789         defer os.RemoveAll(greetingTempDir)
790         cfg := TestingConfig
791         cfg.Seed = true
792         cfg.DataDir = greetingTempDir
793         seeder, err := NewClient(&cfg)
794         require.NoError(t, err)
795         defer seeder.Close()
796         if ps.ExportClientStatus {
797                 testutil.ExportStatusWriter(seeder, "s")
798         }
799         seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
800         leecherDataDir, err := ioutil.TempDir("", "")
801         require.NoError(t, err)
802         defer os.RemoveAll(leecherDataDir)
803         // cfg.TorrentDataOpener = func() TorrentDataOpener {
804         //      fc, err := filecache.NewCache(leecherDataDir)
805         //      require.NoError(t, err)
806         //      if ps.SetLeecherStorageCapacity {
807         //              fc.SetCapacity(ps.LeecherStorageCapacity)
808         //      }
809         //      store := pieceStore.New(fileCacheDataBackend.New(fc))
810         //      return func(mi *metainfo.Info) storage.I {
811         //              return store.OpenTorrentData(mi)
812         //      }
813         // }()
814         cfg.DataDir = leecherDataDir
815         leecher, _ := NewClient(&cfg)
816         defer leecher.Close()
817         if ps.ExportClientStatus {
818                 testutil.ExportStatusWriter(leecher, "l")
819         }
820         leecherGreeting, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
821                 ret = TorrentSpecFromMetaInfo(mi)
822                 ret.ChunkSize = 2
823                 return
824         }())
825         require.NoError(t, err)
826         assert.True(t, new)
827         psc := leecherGreeting.SubscribePieceStateChanges()
828         defer psc.Close()
829         leecherGreeting.DownloadAll()
830         if ps.Cancel {
831                 leecherGreeting.CancelPieces(0, leecherGreeting.NumPieces())
832         }
833         leecherGreeting.AddPeers([]Peer{
834                 Peer{
835                         IP:   missinggo.AddrIP(seeder.ListenAddr()),
836                         Port: missinggo.AddrPort(seeder.ListenAddr()),
837                 },
838         })
839         completes := make(map[int]bool, 3)
840 values:
841         for {
842                 // started := time.Now()
843                 select {
844                 case _v := <-psc.Values:
845                         // log.Print(time.Since(started))
846                         v := _v.(PieceStateChange)
847                         completes[v.Index] = v.Complete
848                 case <-time.After(100 * time.Millisecond):
849                         break values
850                 }
851         }
852         if ps.Cancel {
853                 assert.EqualValues(t, map[int]bool{0: false, 1: false, 2: false}, completes)
854         } else {
855                 assert.EqualValues(t, map[int]bool{0: true, 1: true, 2: true}, completes)
856         }
857
858 }
859
860 func TestTorrentDownloadAll(t *testing.T) {
861         testDownloadCancel(t, testDownloadCancelParams{})
862 }
863
864 func TestTorrentDownloadAllThenCancel(t *testing.T) {
865         testDownloadCancel(t, testDownloadCancelParams{
866                 Cancel: true,
867         })
868 }
869
870 // Ensure that it's an error for a peer to send an invalid have message.
871 func TestPeerInvalidHave(t *testing.T) {
872         cl, err := NewClient(&TestingConfig)
873         require.NoError(t, err)
874         defer cl.Close()
875         tt, _new, err := cl.AddTorrentSpec(&TorrentSpec{
876                 Info: &metainfo.InfoEx{
877                         Info: metainfo.Info{
878                                 PieceLength: 1,
879                                 Pieces:      make([]byte, 20),
880                                 Files:       []metainfo.FileInfo{{Length: 1}},
881                         },
882                 },
883         })
884         require.NoError(t, err)
885         assert.True(t, _new)
886         defer tt.Drop()
887         cn := &connection{
888                 t: tt.torrent,
889         }
890         assert.NoError(t, cn.peerSentHave(0))
891         assert.Error(t, cn.peerSentHave(1))
892 }
893
894 func TestPieceCompletedInStorageButNotClient(t *testing.T) {
895         greetingTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
896         defer os.RemoveAll(greetingTempDir)
897         cfg := TestingConfig
898         cfg.DataDir = greetingTempDir
899         seeder, err := NewClient(&TestingConfig)
900         require.NoError(t, err)
901         seeder.AddTorrentSpec(&TorrentSpec{
902                 Info: &greetingMetainfo.Info,
903         })
904 }