]> Sergey Matveev's repositories - btrtrc.git/blob - client_test.go
Make opening a torrent in storage an explicit method
[btrtrc.git] / client_test.go
1 package torrent
2
3 import (
4         "encoding/binary"
5         "errors"
6         "fmt"
7         "io"
8         "io/ioutil"
9         "log"
10         "math/rand"
11         "net"
12         "os"
13         "path/filepath"
14         "strings"
15         "sync"
16         "testing"
17         "time"
18
19         _ "github.com/anacrolix/envpprof"
20         "github.com/anacrolix/missinggo"
21         . "github.com/anacrolix/missinggo"
22         "github.com/anacrolix/utp"
23         "github.com/bradfitz/iter"
24         "github.com/stretchr/testify/assert"
25         "github.com/stretchr/testify/require"
26
27         "github.com/anacrolix/torrent/bencode"
28         "github.com/anacrolix/torrent/dht"
29         "github.com/anacrolix/torrent/internal/testutil"
30         "github.com/anacrolix/torrent/iplist"
31         "github.com/anacrolix/torrent/metainfo"
32         "github.com/anacrolix/torrent/storage"
33 )
34
35 func init() {
36         log.SetFlags(log.LstdFlags | log.Llongfile)
37 }
38
39 var TestingConfig = Config{
40         ListenAddr:           "localhost:0",
41         NoDHT:                true,
42         DisableTrackers:      true,
43         NoDefaultBlocklist:   true,
44         DisableMetainfoCache: true,
45         DataDir:              "/dev/null",
46         DHTConfig: dht.ServerConfig{
47                 NoDefaultBootstrap: true,
48         },
49 }
50
51 func TestClientDefault(t *testing.T) {
52         cl, err := NewClient(&TestingConfig)
53         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         // cfg.TorrentDataOpener = func() TorrentDataOpener {
314         //      fc, err := filecache.NewCache(leecherDataDir)
315         //      require.NoError(t, err)
316         //      if ps.SetLeecherStorageCapacity {
317         //              fc.SetCapacity(ps.LeecherStorageCapacity)
318         //      }
319         //      store := pieceStore.New(fileCacheDataBackend.New(fc))
320         //      return func(mi *metainfo.Info) storage.I {
321         //              return store.OpenTorrentData(mi)
322         //      }
323         // }()
324         leecher, err := NewClient(&cfg)
325         require.NoError(t, err)
326         defer leecher.Close()
327         if ps.ExportClientStatus {
328                 testutil.ExportStatusWriter(leecher, "l")
329         }
330         leecherGreeting, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
331                 ret = TorrentSpecFromMetaInfo(mi)
332                 ret.ChunkSize = 2
333                 ret.Storage = storage.NewFile(leecherDataDir)
334                 return
335         }())
336         require.NoError(t, err)
337         assert.True(t, new)
338         leecherGreeting.AddPeers([]Peer{
339                 Peer{
340                         IP:   missinggo.AddrIP(seeder.ListenAddr()),
341                         Port: missinggo.AddrPort(seeder.ListenAddr()),
342                 },
343         })
344         r := leecherGreeting.NewReader()
345         defer r.Close()
346         if ps.Responsive {
347                 r.SetResponsive()
348         }
349         if ps.SetReadahead {
350                 r.SetReadahead(ps.Readahead)
351         }
352         for range iter.N(2) {
353                 pos, err := r.Seek(0, os.SEEK_SET)
354                 assert.NoError(t, err)
355                 assert.EqualValues(t, 0, pos)
356                 _greeting, err := ioutil.ReadAll(r)
357                 assert.NoError(t, err)
358                 assert.EqualValues(t, testutil.GreetingFileContents, _greeting)
359         }
360 }
361
362 // Check that after completing leeching, a leecher transitions to a seeding
363 // correctly. Connected in a chain like so: Seeder <-> Leecher <-> LeecherLeecher.
364 func TestSeedAfterDownloading(t *testing.T) {
365         greetingTempDir, mi := testutil.GreetingTestTorrent()
366         defer os.RemoveAll(greetingTempDir)
367         cfg := TestingConfig
368         cfg.Seed = true
369         cfg.DataDir = greetingTempDir
370         seeder, err := NewClient(&cfg)
371         defer seeder.Close()
372         testutil.ExportStatusWriter(seeder, "s")
373         seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
374         cfg.DataDir, err = ioutil.TempDir("", "")
375         require.NoError(t, err)
376         defer os.RemoveAll(cfg.DataDir)
377         leecher, _ := NewClient(&cfg)
378         defer leecher.Close()
379         testutil.ExportStatusWriter(leecher, "l")
380         cfg.Seed = false
381         // cfg.TorrentDataOpener = nil
382         cfg.DataDir, err = ioutil.TempDir("", "")
383         require.NoError(t, err)
384         defer os.RemoveAll(cfg.DataDir)
385         leecherLeecher, _ := NewClient(&cfg)
386         defer leecherLeecher.Close()
387         testutil.ExportStatusWriter(leecherLeecher, "ll")
388         leecherGreeting, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
389                 ret = TorrentSpecFromMetaInfo(mi)
390                 ret.ChunkSize = 2
391                 return
392         }())
393         llg, _, _ := leecherLeecher.AddTorrentSpec(func() (ret *TorrentSpec) {
394                 ret = TorrentSpecFromMetaInfo(mi)
395                 ret.ChunkSize = 3
396                 return
397         }())
398         // Simultaneously DownloadAll in Leecher, and read the contents
399         // consecutively in LeecherLeecher. This non-deterministically triggered a
400         // case where the leecher wouldn't unchoke the LeecherLeecher.
401         var wg sync.WaitGroup
402         wg.Add(1)
403         go func() {
404                 defer wg.Done()
405                 r := llg.NewReader()
406                 defer r.Close()
407                 b, err := ioutil.ReadAll(r)
408                 require.NoError(t, err)
409                 assert.EqualValues(t, testutil.GreetingFileContents, b)
410         }()
411         leecherGreeting.AddPeers([]Peer{
412                 Peer{
413                         IP:   missinggo.AddrIP(seeder.ListenAddr()),
414                         Port: missinggo.AddrPort(seeder.ListenAddr()),
415                 },
416                 Peer{
417                         IP:   missinggo.AddrIP(leecherLeecher.ListenAddr()),
418                         Port: missinggo.AddrPort(leecherLeecher.ListenAddr()),
419                 },
420         })
421         wg.Add(1)
422         go func() {
423                 defer wg.Done()
424                 leecherGreeting.DownloadAll()
425                 leecher.WaitAll()
426         }()
427         wg.Wait()
428 }
429
430 func TestReadaheadPieces(t *testing.T) {
431         for _, case_ := range []struct {
432                 readaheadBytes, pieceLength int64
433                 readaheadPieces             int
434         }{
435                 {5 * 1024 * 1024, 256 * 1024, 19},
436                 {5 * 1024 * 1024, 5 * 1024 * 1024, 1},
437                 {5*1024*1024 - 1, 5 * 1024 * 1024, 1},
438                 {5 * 1024 * 1024, 5*1024*1024 - 1, 2},
439                 {0, 5 * 1024 * 1024, 0},
440                 {5 * 1024 * 1024, 1048576, 4},
441         } {
442                 pieces := readaheadPieces(case_.readaheadBytes, case_.pieceLength)
443                 assert.Equal(t, case_.readaheadPieces, pieces, "%v", case_)
444         }
445 }
446
447 func TestMergingTrackersByAddingSpecs(t *testing.T) {
448         cl, err := NewClient(&TestingConfig)
449         require.NoError(t, err)
450         defer cl.Close()
451         spec := TorrentSpec{}
452         T, new, _ := cl.AddTorrentSpec(&spec)
453         if !new {
454                 t.FailNow()
455         }
456         spec.Trackers = [][]string{{"http://a"}, {"udp://b"}}
457         _, new, _ = cl.AddTorrentSpec(&spec)
458         if new {
459                 t.FailNow()
460         }
461         assert.EqualValues(t, T.torrent.Trackers[0][0], "http://a")
462         assert.EqualValues(t, T.torrent.Trackers[1][0], "udp://b")
463 }
464
465 type badStorage struct{}
466
467 func (me badStorage) OpenTorrent(*metainfo.InfoEx) (storage.Torrent, error) {
468         return me, nil
469 }
470
471 func (me badStorage) Close() error {
472         return nil
473 }
474
475 func (me badStorage) Piece(p metainfo.Piece) storage.Piece {
476         return badStoragePiece{p}
477 }
478
479 type badStoragePiece struct {
480         p metainfo.Piece
481 }
482
483 func (me badStoragePiece) WriteAt(b []byte, off int64) (int, error) {
484         return 0, nil
485 }
486
487 func (me badStoragePiece) GetIsComplete() bool {
488         return true
489 }
490
491 func (me badStoragePiece) MarkComplete() error {
492         return errors.New("psyyyyyyyche")
493 }
494
495 func (me badStoragePiece) randomlyTruncatedDataString() string {
496         return "hello, world\n"[:rand.Intn(14)]
497 }
498
499 func (me badStoragePiece) ReadAt(b []byte, off int64) (n int, err error) {
500         r := strings.NewReader(me.randomlyTruncatedDataString())
501         return r.ReadAt(b, off+me.p.Offset())
502 }
503
504 // We read from a piece which is marked completed, but is missing data.
505 func TestCompletedPieceWrongSize(t *testing.T) {
506         cfg := TestingConfig
507         cfg.DefaultStorage = badStorage{}
508         cl, _ := NewClient(&cfg)
509         defer cl.Close()
510         tt, new, err := cl.AddTorrentSpec(&TorrentSpec{
511                 Info: &metainfo.InfoEx{
512                         Info: metainfo.Info{
513                                 PieceLength: 15,
514                                 Pieces:      make([]byte, 20),
515                                 Files: []metainfo.FileInfo{
516                                         metainfo.FileInfo{Path: []string{"greeting"}, Length: 13},
517                                 },
518                         },
519                 },
520         })
521         require.NoError(t, err)
522         defer tt.Drop()
523         assert.True(t, new)
524         r := tt.NewReader()
525         defer r.Close()
526         b, err := ioutil.ReadAll(r)
527         assert.Len(t, b, 13)
528         assert.NoError(t, err)
529 }
530
531 func BenchmarkAddLargeTorrent(b *testing.B) {
532         cfg := TestingConfig
533         cfg.DisableTCP = true
534         cfg.DisableUTP = true
535         cfg.ListenAddr = "redonk"
536         cl, _ := NewClient(&cfg)
537         defer cl.Close()
538         for range iter.N(b.N) {
539                 t, err := cl.AddTorrentFromFile("testdata/bootstrap.dat.torrent")
540                 if err != nil {
541                         b.Fatal(err)
542                 }
543                 t.Drop()
544         }
545 }
546
547 func TestResponsive(t *testing.T) {
548         seederDataDir, mi := testutil.GreetingTestTorrent()
549         defer os.RemoveAll(seederDataDir)
550         cfg := TestingConfig
551         cfg.Seed = true
552         cfg.DataDir = seederDataDir
553         seeder, err := NewClient(&cfg)
554         require.Nil(t, err)
555         defer seeder.Close()
556         seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
557         leecherDataDir, err := ioutil.TempDir("", "")
558         require.Nil(t, err)
559         defer os.RemoveAll(leecherDataDir)
560         cfg = TestingConfig
561         cfg.DataDir = leecherDataDir
562         leecher, err := NewClient(&cfg)
563         require.Nil(t, err)
564         defer leecher.Close()
565         leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
566                 ret = TorrentSpecFromMetaInfo(mi)
567                 ret.ChunkSize = 2
568                 return
569         }())
570         leecherTorrent.AddPeers([]Peer{
571                 Peer{
572                         IP:   missinggo.AddrIP(seeder.ListenAddr()),
573                         Port: missinggo.AddrPort(seeder.ListenAddr()),
574                 },
575         })
576         reader := leecherTorrent.NewReader()
577         defer reader.Close()
578         reader.SetReadahead(0)
579         reader.SetResponsive()
580         b := make([]byte, 2)
581         _, err = reader.Seek(3, os.SEEK_SET)
582         require.NoError(t, err)
583         _, err = io.ReadFull(reader, b)
584         assert.Nil(t, err)
585         assert.EqualValues(t, "lo", string(b))
586         _, err = reader.Seek(11, os.SEEK_SET)
587         require.NoError(t, err)
588         n, err := io.ReadFull(reader, b)
589         assert.Nil(t, err)
590         assert.EqualValues(t, 2, n)
591         assert.EqualValues(t, "d\n", string(b))
592 }
593
594 func TestTorrentDroppedDuringResponsiveRead(t *testing.T) {
595         seederDataDir, mi := testutil.GreetingTestTorrent()
596         defer os.RemoveAll(seederDataDir)
597         cfg := TestingConfig
598         cfg.Seed = true
599         cfg.DataDir = seederDataDir
600         seeder, err := NewClient(&cfg)
601         require.Nil(t, err)
602         defer seeder.Close()
603         seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
604         leecherDataDir, err := ioutil.TempDir("", "")
605         require.Nil(t, err)
606         defer os.RemoveAll(leecherDataDir)
607         cfg = TestingConfig
608         cfg.DataDir = leecherDataDir
609         leecher, err := NewClient(&cfg)
610         require.Nil(t, err)
611         defer leecher.Close()
612         leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
613                 ret = TorrentSpecFromMetaInfo(mi)
614                 ret.ChunkSize = 2
615                 return
616         }())
617         leecherTorrent.AddPeers([]Peer{
618                 Peer{
619                         IP:   missinggo.AddrIP(seeder.ListenAddr()),
620                         Port: missinggo.AddrPort(seeder.ListenAddr()),
621                 },
622         })
623         reader := leecherTorrent.NewReader()
624         defer reader.Close()
625         reader.SetReadahead(0)
626         reader.SetResponsive()
627         b := make([]byte, 2)
628         _, err = reader.Seek(3, os.SEEK_SET)
629         require.NoError(t, err)
630         _, err = io.ReadFull(reader, b)
631         assert.Nil(t, err)
632         assert.EqualValues(t, "lo", string(b))
633         go leecherTorrent.Drop()
634         _, err = reader.Seek(11, os.SEEK_SET)
635         require.NoError(t, err)
636         n, err := reader.Read(b)
637         assert.EqualError(t, err, "torrent closed")
638         assert.EqualValues(t, 0, n)
639 }
640
641 func TestDHTInheritBlocklist(t *testing.T) {
642         ipl := iplist.New(nil)
643         require.NotNil(t, ipl)
644         cfg := TestingConfig
645         cfg.IPBlocklist = ipl
646         cfg.NoDHT = false
647         cl, err := NewClient(&cfg)
648         require.NoError(t, err)
649         defer cl.Close()
650         require.Equal(t, ipl, cl.DHT().IPBlocklist())
651 }
652
653 // Check that stuff is merged in subsequent AddTorrentSpec for the same
654 // infohash.
655 func TestAddTorrentSpecMerging(t *testing.T) {
656         cl, err := NewClient(&TestingConfig)
657         require.NoError(t, err)
658         defer cl.Close()
659         dir, mi := testutil.GreetingTestTorrent()
660         defer os.RemoveAll(dir)
661         var ts TorrentSpec
662         missinggo.CopyExact(&ts.InfoHash, mi.Info.Hash)
663         tt, new, err := cl.AddTorrentSpec(&ts)
664         require.NoError(t, err)
665         require.True(t, new)
666         require.Nil(t, tt.Info())
667         _, new, err = cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
668         require.NoError(t, err)
669         require.False(t, new)
670         require.NotNil(t, tt.Info())
671 }
672
673 // Check that torrent Info is obtained from the metainfo file cache.
674 func TestAddTorrentMetainfoInCache(t *testing.T) {
675         cfg := TestingConfig
676         cfg.DisableMetainfoCache = false
677         cfg.ConfigDir, _ = ioutil.TempDir(os.TempDir(), "")
678         defer os.RemoveAll(cfg.ConfigDir)
679         cl, err := NewClient(&cfg)
680         require.NoError(t, err)
681         defer cl.Close()
682         dir, mi := testutil.GreetingTestTorrent()
683         defer os.RemoveAll(dir)
684         tt, new, err := cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
685         require.NoError(t, err)
686         require.True(t, new)
687         require.NotNil(t, tt.Info())
688         _, err = os.Stat(filepath.Join(cfg.ConfigDir, "torrents", fmt.Sprintf("%x.torrent", mi.Info.Hash.Bytes())))
689         require.NoError(t, err)
690         // Contains only the infohash.
691         var ts TorrentSpec
692         missinggo.CopyExact(&ts.InfoHash, mi.Info.Hash)
693         _, ok := cl.Torrent(ts.InfoHash)
694         require.True(t, ok)
695         tt.Drop()
696         _, ok = cl.Torrent(ts.InfoHash)
697         require.False(t, ok)
698         tt, new, err = cl.AddTorrentSpec(&ts)
699         require.NoError(t, err)
700         require.True(t, new)
701         // Obtained from the metainfo cache.
702         require.NotNil(t, tt.Info())
703 }
704
705 func TestTorrentDroppedBeforeGotInfo(t *testing.T) {
706         dir, mi := testutil.GreetingTestTorrent()
707         os.RemoveAll(dir)
708         cl, _ := NewClient(&TestingConfig)
709         defer cl.Close()
710         var ts TorrentSpec
711         CopyExact(&ts.InfoHash, mi.Info.Hash)
712         tt, _, _ := cl.AddTorrentSpec(&ts)
713         tt.Drop()
714         assert.EqualValues(t, 0, len(cl.Torrents()))
715         select {
716         case <-tt.GotInfo():
717                 t.FailNow()
718         default:
719         }
720 }
721
722 // func testAddTorrentPriorPieceCompletion(t *testing.T, alreadyCompleted bool) {
723 //      fileCacheDir, err := ioutil.TempDir("", "")
724 //      require.NoError(t, err)
725 //      defer os.RemoveAll(fileCacheDir)
726 //      fileCache, err := filecache.NewCache(fileCacheDir)
727 //      require.NoError(t, err)
728 //      greetingDataTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
729 //      defer os.RemoveAll(greetingDataTempDir)
730 //      filePieceStore := pieceStore.New(fileCacheDataBackend.New(fileCache))
731 //      greetingData := filePieceStore.OpenTorrentData(&greetingMetainfo.Info.Info)
732 //      written, err := greetingData.WriteAt([]byte(testutil.GreetingFileContents), 0)
733 //      require.Equal(t, len(testutil.GreetingFileContents), written)
734 //      require.NoError(t, err)
735 //      for i := 0; i < greetingMetainfo.Info.NumPieces(); i++ {
736 //              // p := greetingMetainfo.Info.Piece(i)
737 //              if alreadyCompleted {
738 //                      err := greetingData.PieceCompleted(i)
739 //                      assert.NoError(t, err)
740 //              }
741 //      }
742 //      cfg := TestingConfig
743 //      // TODO: Disable network option?
744 //      cfg.DisableTCP = true
745 //      cfg.DisableUTP = true
746 //      // cfg.DefaultStorage = filePieceStore
747 //      cl, err := NewClient(&cfg)
748 //      require.NoError(t, err)
749 //      defer cl.Close()
750 //      tt, err := cl.AddTorrent(greetingMetainfo)
751 //      require.NoError(t, err)
752 //      psrs := tt.PieceStateRuns()
753 //      assert.Len(t, psrs, 1)
754 //      assert.EqualValues(t, 3, psrs[0].Length)
755 //      assert.Equal(t, alreadyCompleted, psrs[0].Complete)
756 //      if alreadyCompleted {
757 //              r := tt.NewReader()
758 //              b, err := ioutil.ReadAll(r)
759 //              assert.NoError(t, err)
760 //              assert.EqualValues(t, testutil.GreetingFileContents, b)
761 //      }
762 // }
763
764 // func TestAddTorrentPiecesAlreadyCompleted(t *testing.T) {
765 //      testAddTorrentPriorPieceCompletion(t, true)
766 // }
767
768 // func TestAddTorrentPiecesNotAlreadyCompleted(t *testing.T) {
769 //      testAddTorrentPriorPieceCompletion(t, false)
770 // }
771
772 func TestAddMetainfoWithNodes(t *testing.T) {
773         cfg := TestingConfig
774         cfg.NoDHT = false
775         // For now, we want to just jam the nodes into the table, without
776         // verifying them first. Also the DHT code doesn't support mixing secure
777         // and insecure nodes if security is enabled (yet).
778         cfg.DHTConfig.NoSecurity = true
779         cl, err := NewClient(&cfg)
780         require.NoError(t, err)
781         defer cl.Close()
782         assert.EqualValues(t, cl.DHT().NumNodes(), 0)
783         tt, err := cl.AddTorrentFromFile("metainfo/testdata/issue_65a.torrent")
784         require.NoError(t, err)
785         assert.Len(t, tt.torrent.Trackers, 5)
786         assert.EqualValues(t, 6, cl.DHT().NumNodes())
787 }
788
789 type testDownloadCancelParams struct {
790         Responsive                bool
791         Readahead                 int64
792         SetReadahead              bool
793         ExportClientStatus        bool
794         SetLeecherStorageCapacity bool
795         LeecherStorageCapacity    int64
796         Cancel                    bool
797 }
798
799 func testDownloadCancel(t *testing.T, ps testDownloadCancelParams) {
800         greetingTempDir, mi := testutil.GreetingTestTorrent()
801         defer os.RemoveAll(greetingTempDir)
802         cfg := TestingConfig
803         cfg.Seed = true
804         cfg.DataDir = greetingTempDir
805         seeder, err := NewClient(&cfg)
806         require.NoError(t, err)
807         defer seeder.Close()
808         if ps.ExportClientStatus {
809                 testutil.ExportStatusWriter(seeder, "s")
810         }
811         seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
812         leecherDataDir, err := ioutil.TempDir("", "")
813         require.NoError(t, err)
814         defer os.RemoveAll(leecherDataDir)
815         // cfg.TorrentDataOpener = func() TorrentDataOpener {
816         //      fc, err := filecache.NewCache(leecherDataDir)
817         //      require.NoError(t, err)
818         //      if ps.SetLeecherStorageCapacity {
819         //              fc.SetCapacity(ps.LeecherStorageCapacity)
820         //      }
821         //      store := pieceStore.New(fileCacheDataBackend.New(fc))
822         //      return func(mi *metainfo.Info) storage.I {
823         //              return store.OpenTorrentData(mi)
824         //      }
825         // }()
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 }