]> Sergey Matveev's repositories - btrtrc.git/blob - client_test.go
Fix missinggo import
[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(storage.FileCacheFileStore{fc})
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         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, _ := NewClient(&cfg)
373         defer leecher.Close()
374         testutil.ExportStatusWriter(leecher, "l")
375         cfg.Seed = false
376         // cfg.TorrentDataOpener = nil
377         cfg.DataDir, err = ioutil.TempDir("", "")
378         require.NoError(t, err)
379         defer os.RemoveAll(cfg.DataDir)
380         leecherLeecher, _ := NewClient(&cfg)
381         defer leecherLeecher.Close()
382         testutil.ExportStatusWriter(leecherLeecher, "ll")
383         leecherGreeting, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
384                 ret = TorrentSpecFromMetaInfo(mi)
385                 ret.ChunkSize = 2
386                 return
387         }())
388         llg, _, _ := leecherLeecher.AddTorrentSpec(func() (ret *TorrentSpec) {
389                 ret = TorrentSpecFromMetaInfo(mi)
390                 ret.ChunkSize = 3
391                 return
392         }())
393         // Simultaneously DownloadAll in Leecher, and read the contents
394         // consecutively in LeecherLeecher. This non-deterministically triggered a
395         // case where the leecher wouldn't unchoke the LeecherLeecher.
396         var wg sync.WaitGroup
397         wg.Add(1)
398         go func() {
399                 defer wg.Done()
400                 r := llg.NewReader()
401                 defer r.Close()
402                 b, err := ioutil.ReadAll(r)
403                 require.NoError(t, err)
404                 assert.EqualValues(t, testutil.GreetingFileContents, b)
405         }()
406         leecherGreeting.AddPeers([]Peer{
407                 Peer{
408                         IP:   missinggo.AddrIP(seeder.ListenAddr()),
409                         Port: missinggo.AddrPort(seeder.ListenAddr()),
410                 },
411                 Peer{
412                         IP:   missinggo.AddrIP(leecherLeecher.ListenAddr()),
413                         Port: missinggo.AddrPort(leecherLeecher.ListenAddr()),
414                 },
415         })
416         wg.Add(1)
417         go func() {
418                 defer wg.Done()
419                 leecherGreeting.DownloadAll()
420                 leecher.WaitAll()
421         }()
422         wg.Wait()
423 }
424
425 func TestReadaheadPieces(t *testing.T) {
426         for _, case_ := range []struct {
427                 readaheadBytes, pieceLength int64
428                 readaheadPieces             int
429         }{
430                 {5 * 1024 * 1024, 256 * 1024, 19},
431                 {5 * 1024 * 1024, 5 * 1024 * 1024, 1},
432                 {5*1024*1024 - 1, 5 * 1024 * 1024, 1},
433                 {5 * 1024 * 1024, 5*1024*1024 - 1, 2},
434                 {0, 5 * 1024 * 1024, 0},
435                 {5 * 1024 * 1024, 1048576, 4},
436         } {
437                 pieces := readaheadPieces(case_.readaheadBytes, case_.pieceLength)
438                 assert.Equal(t, case_.readaheadPieces, pieces, "%v", case_)
439         }
440 }
441
442 func TestMergingTrackersByAddingSpecs(t *testing.T) {
443         cl, err := NewClient(&TestingConfig)
444         require.NoError(t, err)
445         defer cl.Close()
446         spec := TorrentSpec{}
447         T, new, _ := cl.AddTorrentSpec(&spec)
448         if !new {
449                 t.FailNow()
450         }
451         spec.Trackers = [][]string{{"http://a"}, {"udp://b"}}
452         _, new, _ = cl.AddTorrentSpec(&spec)
453         if new {
454                 t.FailNow()
455         }
456         assert.EqualValues(t, T.torrent.Trackers[0][0], "http://a")
457         assert.EqualValues(t, T.torrent.Trackers[1][0], "udp://b")
458 }
459
460 type badStorage struct{}
461
462 func (me badStorage) OpenTorrent(*metainfo.InfoEx) (storage.Torrent, error) {
463         return me, nil
464 }
465
466 func (me badStorage) Close() error {
467         return nil
468 }
469
470 func (me badStorage) Piece(p metainfo.Piece) storage.Piece {
471         return badStoragePiece{p}
472 }
473
474 type badStoragePiece struct {
475         p metainfo.Piece
476 }
477
478 func (me badStoragePiece) WriteAt(b []byte, off int64) (int, error) {
479         return 0, nil
480 }
481
482 func (me badStoragePiece) GetIsComplete() bool {
483         return true
484 }
485
486 func (me badStoragePiece) MarkComplete() error {
487         return errors.New("psyyyyyyyche")
488 }
489
490 func (me badStoragePiece) randomlyTruncatedDataString() string {
491         return "hello, world\n"[:rand.Intn(14)]
492 }
493
494 func (me badStoragePiece) ReadAt(b []byte, off int64) (n int, err error) {
495         r := strings.NewReader(me.randomlyTruncatedDataString())
496         return r.ReadAt(b, off+me.p.Offset())
497 }
498
499 // We read from a piece which is marked completed, but is missing data.
500 func TestCompletedPieceWrongSize(t *testing.T) {
501         cfg := TestingConfig
502         cfg.DefaultStorage = badStorage{}
503         cl, _ := NewClient(&cfg)
504         defer cl.Close()
505         tt, new, err := cl.AddTorrentSpec(&TorrentSpec{
506                 Info: &metainfo.InfoEx{
507                         Info: metainfo.Info{
508                                 PieceLength: 15,
509                                 Pieces:      make([]byte, 20),
510                                 Files: []metainfo.FileInfo{
511                                         metainfo.FileInfo{Path: []string{"greeting"}, Length: 13},
512                                 },
513                         },
514                 },
515         })
516         require.NoError(t, err)
517         defer tt.Drop()
518         assert.True(t, new)
519         r := tt.NewReader()
520         defer r.Close()
521         b, err := ioutil.ReadAll(r)
522         assert.Len(t, b, 13)
523         assert.NoError(t, err)
524 }
525
526 func BenchmarkAddLargeTorrent(b *testing.B) {
527         cfg := TestingConfig
528         cfg.DisableTCP = true
529         cfg.DisableUTP = true
530         cfg.ListenAddr = "redonk"
531         cl, _ := NewClient(&cfg)
532         defer cl.Close()
533         for range iter.N(b.N) {
534                 t, err := cl.AddTorrentFromFile("testdata/bootstrap.dat.torrent")
535                 if err != nil {
536                         b.Fatal(err)
537                 }
538                 t.Drop()
539         }
540 }
541
542 func TestResponsive(t *testing.T) {
543         seederDataDir, mi := testutil.GreetingTestTorrent()
544         defer os.RemoveAll(seederDataDir)
545         cfg := TestingConfig
546         cfg.Seed = true
547         cfg.DataDir = seederDataDir
548         seeder, err := NewClient(&cfg)
549         require.Nil(t, err)
550         defer seeder.Close()
551         seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
552         leecherDataDir, err := ioutil.TempDir("", "")
553         require.Nil(t, err)
554         defer os.RemoveAll(leecherDataDir)
555         cfg = TestingConfig
556         cfg.DataDir = leecherDataDir
557         leecher, err := NewClient(&cfg)
558         require.Nil(t, err)
559         defer leecher.Close()
560         leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
561                 ret = TorrentSpecFromMetaInfo(mi)
562                 ret.ChunkSize = 2
563                 return
564         }())
565         leecherTorrent.AddPeers([]Peer{
566                 Peer{
567                         IP:   missinggo.AddrIP(seeder.ListenAddr()),
568                         Port: missinggo.AddrPort(seeder.ListenAddr()),
569                 },
570         })
571         reader := leecherTorrent.NewReader()
572         defer reader.Close()
573         reader.SetReadahead(0)
574         reader.SetResponsive()
575         b := make([]byte, 2)
576         _, err = reader.Seek(3, os.SEEK_SET)
577         require.NoError(t, err)
578         _, err = io.ReadFull(reader, b)
579         assert.Nil(t, err)
580         assert.EqualValues(t, "lo", string(b))
581         _, err = reader.Seek(11, os.SEEK_SET)
582         require.NoError(t, err)
583         n, err := io.ReadFull(reader, b)
584         assert.Nil(t, err)
585         assert.EqualValues(t, 2, n)
586         assert.EqualValues(t, "d\n", string(b))
587 }
588
589 func TestTorrentDroppedDuringResponsiveRead(t *testing.T) {
590         seederDataDir, mi := testutil.GreetingTestTorrent()
591         defer os.RemoveAll(seederDataDir)
592         cfg := TestingConfig
593         cfg.Seed = true
594         cfg.DataDir = seederDataDir
595         seeder, err := NewClient(&cfg)
596         require.Nil(t, err)
597         defer seeder.Close()
598         seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
599         leecherDataDir, err := ioutil.TempDir("", "")
600         require.Nil(t, err)
601         defer os.RemoveAll(leecherDataDir)
602         cfg = TestingConfig
603         cfg.DataDir = leecherDataDir
604         leecher, err := NewClient(&cfg)
605         require.Nil(t, err)
606         defer leecher.Close()
607         leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
608                 ret = TorrentSpecFromMetaInfo(mi)
609                 ret.ChunkSize = 2
610                 return
611         }())
612         leecherTorrent.AddPeers([]Peer{
613                 Peer{
614                         IP:   missinggo.AddrIP(seeder.ListenAddr()),
615                         Port: missinggo.AddrPort(seeder.ListenAddr()),
616                 },
617         })
618         reader := leecherTorrent.NewReader()
619         defer reader.Close()
620         reader.SetReadahead(0)
621         reader.SetResponsive()
622         b := make([]byte, 2)
623         _, err = reader.Seek(3, os.SEEK_SET)
624         require.NoError(t, err)
625         _, err = io.ReadFull(reader, b)
626         assert.Nil(t, err)
627         assert.EqualValues(t, "lo", string(b))
628         go leecherTorrent.Drop()
629         _, err = reader.Seek(11, os.SEEK_SET)
630         require.NoError(t, err)
631         n, err := reader.Read(b)
632         assert.EqualError(t, err, "torrent closed")
633         assert.EqualValues(t, 0, n)
634 }
635
636 func TestDHTInheritBlocklist(t *testing.T) {
637         ipl := iplist.New(nil)
638         require.NotNil(t, ipl)
639         cfg := TestingConfig
640         cfg.IPBlocklist = ipl
641         cfg.NoDHT = false
642         cl, err := NewClient(&cfg)
643         require.NoError(t, err)
644         defer cl.Close()
645         require.Equal(t, ipl, cl.DHT().IPBlocklist())
646 }
647
648 // Check that stuff is merged in subsequent AddTorrentSpec for the same
649 // infohash.
650 func TestAddTorrentSpecMerging(t *testing.T) {
651         cl, err := NewClient(&TestingConfig)
652         require.NoError(t, err)
653         defer cl.Close()
654         dir, mi := testutil.GreetingTestTorrent()
655         defer os.RemoveAll(dir)
656         var ts TorrentSpec
657         missinggo.CopyExact(&ts.InfoHash, mi.Info.Hash)
658         tt, new, err := cl.AddTorrentSpec(&ts)
659         require.NoError(t, err)
660         require.True(t, new)
661         require.Nil(t, tt.Info())
662         _, new, err = cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
663         require.NoError(t, err)
664         require.False(t, new)
665         require.NotNil(t, tt.Info())
666 }
667
668 // Check that torrent Info is obtained from the metainfo file cache.
669 func TestAddTorrentMetainfoInCache(t *testing.T) {
670         cfg := TestingConfig
671         cfg.DisableMetainfoCache = false
672         cfg.ConfigDir, _ = ioutil.TempDir(os.TempDir(), "")
673         defer os.RemoveAll(cfg.ConfigDir)
674         cl, err := NewClient(&cfg)
675         require.NoError(t, err)
676         defer cl.Close()
677         dir, mi := testutil.GreetingTestTorrent()
678         defer os.RemoveAll(dir)
679         tt, new, err := cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
680         require.NoError(t, err)
681         require.True(t, new)
682         require.NotNil(t, tt.Info())
683         _, err = os.Stat(filepath.Join(cfg.ConfigDir, "torrents", fmt.Sprintf("%x.torrent", mi.Info.Hash.Bytes())))
684         require.NoError(t, err)
685         // Contains only the infohash.
686         var ts TorrentSpec
687         missinggo.CopyExact(&ts.InfoHash, mi.Info.Hash)
688         _, ok := cl.Torrent(ts.InfoHash)
689         require.True(t, ok)
690         tt.Drop()
691         _, ok = cl.Torrent(ts.InfoHash)
692         require.False(t, ok)
693         tt, new, err = cl.AddTorrentSpec(&ts)
694         require.NoError(t, err)
695         require.True(t, new)
696         // Obtained from the metainfo cache.
697         require.NotNil(t, tt.Info())
698 }
699
700 func TestTorrentDroppedBeforeGotInfo(t *testing.T) {
701         dir, mi := testutil.GreetingTestTorrent()
702         os.RemoveAll(dir)
703         cl, _ := NewClient(&TestingConfig)
704         defer cl.Close()
705         var ts TorrentSpec
706         missinggo.CopyExact(&ts.InfoHash, mi.Info.Hash)
707         tt, _, _ := cl.AddTorrentSpec(&ts)
708         tt.Drop()
709         assert.EqualValues(t, 0, len(cl.Torrents()))
710         select {
711         case <-tt.GotInfo():
712                 t.FailNow()
713         default:
714         }
715 }
716
717 func writeTorrentData(ts storage.Torrent, info *metainfo.InfoEx, b []byte) {
718         for i := range iter.N(info.NumPieces()) {
719                 n, err := ts.Piece(info.Piece(i)).WriteAt(b, 0)
720                 b = b[n:]
721                 log.Print(err)
722         }
723 }
724
725 func testAddTorrentPriorPieceCompletion(t *testing.T, alreadyCompleted bool) {
726         fileCacheDir, err := ioutil.TempDir("", "")
727         require.NoError(t, err)
728         defer os.RemoveAll(fileCacheDir)
729         fileCache, err := filecache.NewCache(fileCacheDir)
730         require.NoError(t, err)
731         greetingDataTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
732         defer os.RemoveAll(greetingDataTempDir)
733         filePieceStore := storage.NewPieceFileStorage(storage.FileCacheFileStore{fileCache})
734         greetingData, err := filePieceStore.OpenTorrent(&greetingMetainfo.Info)
735         require.NoError(t, err)
736         writeTorrentData(greetingData, &greetingMetainfo.Info, []byte(testutil.GreetingFileContents))
737         // require.Equal(t, len(testutil.GreetingFileContents), written)
738         // require.NoError(t, err)
739         for i := 0; i < greetingMetainfo.Info.NumPieces(); i++ {
740                 p := greetingMetainfo.Info.Piece(i)
741                 if alreadyCompleted {
742                         err := greetingData.Piece(p).MarkComplete()
743                         assert.NoError(t, err)
744                 }
745         }
746         cfg := TestingConfig
747         // TODO: Disable network option?
748         cfg.DisableTCP = true
749         cfg.DisableUTP = true
750         cfg.DefaultStorage = filePieceStore
751         cl, err := NewClient(&cfg)
752         require.NoError(t, err)
753         defer cl.Close()
754         tt, err := cl.AddTorrent(greetingMetainfo)
755         require.NoError(t, err)
756         psrs := tt.PieceStateRuns()
757         assert.Len(t, psrs, 1)
758         assert.EqualValues(t, 3, psrs[0].Length)
759         assert.Equal(t, alreadyCompleted, psrs[0].Complete)
760         if alreadyCompleted {
761                 r := tt.NewReader()
762                 b, err := ioutil.ReadAll(r)
763                 assert.NoError(t, err)
764                 assert.EqualValues(t, testutil.GreetingFileContents, b)
765         }
766 }
767
768 func TestAddTorrentPiecesAlreadyCompleted(t *testing.T) {
769         testAddTorrentPriorPieceCompletion(t, true)
770 }
771
772 func TestAddTorrentPiecesNotAlreadyCompleted(t *testing.T) {
773         testAddTorrentPriorPieceCompletion(t, false)
774 }
775
776 func TestAddMetainfoWithNodes(t *testing.T) {
777         cfg := TestingConfig
778         cfg.NoDHT = false
779         // For now, we want to just jam the nodes into the table, without
780         // verifying them first. Also the DHT code doesn't support mixing secure
781         // and insecure nodes if security is enabled (yet).
782         cfg.DHTConfig.NoSecurity = true
783         cl, err := NewClient(&cfg)
784         require.NoError(t, err)
785         defer cl.Close()
786         assert.EqualValues(t, cl.DHT().NumNodes(), 0)
787         tt, err := cl.AddTorrentFromFile("metainfo/testdata/issue_65a.torrent")
788         require.NoError(t, err)
789         assert.Len(t, tt.torrent.Trackers, 5)
790         assert.EqualValues(t, 6, cl.DHT().NumNodes())
791 }
792
793 type testDownloadCancelParams struct {
794         Responsive                bool
795         Readahead                 int64
796         SetReadahead              bool
797         ExportClientStatus        bool
798         SetLeecherStorageCapacity bool
799         LeecherStorageCapacity    int64
800         Cancel                    bool
801 }
802
803 func testDownloadCancel(t *testing.T, ps testDownloadCancelParams) {
804         greetingTempDir, mi := testutil.GreetingTestTorrent()
805         defer os.RemoveAll(greetingTempDir)
806         cfg := TestingConfig
807         cfg.Seed = true
808         cfg.DataDir = greetingTempDir
809         seeder, err := NewClient(&cfg)
810         require.NoError(t, err)
811         defer seeder.Close()
812         if ps.ExportClientStatus {
813                 testutil.ExportStatusWriter(seeder, "s")
814         }
815         seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
816         leecherDataDir, err := ioutil.TempDir("", "")
817         require.NoError(t, err)
818         defer os.RemoveAll(leecherDataDir)
819         fc, err := filecache.NewCache(leecherDataDir)
820         require.NoError(t, err)
821         if ps.SetLeecherStorageCapacity {
822                 fc.SetCapacity(ps.LeecherStorageCapacity)
823         }
824         cfg.DefaultStorage = storage.NewPieceFileStorage(storage.FileCacheFileStore{fc})
825         cfg.DataDir = leecherDataDir
826         leecher, _ := NewClient(&cfg)
827         defer leecher.Close()
828         if ps.ExportClientStatus {
829                 testutil.ExportStatusWriter(leecher, "l")
830         }
831         leecherGreeting, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
832                 ret = TorrentSpecFromMetaInfo(mi)
833                 ret.ChunkSize = 2
834                 return
835         }())
836         require.NoError(t, err)
837         assert.True(t, new)
838         psc := leecherGreeting.SubscribePieceStateChanges()
839         defer psc.Close()
840         leecherGreeting.DownloadAll()
841         if ps.Cancel {
842                 leecherGreeting.CancelPieces(0, leecherGreeting.NumPieces())
843         }
844         leecherGreeting.AddPeers([]Peer{
845                 Peer{
846                         IP:   missinggo.AddrIP(seeder.ListenAddr()),
847                         Port: missinggo.AddrPort(seeder.ListenAddr()),
848                 },
849         })
850         completes := make(map[int]bool, 3)
851 values:
852         for {
853                 // started := time.Now()
854                 select {
855                 case _v := <-psc.Values:
856                         // log.Print(time.Since(started))
857                         v := _v.(PieceStateChange)
858                         completes[v.Index] = v.Complete
859                 case <-time.After(100 * time.Millisecond):
860                         break values
861                 }
862         }
863         if ps.Cancel {
864                 assert.EqualValues(t, map[int]bool{0: false, 1: false, 2: false}, completes)
865         } else {
866                 assert.EqualValues(t, map[int]bool{0: true, 1: true, 2: true}, completes)
867         }
868
869 }
870
871 func TestTorrentDownloadAll(t *testing.T) {
872         testDownloadCancel(t, testDownloadCancelParams{})
873 }
874
875 func TestTorrentDownloadAllThenCancel(t *testing.T) {
876         testDownloadCancel(t, testDownloadCancelParams{
877                 Cancel: true,
878         })
879 }
880
881 // Ensure that it's an error for a peer to send an invalid have message.
882 func TestPeerInvalidHave(t *testing.T) {
883         cl, err := NewClient(&TestingConfig)
884         require.NoError(t, err)
885         defer cl.Close()
886         tt, _new, err := cl.AddTorrentSpec(&TorrentSpec{
887                 Info: &metainfo.InfoEx{
888                         Info: metainfo.Info{
889                                 PieceLength: 1,
890                                 Pieces:      make([]byte, 20),
891                                 Files:       []metainfo.FileInfo{{Length: 1}},
892                         },
893                 },
894         })
895         require.NoError(t, err)
896         assert.True(t, _new)
897         defer tt.Drop()
898         cn := &connection{
899                 t: tt.torrent,
900         }
901         assert.NoError(t, cn.peerSentHave(0))
902         assert.Error(t, cn.peerSentHave(1))
903 }
904
905 func TestPieceCompletedInStorageButNotClient(t *testing.T) {
906         greetingTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
907         defer os.RemoveAll(greetingTempDir)
908         cfg := TestingConfig
909         cfg.DataDir = greetingTempDir
910         seeder, err := NewClient(&TestingConfig)
911         require.NoError(t, err)
912         seeder.AddTorrentSpec(&TorrentSpec{
913                 Info: &greetingMetainfo.Info,
914         })
915 }