15 "github.com/bradfitz/iter"
16 "github.com/stretchr/testify/assert"
17 "github.com/stretchr/testify/require"
18 "golang.org/x/time/rate"
20 "github.com/anacrolix/dht/v2"
21 _ "github.com/anacrolix/envpprof"
22 "github.com/anacrolix/missinggo"
23 "github.com/anacrolix/missinggo/v2/filecache"
25 "github.com/anacrolix/torrent/bencode"
26 "github.com/anacrolix/torrent/internal/testutil"
27 "github.com/anacrolix/torrent/iplist"
28 "github.com/anacrolix/torrent/metainfo"
29 "github.com/anacrolix/torrent/storage"
32 func TestingConfig() *ClientConfig {
33 cfg := NewDefaultClientConfig()
34 cfg.ListenHost = LoopbackListenHost
36 cfg.DataDir = tempDir()
37 cfg.DisableTrackers = true
38 cfg.NoDefaultPortForwarding = true
39 cfg.DisableAcceptRateLimiting = true
42 //cfg.Logger = cfg.Logger.WithText(func(m log.Msg) string {
44 // m.Values(func(i interface{}) bool {
45 // t += fmt.Sprintf("\n%[1]T: %[1]v", i)
53 func TestClientDefault(t *testing.T) {
54 cl, err := NewClient(TestingConfig())
55 require.NoError(t, err)
59 func TestClientNilConfig(t *testing.T) {
60 cl, err := NewClient(nil)
61 require.NoError(t, err)
65 func TestBoltPieceCompletionClosedWhenClientClosed(t *testing.T) {
66 cfg := TestingConfig()
67 pc, err := storage.NewBoltPieceCompletion(cfg.DataDir)
68 require.NoError(t, err)
69 ci := storage.NewFileWithCompletion(cfg.DataDir, pc)
71 cfg.DefaultStorage = ci
72 cl, err := NewClient(cfg)
73 require.NoError(t, err)
75 // And again, https://github.com/anacrolix/torrent/issues/158
76 cl, err = NewClient(cfg)
77 require.NoError(t, err)
81 func TestAddDropTorrent(t *testing.T) {
82 cl, err := NewClient(TestingConfig())
83 require.NoError(t, err)
85 dir, mi := testutil.GreetingTestTorrent()
86 defer os.RemoveAll(dir)
87 tt, new, err := cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
88 require.NoError(t, err)
90 tt.SetMaxEstablishedConns(0)
91 tt.SetMaxEstablishedConns(1)
95 func TestAddTorrentNoSupportedTrackerSchemes(t *testing.T) {
100 func TestAddTorrentNoUsableURLs(t *testing.T) {
105 func TestAddPeersToUnknownTorrent(t *testing.T) {
110 func TestPieceHashSize(t *testing.T) {
111 assert.Equal(t, 20, pieceHash.Size())
114 func TestTorrentInitialState(t *testing.T) {
115 dir, mi := testutil.GreetingTestTorrent()
116 defer os.RemoveAll(dir)
118 config: TestingConfig(),
121 tor := cl.newTorrent(
123 storage.NewFileWithCompletion(tempDir(), storage.NewMapPieceCompletion()),
127 err := tor.setInfoBytes(mi.InfoBytes)
129 require.NoError(t, err)
130 require.Len(t, tor.pieces, 3)
131 tor.pendAllChunkSpecs(0)
133 assert.EqualValues(t, 3, tor.pieceNumPendingChunks(0))
135 assert.EqualValues(t, chunkSpec{4, 1}, chunkIndexSpec(2, tor.pieceLength(0), tor.chunkSize))
138 func TestReducedDialTimeout(t *testing.T) {
139 cfg := NewDefaultClientConfig()
140 for _, _case := range []struct {
144 ExpectedReduced time.Duration
146 {cfg.NominalDialTimeout, 40, 0, cfg.NominalDialTimeout},
147 {cfg.NominalDialTimeout, 40, 1, cfg.NominalDialTimeout},
148 {cfg.NominalDialTimeout, 40, 39, cfg.NominalDialTimeout},
149 {cfg.NominalDialTimeout, 40, 40, cfg.NominalDialTimeout / 2},
150 {cfg.NominalDialTimeout, 40, 80, cfg.NominalDialTimeout / 3},
151 {cfg.NominalDialTimeout, 40, 4000, cfg.NominalDialTimeout / 101},
153 reduced := reducedDialTimeout(cfg.MinDialTimeout, _case.Max, _case.HalfOpenLimit, _case.PendingPeers)
154 expected := _case.ExpectedReduced
155 if expected < cfg.MinDialTimeout {
156 expected = cfg.MinDialTimeout
158 if reduced != expected {
159 t.Fatalf("expected %s, got %s", _case.ExpectedReduced, reduced)
164 func TestAddDropManyTorrents(t *testing.T) {
165 cl, err := NewClient(TestingConfig())
166 require.NoError(t, err)
168 for i := range iter.N(1000) {
170 binary.PutVarint(spec.InfoHash[:], int64(i))
171 tt, new, err := cl.AddTorrentSpec(&spec)
172 assert.NoError(t, err)
178 type fileCacheClientStorageFactoryParams struct {
181 Wrapper func(*filecache.Cache) storage.ClientImpl
184 func newFileCacheClientStorageFactory(ps fileCacheClientStorageFactoryParams) storageFactory {
185 return func(dataDir string) storage.ClientImpl {
186 fc, err := filecache.NewCache(dataDir)
191 fc.SetCapacity(ps.Capacity)
193 return ps.Wrapper(fc)
197 type storageFactory func(string) storage.ClientImpl
199 func TestClientTransferDefault(t *testing.T) {
200 testClientTransfer(t, testClientTransferParams{
201 ExportClientStatus: true,
202 LeecherStorage: newFileCacheClientStorageFactory(fileCacheClientStorageFactoryParams{
203 Wrapper: fileCachePieceResourceStorage,
208 func TestClientTransferRateLimitedUpload(t *testing.T) {
209 started := time.Now()
210 testClientTransfer(t, testClientTransferParams{
211 // We are uploading 13 bytes (the length of the greeting torrent). The
212 // chunks are 2 bytes in length. Then the smallest burst we can run
213 // with is 2. Time taken is (13-burst)/rate.
214 SeederUploadRateLimiter: rate.NewLimiter(11, 2),
215 ExportClientStatus: true,
217 require.True(t, time.Since(started) > time.Second)
220 func TestClientTransferRateLimitedDownload(t *testing.T) {
221 testClientTransfer(t, testClientTransferParams{
222 LeecherDownloadRateLimiter: rate.NewLimiter(512, 512),
226 func fileCachePieceResourceStorage(fc *filecache.Cache) storage.ClientImpl {
227 return storage.NewResourcePieces(fc.AsResourceProvider())
230 func testClientTransferSmallCache(t *testing.T, setReadahead bool, readahead int64) {
231 testClientTransfer(t, testClientTransferParams{
232 LeecherStorage: newFileCacheClientStorageFactory(fileCacheClientStorageFactoryParams{
234 // Going below the piece length means it can't complete a piece so
235 // that it can be hashed.
237 Wrapper: fileCachePieceResourceStorage,
239 SetReadahead: setReadahead,
240 // Can't readahead too far or the cache will thrash and drop data we
242 Readahead: readahead,
243 ExportClientStatus: true,
247 func TestClientTransferSmallCachePieceSizedReadahead(t *testing.T) {
248 testClientTransferSmallCache(t, true, 5)
251 func TestClientTransferSmallCacheLargeReadahead(t *testing.T) {
252 testClientTransferSmallCache(t, true, 15)
255 func TestClientTransferSmallCacheDefaultReadahead(t *testing.T) {
256 testClientTransferSmallCache(t, false, -1)
259 func TestClientTransferVarious(t *testing.T) {
261 for _, ls := range []struct {
265 {"Filecache", newFileCacheClientStorageFactory(fileCacheClientStorageFactoryParams{
266 Wrapper: fileCachePieceResourceStorage,
268 {"Boltdb", storage.NewBoltDB},
270 t.Run(fmt.Sprintf("LeecherStorage=%s", ls.name), func(t *testing.T) {
272 for _, ss := range []struct {
274 f func(string) storage.ClientImpl
276 {"File", storage.NewFile},
277 {"Mmap", storage.NewMMap},
279 t.Run(fmt.Sprintf("%sSeederStorage", ss.name), func(t *testing.T) {
280 for _, responsive := range []bool{false, true} {
281 t.Run(fmt.Sprintf("Responsive=%v", responsive), func(t *testing.T) {
282 t.Run("NoReadahead", func(t *testing.T) {
283 testClientTransfer(t, testClientTransferParams{
284 Responsive: responsive,
286 LeecherStorage: ls.f,
289 for _, readahead := range []int64{-1, 0, 1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14, 15, 20} {
290 t.Run(fmt.Sprintf("readahead=%v", readahead), func(t *testing.T) {
291 testClientTransfer(t, testClientTransferParams{
293 Responsive: responsive,
295 Readahead: readahead,
296 LeecherStorage: ls.f,
308 type testClientTransferParams struct {
312 ExportClientStatus bool
313 LeecherStorage func(string) storage.ClientImpl
314 SeederStorage func(string) storage.ClientImpl
315 SeederUploadRateLimiter *rate.Limiter
316 LeecherDownloadRateLimiter *rate.Limiter
319 // Creates a seeder and a leecher, and ensures the data transfers when a read
320 // is attempted on the leecher.
321 func testClientTransfer(t *testing.T, ps testClientTransferParams) {
322 greetingTempDir, mi := testutil.GreetingTestTorrent()
323 defer os.RemoveAll(greetingTempDir)
324 // Create seeder and a Torrent.
325 cfg := TestingConfig()
327 if ps.SeederUploadRateLimiter != nil {
328 cfg.UploadRateLimiter = ps.SeederUploadRateLimiter
330 // cfg.ListenAddr = "localhost:4000"
331 if ps.SeederStorage != nil {
332 cfg.DefaultStorage = ps.SeederStorage(greetingTempDir)
333 defer cfg.DefaultStorage.Close()
335 cfg.DataDir = greetingTempDir
337 seeder, err := NewClient(cfg)
338 require.NoError(t, err)
339 if ps.ExportClientStatus {
340 defer testutil.ExportStatusWriter(seeder, "s")()
342 seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
343 // Run a Stats right after Closing the Client. This will trigger the Stats
344 // panic in #214 caused by RemoteAddr on Closed uTP sockets.
345 defer seederTorrent.Stats()
347 seederTorrent.VerifyData()
348 // Create leecher and a Torrent.
349 leecherDataDir, err := ioutil.TempDir("", "")
350 require.NoError(t, err)
351 defer os.RemoveAll(leecherDataDir)
352 cfg = TestingConfig()
353 if ps.LeecherStorage == nil {
354 cfg.DataDir = leecherDataDir
356 cfg.DefaultStorage = ps.LeecherStorage(leecherDataDir)
358 if ps.LeecherDownloadRateLimiter != nil {
359 cfg.DownloadRateLimiter = ps.LeecherDownloadRateLimiter
363 leecher, err := NewClient(cfg)
364 require.NoError(t, err)
365 defer leecher.Close()
366 if ps.ExportClientStatus {
367 defer testutil.ExportStatusWriter(leecher, "l")()
369 leecherTorrent, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
370 ret = TorrentSpecFromMetaInfo(mi)
374 require.NoError(t, err)
377 //// This was used when observing coalescing of piece state changes.
378 //logPieceStateChanges(leecherTorrent)
380 // Now do some things with leecher and seeder.
381 leecherTorrent.AddClientPeer(seeder)
382 // The Torrent should not be interested in obtaining peers, so the one we
383 // just added should be the only one.
384 assert.False(t, leecherTorrent.Seeding())
385 assert.EqualValues(t, 1, leecherTorrent.Stats().PendingPeers)
386 r := leecherTorrent.NewReader()
392 r.SetReadahead(ps.Readahead)
394 assertReadAllGreeting(t, r)
396 seederStats := seederTorrent.Stats()
397 assert.True(t, 13 <= seederStats.BytesWrittenData.Int64())
398 assert.True(t, 8 <= seederStats.ChunksWritten.Int64())
400 leecherStats := leecherTorrent.Stats()
401 assert.True(t, 13 <= leecherStats.BytesReadData.Int64())
402 assert.True(t, 8 <= leecherStats.ChunksRead.Int64())
404 // Try reading through again for the cases where the torrent data size
405 // exceeds the size of the cache.
406 assertReadAllGreeting(t, r)
409 func assertReadAllGreeting(t *testing.T, r io.ReadSeeker) {
410 pos, err := r.Seek(0, io.SeekStart)
411 assert.NoError(t, err)
412 assert.EqualValues(t, 0, pos)
413 _greeting, err := ioutil.ReadAll(r)
414 assert.NoError(t, err)
415 assert.EqualValues(t, testutil.GreetingFileContents, _greeting)
418 // Check that after completing leeching, a leecher transitions to a seeding
419 // correctly. Connected in a chain like so: Seeder <-> Leecher <-> LeecherLeecher.
420 func TestSeedAfterDownloading(t *testing.T) {
421 greetingTempDir, mi := testutil.GreetingTestTorrent()
422 defer os.RemoveAll(greetingTempDir)
424 cfg := TestingConfig()
426 cfg.DataDir = greetingTempDir
427 seeder, err := NewClient(cfg)
428 require.NoError(t, err)
430 defer testutil.ExportStatusWriter(seeder, "s")()
431 seederTorrent, ok, err := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
432 require.NoError(t, err)
434 seederTorrent.VerifyData()
436 cfg = TestingConfig()
438 cfg.DataDir, err = ioutil.TempDir("", "")
439 require.NoError(t, err)
440 defer os.RemoveAll(cfg.DataDir)
441 leecher, err := NewClient(cfg)
442 require.NoError(t, err)
443 defer leecher.Close()
444 defer testutil.ExportStatusWriter(leecher, "l")()
446 cfg = TestingConfig()
448 cfg.DataDir, err = ioutil.TempDir("", "")
449 require.NoError(t, err)
450 defer os.RemoveAll(cfg.DataDir)
451 leecherLeecher, _ := NewClient(cfg)
452 require.NoError(t, err)
453 defer leecherLeecher.Close()
454 defer testutil.ExportStatusWriter(leecherLeecher, "ll")()
455 leecherGreeting, ok, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
456 ret = TorrentSpecFromMetaInfo(mi)
460 require.NoError(t, err)
462 llg, ok, err := leecherLeecher.AddTorrentSpec(func() (ret *TorrentSpec) {
463 ret = TorrentSpecFromMetaInfo(mi)
467 require.NoError(t, err)
469 // Simultaneously DownloadAll in Leecher, and read the contents
470 // consecutively in LeecherLeecher. This non-deterministically triggered a
471 // case where the leecher wouldn't unchoke the LeecherLeecher.
472 var wg sync.WaitGroup
478 b, err := ioutil.ReadAll(r)
479 require.NoError(t, err)
480 assert.EqualValues(t, testutil.GreetingFileContents, b)
482 done := make(chan struct{})
484 go leecherGreeting.AddClientPeer(seeder)
485 go leecherGreeting.AddClientPeer(leecherLeecher)
489 leecherGreeting.DownloadAll()
495 func TestMergingTrackersByAddingSpecs(t *testing.T) {
496 cl, err := NewClient(TestingConfig())
497 require.NoError(t, err)
499 spec := TorrentSpec{}
500 T, new, _ := cl.AddTorrentSpec(&spec)
504 spec.Trackers = [][]string{{"http://a"}, {"udp://b"}}
505 _, new, _ = cl.AddTorrentSpec(&spec)
507 assert.EqualValues(t, [][]string{{"http://a"}, {"udp://b"}}, T.metainfo.AnnounceList)
508 // Because trackers are disabled in TestingConfig.
509 assert.EqualValues(t, 0, len(T.trackerAnnouncers))
512 // We read from a piece which is marked completed, but is missing data.
513 func TestCompletedPieceWrongSize(t *testing.T) {
514 cfg := TestingConfig()
515 cfg.DefaultStorage = badStorage{}
516 cl, err := NewClient(cfg)
517 require.NoError(t, err)
519 info := metainfo.Info{
521 Pieces: make([]byte, 20),
522 Files: []metainfo.FileInfo{
523 {Path: []string{"greeting"}, Length: 13},
526 b, err := bencode.Marshal(info)
527 require.NoError(t, err)
528 tt, new, err := cl.AddTorrentSpec(&TorrentSpec{
530 InfoHash: metainfo.HashBytes(b),
532 require.NoError(t, err)
537 b, err = ioutil.ReadAll(r)
539 assert.NoError(t, err)
542 func BenchmarkAddLargeTorrent(b *testing.B) {
543 cfg := TestingConfig()
544 cfg.DisableTCP = true
545 cfg.DisableUTP = true
546 cl, err := NewClient(cfg)
547 require.NoError(b, err)
550 for range iter.N(b.N) {
551 t, err := cl.AddTorrentFromFile("testdata/bootstrap.dat.torrent")
559 func TestResponsive(t *testing.T) {
560 seederDataDir, mi := testutil.GreetingTestTorrent()
561 defer os.RemoveAll(seederDataDir)
562 cfg := TestingConfig()
564 cfg.DataDir = seederDataDir
565 seeder, err := NewClient(cfg)
568 seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
569 seederTorrent.VerifyData()
570 leecherDataDir, err := ioutil.TempDir("", "")
572 defer os.RemoveAll(leecherDataDir)
573 cfg = TestingConfig()
574 cfg.DataDir = leecherDataDir
575 leecher, err := NewClient(cfg)
577 defer leecher.Close()
578 leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
579 ret = TorrentSpecFromMetaInfo(mi)
583 leecherTorrent.AddClientPeer(seeder)
584 reader := leecherTorrent.NewReader()
586 reader.SetReadahead(0)
587 reader.SetResponsive()
589 _, err = reader.Seek(3, io.SeekStart)
590 require.NoError(t, err)
591 _, err = io.ReadFull(reader, b)
593 assert.EqualValues(t, "lo", string(b))
594 _, err = reader.Seek(11, io.SeekStart)
595 require.NoError(t, err)
596 n, err := io.ReadFull(reader, b)
598 assert.EqualValues(t, 2, n)
599 assert.EqualValues(t, "d\n", string(b))
602 func TestTorrentDroppedDuringResponsiveRead(t *testing.T) {
603 seederDataDir, mi := testutil.GreetingTestTorrent()
604 defer os.RemoveAll(seederDataDir)
605 cfg := TestingConfig()
607 cfg.DataDir = seederDataDir
608 seeder, err := NewClient(cfg)
611 seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
612 seederTorrent.VerifyData()
613 leecherDataDir, err := ioutil.TempDir("", "")
615 defer os.RemoveAll(leecherDataDir)
616 cfg = TestingConfig()
617 cfg.DataDir = leecherDataDir
618 leecher, err := NewClient(cfg)
620 defer leecher.Close()
621 leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
622 ret = TorrentSpecFromMetaInfo(mi)
626 leecherTorrent.AddClientPeer(seeder)
627 reader := leecherTorrent.NewReader()
629 reader.SetReadahead(0)
630 reader.SetResponsive()
632 _, err = reader.Seek(3, io.SeekStart)
633 require.NoError(t, err)
634 _, err = io.ReadFull(reader, b)
636 assert.EqualValues(t, "lo", string(b))
637 go leecherTorrent.Drop()
638 _, err = reader.Seek(11, io.SeekStart)
639 require.NoError(t, err)
640 n, err := reader.Read(b)
641 assert.EqualError(t, err, "torrent closed")
642 assert.EqualValues(t, 0, n)
645 func TestDHTInheritBlocklist(t *testing.T) {
646 ipl := iplist.New(nil)
647 require.NotNil(t, ipl)
648 cfg := TestingConfig()
649 cfg.IPBlocklist = ipl
651 cl, err := NewClient(cfg)
652 require.NoError(t, err)
655 cl.eachDhtServer(func(s *dht.Server) {
656 assert.Equal(t, ipl, s.IPBlocklist())
659 assert.EqualValues(t, 2, numServers)
662 // Check that stuff is merged in subsequent AddTorrentSpec for the same
664 func TestAddTorrentSpecMerging(t *testing.T) {
665 cl, err := NewClient(TestingConfig())
666 require.NoError(t, err)
668 dir, mi := testutil.GreetingTestTorrent()
669 defer os.RemoveAll(dir)
670 tt, new, err := cl.AddTorrentSpec(&TorrentSpec{
671 InfoHash: mi.HashInfoBytes(),
673 require.NoError(t, err)
675 require.Nil(t, tt.Info())
676 _, new, err = cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
677 require.NoError(t, err)
678 require.False(t, new)
679 require.NotNil(t, tt.Info())
682 func TestTorrentDroppedBeforeGotInfo(t *testing.T) {
683 dir, mi := testutil.GreetingTestTorrent()
685 cl, _ := NewClient(TestingConfig())
687 tt, _, _ := cl.AddTorrentSpec(&TorrentSpec{
688 InfoHash: mi.HashInfoBytes(),
691 assert.EqualValues(t, 0, len(cl.Torrents()))
699 func writeTorrentData(ts *storage.Torrent, info metainfo.Info, b []byte) {
700 for i := range iter.N(info.NumPieces()) {
702 ts.Piece(p).WriteAt(b[p.Offset():p.Offset()+p.Length()], 0)
706 func testAddTorrentPriorPieceCompletion(t *testing.T, alreadyCompleted bool, csf func(*filecache.Cache) storage.ClientImpl) {
707 fileCacheDir, err := ioutil.TempDir("", "")
708 require.NoError(t, err)
709 defer os.RemoveAll(fileCacheDir)
710 fileCache, err := filecache.NewCache(fileCacheDir)
711 require.NoError(t, err)
712 greetingDataTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
713 defer os.RemoveAll(greetingDataTempDir)
714 filePieceStore := csf(fileCache)
715 defer filePieceStore.Close()
716 info, err := greetingMetainfo.UnmarshalInfo()
717 require.NoError(t, err)
718 ih := greetingMetainfo.HashInfoBytes()
719 greetingData, err := storage.NewClient(filePieceStore).OpenTorrent(&info, ih)
720 require.NoError(t, err)
721 writeTorrentData(greetingData, info, []byte(testutil.GreetingFileContents))
722 // require.Equal(t, len(testutil.GreetingFileContents), written)
723 // require.NoError(t, err)
724 for i := 0; i < info.NumPieces(); i++ {
726 if alreadyCompleted {
727 require.NoError(t, greetingData.Piece(p).MarkComplete())
730 cfg := TestingConfig()
731 // TODO: Disable network option?
732 cfg.DisableTCP = true
733 cfg.DisableUTP = true
734 cfg.DefaultStorage = filePieceStore
735 cl, err := NewClient(cfg)
736 require.NoError(t, err)
738 tt, err := cl.AddTorrent(greetingMetainfo)
739 require.NoError(t, err)
740 psrs := tt.PieceStateRuns()
741 assert.Len(t, psrs, 1)
742 assert.EqualValues(t, 3, psrs[0].Length)
743 assert.Equal(t, alreadyCompleted, psrs[0].Complete)
744 if alreadyCompleted {
746 b, err := ioutil.ReadAll(r)
747 assert.NoError(t, err)
748 assert.EqualValues(t, testutil.GreetingFileContents, b)
752 func TestAddTorrentPiecesAlreadyCompleted(t *testing.T) {
753 testAddTorrentPriorPieceCompletion(t, true, fileCachePieceResourceStorage)
756 func TestAddTorrentPiecesNotAlreadyCompleted(t *testing.T) {
757 testAddTorrentPriorPieceCompletion(t, false, fileCachePieceResourceStorage)
760 func TestAddMetainfoWithNodes(t *testing.T) {
761 cfg := TestingConfig()
762 cfg.ListenHost = func(string) string { return "" }
764 cfg.DhtStartingNodes = func() ([]dht.Addr, error) { return nil, nil }
765 // For now, we want to just jam the nodes into the table, without
766 // verifying them first. Also the DHT code doesn't support mixing secure
767 // and insecure nodes if security is enabled (yet).
768 // cfg.DHTConfig.NoSecurity = true
769 cl, err := NewClient(cfg)
770 require.NoError(t, err)
772 sum := func() (ret int64) {
773 cl.eachDhtServer(func(s *dht.Server) {
774 ret += s.Stats().OutboundQueriesAttempted
778 assert.EqualValues(t, 0, sum())
779 tt, err := cl.AddTorrentFromFile("metainfo/testdata/issue_65a.torrent")
780 require.NoError(t, err)
781 // Nodes are not added or exposed in Torrent's metainfo. We just randomly
782 // check if the announce-list is here instead. TODO: Add nodes.
783 assert.Len(t, tt.metainfo.AnnounceList, 5)
784 // There are 6 nodes in the torrent file.
785 for sum() != int64(6*len(cl.dhtServers)) {
786 time.Sleep(time.Millisecond)
790 type testDownloadCancelParams struct {
791 SetLeecherStorageCapacity bool
792 LeecherStorageCapacity int64
796 func testDownloadCancel(t *testing.T, ps testDownloadCancelParams) {
797 greetingTempDir, mi := testutil.GreetingTestTorrent()
798 defer os.RemoveAll(greetingTempDir)
799 cfg := TestingConfig()
801 cfg.DataDir = greetingTempDir
802 seeder, err := NewClient(cfg)
803 require.NoError(t, err)
805 defer testutil.ExportStatusWriter(seeder, "s")()
806 seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
807 seederTorrent.VerifyData()
808 leecherDataDir, err := ioutil.TempDir("", "")
809 require.NoError(t, err)
810 defer os.RemoveAll(leecherDataDir)
811 fc, err := filecache.NewCache(leecherDataDir)
812 require.NoError(t, err)
813 if ps.SetLeecherStorageCapacity {
814 fc.SetCapacity(ps.LeecherStorageCapacity)
816 cfg.DefaultStorage = storage.NewResourcePieces(fc.AsResourceProvider())
817 cfg.DataDir = leecherDataDir
818 leecher, err := NewClient(cfg)
819 require.NoError(t, err)
820 defer leecher.Close()
821 defer testutil.ExportStatusWriter(leecher, "l")()
822 leecherGreeting, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
823 ret = TorrentSpecFromMetaInfo(mi)
827 require.NoError(t, err)
829 psc := leecherGreeting.SubscribePieceStateChanges()
832 leecherGreeting.cl.lock()
833 leecherGreeting.downloadPiecesLocked(0, leecherGreeting.numPieces())
835 leecherGreeting.cancelPiecesLocked(0, leecherGreeting.NumPieces())
837 leecherGreeting.cl.unlock()
838 done := make(chan struct{})
840 go leecherGreeting.AddClientPeer(seeder)
841 completes := make(map[int]bool, 3)
842 expected := func() map[int]bool {
844 return map[int]bool{0: false, 1: false, 2: false}
846 return map[int]bool{0: true, 1: true, 2: true}
849 for !reflect.DeepEqual(completes, expected) {
851 v := _v.(PieceStateChange)
852 completes[v.Index] = v.Complete
856 func TestTorrentDownloadAll(t *testing.T) {
857 testDownloadCancel(t, testDownloadCancelParams{})
860 func TestTorrentDownloadAllThenCancel(t *testing.T) {
861 testDownloadCancel(t, testDownloadCancelParams{
866 // Ensure that it's an error for a peer to send an invalid have message.
867 func TestPeerInvalidHave(t *testing.T) {
868 cl, err := NewClient(TestingConfig())
869 require.NoError(t, err)
871 info := metainfo.Info{
873 Pieces: make([]byte, 20),
874 Files: []metainfo.FileInfo{{Length: 1}},
876 infoBytes, err := bencode.Marshal(info)
877 require.NoError(t, err)
878 tt, _new, err := cl.AddTorrentSpec(&TorrentSpec{
879 InfoBytes: infoBytes,
880 InfoHash: metainfo.HashBytes(infoBytes),
881 Storage: badStorage{},
883 require.NoError(t, err)
889 assert.NoError(t, cn.peerSentHave(0))
890 assert.Error(t, cn.peerSentHave(1))
893 func TestPieceCompletedInStorageButNotClient(t *testing.T) {
894 greetingTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
895 defer os.RemoveAll(greetingTempDir)
896 cfg := TestingConfig()
897 cfg.DataDir = greetingTempDir
898 seeder, err := NewClient(TestingConfig())
899 require.NoError(t, err)
900 seeder.AddTorrentSpec(&TorrentSpec{
901 InfoBytes: greetingMetainfo.InfoBytes,
905 // Check that when the listen port is 0, all the protocols listened on have
906 // the same port, and it isn't zero.
907 func TestClientDynamicListenPortAllProtocols(t *testing.T) {
908 cl, err := NewClient(TestingConfig())
909 require.NoError(t, err)
911 port := cl.LocalPort()
912 assert.NotEqual(t, 0, port)
913 cl.eachListener(func(s listener) bool {
914 assert.Equal(t, port, missinggo.AddrPort(s.Addr()))
919 func TestClientDynamicListenTCPOnly(t *testing.T) {
920 cfg := TestingConfig()
921 cfg.DisableUTP = true
922 cfg.DisableTCP = false
923 cl, err := NewClient(cfg)
924 require.NoError(t, err)
926 assert.NotEqual(t, 0, cl.LocalPort())
929 func TestClientDynamicListenUTPOnly(t *testing.T) {
930 cfg := TestingConfig()
931 cfg.DisableTCP = true
932 cfg.DisableUTP = false
933 cl, err := NewClient(cfg)
934 require.NoError(t, err)
936 assert.NotEqual(t, 0, cl.LocalPort())
939 func totalConns(tts []*Torrent) (ret int) {
940 for _, tt := range tts {
948 func TestSetMaxEstablishedConn(t *testing.T) {
950 ih := testutil.GreetingMetaInfo().HashInfoBytes()
951 cfg := TestingConfig()
952 cfg.DisableAcceptRateLimiting = true
953 cfg.dropDuplicatePeerIds = true
954 for i := range iter.N(3) {
955 cl, err := NewClient(cfg)
956 require.NoError(t, err)
958 tt, _ := cl.AddTorrentInfoHash(ih)
959 tt.SetMaxEstablishedConns(2)
960 defer testutil.ExportStatusWriter(cl, fmt.Sprintf("%d", i))()
961 tts = append(tts, tt)
964 for _, tt := range tts {
965 for _, _tt := range tts {
967 tt.AddClientPeer(_tt.cl)
972 waitTotalConns := func(num int) {
973 for totalConns(tts) != num {
975 time.Sleep(time.Millisecond)
980 tts[0].SetMaxEstablishedConns(1)
982 tts[0].SetMaxEstablishedConns(0)
984 tts[0].SetMaxEstablishedConns(1)
987 tts[0].SetMaxEstablishedConns(2)
992 // Creates a file containing its own name as data. Make a metainfo from that, adds it to the given
993 // client, and returns a magnet link.
994 func makeMagnet(t *testing.T, cl *Client, dir string, name string) string {
995 os.MkdirAll(dir, 0770)
996 file, err := os.Create(filepath.Join(dir, name))
997 require.NoError(t, err)
998 file.Write([]byte(name))
1000 mi := metainfo.MetaInfo{}
1002 info := metainfo.Info{PieceLength: 256 * 1024}
1003 err = info.BuildFromFilePath(filepath.Join(dir, name))
1004 require.NoError(t, err)
1005 mi.InfoBytes, err = bencode.Marshal(info)
1006 require.NoError(t, err)
1007 magnet := mi.Magnet(name, mi.HashInfoBytes()).String()
1008 tr, err := cl.AddTorrent(&mi)
1009 require.NoError(t, err)
1010 require.True(t, tr.Seeding())
1015 // https://github.com/anacrolix/torrent/issues/114
1016 func TestMultipleTorrentsWithEncryption(t *testing.T) {
1017 testSeederLeecherPair(
1019 func(cfg *ClientConfig) {
1020 cfg.HeaderObfuscationPolicy.Preferred = true
1021 cfg.HeaderObfuscationPolicy.RequirePreferred = true
1023 func(cfg *ClientConfig) {
1024 cfg.HeaderObfuscationPolicy.RequirePreferred = false
1029 // Test that the leecher can download a torrent in its entirety from the seeder. Note that the
1030 // seeder config is done first.
1031 func testSeederLeecherPair(t *testing.T, seeder func(*ClientConfig), leecher func(*ClientConfig)) {
1032 cfg := TestingConfig()
1034 cfg.DataDir = filepath.Join(cfg.DataDir, "server")
1035 os.Mkdir(cfg.DataDir, 0755)
1037 server, err := NewClient(cfg)
1038 require.NoError(t, err)
1039 defer server.Close()
1040 defer testutil.ExportStatusWriter(server, "s")()
1041 magnet1 := makeMagnet(t, server, cfg.DataDir, "test1")
1042 // Extra torrents are added to test the seeder having to match incoming obfuscated headers
1043 // against more than one torrent. See issue #114
1044 makeMagnet(t, server, cfg.DataDir, "test2")
1045 for i := 0; i < 100; i++ {
1046 makeMagnet(t, server, cfg.DataDir, fmt.Sprintf("test%d", i+2))
1048 cfg = TestingConfig()
1049 cfg.DataDir = filepath.Join(cfg.DataDir, "client")
1051 client, err := NewClient(cfg)
1052 require.NoError(t, err)
1053 defer client.Close()
1054 defer testutil.ExportStatusWriter(client, "c")()
1055 tr, err := client.AddMagnet(magnet1)
1056 require.NoError(t, err)
1057 tr.AddClientPeer(server)
1063 // This appears to be the situation with the S3 BitTorrent client.
1064 func TestObfuscatedHeaderFallbackSeederDisallowsLeecherPrefers(t *testing.T) {
1065 // Leecher prefers obfuscation, but the seeder does not allow it.
1066 testSeederLeecherPair(
1068 func(cfg *ClientConfig) {
1069 cfg.HeaderObfuscationPolicy.Preferred = false
1070 cfg.HeaderObfuscationPolicy.RequirePreferred = true
1072 func(cfg *ClientConfig) {
1073 cfg.HeaderObfuscationPolicy.Preferred = true
1074 cfg.HeaderObfuscationPolicy.RequirePreferred = false
1079 func TestObfuscatedHeaderFallbackSeederRequiresLeecherPrefersNot(t *testing.T) {
1080 // Leecher prefers no obfuscation, but the seeder enforces it.
1081 testSeederLeecherPair(
1083 func(cfg *ClientConfig) {
1084 cfg.HeaderObfuscationPolicy.Preferred = true
1085 cfg.HeaderObfuscationPolicy.RequirePreferred = true
1087 func(cfg *ClientConfig) {
1088 cfg.HeaderObfuscationPolicy.Preferred = false
1089 cfg.HeaderObfuscationPolicy.RequirePreferred = false
1094 func TestClientAddressInUse(t *testing.T) {
1095 s, _ := NewUtpSocket("udp", ":50007", nil)
1099 cfg := TestingConfig().SetListenAddr(":50007")
1100 cl, err := NewClient(cfg)
1101 require.Error(t, err)
1105 func TestClientHasDhtServersWhenUtpDisabled(t *testing.T) {
1106 cc := TestingConfig()
1107 cc.DisableUTP = true
1109 cl, err := NewClient(cc)
1110 require.NoError(t, err)
1112 assert.NotEmpty(t, cl.DhtServers())
1115 func TestIssue335(t *testing.T) {
1116 dir, mi := testutil.GreetingTestTorrent()
1117 defer os.RemoveAll(dir)
1118 cfg := TestingConfig()
1122 comp, err := storage.NewBoltPieceCompletion(dir)
1123 require.NoError(t, err)
1125 cfg.DefaultStorage = storage.NewMMapWithCompletion(dir, comp)
1126 cl, err := NewClient(cfg)
1127 require.NoError(t, err)
1129 tor, new, err := cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
1130 require.NoError(t, err)
1132 require.True(t, cl.WaitAll())
1134 _, new, err = cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
1135 require.NoError(t, err)
1137 require.True(t, cl.WaitAll())