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/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
43 func TestClientDefault(t *testing.T) {
44 cl, err := NewClient(TestingConfig())
45 require.NoError(t, err)
49 func TestClientNilConfig(t *testing.T) {
50 cl, err := NewClient(nil)
51 require.NoError(t, err)
55 func TestBoltPieceCompletionClosedWhenClientClosed(t *testing.T) {
56 cfg := TestingConfig()
57 pc, err := storage.NewBoltPieceCompletion(cfg.DataDir)
58 require.NoError(t, err)
59 ci := storage.NewFileWithCompletion(cfg.DataDir, pc)
61 cfg.DefaultStorage = ci
62 cl, err := NewClient(cfg)
63 require.NoError(t, err)
65 // And again, https://github.com/anacrolix/torrent/issues/158
66 cl, err = NewClient(cfg)
67 require.NoError(t, err)
71 func TestAddDropTorrent(t *testing.T) {
72 cl, err := NewClient(TestingConfig())
73 require.NoError(t, err)
75 dir, mi := testutil.GreetingTestTorrent()
76 defer os.RemoveAll(dir)
77 tt, new, err := cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
78 require.NoError(t, err)
80 tt.SetMaxEstablishedConns(0)
81 tt.SetMaxEstablishedConns(1)
85 func TestAddTorrentNoSupportedTrackerSchemes(t *testing.T) {
90 func TestAddTorrentNoUsableURLs(t *testing.T) {
95 func TestAddPeersToUnknownTorrent(t *testing.T) {
100 func TestPieceHashSize(t *testing.T) {
101 assert.Equal(t, 20, pieceHash.Size())
104 func TestTorrentInitialState(t *testing.T) {
105 dir, mi := testutil.GreetingTestTorrent()
106 defer os.RemoveAll(dir)
108 config: &ClientConfig{},
111 tor := cl.newTorrent(
113 storage.NewFileWithCompletion(tempDir(), storage.NewMapPieceCompletion()),
117 err := tor.setInfoBytes(mi.InfoBytes)
119 require.NoError(t, err)
120 require.Len(t, tor.pieces, 3)
121 tor.pendAllChunkSpecs(0)
123 assert.EqualValues(t, 3, tor.pieceNumPendingChunks(0))
125 assert.EqualValues(t, chunkSpec{4, 1}, chunkIndexSpec(2, tor.pieceLength(0), tor.chunkSize))
128 func TestReducedDialTimeout(t *testing.T) {
129 cfg := NewDefaultClientConfig()
130 for _, _case := range []struct {
134 ExpectedReduced time.Duration
136 {cfg.NominalDialTimeout, 40, 0, cfg.NominalDialTimeout},
137 {cfg.NominalDialTimeout, 40, 1, cfg.NominalDialTimeout},
138 {cfg.NominalDialTimeout, 40, 39, cfg.NominalDialTimeout},
139 {cfg.NominalDialTimeout, 40, 40, cfg.NominalDialTimeout / 2},
140 {cfg.NominalDialTimeout, 40, 80, cfg.NominalDialTimeout / 3},
141 {cfg.NominalDialTimeout, 40, 4000, cfg.NominalDialTimeout / 101},
143 reduced := reducedDialTimeout(cfg.MinDialTimeout, _case.Max, _case.HalfOpenLimit, _case.PendingPeers)
144 expected := _case.ExpectedReduced
145 if expected < cfg.MinDialTimeout {
146 expected = cfg.MinDialTimeout
148 if reduced != expected {
149 t.Fatalf("expected %s, got %s", _case.ExpectedReduced, reduced)
154 func TestAddDropManyTorrents(t *testing.T) {
155 cl, err := NewClient(TestingConfig())
156 require.NoError(t, err)
158 for i := range iter.N(1000) {
160 binary.PutVarint(spec.InfoHash[:], int64(i))
161 tt, new, err := cl.AddTorrentSpec(&spec)
162 assert.NoError(t, err)
168 type FileCacheClientStorageFactoryParams struct {
171 Wrapper func(*filecache.Cache) storage.ClientImpl
174 func NewFileCacheClientStorageFactory(ps FileCacheClientStorageFactoryParams) storageFactory {
175 return func(dataDir string) storage.ClientImpl {
176 fc, err := filecache.NewCache(dataDir)
181 fc.SetCapacity(ps.Capacity)
183 return ps.Wrapper(fc)
187 type storageFactory func(string) storage.ClientImpl
189 func TestClientTransferDefault(t *testing.T) {
190 testClientTransfer(t, testClientTransferParams{
191 ExportClientStatus: true,
192 LeecherStorage: NewFileCacheClientStorageFactory(FileCacheClientStorageFactoryParams{
193 Wrapper: fileCachePieceResourceStorage,
198 func TestClientTransferRateLimitedUpload(t *testing.T) {
199 started := time.Now()
200 testClientTransfer(t, testClientTransferParams{
201 // We are uploading 13 bytes (the length of the greeting torrent). The
202 // chunks are 2 bytes in length. Then the smallest burst we can run
203 // with is 2. Time taken is (13-burst)/rate.
204 SeederUploadRateLimiter: rate.NewLimiter(11, 2),
205 ExportClientStatus: true,
207 require.True(t, time.Since(started) > time.Second)
210 func TestClientTransferRateLimitedDownload(t *testing.T) {
211 testClientTransfer(t, testClientTransferParams{
212 LeecherDownloadRateLimiter: rate.NewLimiter(512, 512),
216 func fileCachePieceResourceStorage(fc *filecache.Cache) storage.ClientImpl {
217 return storage.NewResourcePieces(fc.AsResourceProvider())
220 func TestClientTransferSmallCache(t *testing.T) {
221 testClientTransfer(t, testClientTransferParams{
222 LeecherStorage: NewFileCacheClientStorageFactory(FileCacheClientStorageFactoryParams{
224 // Going below the piece length means it can't complete a piece so
225 // that it can be hashed.
227 Wrapper: fileCachePieceResourceStorage,
230 // Can't readahead too far or the cache will thrash and drop data we
233 ExportClientStatus: true,
237 func TestClientTransferVarious(t *testing.T) {
239 for _, ls := range []storageFactory{
240 NewFileCacheClientStorageFactory(FileCacheClientStorageFactoryParams{
241 Wrapper: fileCachePieceResourceStorage,
246 for _, ss := range []func(string) storage.ClientImpl{
250 for _, responsive := range []bool{false, true} {
251 testClientTransfer(t, testClientTransferParams{
252 Responsive: responsive,
256 for _, readahead := range []int64{-1, 0, 1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14, 15, 20} {
257 testClientTransfer(t, testClientTransferParams{
259 Responsive: responsive,
261 Readahead: readahead,
270 type testClientTransferParams struct {
274 ExportClientStatus bool
275 LeecherStorage func(string) storage.ClientImpl
276 SeederStorage func(string) storage.ClientImpl
277 SeederUploadRateLimiter *rate.Limiter
278 LeecherDownloadRateLimiter *rate.Limiter
281 // Creates a seeder and a leecher, and ensures the data transfers when a read
282 // is attempted on the leecher.
283 func testClientTransfer(t *testing.T, ps testClientTransferParams) {
284 greetingTempDir, mi := testutil.GreetingTestTorrent()
285 defer os.RemoveAll(greetingTempDir)
286 // Create seeder and a Torrent.
287 cfg := TestingConfig()
289 if ps.SeederUploadRateLimiter != nil {
290 cfg.UploadRateLimiter = ps.SeederUploadRateLimiter
292 // cfg.ListenAddr = "localhost:4000"
293 if ps.SeederStorage != nil {
294 cfg.DefaultStorage = ps.SeederStorage(greetingTempDir)
295 defer cfg.DefaultStorage.Close()
297 cfg.DataDir = greetingTempDir
299 seeder, err := NewClient(cfg)
300 require.NoError(t, err)
301 if ps.ExportClientStatus {
302 defer testutil.ExportStatusWriter(seeder, "s")()
304 seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
305 // Run a Stats right after Closing the Client. This will trigger the Stats
306 // panic in #214 caused by RemoteAddr on Closed uTP sockets.
307 defer seederTorrent.Stats()
309 seederTorrent.VerifyData()
310 // Create leecher and a Torrent.
311 leecherDataDir, err := ioutil.TempDir("", "")
312 require.NoError(t, err)
313 defer os.RemoveAll(leecherDataDir)
314 cfg = TestingConfig()
315 if ps.LeecherStorage == nil {
316 cfg.DataDir = leecherDataDir
318 cfg.DefaultStorage = ps.LeecherStorage(leecherDataDir)
320 if ps.LeecherDownloadRateLimiter != nil {
321 cfg.DownloadRateLimiter = ps.LeecherDownloadRateLimiter
324 leecher, err := NewClient(cfg)
325 require.NoError(t, err)
326 defer leecher.Close()
327 if ps.ExportClientStatus {
328 defer testutil.ExportStatusWriter(leecher, "l")()
330 leecherTorrent, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
331 ret = TorrentSpecFromMetaInfo(mi)
335 require.NoError(t, err)
337 // Now do some things with leecher and seeder.
338 leecherTorrent.AddClientPeer(seeder)
339 // The Torrent should not be interested in obtaining peers, so the one we
340 // just added should be the only one.
341 assert.False(t, leecherTorrent.Seeding())
342 assert.EqualValues(t, 1, leecherTorrent.Stats().PendingPeers)
343 r := leecherTorrent.NewReader()
349 r.SetReadahead(ps.Readahead)
351 assertReadAllGreeting(t, r)
353 seederStats := seederTorrent.Stats()
354 assert.True(t, 13 <= seederStats.BytesWrittenData.Int64())
355 assert.True(t, 8 <= seederStats.ChunksWritten.Int64())
357 leecherStats := leecherTorrent.Stats()
358 assert.True(t, 13 <= leecherStats.BytesReadData.Int64())
359 assert.True(t, 8 <= leecherStats.ChunksRead.Int64())
361 // Try reading through again for the cases where the torrent data size
362 // exceeds the size of the cache.
363 assertReadAllGreeting(t, r)
366 func assertReadAllGreeting(t *testing.T, r io.ReadSeeker) {
367 pos, err := r.Seek(0, io.SeekStart)
368 assert.NoError(t, err)
369 assert.EqualValues(t, 0, pos)
370 _greeting, err := ioutil.ReadAll(r)
371 assert.NoError(t, err)
372 assert.EqualValues(t, testutil.GreetingFileContents, _greeting)
375 // Check that after completing leeching, a leecher transitions to a seeding
376 // correctly. Connected in a chain like so: Seeder <-> Leecher <-> LeecherLeecher.
377 func TestSeedAfterDownloading(t *testing.T) {
378 greetingTempDir, mi := testutil.GreetingTestTorrent()
379 defer os.RemoveAll(greetingTempDir)
381 cfg := TestingConfig()
383 cfg.DataDir = greetingTempDir
384 seeder, err := NewClient(cfg)
385 require.NoError(t, err)
387 defer testutil.ExportStatusWriter(seeder, "s")()
388 seederTorrent, ok, err := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
389 require.NoError(t, err)
391 seederTorrent.VerifyData()
393 cfg = TestingConfig()
395 cfg.DataDir, err = ioutil.TempDir("", "")
396 require.NoError(t, err)
397 defer os.RemoveAll(cfg.DataDir)
398 leecher, err := NewClient(cfg)
399 require.NoError(t, err)
400 defer leecher.Close()
401 defer testutil.ExportStatusWriter(leecher, "l")()
403 cfg = TestingConfig()
405 cfg.DataDir, err = ioutil.TempDir("", "")
406 require.NoError(t, err)
407 defer os.RemoveAll(cfg.DataDir)
408 leecherLeecher, _ := NewClient(cfg)
409 require.NoError(t, err)
410 defer leecherLeecher.Close()
411 defer testutil.ExportStatusWriter(leecherLeecher, "ll")()
412 leecherGreeting, ok, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
413 ret = TorrentSpecFromMetaInfo(mi)
417 require.NoError(t, err)
419 llg, ok, err := leecherLeecher.AddTorrentSpec(func() (ret *TorrentSpec) {
420 ret = TorrentSpecFromMetaInfo(mi)
424 require.NoError(t, err)
426 // Simultaneously DownloadAll in Leecher, and read the contents
427 // consecutively in LeecherLeecher. This non-deterministically triggered a
428 // case where the leecher wouldn't unchoke the LeecherLeecher.
429 var wg sync.WaitGroup
435 b, err := ioutil.ReadAll(r)
436 require.NoError(t, err)
437 assert.EqualValues(t, testutil.GreetingFileContents, b)
439 done := make(chan struct{})
441 go leecherGreeting.AddClientPeer(seeder)
442 go leecherGreeting.AddClientPeer(leecherLeecher)
446 leecherGreeting.DownloadAll()
452 func TestMergingTrackersByAddingSpecs(t *testing.T) {
453 cl, err := NewClient(TestingConfig())
454 require.NoError(t, err)
456 spec := TorrentSpec{}
457 T, new, _ := cl.AddTorrentSpec(&spec)
461 spec.Trackers = [][]string{{"http://a"}, {"udp://b"}}
462 _, new, _ = cl.AddTorrentSpec(&spec)
464 assert.EqualValues(t, [][]string{{"http://a"}, {"udp://b"}}, T.metainfo.AnnounceList)
465 // Because trackers are disabled in TestingConfig.
466 assert.EqualValues(t, 0, len(T.trackerAnnouncers))
469 // We read from a piece which is marked completed, but is missing data.
470 func TestCompletedPieceWrongSize(t *testing.T) {
471 cfg := TestingConfig()
472 cfg.DefaultStorage = badStorage{}
473 cl, err := NewClient(cfg)
474 require.NoError(t, err)
476 info := metainfo.Info{
478 Pieces: make([]byte, 20),
479 Files: []metainfo.FileInfo{
480 {Path: []string{"greeting"}, Length: 13},
483 b, err := bencode.Marshal(info)
484 require.NoError(t, err)
485 tt, new, err := cl.AddTorrentSpec(&TorrentSpec{
487 InfoHash: metainfo.HashBytes(b),
489 require.NoError(t, err)
494 b, err = ioutil.ReadAll(r)
496 assert.NoError(t, err)
499 func BenchmarkAddLargeTorrent(b *testing.B) {
500 cfg := TestingConfig()
501 cfg.DisableTCP = true
502 cfg.DisableUTP = true
503 cl, err := NewClient(cfg)
504 require.NoError(b, err)
507 for range iter.N(b.N) {
508 t, err := cl.AddTorrentFromFile("testdata/bootstrap.dat.torrent")
516 func TestResponsive(t *testing.T) {
517 seederDataDir, mi := testutil.GreetingTestTorrent()
518 defer os.RemoveAll(seederDataDir)
519 cfg := TestingConfig()
521 cfg.DataDir = seederDataDir
522 seeder, err := NewClient(cfg)
525 seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
526 seederTorrent.VerifyData()
527 leecherDataDir, err := ioutil.TempDir("", "")
529 defer os.RemoveAll(leecherDataDir)
530 cfg = TestingConfig()
531 cfg.DataDir = leecherDataDir
532 leecher, err := NewClient(cfg)
534 defer leecher.Close()
535 leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
536 ret = TorrentSpecFromMetaInfo(mi)
540 leecherTorrent.AddClientPeer(seeder)
541 reader := leecherTorrent.NewReader()
543 reader.SetReadahead(0)
544 reader.SetResponsive()
546 _, err = reader.Seek(3, io.SeekStart)
547 require.NoError(t, err)
548 _, err = io.ReadFull(reader, b)
550 assert.EqualValues(t, "lo", string(b))
551 _, err = reader.Seek(11, io.SeekStart)
552 require.NoError(t, err)
553 n, err := io.ReadFull(reader, b)
555 assert.EqualValues(t, 2, n)
556 assert.EqualValues(t, "d\n", string(b))
559 func TestTorrentDroppedDuringResponsiveRead(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 go leecherTorrent.Drop()
595 _, err = reader.Seek(11, io.SeekStart)
596 require.NoError(t, err)
597 n, err := reader.Read(b)
598 assert.EqualError(t, err, "torrent closed")
599 assert.EqualValues(t, 0, n)
602 func TestDHTInheritBlocklist(t *testing.T) {
603 ipl := iplist.New(nil)
604 require.NotNil(t, ipl)
605 cfg := TestingConfig()
606 cfg.IPBlocklist = ipl
608 cl, err := NewClient(cfg)
609 require.NoError(t, err)
612 cl.eachDhtServer(func(s *dht.Server) {
613 assert.Equal(t, ipl, s.IPBlocklist())
616 assert.EqualValues(t, 2, numServers)
619 // Check that stuff is merged in subsequent AddTorrentSpec for the same
621 func TestAddTorrentSpecMerging(t *testing.T) {
622 cl, err := NewClient(TestingConfig())
623 require.NoError(t, err)
625 dir, mi := testutil.GreetingTestTorrent()
626 defer os.RemoveAll(dir)
627 tt, new, err := cl.AddTorrentSpec(&TorrentSpec{
628 InfoHash: mi.HashInfoBytes(),
630 require.NoError(t, err)
632 require.Nil(t, tt.Info())
633 _, new, err = cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
634 require.NoError(t, err)
635 require.False(t, new)
636 require.NotNil(t, tt.Info())
639 func TestTorrentDroppedBeforeGotInfo(t *testing.T) {
640 dir, mi := testutil.GreetingTestTorrent()
642 cl, _ := NewClient(TestingConfig())
644 tt, _, _ := cl.AddTorrentSpec(&TorrentSpec{
645 InfoHash: mi.HashInfoBytes(),
648 assert.EqualValues(t, 0, len(cl.Torrents()))
656 func writeTorrentData(ts *storage.Torrent, info metainfo.Info, b []byte) {
657 for i := range iter.N(info.NumPieces()) {
659 ts.Piece(p).WriteAt(b[p.Offset():p.Offset()+p.Length()], 0)
663 func testAddTorrentPriorPieceCompletion(t *testing.T, alreadyCompleted bool, csf func(*filecache.Cache) storage.ClientImpl) {
664 fileCacheDir, err := ioutil.TempDir("", "")
665 require.NoError(t, err)
666 defer os.RemoveAll(fileCacheDir)
667 fileCache, err := filecache.NewCache(fileCacheDir)
668 require.NoError(t, err)
669 greetingDataTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
670 defer os.RemoveAll(greetingDataTempDir)
671 filePieceStore := csf(fileCache)
672 defer filePieceStore.Close()
673 info, err := greetingMetainfo.UnmarshalInfo()
674 require.NoError(t, err)
675 ih := greetingMetainfo.HashInfoBytes()
676 greetingData, err := storage.NewClient(filePieceStore).OpenTorrent(&info, ih)
677 require.NoError(t, err)
678 writeTorrentData(greetingData, info, []byte(testutil.GreetingFileContents))
679 // require.Equal(t, len(testutil.GreetingFileContents), written)
680 // require.NoError(t, err)
681 for i := 0; i < info.NumPieces(); i++ {
683 if alreadyCompleted {
684 require.NoError(t, greetingData.Piece(p).MarkComplete())
687 cfg := TestingConfig()
688 // TODO: Disable network option?
689 cfg.DisableTCP = true
690 cfg.DisableUTP = true
691 cfg.DefaultStorage = filePieceStore
692 cl, err := NewClient(cfg)
693 require.NoError(t, err)
695 tt, err := cl.AddTorrent(greetingMetainfo)
696 require.NoError(t, err)
697 psrs := tt.PieceStateRuns()
698 assert.Len(t, psrs, 1)
699 assert.EqualValues(t, 3, psrs[0].Length)
700 assert.Equal(t, alreadyCompleted, psrs[0].Complete)
701 if alreadyCompleted {
703 b, err := ioutil.ReadAll(r)
704 assert.NoError(t, err)
705 assert.EqualValues(t, testutil.GreetingFileContents, b)
709 func TestAddTorrentPiecesAlreadyCompleted(t *testing.T) {
710 testAddTorrentPriorPieceCompletion(t, true, fileCachePieceResourceStorage)
713 func TestAddTorrentPiecesNotAlreadyCompleted(t *testing.T) {
714 testAddTorrentPriorPieceCompletion(t, false, fileCachePieceResourceStorage)
717 func TestAddMetainfoWithNodes(t *testing.T) {
718 cfg := TestingConfig()
719 cfg.ListenHost = func(string) string { return "" }
721 cfg.DhtStartingNodes = func() ([]dht.Addr, error) { return nil, nil }
722 // For now, we want to just jam the nodes into the table, without
723 // verifying them first. Also the DHT code doesn't support mixing secure
724 // and insecure nodes if security is enabled (yet).
725 // cfg.DHTConfig.NoSecurity = true
726 cl, err := NewClient(cfg)
727 require.NoError(t, err)
729 sum := func() (ret int64) {
730 cl.eachDhtServer(func(s *dht.Server) {
731 ret += s.Stats().OutboundQueriesAttempted
735 assert.EqualValues(t, 0, sum())
736 tt, err := cl.AddTorrentFromFile("metainfo/testdata/issue_65a.torrent")
737 require.NoError(t, err)
738 // Nodes are not added or exposed in Torrent's metainfo. We just randomly
739 // check if the announce-list is here instead. TODO: Add nodes.
740 assert.Len(t, tt.metainfo.AnnounceList, 5)
741 // There are 6 nodes in the torrent file.
742 for sum() != int64(6*len(cl.dhtServers)) {
743 time.Sleep(time.Millisecond)
747 type testDownloadCancelParams struct {
748 SetLeecherStorageCapacity bool
749 LeecherStorageCapacity int64
753 func testDownloadCancel(t *testing.T, ps testDownloadCancelParams) {
754 greetingTempDir, mi := testutil.GreetingTestTorrent()
755 defer os.RemoveAll(greetingTempDir)
756 cfg := TestingConfig()
758 cfg.DataDir = greetingTempDir
759 seeder, err := NewClient(cfg)
760 require.NoError(t, err)
762 defer testutil.ExportStatusWriter(seeder, "s")()
763 seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
764 seederTorrent.VerifyData()
765 leecherDataDir, err := ioutil.TempDir("", "")
766 require.NoError(t, err)
767 defer os.RemoveAll(leecherDataDir)
768 fc, err := filecache.NewCache(leecherDataDir)
769 require.NoError(t, err)
770 if ps.SetLeecherStorageCapacity {
771 fc.SetCapacity(ps.LeecherStorageCapacity)
773 cfg.DefaultStorage = storage.NewResourcePieces(fc.AsResourceProvider())
774 cfg.DataDir = leecherDataDir
775 leecher, err := NewClient(cfg)
776 require.NoError(t, err)
777 defer leecher.Close()
778 defer testutil.ExportStatusWriter(leecher, "l")()
779 leecherGreeting, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
780 ret = TorrentSpecFromMetaInfo(mi)
784 require.NoError(t, err)
786 psc := leecherGreeting.SubscribePieceStateChanges()
789 leecherGreeting.cl.lock()
790 leecherGreeting.downloadPiecesLocked(0, leecherGreeting.numPieces())
792 leecherGreeting.cancelPiecesLocked(0, leecherGreeting.NumPieces())
794 leecherGreeting.cl.unlock()
795 done := make(chan struct{})
797 go leecherGreeting.AddClientPeer(seeder)
798 completes := make(map[int]bool, 3)
799 expected := func() map[int]bool {
801 return map[int]bool{0: false, 1: false, 2: false}
803 return map[int]bool{0: true, 1: true, 2: true}
806 for !reflect.DeepEqual(completes, expected) {
808 v := _v.(PieceStateChange)
809 completes[v.Index] = v.Complete
813 func TestTorrentDownloadAll(t *testing.T) {
814 testDownloadCancel(t, testDownloadCancelParams{})
817 func TestTorrentDownloadAllThenCancel(t *testing.T) {
818 testDownloadCancel(t, testDownloadCancelParams{
823 // Ensure that it's an error for a peer to send an invalid have message.
824 func TestPeerInvalidHave(t *testing.T) {
825 cl, err := NewClient(TestingConfig())
826 require.NoError(t, err)
828 info := metainfo.Info{
830 Pieces: make([]byte, 20),
831 Files: []metainfo.FileInfo{{Length: 1}},
833 infoBytes, err := bencode.Marshal(info)
834 require.NoError(t, err)
835 tt, _new, err := cl.AddTorrentSpec(&TorrentSpec{
836 InfoBytes: infoBytes,
837 InfoHash: metainfo.HashBytes(infoBytes),
838 Storage: badStorage{},
840 require.NoError(t, err)
846 assert.NoError(t, cn.peerSentHave(0))
847 assert.Error(t, cn.peerSentHave(1))
850 func TestPieceCompletedInStorageButNotClient(t *testing.T) {
851 greetingTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
852 defer os.RemoveAll(greetingTempDir)
853 cfg := TestingConfig()
854 cfg.DataDir = greetingTempDir
855 seeder, err := NewClient(TestingConfig())
856 require.NoError(t, err)
857 seeder.AddTorrentSpec(&TorrentSpec{
858 InfoBytes: greetingMetainfo.InfoBytes,
862 // Check that when the listen port is 0, all the protocols listened on have
863 // the same port, and it isn't zero.
864 func TestClientDynamicListenPortAllProtocols(t *testing.T) {
865 cl, err := NewClient(TestingConfig())
866 require.NoError(t, err)
868 port := cl.LocalPort()
869 assert.NotEqual(t, 0, port)
870 cl.eachListener(func(s socket) bool {
871 assert.Equal(t, port, missinggo.AddrPort(s.Addr()))
876 func TestClientDynamicListenTCPOnly(t *testing.T) {
877 cfg := TestingConfig()
878 cfg.DisableUTP = true
879 cfg.DisableTCP = false
880 cl, err := NewClient(cfg)
881 require.NoError(t, err)
883 assert.NotEqual(t, 0, cl.LocalPort())
886 func TestClientDynamicListenUTPOnly(t *testing.T) {
887 cfg := TestingConfig()
888 cfg.DisableTCP = true
889 cfg.DisableUTP = false
890 cl, err := NewClient(cfg)
891 require.NoError(t, err)
893 assert.NotEqual(t, 0, cl.LocalPort())
896 func totalConns(tts []*Torrent) (ret int) {
897 for _, tt := range tts {
905 func TestSetMaxEstablishedConn(t *testing.T) {
907 ih := testutil.GreetingMetaInfo().HashInfoBytes()
908 cfg := TestingConfig()
909 cfg.DisableAcceptRateLimiting = true
910 cfg.dropDuplicatePeerIds = true
911 for i := range iter.N(3) {
912 cl, err := NewClient(cfg)
913 require.NoError(t, err)
915 tt, _ := cl.AddTorrentInfoHash(ih)
916 tt.SetMaxEstablishedConns(2)
917 defer testutil.ExportStatusWriter(cl, fmt.Sprintf("%d", i))()
918 tts = append(tts, tt)
921 for _, tt := range tts {
922 for _, _tt := range tts {
924 tt.AddClientPeer(_tt.cl)
929 waitTotalConns := func(num int) {
930 for totalConns(tts) != num {
932 time.Sleep(time.Millisecond)
937 tts[0].SetMaxEstablishedConns(1)
939 tts[0].SetMaxEstablishedConns(0)
941 tts[0].SetMaxEstablishedConns(1)
944 tts[0].SetMaxEstablishedConns(2)
949 // Creates a file containing its own name as data. Make a metainfo from that, adds it to the given
950 // client, and returns a magnet link.
951 func makeMagnet(t *testing.T, cl *Client, dir string, name string) string {
952 os.MkdirAll(dir, 0770)
953 file, err := os.Create(filepath.Join(dir, name))
954 require.NoError(t, err)
955 file.Write([]byte(name))
957 mi := metainfo.MetaInfo{}
959 info := metainfo.Info{PieceLength: 256 * 1024}
960 err = info.BuildFromFilePath(filepath.Join(dir, name))
961 require.NoError(t, err)
962 mi.InfoBytes, err = bencode.Marshal(info)
963 require.NoError(t, err)
964 magnet := mi.Magnet(name, mi.HashInfoBytes()).String()
965 tr, err := cl.AddTorrent(&mi)
966 require.NoError(t, err)
967 require.True(t, tr.Seeding())
972 // https://github.com/anacrolix/torrent/issues/114
973 func TestMultipleTorrentsWithEncryption(t *testing.T) {
974 testSeederLeecherPair(
976 func(cfg *ClientConfig) {
977 cfg.HeaderObfuscationPolicy.Preferred = true
978 cfg.HeaderObfuscationPolicy.RequirePreferred = true
980 func(cfg *ClientConfig) {
981 cfg.HeaderObfuscationPolicy.RequirePreferred = false
986 // Test that the leecher can download a torrent in its entirety from the seeder. Note that the
987 // seeder config is done first.
988 func testSeederLeecherPair(t *testing.T, seeder func(*ClientConfig), leecher func(*ClientConfig)) {
989 cfg := TestingConfig()
991 cfg.DataDir = filepath.Join(cfg.DataDir, "server")
992 os.Mkdir(cfg.DataDir, 0755)
994 server, err := NewClient(cfg)
995 require.NoError(t, err)
997 defer testutil.ExportStatusWriter(server, "s")()
998 magnet1 := makeMagnet(t, server, cfg.DataDir, "test1")
999 // Extra torrents are added to test the seeder having to match incoming obfuscated headers
1000 // against more than one torrent. See issue #114
1001 makeMagnet(t, server, cfg.DataDir, "test2")
1002 for i := 0; i < 100; i++ {
1003 makeMagnet(t, server, cfg.DataDir, fmt.Sprintf("test%d", i+2))
1005 cfg = TestingConfig()
1006 cfg.DataDir = filepath.Join(cfg.DataDir, "client")
1008 client, err := NewClient(cfg)
1009 require.NoError(t, err)
1010 defer client.Close()
1011 defer testutil.ExportStatusWriter(client, "c")()
1012 tr, err := client.AddMagnet(magnet1)
1013 require.NoError(t, err)
1014 tr.AddClientPeer(server)
1020 // This appears to be the situation with the S3 BitTorrent client.
1021 func TestObfuscatedHeaderFallbackSeederDisallowsLeecherPrefers(t *testing.T) {
1022 // Leecher prefers obfuscation, but the seeder does not allow it.
1023 testSeederLeecherPair(
1025 func(cfg *ClientConfig) {
1026 cfg.HeaderObfuscationPolicy.Preferred = false
1027 cfg.HeaderObfuscationPolicy.RequirePreferred = true
1029 func(cfg *ClientConfig) {
1030 cfg.HeaderObfuscationPolicy.Preferred = true
1031 cfg.HeaderObfuscationPolicy.RequirePreferred = false
1036 func TestObfuscatedHeaderFallbackSeederRequiresLeecherPrefersNot(t *testing.T) {
1037 // Leecher prefers no obfuscation, but the seeder enforces it.
1038 testSeederLeecherPair(
1040 func(cfg *ClientConfig) {
1041 cfg.HeaderObfuscationPolicy.Preferred = true
1042 cfg.HeaderObfuscationPolicy.RequirePreferred = true
1044 func(cfg *ClientConfig) {
1045 cfg.HeaderObfuscationPolicy.Preferred = false
1046 cfg.HeaderObfuscationPolicy.RequirePreferred = false
1051 func TestClientAddressInUse(t *testing.T) {
1052 s, _ := NewUtpSocket("udp", ":50007", nil)
1056 cfg := TestingConfig().SetListenAddr(":50007")
1057 cl, err := NewClient(cfg)
1058 require.Error(t, err)
1062 func TestClientHasDhtServersWhenUtpDisabled(t *testing.T) {
1063 cc := TestingConfig()
1064 cc.DisableUTP = true
1066 cl, err := NewClient(cc)
1067 require.NoError(t, err)
1069 assert.NotEmpty(t, cl.DhtServers())