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