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"
21 _ "github.com/anacrolix/envpprof"
22 "github.com/anacrolix/missinggo"
23 "github.com/anacrolix/missinggo/filecache"
24 "github.com/anacrolix/torrent/bencode"
25 "github.com/anacrolix/torrent/internal/testutil"
26 "github.com/anacrolix/torrent/iplist"
27 "github.com/anacrolix/torrent/metainfo"
28 "github.com/anacrolix/torrent/storage"
31 func TestingConfig() *ClientConfig {
32 cfg := NewDefaultClientConfig()
33 cfg.ListenHost = LoopbackListenHost
35 cfg.DataDir = tempDir()
36 cfg.DisableTrackers = true
37 cfg.NoDefaultPortForwarding = true
38 cfg.DisableAcceptRateLimiting = true
42 func TestClientDefault(t *testing.T) {
43 cl, err := NewClient(TestingConfig())
44 require.NoError(t, err)
48 func TestClientNilConfig(t *testing.T) {
49 cl, err := NewClient(nil)
50 require.NoError(t, err)
54 func TestBoltPieceCompletionClosedWhenClientClosed(t *testing.T) {
55 cfg := TestingConfig()
56 pc, err := storage.NewBoltPieceCompletion(cfg.DataDir)
57 require.NoError(t, err)
58 ci := storage.NewFileWithCompletion(cfg.DataDir, pc)
60 cfg.DefaultStorage = ci
61 cl, err := NewClient(cfg)
62 require.NoError(t, err)
64 // And again, https://github.com/anacrolix/torrent/issues/158
65 cl, err = NewClient(cfg)
66 require.NoError(t, err)
70 func TestAddDropTorrent(t *testing.T) {
71 cl, err := NewClient(TestingConfig())
72 require.NoError(t, err)
74 dir, mi := testutil.GreetingTestTorrent()
75 defer os.RemoveAll(dir)
76 tt, new, err := cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
77 require.NoError(t, err)
79 tt.SetMaxEstablishedConns(0)
80 tt.SetMaxEstablishedConns(1)
84 func TestAddTorrentNoSupportedTrackerSchemes(t *testing.T) {
89 func TestAddTorrentNoUsableURLs(t *testing.T) {
94 func TestAddPeersToUnknownTorrent(t *testing.T) {
99 func TestPieceHashSize(t *testing.T) {
100 assert.Equal(t, 20, pieceHash.Size())
103 func TestTorrentInitialState(t *testing.T) {
104 dir, mi := testutil.GreetingTestTorrent()
105 defer os.RemoveAll(dir)
107 config: &ClientConfig{},
110 tor := cl.newTorrent(
112 storage.NewFileWithCompletion(tempDir(), storage.NewMapPieceCompletion()),
116 err := tor.setInfoBytes(mi.InfoBytes)
118 require.NoError(t, err)
119 require.Len(t, tor.pieces, 3)
120 tor.pendAllChunkSpecs(0)
122 assert.EqualValues(t, 3, tor.pieceNumPendingChunks(0))
124 assert.EqualValues(t, chunkSpec{4, 1}, chunkIndexSpec(2, tor.pieceLength(0), tor.chunkSize))
127 func TestReducedDialTimeout(t *testing.T) {
128 cfg := NewDefaultClientConfig()
129 for _, _case := range []struct {
133 ExpectedReduced time.Duration
135 {cfg.NominalDialTimeout, 40, 0, cfg.NominalDialTimeout},
136 {cfg.NominalDialTimeout, 40, 1, cfg.NominalDialTimeout},
137 {cfg.NominalDialTimeout, 40, 39, cfg.NominalDialTimeout},
138 {cfg.NominalDialTimeout, 40, 40, cfg.NominalDialTimeout / 2},
139 {cfg.NominalDialTimeout, 40, 80, cfg.NominalDialTimeout / 3},
140 {cfg.NominalDialTimeout, 40, 4000, cfg.NominalDialTimeout / 101},
142 reduced := reducedDialTimeout(cfg.MinDialTimeout, _case.Max, _case.HalfOpenLimit, _case.PendingPeers)
143 expected := _case.ExpectedReduced
144 if expected < cfg.MinDialTimeout {
145 expected = cfg.MinDialTimeout
147 if reduced != expected {
148 t.Fatalf("expected %s, got %s", _case.ExpectedReduced, reduced)
153 func TestAddDropManyTorrents(t *testing.T) {
154 cl, err := NewClient(TestingConfig())
155 require.NoError(t, err)
157 for i := range iter.N(1000) {
159 binary.PutVarint(spec.InfoHash[:], int64(i))
160 tt, new, err := cl.AddTorrentSpec(&spec)
161 assert.NoError(t, err)
167 type FileCacheClientStorageFactoryParams struct {
170 Wrapper func(*filecache.Cache) storage.ClientImpl
173 func NewFileCacheClientStorageFactory(ps FileCacheClientStorageFactoryParams) storageFactory {
174 return func(dataDir string) storage.ClientImpl {
175 fc, err := filecache.NewCache(dataDir)
180 fc.SetCapacity(ps.Capacity)
182 return ps.Wrapper(fc)
186 type storageFactory func(string) storage.ClientImpl
188 func TestClientTransferDefault(t *testing.T) {
189 testClientTransfer(t, testClientTransferParams{
190 ExportClientStatus: true,
191 LeecherStorage: NewFileCacheClientStorageFactory(FileCacheClientStorageFactoryParams{
192 Wrapper: fileCachePieceResourceStorage,
197 func TestClientTransferRateLimitedUpload(t *testing.T) {
198 started := time.Now()
199 testClientTransfer(t, testClientTransferParams{
200 // We are uploading 13 bytes (the length of the greeting torrent). The
201 // chunks are 2 bytes in length. Then the smallest burst we can run
202 // with is 2. Time taken is (13-burst)/rate.
203 SeederUploadRateLimiter: rate.NewLimiter(11, 2),
204 ExportClientStatus: true,
206 require.True(t, time.Since(started) > time.Second)
209 func TestClientTransferRateLimitedDownload(t *testing.T) {
210 testClientTransfer(t, testClientTransferParams{
211 LeecherDownloadRateLimiter: rate.NewLimiter(512, 512),
215 func fileCachePieceResourceStorage(fc *filecache.Cache) storage.ClientImpl {
216 return storage.NewResourcePieces(fc.AsResourceProvider())
219 func TestClientTransferSmallCache(t *testing.T) {
220 testClientTransfer(t, testClientTransferParams{
221 LeecherStorage: NewFileCacheClientStorageFactory(FileCacheClientStorageFactoryParams{
223 // Going below the piece length means it can't complete a piece so
224 // that it can be hashed.
226 Wrapper: fileCachePieceResourceStorage,
229 // Can't readahead too far or the cache will thrash and drop data we
232 ExportClientStatus: true,
236 func TestClientTransferVarious(t *testing.T) {
238 for _, ls := range []storageFactory{
239 NewFileCacheClientStorageFactory(FileCacheClientStorageFactoryParams{
240 Wrapper: fileCachePieceResourceStorage,
245 for _, ss := range []func(string) storage.ClientImpl{
249 for _, responsive := range []bool{false, true} {
250 testClientTransfer(t, testClientTransferParams{
251 Responsive: responsive,
255 for _, readahead := range []int64{-1, 0, 1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14, 15, 20} {
256 testClientTransfer(t, testClientTransferParams{
258 Responsive: responsive,
260 Readahead: readahead,
269 type testClientTransferParams struct {
273 ExportClientStatus bool
274 LeecherStorage func(string) storage.ClientImpl
275 SeederStorage func(string) storage.ClientImpl
276 SeederUploadRateLimiter *rate.Limiter
277 LeecherDownloadRateLimiter *rate.Limiter
280 // Creates a seeder and a leecher, and ensures the data transfers when a read
281 // is attempted on the leecher.
282 func testClientTransfer(t *testing.T, ps testClientTransferParams) {
283 greetingTempDir, mi := testutil.GreetingTestTorrent()
284 defer os.RemoveAll(greetingTempDir)
285 // Create seeder and a Torrent.
286 cfg := TestingConfig()
288 if ps.SeederUploadRateLimiter != nil {
289 cfg.UploadRateLimiter = ps.SeederUploadRateLimiter
291 // cfg.ListenAddr = "localhost:4000"
292 if ps.SeederStorage != nil {
293 cfg.DefaultStorage = ps.SeederStorage(greetingTempDir)
294 defer cfg.DefaultStorage.Close()
296 cfg.DataDir = greetingTempDir
298 seeder, err := NewClient(cfg)
299 require.NoError(t, err)
300 if ps.ExportClientStatus {
301 defer testutil.ExportStatusWriter(seeder, "s")()
303 seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
304 // Run a Stats right after Closing the Client. This will trigger the Stats
305 // panic in #214 caused by RemoteAddr on Closed uTP sockets.
306 defer seederTorrent.Stats()
308 seederTorrent.VerifyData()
309 // Create leecher and a Torrent.
310 leecherDataDir, err := ioutil.TempDir("", "")
311 require.NoError(t, err)
312 defer os.RemoveAll(leecherDataDir)
313 cfg = TestingConfig()
314 if ps.LeecherStorage == nil {
315 cfg.DataDir = leecherDataDir
317 cfg.DefaultStorage = ps.LeecherStorage(leecherDataDir)
319 if ps.LeecherDownloadRateLimiter != nil {
320 cfg.DownloadRateLimiter = ps.LeecherDownloadRateLimiter
323 leecher, err := NewClient(cfg)
324 require.NoError(t, err)
325 defer leecher.Close()
326 if ps.ExportClientStatus {
327 defer testutil.ExportStatusWriter(leecher, "l")()
329 leecherTorrent, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
330 ret = TorrentSpecFromMetaInfo(mi)
334 require.NoError(t, err)
336 // Now do some things with leecher and seeder.
337 leecherTorrent.AddClientPeer(seeder)
338 // The Torrent should not be interested in obtaining peers, so the one we
339 // just added should be the only one.
340 assert.False(t, leecherTorrent.Seeding())
341 assert.EqualValues(t, 1, leecherTorrent.Stats().PendingPeers)
342 r := leecherTorrent.NewReader()
348 r.SetReadahead(ps.Readahead)
350 assertReadAllGreeting(t, r)
352 seederStats := seederTorrent.Stats()
353 assert.True(t, 13 <= seederStats.BytesWrittenData.Int64())
354 assert.True(t, 8 <= seederStats.ChunksWritten.Int64())
356 leecherStats := leecherTorrent.Stats()
357 assert.True(t, 13 <= leecherStats.BytesReadData.Int64())
358 assert.True(t, 8 <= leecherStats.ChunksRead.Int64())
360 // Try reading through again for the cases where the torrent data size
361 // exceeds the size of the cache.
362 assertReadAllGreeting(t, r)
365 func assertReadAllGreeting(t *testing.T, r io.ReadSeeker) {
366 pos, err := r.Seek(0, io.SeekStart)
367 assert.NoError(t, err)
368 assert.EqualValues(t, 0, pos)
369 _greeting, err := ioutil.ReadAll(r)
370 assert.NoError(t, err)
371 assert.EqualValues(t, testutil.GreetingFileContents, _greeting)
374 // Check that after completing leeching, a leecher transitions to a seeding
375 // correctly. Connected in a chain like so: Seeder <-> Leecher <-> LeecherLeecher.
376 func TestSeedAfterDownloading(t *testing.T) {
377 greetingTempDir, mi := testutil.GreetingTestTorrent()
378 defer os.RemoveAll(greetingTempDir)
380 cfg := TestingConfig()
382 cfg.DataDir = greetingTempDir
383 seeder, err := NewClient(cfg)
384 require.NoError(t, err)
386 defer testutil.ExportStatusWriter(seeder, "s")()
387 seederTorrent, ok, err := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
388 require.NoError(t, err)
390 seederTorrent.VerifyData()
392 cfg = TestingConfig()
394 cfg.DataDir, err = ioutil.TempDir("", "")
395 require.NoError(t, err)
396 defer os.RemoveAll(cfg.DataDir)
397 leecher, err := NewClient(cfg)
398 require.NoError(t, err)
399 defer leecher.Close()
400 defer testutil.ExportStatusWriter(leecher, "l")()
402 cfg = TestingConfig()
404 cfg.DataDir, err = ioutil.TempDir("", "")
405 require.NoError(t, err)
406 defer os.RemoveAll(cfg.DataDir)
407 leecherLeecher, _ := NewClient(cfg)
408 require.NoError(t, err)
409 defer leecherLeecher.Close()
410 defer testutil.ExportStatusWriter(leecherLeecher, "ll")()
411 leecherGreeting, ok, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
412 ret = TorrentSpecFromMetaInfo(mi)
416 require.NoError(t, err)
418 llg, ok, err := leecherLeecher.AddTorrentSpec(func() (ret *TorrentSpec) {
419 ret = TorrentSpecFromMetaInfo(mi)
423 require.NoError(t, err)
425 // Simultaneously DownloadAll in Leecher, and read the contents
426 // consecutively in LeecherLeecher. This non-deterministically triggered a
427 // case where the leecher wouldn't unchoke the LeecherLeecher.
428 var wg sync.WaitGroup
434 b, err := ioutil.ReadAll(r)
435 require.NoError(t, err)
436 assert.EqualValues(t, testutil.GreetingFileContents, b)
438 done := make(chan struct{})
440 go leecherGreeting.AddClientPeer(seeder)
441 go leecherGreeting.AddClientPeer(leecherLeecher)
445 leecherGreeting.DownloadAll()
451 func TestMergingTrackersByAddingSpecs(t *testing.T) {
452 cl, err := NewClient(TestingConfig())
453 require.NoError(t, err)
455 spec := TorrentSpec{}
456 T, new, _ := cl.AddTorrentSpec(&spec)
460 spec.Trackers = [][]string{{"http://a"}, {"udp://b"}}
461 _, new, _ = cl.AddTorrentSpec(&spec)
463 assert.EqualValues(t, [][]string{{"http://a"}, {"udp://b"}}, T.metainfo.AnnounceList)
464 // Because trackers are disabled in TestingConfig.
465 assert.EqualValues(t, 0, len(T.trackerAnnouncers))
468 // We read from a piece which is marked completed, but is missing data.
469 func TestCompletedPieceWrongSize(t *testing.T) {
470 cfg := TestingConfig()
471 cfg.DefaultStorage = badStorage{}
472 cl, err := NewClient(cfg)
473 require.NoError(t, err)
475 info := metainfo.Info{
477 Pieces: make([]byte, 20),
478 Files: []metainfo.FileInfo{
479 {Path: []string{"greeting"}, Length: 13},
482 b, err := bencode.Marshal(info)
483 require.NoError(t, err)
484 tt, new, err := cl.AddTorrentSpec(&TorrentSpec{
486 InfoHash: metainfo.HashBytes(b),
488 require.NoError(t, err)
493 b, err = ioutil.ReadAll(r)
495 assert.NoError(t, err)
498 func BenchmarkAddLargeTorrent(b *testing.B) {
499 cfg := TestingConfig()
500 cfg.DisableTCP = true
501 cfg.DisableUTP = true
502 cl, err := NewClient(cfg)
503 require.NoError(b, err)
506 for range iter.N(b.N) {
507 t, err := cl.AddTorrentFromFile("testdata/bootstrap.dat.torrent")
515 func TestResponsive(t *testing.T) {
516 seederDataDir, mi := testutil.GreetingTestTorrent()
517 defer os.RemoveAll(seederDataDir)
518 cfg := TestingConfig()
520 cfg.DataDir = seederDataDir
521 seeder, err := NewClient(cfg)
524 seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
525 seederTorrent.VerifyData()
526 leecherDataDir, err := ioutil.TempDir("", "")
528 defer os.RemoveAll(leecherDataDir)
529 cfg = TestingConfig()
530 cfg.DataDir = leecherDataDir
531 leecher, err := NewClient(cfg)
533 defer leecher.Close()
534 leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
535 ret = TorrentSpecFromMetaInfo(mi)
539 leecherTorrent.AddClientPeer(seeder)
540 reader := leecherTorrent.NewReader()
542 reader.SetReadahead(0)
543 reader.SetResponsive()
545 _, err = reader.Seek(3, io.SeekStart)
546 require.NoError(t, err)
547 _, err = io.ReadFull(reader, b)
549 assert.EqualValues(t, "lo", string(b))
550 _, err = reader.Seek(11, io.SeekStart)
551 require.NoError(t, err)
552 n, err := io.ReadFull(reader, b)
554 assert.EqualValues(t, 2, n)
555 assert.EqualValues(t, "d\n", string(b))
558 func TestTorrentDroppedDuringResponsiveRead(t *testing.T) {
559 seederDataDir, mi := testutil.GreetingTestTorrent()
560 defer os.RemoveAll(seederDataDir)
561 cfg := TestingConfig()
563 cfg.DataDir = seederDataDir
564 seeder, err := NewClient(cfg)
567 seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
568 seederTorrent.VerifyData()
569 leecherDataDir, err := ioutil.TempDir("", "")
571 defer os.RemoveAll(leecherDataDir)
572 cfg = TestingConfig()
573 cfg.DataDir = leecherDataDir
574 leecher, err := NewClient(cfg)
576 defer leecher.Close()
577 leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
578 ret = TorrentSpecFromMetaInfo(mi)
582 leecherTorrent.AddClientPeer(seeder)
583 reader := leecherTorrent.NewReader()
585 reader.SetReadahead(0)
586 reader.SetResponsive()
588 _, err = reader.Seek(3, io.SeekStart)
589 require.NoError(t, err)
590 _, err = io.ReadFull(reader, b)
592 assert.EqualValues(t, "lo", string(b))
593 go leecherTorrent.Drop()
594 _, err = reader.Seek(11, io.SeekStart)
595 require.NoError(t, err)
596 n, err := reader.Read(b)
597 assert.EqualError(t, err, "torrent closed")
598 assert.EqualValues(t, 0, n)
601 func TestDHTInheritBlocklist(t *testing.T) {
602 ipl := iplist.New(nil)
603 require.NotNil(t, ipl)
604 cfg := TestingConfig()
605 cfg.IPBlocklist = ipl
607 cl, err := NewClient(cfg)
608 require.NoError(t, err)
611 cl.eachDhtServer(func(s *dht.Server) {
612 assert.Equal(t, ipl, s.IPBlocklist())
615 assert.EqualValues(t, 2, numServers)
618 // Check that stuff is merged in subsequent AddTorrentSpec for the same
620 func TestAddTorrentSpecMerging(t *testing.T) {
621 cl, err := NewClient(TestingConfig())
622 require.NoError(t, err)
624 dir, mi := testutil.GreetingTestTorrent()
625 defer os.RemoveAll(dir)
626 tt, new, err := cl.AddTorrentSpec(&TorrentSpec{
627 InfoHash: mi.HashInfoBytes(),
629 require.NoError(t, err)
631 require.Nil(t, tt.Info())
632 _, new, err = cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
633 require.NoError(t, err)
634 require.False(t, new)
635 require.NotNil(t, tt.Info())
638 func TestTorrentDroppedBeforeGotInfo(t *testing.T) {
639 dir, mi := testutil.GreetingTestTorrent()
641 cl, _ := NewClient(TestingConfig())
643 tt, _, _ := cl.AddTorrentSpec(&TorrentSpec{
644 InfoHash: mi.HashInfoBytes(),
647 assert.EqualValues(t, 0, len(cl.Torrents()))
655 func writeTorrentData(ts *storage.Torrent, info metainfo.Info, b []byte) {
656 for i := range iter.N(info.NumPieces()) {
658 ts.Piece(p).WriteAt(b[p.Offset():p.Offset()+p.Length()], 0)
662 func testAddTorrentPriorPieceCompletion(t *testing.T, alreadyCompleted bool, csf func(*filecache.Cache) storage.ClientImpl) {
663 fileCacheDir, err := ioutil.TempDir("", "")
664 require.NoError(t, err)
665 defer os.RemoveAll(fileCacheDir)
666 fileCache, err := filecache.NewCache(fileCacheDir)
667 require.NoError(t, err)
668 greetingDataTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
669 defer os.RemoveAll(greetingDataTempDir)
670 filePieceStore := csf(fileCache)
671 defer filePieceStore.Close()
672 info, err := greetingMetainfo.UnmarshalInfo()
673 require.NoError(t, err)
674 ih := greetingMetainfo.HashInfoBytes()
675 greetingData, err := storage.NewClient(filePieceStore).OpenTorrent(&info, ih)
676 require.NoError(t, err)
677 writeTorrentData(greetingData, info, []byte(testutil.GreetingFileContents))
678 // require.Equal(t, len(testutil.GreetingFileContents), written)
679 // require.NoError(t, err)
680 for i := 0; i < info.NumPieces(); i++ {
682 if alreadyCompleted {
683 require.NoError(t, greetingData.Piece(p).MarkComplete())
686 cfg := TestingConfig()
687 // TODO: Disable network option?
688 cfg.DisableTCP = true
689 cfg.DisableUTP = true
690 cfg.DefaultStorage = filePieceStore
691 cl, err := NewClient(cfg)
692 require.NoError(t, err)
694 tt, err := cl.AddTorrent(greetingMetainfo)
695 require.NoError(t, err)
696 psrs := tt.PieceStateRuns()
697 assert.Len(t, psrs, 1)
698 assert.EqualValues(t, 3, psrs[0].Length)
699 assert.Equal(t, alreadyCompleted, psrs[0].Complete)
700 if alreadyCompleted {
702 b, err := ioutil.ReadAll(r)
703 assert.NoError(t, err)
704 assert.EqualValues(t, testutil.GreetingFileContents, b)
708 func TestAddTorrentPiecesAlreadyCompleted(t *testing.T) {
709 testAddTorrentPriorPieceCompletion(t, true, fileCachePieceResourceStorage)
712 func TestAddTorrentPiecesNotAlreadyCompleted(t *testing.T) {
713 testAddTorrentPriorPieceCompletion(t, false, fileCachePieceResourceStorage)
716 func TestAddMetainfoWithNodes(t *testing.T) {
717 cfg := TestingConfig()
718 cfg.ListenHost = func(string) string { return "" }
720 cfg.DhtStartingNodes = func() ([]dht.Addr, error) { return nil, nil }
721 // For now, we want to just jam the nodes into the table, without
722 // verifying them first. Also the DHT code doesn't support mixing secure
723 // and insecure nodes if security is enabled (yet).
724 // cfg.DHTConfig.NoSecurity = true
725 cl, err := NewClient(cfg)
726 require.NoError(t, err)
728 sum := func() (ret int64) {
729 cl.eachDhtServer(func(s *dht.Server) {
730 ret += s.Stats().OutboundQueriesAttempted
734 assert.EqualValues(t, 0, sum())
735 tt, err := cl.AddTorrentFromFile("metainfo/testdata/issue_65a.torrent")
736 require.NoError(t, err)
737 // Nodes are not added or exposed in Torrent's metainfo. We just randomly
738 // check if the announce-list is here instead. TODO: Add nodes.
739 assert.Len(t, tt.metainfo.AnnounceList, 5)
740 // There are 6 nodes in the torrent file.
741 for sum() != int64(6*len(cl.dhtServers)) {
742 time.Sleep(time.Millisecond)
746 type testDownloadCancelParams struct {
747 SetLeecherStorageCapacity bool
748 LeecherStorageCapacity int64
752 func testDownloadCancel(t *testing.T, ps testDownloadCancelParams) {
753 greetingTempDir, mi := testutil.GreetingTestTorrent()
754 defer os.RemoveAll(greetingTempDir)
755 cfg := TestingConfig()
757 cfg.DataDir = greetingTempDir
758 seeder, err := NewClient(cfg)
759 require.NoError(t, err)
761 defer testutil.ExportStatusWriter(seeder, "s")()
762 seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
763 seederTorrent.VerifyData()
764 leecherDataDir, err := ioutil.TempDir("", "")
765 require.NoError(t, err)
766 defer os.RemoveAll(leecherDataDir)
767 fc, err := filecache.NewCache(leecherDataDir)
768 require.NoError(t, err)
769 if ps.SetLeecherStorageCapacity {
770 fc.SetCapacity(ps.LeecherStorageCapacity)
772 cfg.DefaultStorage = storage.NewResourcePieces(fc.AsResourceProvider())
773 cfg.DataDir = leecherDataDir
774 leecher, _ := NewClient(cfg)
775 defer leecher.Close()
776 defer testutil.ExportStatusWriter(leecher, "l")()
777 leecherGreeting, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
778 ret = TorrentSpecFromMetaInfo(mi)
782 require.NoError(t, err)
784 psc := leecherGreeting.SubscribePieceStateChanges()
787 leecherGreeting.cl.lock()
788 leecherGreeting.downloadPiecesLocked(0, leecherGreeting.numPieces())
790 leecherGreeting.cancelPiecesLocked(0, leecherGreeting.NumPieces())
792 leecherGreeting.cl.unlock()
793 done := make(chan struct{})
795 go leecherGreeting.AddClientPeer(seeder)
796 completes := make(map[int]bool, 3)
797 expected := func() map[int]bool {
799 return map[int]bool{0: false, 1: false, 2: false}
801 return map[int]bool{0: true, 1: true, 2: true}
804 for !reflect.DeepEqual(completes, expected) {
806 v := _v.(PieceStateChange)
807 completes[v.Index] = v.Complete
811 func TestTorrentDownloadAll(t *testing.T) {
812 testDownloadCancel(t, testDownloadCancelParams{})
815 func TestTorrentDownloadAllThenCancel(t *testing.T) {
816 testDownloadCancel(t, testDownloadCancelParams{
821 // Ensure that it's an error for a peer to send an invalid have message.
822 func TestPeerInvalidHave(t *testing.T) {
823 cl, err := NewClient(TestingConfig())
824 require.NoError(t, err)
826 info := metainfo.Info{
828 Pieces: make([]byte, 20),
829 Files: []metainfo.FileInfo{{Length: 1}},
831 infoBytes, err := bencode.Marshal(info)
832 require.NoError(t, err)
833 tt, _new, err := cl.AddTorrentSpec(&TorrentSpec{
834 InfoBytes: infoBytes,
835 InfoHash: metainfo.HashBytes(infoBytes),
836 Storage: badStorage{},
838 require.NoError(t, err)
844 assert.NoError(t, cn.peerSentHave(0))
845 assert.Error(t, cn.peerSentHave(1))
848 func TestPieceCompletedInStorageButNotClient(t *testing.T) {
849 greetingTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
850 defer os.RemoveAll(greetingTempDir)
851 cfg := TestingConfig()
852 cfg.DataDir = greetingTempDir
853 seeder, err := NewClient(TestingConfig())
854 require.NoError(t, err)
855 seeder.AddTorrentSpec(&TorrentSpec{
856 InfoBytes: greetingMetainfo.InfoBytes,
860 // Check that when the listen port is 0, all the protocols listened on have
861 // the same port, and it isn't zero.
862 func TestClientDynamicListenPortAllProtocols(t *testing.T) {
863 cl, err := NewClient(TestingConfig())
864 require.NoError(t, err)
866 port := cl.LocalPort()
867 assert.NotEqual(t, 0, port)
868 cl.eachListener(func(s socket) bool {
869 assert.Equal(t, port, missinggo.AddrPort(s.Addr()))
874 func TestClientDynamicListenTCPOnly(t *testing.T) {
875 cfg := TestingConfig()
876 cfg.DisableUTP = true
877 cfg.DisableTCP = false
878 cl, err := NewClient(cfg)
879 require.NoError(t, err)
881 assert.NotEqual(t, 0, cl.LocalPort())
884 func TestClientDynamicListenUTPOnly(t *testing.T) {
885 cfg := TestingConfig()
886 cfg.DisableTCP = true
887 cfg.DisableUTP = false
888 cl, err := NewClient(cfg)
889 require.NoError(t, err)
891 assert.NotEqual(t, 0, cl.LocalPort())
894 func totalConns(tts []*Torrent) (ret int) {
895 for _, tt := range tts {
903 func TestSetMaxEstablishedConn(t *testing.T) {
905 ih := testutil.GreetingMetaInfo().HashInfoBytes()
906 cfg := TestingConfig()
907 cfg.DisableAcceptRateLimiting = true
908 cfg.dropDuplicatePeerIds = true
909 for i := range iter.N(3) {
910 cl, err := NewClient(cfg)
911 require.NoError(t, err)
913 tt, _ := cl.AddTorrentInfoHash(ih)
914 tt.SetMaxEstablishedConns(2)
915 defer testutil.ExportStatusWriter(cl, fmt.Sprintf("%d", i))()
916 tts = append(tts, tt)
919 for _, tt := range tts {
920 for _, _tt := range tts {
922 tt.AddClientPeer(_tt.cl)
927 waitTotalConns := func(num int) {
928 for totalConns(tts) != num {
930 time.Sleep(time.Millisecond)
935 tts[0].SetMaxEstablishedConns(1)
937 tts[0].SetMaxEstablishedConns(0)
939 tts[0].SetMaxEstablishedConns(1)
942 tts[0].SetMaxEstablishedConns(2)
947 // Creates a file containing its own name as data. Make a metainfo from that, adds it to the given
948 // client, and returns a magnet link.
949 func makeMagnet(t *testing.T, cl *Client, dir string, name string) string {
950 os.MkdirAll(dir, 0770)
951 file, err := os.Create(filepath.Join(dir, name))
952 require.NoError(t, err)
953 file.Write([]byte(name))
955 mi := metainfo.MetaInfo{}
957 info := metainfo.Info{PieceLength: 256 * 1024}
958 err = info.BuildFromFilePath(filepath.Join(dir, name))
959 require.NoError(t, err)
960 mi.InfoBytes, err = bencode.Marshal(info)
961 require.NoError(t, err)
962 magnet := mi.Magnet(name, mi.HashInfoBytes()).String()
963 tr, err := cl.AddTorrent(&mi)
964 require.NoError(t, err)
965 require.True(t, tr.Seeding())
970 // https://github.com/anacrolix/torrent/issues/114
971 func TestMultipleTorrentsWithEncryption(t *testing.T) {
972 testSeederLeecherPair(
974 func(cfg *ClientConfig) {
975 cfg.HeaderObfuscationPolicy.Preferred = true
976 cfg.HeaderObfuscationPolicy.RequirePreferred = true
978 func(cfg *ClientConfig) {
979 cfg.HeaderObfuscationPolicy.RequirePreferred = false
984 // Test that the leecher can download a torrent in its entirety from the seeder. Note that the
985 // seeder config is done first.
986 func testSeederLeecherPair(t *testing.T, seeder func(*ClientConfig), leecher func(*ClientConfig)) {
987 cfg := TestingConfig()
989 cfg.DataDir = filepath.Join(cfg.DataDir, "server")
990 os.Mkdir(cfg.DataDir, 0755)
992 server, err := NewClient(cfg)
993 require.NoError(t, err)
995 defer testutil.ExportStatusWriter(server, "s")()
996 magnet1 := makeMagnet(t, server, cfg.DataDir, "test1")
997 // Extra torrents are added to test the seeder having to match incoming obfuscated headers
998 // against more than one torrent. See issue #114
999 makeMagnet(t, server, cfg.DataDir, "test2")
1000 for i := 0; i < 100; i++ {
1001 makeMagnet(t, server, cfg.DataDir, fmt.Sprintf("test%d", i+2))
1003 cfg = TestingConfig()
1004 cfg.DataDir = filepath.Join(cfg.DataDir, "client")
1006 client, err := NewClient(cfg)
1007 require.NoError(t, err)
1008 defer client.Close()
1009 defer testutil.ExportStatusWriter(client, "c")()
1010 tr, err := client.AddMagnet(magnet1)
1011 require.NoError(t, err)
1012 tr.AddClientPeer(server)
1018 // This appears to be the situation with the S3 BitTorrent client.
1019 func TestObfuscatedHeaderFallbackSeederDisallowsLeecherPrefers(t *testing.T) {
1020 // Leecher prefers obfuscation, but the seeder does not allow it.
1021 testSeederLeecherPair(
1023 func(cfg *ClientConfig) {
1024 cfg.HeaderObfuscationPolicy.Preferred = false
1025 cfg.HeaderObfuscationPolicy.RequirePreferred = true
1027 func(cfg *ClientConfig) {
1028 cfg.HeaderObfuscationPolicy.Preferred = true
1029 cfg.HeaderObfuscationPolicy.RequirePreferred = false
1034 func TestObfuscatedHeaderFallbackSeederRequiresLeecherPrefersNot(t *testing.T) {
1035 // Leecher prefers no obfuscation, but the seeder enforces it.
1036 testSeederLeecherPair(
1038 func(cfg *ClientConfig) {
1039 cfg.HeaderObfuscationPolicy.Preferred = true
1040 cfg.HeaderObfuscationPolicy.RequirePreferred = true
1042 func(cfg *ClientConfig) {
1043 cfg.HeaderObfuscationPolicy.Preferred = false
1044 cfg.HeaderObfuscationPolicy.RequirePreferred = false
1049 func TestClientAddressInUse(t *testing.T) {
1050 s, _ := NewUtpSocket("udp", ":50007", nil)
1054 cfg := TestingConfig().SetListenAddr(":50007")
1055 cl, err := NewClient(cfg)
1056 require.Error(t, err)
1060 func TestClientHasDhtServersWhenUtpDisabled(t *testing.T) {
1061 cc := TestingConfig()
1062 cc.DisableUTP = true
1064 cl, err := NewClient(cc)
1065 require.NoError(t, err)
1067 assert.NotEmpty(t, cl.DhtServers())