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