import (
"encoding/binary"
- "errors"
"fmt"
"io"
- "io/ioutil"
- "log"
- "math/rand"
"net"
+ "net/netip"
"os"
- "strings"
- "sync"
+ "path/filepath"
+ "reflect"
"testing"
+ "testing/iotest"
"time"
- _ "github.com/anacrolix/envpprof"
- "github.com/anacrolix/missinggo"
- "github.com/anacrolix/missinggo/filecache"
- "github.com/anacrolix/missinggo/pubsub"
- "github.com/anacrolix/utp"
- "github.com/bradfitz/iter"
+ "github.com/anacrolix/dht/v2"
+ "github.com/anacrolix/log"
+ "github.com/anacrolix/missinggo/v2"
+ "github.com/anacrolix/missinggo/v2/filecache"
+ "github.com/frankban/quicktest"
+ qt "github.com/frankban/quicktest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anacrolix/torrent/bencode"
- "github.com/anacrolix/torrent/dht"
"github.com/anacrolix/torrent/internal/testutil"
"github.com/anacrolix/torrent/iplist"
"github.com/anacrolix/torrent/metainfo"
"github.com/anacrolix/torrent/storage"
)
-func init() {
- log.SetFlags(log.LstdFlags | log.Llongfile)
-}
-
-var TestingConfig = Config{
- ListenAddr: "localhost:0",
- NoDHT: true,
- DisableTrackers: true,
- DataDir: "/dev/null",
- DHTConfig: dht.ServerConfig{
- NoDefaultBootstrap: true,
- },
+func TestClientDefault(t *testing.T) {
+ cl, err := NewClient(TestingConfig(t))
+ require.NoError(t, err)
+ require.Empty(t, cl.Close())
}
-func TestClientDefault(t *testing.T) {
- cl, err := NewClient(&TestingConfig)
+func TestClientNilConfig(t *testing.T) {
+ // The default config will put crap in the working directory.
+ origDir, _ := os.Getwd()
+ defer os.Chdir(origDir)
+ os.Chdir(t.TempDir())
+ cl, err := NewClient(nil)
require.NoError(t, err)
- cl.Close()
+ require.Empty(t, cl.Close())
}
func TestAddDropTorrent(t *testing.T) {
- cl, err := NewClient(&TestingConfig)
+ cl, err := NewClient(TestingConfig(t))
require.NoError(t, err)
defer cl.Close()
dir, mi := testutil.GreetingTestTorrent()
tt, new, err := cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
require.NoError(t, err)
assert.True(t, new)
+ tt.SetMaxEstablishedConns(0)
+ tt.SetMaxEstablishedConns(1)
tt.Drop()
}
func TestAddTorrentNoSupportedTrackerSchemes(t *testing.T) {
+ // TODO?
t.SkipNow()
}
func TestAddTorrentNoUsableURLs(t *testing.T) {
+ // TODO?
t.SkipNow()
}
func TestAddPeersToUnknownTorrent(t *testing.T) {
+ // TODO?
t.SkipNow()
}
func TestPieceHashSize(t *testing.T) {
- if pieceHash.Size() != 20 {
- t.FailNow()
- }
+ assert.Equal(t, 20, pieceHash.Size())
}
func TestTorrentInitialState(t *testing.T) {
dir, mi := testutil.GreetingTestTorrent()
defer os.RemoveAll(dir)
- tor := &Torrent{
- infoHash: mi.Info.Hash(),
- pieceStateChanges: pubsub.NewPubSub(),
- }
- tor.chunkSize = 2
- tor.storageOpener = storage.NewFile(dir)
- // Needed to lock for asynchronous piece verification.
- tor.cl = new(Client)
- err := tor.setInfoBytes(mi.Info.Bytes)
+ var cl Client
+ cl.init(TestingConfig(t))
+ cl.initLogger()
+ tor := cl.newTorrent(
+ mi.HashInfoBytes(),
+ storage.NewFileWithCompletion(t.TempDir(), storage.NewMapPieceCompletion()),
+ )
+ tor.setChunkSize(2)
+ tor.cl.lock()
+ err := tor.setInfoBytesLocked(mi.InfoBytes)
+ tor.cl.unlock()
require.NoError(t, err)
require.Len(t, tor.pieces, 3)
tor.pendAllChunkSpecs(0)
- tor.cl.mu.Lock()
+ tor.cl.lock()
assert.EqualValues(t, 3, tor.pieceNumPendingChunks(0))
- tor.cl.mu.Unlock()
- assert.EqualValues(t, chunkSpec{4, 1}, chunkIndexSpec(2, tor.pieceLength(0), tor.chunkSize))
-}
-
-func TestUnmarshalPEXMsg(t *testing.T) {
- var m peerExchangeMessage
- if err := bencode.Unmarshal([]byte("d5:added12:\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0ce"), &m); err != nil {
- t.Fatal(err)
- }
- if len(m.Added) != 2 {
- t.FailNow()
- }
- if m.Added[0].Port != 0x506 {
- t.FailNow()
- }
+ tor.cl.unlock()
+ assert.EqualValues(t, ChunkSpec{4, 1}, chunkIndexSpec(2, tor.pieceLength(0), tor.chunkSize))
}
func TestReducedDialTimeout(t *testing.T) {
+ cfg := NewDefaultClientConfig()
for _, _case := range []struct {
Max time.Duration
HalfOpenLimit int
PendingPeers int
ExpectedReduced time.Duration
}{
- {nominalDialTimeout, 40, 0, nominalDialTimeout},
- {nominalDialTimeout, 40, 1, nominalDialTimeout},
- {nominalDialTimeout, 40, 39, nominalDialTimeout},
- {nominalDialTimeout, 40, 40, nominalDialTimeout / 2},
- {nominalDialTimeout, 40, 80, nominalDialTimeout / 3},
- {nominalDialTimeout, 40, 4000, nominalDialTimeout / 101},
+ {cfg.NominalDialTimeout, 40, 0, cfg.NominalDialTimeout},
+ {cfg.NominalDialTimeout, 40, 1, cfg.NominalDialTimeout},
+ {cfg.NominalDialTimeout, 40, 39, cfg.NominalDialTimeout},
+ {cfg.NominalDialTimeout, 40, 40, cfg.NominalDialTimeout / 2},
+ {cfg.NominalDialTimeout, 40, 80, cfg.NominalDialTimeout / 3},
+ {cfg.NominalDialTimeout, 40, 4000, cfg.NominalDialTimeout / 101},
} {
- reduced := reducedDialTimeout(_case.Max, _case.HalfOpenLimit, _case.PendingPeers)
+ reduced := reducedDialTimeout(cfg.MinDialTimeout, _case.Max, _case.HalfOpenLimit, _case.PendingPeers)
expected := _case.ExpectedReduced
- if expected < minDialTimeout {
- expected = minDialTimeout
+ if expected < cfg.MinDialTimeout {
+ expected = cfg.MinDialTimeout
}
if reduced != expected {
t.Fatalf("expected %s, got %s", _case.ExpectedReduced, reduced)
}
}
-func TestUTPRawConn(t *testing.T) {
- l, err := utp.NewSocket("udp", "")
- if err != nil {
- t.Fatal(err)
- }
- defer l.Close()
- go func() {
- for {
- _, err := l.Accept()
- if err != nil {
- break
- }
- }
- }()
- // Connect a UTP peer to see if the RawConn will still work.
- s, _ := utp.NewSocket("udp", "")
- defer s.Close()
- utpPeer, err := s.Dial(fmt.Sprintf("localhost:%d", missinggo.AddrPort(l.Addr())))
- if err != nil {
- t.Fatalf("error dialing utp listener: %s", err)
- }
- defer utpPeer.Close()
- peer, err := net.ListenPacket("udp", ":0")
- if err != nil {
- t.Fatal(err)
- }
- defer peer.Close()
-
- msgsReceived := 0
- // How many messages to send. I've set this to double the channel buffer
- // size in the raw packetConn.
- const N = 200
- readerStopped := make(chan struct{})
- // The reader goroutine.
- go func() {
- defer close(readerStopped)
- b := make([]byte, 500)
- for i := 0; i < N; i++ {
- n, _, err := l.ReadFrom(b)
- if err != nil {
- t.Fatalf("error reading from raw conn: %s", err)
- }
- msgsReceived++
- var d int
- fmt.Sscan(string(b[:n]), &d)
- if d != i {
- log.Printf("got wrong number: expected %d, got %d", i, d)
- }
- }
- }()
- udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("localhost:%d", missinggo.AddrPort(l.Addr())))
- if err != nil {
- t.Fatal(err)
- }
- for i := 0; i < N; i++ {
- _, err := peer.WriteTo([]byte(fmt.Sprintf("%d", i)), udpAddr)
- if err != nil {
- t.Fatal(err)
- }
- time.Sleep(time.Microsecond)
- }
- select {
- case <-readerStopped:
- case <-time.After(time.Second):
- t.Fatal("reader timed out")
- }
- if msgsReceived != N {
- t.Fatalf("messages received: %d", msgsReceived)
- }
-}
-
-func TestTwoClientsArbitraryPorts(t *testing.T) {
- for i := 0; i < 2; i++ {
- cl, err := NewClient(&TestingConfig)
- if err != nil {
- t.Fatal(err)
- }
- defer cl.Close()
- }
-}
-
func TestAddDropManyTorrents(t *testing.T) {
- cl, err := NewClient(&TestingConfig)
+ cl, err := NewClient(TestingConfig(t))
require.NoError(t, err)
defer cl.Close()
- for i := range iter.N(1000) {
+ for i := 0; i < 1000; i += 1 {
var spec TorrentSpec
binary.PutVarint(spec.InfoHash[:], int64(i))
tt, new, err := cl.AddTorrentSpec(&spec)
}
}
-func TestClientTransferDefault(t *testing.T) {
- testClientTransfer(t, testClientTransferParams{
- ExportClientStatus: true,
- })
-}
-
-func TestClientTransferSmallCache(t *testing.T) {
- testClientTransfer(t, testClientTransferParams{
- SetLeecherStorageCapacity: true,
- // Going below the piece length means it can't complete a piece so
- // that it can be hashed.
- LeecherStorageCapacity: 5,
- SetReadahead: true,
- // Can't readahead too far or the cache will thrash and drop data we
- // thought we had.
- Readahead: 0,
- ExportClientStatus: true,
- })
-}
-
-func TestClientTransferVarious(t *testing.T) {
- for _, ss := range []func(string) storage.I{
- storage.NewFile,
- storage.NewMMap,
- } {
- for _, responsive := range []bool{false, true} {
- testClientTransfer(t, testClientTransferParams{
- Responsive: responsive,
- SeederStorage: ss,
- })
- for _, readahead := range []int64{-1, 0, 1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14, 15, 20} {
- testClientTransfer(t, testClientTransferParams{
- SeederStorage: ss,
- Responsive: responsive,
- SetReadahead: true,
- Readahead: readahead,
- })
- }
- }
- }
-}
-
-type testClientTransferParams struct {
- Responsive bool
- Readahead int64
- SetReadahead bool
- ExportClientStatus bool
- SetLeecherStorageCapacity bool
- LeecherStorageCapacity int64
- SeederStorage func(string) storage.I
-}
-
-func testClientTransfer(t *testing.T, ps testClientTransferParams) {
- greetingTempDir, mi := testutil.GreetingTestTorrent()
- defer os.RemoveAll(greetingTempDir)
- cfg := TestingConfig
- cfg.Seed = true
- if ps.SeederStorage != nil {
- cfg.DefaultStorage = ps.SeederStorage(greetingTempDir)
- } else {
- cfg.DataDir = greetingTempDir
- }
- seeder, err := NewClient(&cfg)
- require.NoError(t, err)
- defer seeder.Close()
- if ps.ExportClientStatus {
- testutil.ExportStatusWriter(seeder, "s")
- }
- _, new, err := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
- require.NoError(t, err)
- assert.True(t, new)
- leecherDataDir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(leecherDataDir)
- fc, err := filecache.NewCache(leecherDataDir)
- require.NoError(t, err)
- if ps.SetLeecherStorageCapacity {
- fc.SetCapacity(ps.LeecherStorageCapacity)
- }
- cfg.DefaultStorage = storage.NewPieceFileStorage(fc.AsFileStore())
- leecher, err := NewClient(&cfg)
- require.NoError(t, err)
- defer leecher.Close()
- if ps.ExportClientStatus {
- testutil.ExportStatusWriter(leecher, "l")
- }
- leecherGreeting, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
- ret = TorrentSpecFromMetaInfo(mi)
- ret.ChunkSize = 2
- ret.Storage = storage.NewFile(leecherDataDir)
- return
- }())
- require.NoError(t, err)
- assert.True(t, new)
- leecherGreeting.AddPeers([]Peer{
- Peer{
- IP: missinggo.AddrIP(seeder.ListenAddr()),
- Port: missinggo.AddrPort(seeder.ListenAddr()),
+func fileCachePieceResourceStorage(fc *filecache.Cache) storage.ClientImpl {
+ return storage.NewResourcePiecesOpts(
+ fc.AsResourceProvider(),
+ storage.ResourcePiecesOpts{
+ LeaveIncompleteChunks: true,
},
- })
- r := leecherGreeting.NewReader()
- defer r.Close()
- if ps.Responsive {
- r.SetResponsive()
- }
- if ps.SetReadahead {
- r.SetReadahead(ps.Readahead)
- }
- for range iter.N(2) {
- pos, err := r.Seek(0, os.SEEK_SET)
- assert.NoError(t, err)
- assert.EqualValues(t, 0, pos)
- _greeting, err := ioutil.ReadAll(r)
- assert.NoError(t, err)
- assert.EqualValues(t, testutil.GreetingFileContents, _greeting)
- }
-}
-
-// Check that after completing leeching, a leecher transitions to a seeding
-// correctly. Connected in a chain like so: Seeder <-> Leecher <-> LeecherLeecher.
-func TestSeedAfterDownloading(t *testing.T) {
- greetingTempDir, mi := testutil.GreetingTestTorrent()
- defer os.RemoveAll(greetingTempDir)
- cfg := TestingConfig
- cfg.Seed = true
- cfg.DataDir = greetingTempDir
- seeder, err := NewClient(&cfg)
- require.NoError(t, err)
- defer seeder.Close()
- testutil.ExportStatusWriter(seeder, "s")
- seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
- cfg.DataDir, err = ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(cfg.DataDir)
- leecher, err := NewClient(&cfg)
- require.NoError(t, err)
- defer leecher.Close()
- testutil.ExportStatusWriter(leecher, "l")
- cfg.Seed = false
- // cfg.TorrentDataOpener = nil
- cfg.DataDir, err = ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(cfg.DataDir)
- leecherLeecher, _ := NewClient(&cfg)
- defer leecherLeecher.Close()
- testutil.ExportStatusWriter(leecherLeecher, "ll")
- leecherGreeting, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
- ret = TorrentSpecFromMetaInfo(mi)
- ret.ChunkSize = 2
- return
- }())
- llg, _, _ := leecherLeecher.AddTorrentSpec(func() (ret *TorrentSpec) {
- ret = TorrentSpecFromMetaInfo(mi)
- ret.ChunkSize = 3
- return
- }())
- // Simultaneously DownloadAll in Leecher, and read the contents
- // consecutively in LeecherLeecher. This non-deterministically triggered a
- // case where the leecher wouldn't unchoke the LeecherLeecher.
- var wg sync.WaitGroup
- wg.Add(1)
- go func() {
- defer wg.Done()
- r := llg.NewReader()
- defer r.Close()
- b, err := ioutil.ReadAll(r)
- require.NoError(t, err)
- assert.EqualValues(t, testutil.GreetingFileContents, b)
- }()
- leecherGreeting.AddPeers([]Peer{
- Peer{
- IP: missinggo.AddrIP(seeder.ListenAddr()),
- Port: missinggo.AddrPort(seeder.ListenAddr()),
- },
- Peer{
- IP: missinggo.AddrIP(leecherLeecher.ListenAddr()),
- Port: missinggo.AddrPort(leecherLeecher.ListenAddr()),
- },
- })
- wg.Add(1)
- go func() {
- defer wg.Done()
- leecherGreeting.DownloadAll()
- leecher.WaitAll()
- }()
- wg.Wait()
+ )
}
func TestMergingTrackersByAddingSpecs(t *testing.T) {
- cl, err := NewClient(&TestingConfig)
+ cl, err := NewClient(TestingConfig(t))
require.NoError(t, err)
defer cl.Close()
spec := TorrentSpec{}
}
spec.Trackers = [][]string{{"http://a"}, {"udp://b"}}
_, new, _ = cl.AddTorrentSpec(&spec)
- if new {
- t.FailNow()
- }
- assert.EqualValues(t, T.trackers[0][0], "http://a")
- assert.EqualValues(t, T.trackers[1][0], "udp://b")
-}
-
-type badStorage struct{}
-
-func (bs badStorage) OpenTorrent(*metainfo.InfoEx) (storage.Torrent, error) {
- return bs, nil
-}
-
-func (bs badStorage) Close() error {
- return nil
-}
-
-func (bs badStorage) Piece(p metainfo.Piece) storage.Piece {
- return badStoragePiece{p}
-}
-
-type badStoragePiece struct {
- p metainfo.Piece
-}
-
-func (p badStoragePiece) WriteAt(b []byte, off int64) (int, error) {
- return 0, nil
-}
-
-func (p badStoragePiece) GetIsComplete() bool {
- return true
-}
-
-func (p badStoragePiece) MarkComplete() error {
- return errors.New("psyyyyyyyche")
-}
-
-func (p badStoragePiece) randomlyTruncatedDataString() string {
- return "hello, world\n"[:rand.Intn(14)]
-}
-
-func (p badStoragePiece) ReadAt(b []byte, off int64) (n int, err error) {
- r := strings.NewReader(p.randomlyTruncatedDataString())
- return r.ReadAt(b, off+p.p.Offset())
+ assert.False(t, new)
+ assert.EqualValues(t, [][]string{{"http://a"}, {"udp://b"}}, T.metainfo.AnnounceList)
+ // Because trackers are disabled in TestingConfig.
+ assert.EqualValues(t, 0, len(T.trackerAnnouncers))
}
// We read from a piece which is marked completed, but is missing data.
func TestCompletedPieceWrongSize(t *testing.T) {
- cfg := TestingConfig
+ cfg := TestingConfig(t)
cfg.DefaultStorage = badStorage{}
- cl, _ := NewClient(&cfg)
+ cl, err := NewClient(cfg)
+ require.NoError(t, err)
defer cl.Close()
- ie := metainfo.InfoEx{
- Info: metainfo.Info{
- PieceLength: 15,
- Pieces: make([]byte, 20),
- Files: []metainfo.FileInfo{
- metainfo.FileInfo{Path: []string{"greeting"}, Length: 13},
- },
+ info := metainfo.Info{
+ PieceLength: 15,
+ Pieces: make([]byte, 20),
+ Files: []metainfo.FileInfo{
+ {Path: []string{"greeting"}, Length: 13},
},
}
- ie.UpdateBytes()
+ b, err := bencode.Marshal(info)
+ require.NoError(t, err)
tt, new, err := cl.AddTorrentSpec(&TorrentSpec{
- Info: &ie,
- InfoHash: ie.Hash(),
+ InfoBytes: b,
+ InfoHash: metainfo.HashBytes(b),
})
require.NoError(t, err)
defer tt.Drop()
assert.True(t, new)
r := tt.NewReader()
defer r.Close()
- b, err := ioutil.ReadAll(r)
- assert.Len(t, b, 13)
- assert.NoError(t, err)
+ quicktest.Check(t, iotest.TestReader(r, []byte(testutil.GreetingFileContents)), quicktest.IsNil)
}
func BenchmarkAddLargeTorrent(b *testing.B) {
- cfg := TestingConfig
+ cfg := TestingConfig(b)
cfg.DisableTCP = true
cfg.DisableUTP = true
- cfg.ListenAddr = "redonk"
- cl, _ := NewClient(&cfg)
+ cl, err := NewClient(cfg)
+ require.NoError(b, err)
defer cl.Close()
- for range iter.N(b.N) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i += 1 {
t, err := cl.AddTorrentFromFile("testdata/bootstrap.dat.torrent")
if err != nil {
b.Fatal(err)
func TestResponsive(t *testing.T) {
seederDataDir, mi := testutil.GreetingTestTorrent()
defer os.RemoveAll(seederDataDir)
- cfg := TestingConfig
+ cfg := TestingConfig(t)
cfg.Seed = true
cfg.DataDir = seederDataDir
- seeder, err := NewClient(&cfg)
+ seeder, err := NewClient(cfg)
require.Nil(t, err)
defer seeder.Close()
- seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
- leecherDataDir, err := ioutil.TempDir("", "")
+ seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
+ seederTorrent.VerifyData()
+ leecherDataDir := t.TempDir()
+ cfg = TestingConfig(t)
+ cfg.DataDir = leecherDataDir
+ leecher, err := NewClient(cfg)
require.Nil(t, err)
- defer os.RemoveAll(leecherDataDir)
- cfg = TestingConfig
+ defer leecher.Close()
+ leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
+ ret = TorrentSpecFromMetaInfo(mi)
+ ret.ChunkSize = 2
+ return
+ }())
+ leecherTorrent.AddClientPeer(seeder)
+ reader := leecherTorrent.NewReader()
+ defer reader.Close()
+ reader.SetReadahead(0)
+ reader.SetResponsive()
+ b := make([]byte, 2)
+ _, err = reader.Seek(3, io.SeekStart)
+ require.NoError(t, err)
+ _, err = io.ReadFull(reader, b)
+ assert.Nil(t, err)
+ assert.EqualValues(t, "lo", string(b))
+ _, err = reader.Seek(11, io.SeekStart)
+ require.NoError(t, err)
+ n, err := io.ReadFull(reader, b)
+ assert.Nil(t, err)
+ assert.EqualValues(t, 2, n)
+ assert.EqualValues(t, "d\n", string(b))
+}
+
+// TestResponsive was the first test to fail if uTP is disabled and TCP sockets dial from the
+// listening port.
+func TestResponsiveTcpOnly(t *testing.T) {
+ seederDataDir, mi := testutil.GreetingTestTorrent()
+ defer os.RemoveAll(seederDataDir)
+ cfg := TestingConfig(t)
+ cfg.DisableUTP = true
+ cfg.Seed = true
+ cfg.DataDir = seederDataDir
+ seeder, err := NewClient(cfg)
+ require.Nil(t, err)
+ defer seeder.Close()
+ seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
+ seederTorrent.VerifyData()
+ leecherDataDir := t.TempDir()
+ cfg = TestingConfig(t)
cfg.DataDir = leecherDataDir
- leecher, err := NewClient(&cfg)
+ leecher, err := NewClient(cfg)
require.Nil(t, err)
defer leecher.Close()
leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
ret.ChunkSize = 2
return
}())
- leecherTorrent.AddPeers([]Peer{
- Peer{
- IP: missinggo.AddrIP(seeder.ListenAddr()),
- Port: missinggo.AddrPort(seeder.ListenAddr()),
- },
- })
+ leecherTorrent.AddClientPeer(seeder)
reader := leecherTorrent.NewReader()
defer reader.Close()
reader.SetReadahead(0)
reader.SetResponsive()
b := make([]byte, 2)
- _, err = reader.Seek(3, os.SEEK_SET)
+ _, err = reader.Seek(3, io.SeekStart)
require.NoError(t, err)
_, err = io.ReadFull(reader, b)
assert.Nil(t, err)
assert.EqualValues(t, "lo", string(b))
- _, err = reader.Seek(11, os.SEEK_SET)
+ _, err = reader.Seek(11, io.SeekStart)
require.NoError(t, err)
n, err := io.ReadFull(reader, b)
assert.Nil(t, err)
func TestTorrentDroppedDuringResponsiveRead(t *testing.T) {
seederDataDir, mi := testutil.GreetingTestTorrent()
defer os.RemoveAll(seederDataDir)
- cfg := TestingConfig
+ cfg := TestingConfig(t)
cfg.Seed = true
cfg.DataDir = seederDataDir
- seeder, err := NewClient(&cfg)
+ seeder, err := NewClient(cfg)
require.Nil(t, err)
defer seeder.Close()
- seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
- leecherDataDir, err := ioutil.TempDir("", "")
- require.Nil(t, err)
- defer os.RemoveAll(leecherDataDir)
- cfg = TestingConfig
+ seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
+ seederTorrent.VerifyData()
+ leecherDataDir := t.TempDir()
+ cfg = TestingConfig(t)
cfg.DataDir = leecherDataDir
- leecher, err := NewClient(&cfg)
+ leecher, err := NewClient(cfg)
require.Nil(t, err)
defer leecher.Close()
leecherTorrent, _, _ := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
ret.ChunkSize = 2
return
}())
- leecherTorrent.AddPeers([]Peer{
- Peer{
- IP: missinggo.AddrIP(seeder.ListenAddr()),
- Port: missinggo.AddrPort(seeder.ListenAddr()),
- },
- })
+ leecherTorrent.AddClientPeer(seeder)
reader := leecherTorrent.NewReader()
defer reader.Close()
reader.SetReadahead(0)
reader.SetResponsive()
b := make([]byte, 2)
- _, err = reader.Seek(3, os.SEEK_SET)
+ _, err = reader.Seek(3, io.SeekStart)
require.NoError(t, err)
_, err = io.ReadFull(reader, b)
assert.Nil(t, err)
assert.EqualValues(t, "lo", string(b))
- go leecherTorrent.Drop()
- _, err = reader.Seek(11, os.SEEK_SET)
+ _, err = reader.Seek(11, io.SeekStart)
require.NoError(t, err)
+ leecherTorrent.Drop()
n, err := reader.Read(b)
assert.EqualError(t, err, "torrent closed")
assert.EqualValues(t, 0, n)
}
-func TestDHTInheritBlocklist(t *testing.T) {
+func TestDhtInheritBlocklist(t *testing.T) {
ipl := iplist.New(nil)
require.NotNil(t, ipl)
- cfg := TestingConfig
+ cfg := TestingConfig(t)
cfg.IPBlocklist = ipl
cfg.NoDHT = false
- cl, err := NewClient(&cfg)
+ cl, err := NewClient(cfg)
require.NoError(t, err)
defer cl.Close()
- require.Equal(t, ipl, cl.DHT().IPBlocklist())
+ numServers := 0
+ cl.eachDhtServer(func(s DhtServer) {
+ t.Log(s)
+ assert.Equal(t, ipl, s.(AnacrolixDhtServerWrapper).Server.IPBlocklist())
+ numServers++
+ })
+ assert.EqualValues(t, 2, numServers)
}
// Check that stuff is merged in subsequent AddTorrentSpec for the same
// infohash.
func TestAddTorrentSpecMerging(t *testing.T) {
- cl, err := NewClient(&TestingConfig)
+ cl, err := NewClient(TestingConfig(t))
require.NoError(t, err)
defer cl.Close()
dir, mi := testutil.GreetingTestTorrent()
defer os.RemoveAll(dir)
tt, new, err := cl.AddTorrentSpec(&TorrentSpec{
- InfoHash: mi.Info.Hash(),
+ InfoHash: mi.HashInfoBytes(),
})
require.NoError(t, err)
require.True(t, new)
func TestTorrentDroppedBeforeGotInfo(t *testing.T) {
dir, mi := testutil.GreetingTestTorrent()
os.RemoveAll(dir)
- cl, _ := NewClient(&TestingConfig)
+ cl, _ := NewClient(TestingConfig(t))
defer cl.Close()
tt, _, _ := cl.AddTorrentSpec(&TorrentSpec{
- InfoHash: mi.Info.Hash(),
+ InfoHash: mi.HashInfoBytes(),
})
tt.Drop()
assert.EqualValues(t, 0, len(cl.Torrents()))
}
}
-func writeTorrentData(ts storage.Torrent, info *metainfo.InfoEx, b []byte) {
- for i := range iter.N(info.NumPieces()) {
- n, _ := ts.Piece(info.Piece(i)).WriteAt(b, 0)
- b = b[n:]
+func writeTorrentData(ts *storage.Torrent, info metainfo.Info, b []byte) {
+ for i := 0; i < info.NumPieces(); i += 1 {
+ p := info.Piece(i)
+ ts.Piece(p).WriteAt(b[p.Offset():p.Offset()+p.Length()], 0)
}
}
-func testAddTorrentPriorPieceCompletion(t *testing.T, alreadyCompleted bool) {
- fileCacheDir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(fileCacheDir)
+func testAddTorrentPriorPieceCompletion(t *testing.T, alreadyCompleted bool, csf func(*filecache.Cache) storage.ClientImpl) {
+ fileCacheDir := t.TempDir()
fileCache, err := filecache.NewCache(fileCacheDir)
require.NoError(t, err)
greetingDataTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
defer os.RemoveAll(greetingDataTempDir)
- filePieceStore := storage.NewPieceFileStorage(fileCache.AsFileStore())
- greetingData, err := filePieceStore.OpenTorrent(&greetingMetainfo.Info)
+ filePieceStore := csf(fileCache)
+ info, err := greetingMetainfo.UnmarshalInfo()
require.NoError(t, err)
- writeTorrentData(greetingData, &greetingMetainfo.Info, []byte(testutil.GreetingFileContents))
+ ih := greetingMetainfo.HashInfoBytes()
+ greetingData, err := storage.NewClient(filePieceStore).OpenTorrent(&info, ih)
+ require.NoError(t, err)
+ writeTorrentData(greetingData, info, []byte(testutil.GreetingFileContents))
// require.Equal(t, len(testutil.GreetingFileContents), written)
// require.NoError(t, err)
- for i := 0; i < greetingMetainfo.Info.NumPieces(); i++ {
- p := greetingMetainfo.Info.Piece(i)
+ for i := 0; i < info.NumPieces(); i++ {
+ p := info.Piece(i)
if alreadyCompleted {
- err := greetingData.Piece(p).MarkComplete()
- assert.NoError(t, err)
+ require.NoError(t, greetingData.Piece(p).MarkComplete())
}
}
- cfg := TestingConfig
+ cfg := TestingConfig(t)
// TODO: Disable network option?
cfg.DisableTCP = true
cfg.DisableUTP = true
cfg.DefaultStorage = filePieceStore
- cl, err := NewClient(&cfg)
+ cl, err := NewClient(cfg)
require.NoError(t, err)
defer cl.Close()
tt, err := cl.AddTorrent(greetingMetainfo)
assert.Equal(t, alreadyCompleted, psrs[0].Complete)
if alreadyCompleted {
r := tt.NewReader()
- b, err := ioutil.ReadAll(r)
- assert.NoError(t, err)
- assert.EqualValues(t, testutil.GreetingFileContents, b)
+ quicktest.Check(t, iotest.TestReader(r, []byte(testutil.GreetingFileContents)), quicktest.IsNil)
}
}
func TestAddTorrentPiecesAlreadyCompleted(t *testing.T) {
- testAddTorrentPriorPieceCompletion(t, true)
+ testAddTorrentPriorPieceCompletion(t, true, fileCachePieceResourceStorage)
}
func TestAddTorrentPiecesNotAlreadyCompleted(t *testing.T) {
- testAddTorrentPriorPieceCompletion(t, false)
+ testAddTorrentPriorPieceCompletion(t, false, fileCachePieceResourceStorage)
}
func TestAddMetainfoWithNodes(t *testing.T) {
- cfg := TestingConfig
+ cfg := TestingConfig(t)
+ cfg.ListenHost = func(string) string { return "" }
cfg.NoDHT = false
- // For now, we want to just jam the nodes into the table, without
- // verifying them first. Also the DHT code doesn't support mixing secure
- // and insecure nodes if security is enabled (yet).
- cfg.DHTConfig.NoSecurity = true
- cl, err := NewClient(&cfg)
+ cfg.DhtStartingNodes = func(string) dht.StartingNodesGetter { return func() ([]dht.Addr, error) { return nil, nil } }
+ // For now, we want to just jam the nodes into the table, without verifying them first. Also the
+ // DHT code doesn't support mixing secure and insecure nodes if security is enabled (yet).
+ // cfg.DHTConfig.NoSecurity = true
+ cl, err := NewClient(cfg)
require.NoError(t, err)
defer cl.Close()
- assert.EqualValues(t, cl.DHT().NumNodes(), 0)
+ sum := func() (ret int64) {
+ cl.eachDhtServer(func(s DhtServer) {
+ ret += s.Stats().(dht.ServerStats).OutboundQueriesAttempted
+ })
+ return
+ }
+ assert.EqualValues(t, 0, sum())
tt, err := cl.AddTorrentFromFile("metainfo/testdata/issue_65a.torrent")
require.NoError(t, err)
- assert.Len(t, tt.trackers, 5)
- assert.EqualValues(t, 6, cl.DHT().NumNodes())
+ // Nodes are not added or exposed in Torrent's metainfo. We just randomly
+ // check if the announce-list is here instead. TODO: Add nodes.
+ assert.Len(t, tt.metainfo.AnnounceList, 5)
+ // There are 6 nodes in the torrent file.
+ for sum() != int64(6*len(cl.dhtServers)) {
+ time.Sleep(time.Millisecond)
+ }
}
type testDownloadCancelParams struct {
- Responsive bool
- Readahead int64
- SetReadahead bool
- ExportClientStatus bool
SetLeecherStorageCapacity bool
LeecherStorageCapacity int64
Cancel bool
func testDownloadCancel(t *testing.T, ps testDownloadCancelParams) {
greetingTempDir, mi := testutil.GreetingTestTorrent()
defer os.RemoveAll(greetingTempDir)
- cfg := TestingConfig
+ cfg := TestingConfig(t)
cfg.Seed = true
cfg.DataDir = greetingTempDir
- seeder, err := NewClient(&cfg)
+ seeder, err := NewClient(cfg)
require.NoError(t, err)
defer seeder.Close()
- if ps.ExportClientStatus {
- testutil.ExportStatusWriter(seeder, "s")
- }
- seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
- leecherDataDir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(leecherDataDir)
+ defer testutil.ExportStatusWriter(seeder, "s", t)()
+ seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
+ seederTorrent.VerifyData()
+ leecherDataDir := t.TempDir()
fc, err := filecache.NewCache(leecherDataDir)
require.NoError(t, err)
if ps.SetLeecherStorageCapacity {
fc.SetCapacity(ps.LeecherStorageCapacity)
}
- cfg.DefaultStorage = storage.NewPieceFileStorage(fc.AsFileStore())
+ cfg.DefaultStorage = storage.NewResourcePieces(fc.AsResourceProvider())
cfg.DataDir = leecherDataDir
- leecher, _ := NewClient(&cfg)
+ leecher, err := NewClient(cfg)
+ require.NoError(t, err)
defer leecher.Close()
- if ps.ExportClientStatus {
- testutil.ExportStatusWriter(leecher, "l")
- }
+ defer testutil.ExportStatusWriter(leecher, "l", t)()
leecherGreeting, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
ret = TorrentSpecFromMetaInfo(mi)
ret.ChunkSize = 2
assert.True(t, new)
psc := leecherGreeting.SubscribePieceStateChanges()
defer psc.Close()
- leecherGreeting.DownloadAll()
+
+ leecherGreeting.cl.lock()
+ leecherGreeting.downloadPiecesLocked(0, leecherGreeting.numPieces())
if ps.Cancel {
- leecherGreeting.CancelPieces(0, leecherGreeting.NumPieces())
+ leecherGreeting.cancelPiecesLocked(0, leecherGreeting.NumPieces(), "")
}
- leecherGreeting.AddPeers([]Peer{
- Peer{
- IP: missinggo.AddrIP(seeder.ListenAddr()),
- Port: missinggo.AddrPort(seeder.ListenAddr()),
- },
- })
+ leecherGreeting.cl.unlock()
+ done := make(chan struct{})
+ defer close(done)
+ go leecherGreeting.AddClientPeer(seeder)
completes := make(map[int]bool, 3)
-values:
- for {
- // started := time.Now()
- select {
- case _v := <-psc.Values:
- // log.Print(time.Since(started))
- v := _v.(PieceStateChange)
- completes[v.Index] = v.Complete
- case <-time.After(100 * time.Millisecond):
- break values
+ expected := func() map[int]bool {
+ if ps.Cancel {
+ return map[int]bool{0: false, 1: false, 2: false}
+ } else {
+ return map[int]bool{0: true, 1: true, 2: true}
}
+ }()
+ for !reflect.DeepEqual(completes, expected) {
+ v := <-psc.Values
+ completes[v.Index] = v.Complete
}
- if ps.Cancel {
- assert.EqualValues(t, map[int]bool{0: false, 1: false, 2: false}, completes)
- } else {
- assert.EqualValues(t, map[int]bool{0: true, 1: true, 2: true}, completes)
- }
-
}
func TestTorrentDownloadAll(t *testing.T) {
// Ensure that it's an error for a peer to send an invalid have message.
func TestPeerInvalidHave(t *testing.T) {
- cl, err := NewClient(&TestingConfig)
+ cfg := TestingConfig(t)
+ cfg.DropMutuallyCompletePeers = false
+ cl, err := NewClient(cfg)
require.NoError(t, err)
defer cl.Close()
- ie := metainfo.InfoEx{
- Info: metainfo.Info{
- PieceLength: 1,
- Pieces: make([]byte, 20),
- Files: []metainfo.FileInfo{{Length: 1}},
- },
+ info := metainfo.Info{
+ PieceLength: 1,
+ Pieces: make([]byte, 20),
+ Files: []metainfo.FileInfo{{Length: 1}},
}
- ie.UpdateBytes()
+ infoBytes, err := bencode.Marshal(info)
+ require.NoError(t, err)
tt, _new, err := cl.AddTorrentSpec(&TorrentSpec{
- Info: &ie,
- InfoHash: ie.Hash(),
+ InfoBytes: infoBytes,
+ InfoHash: metainfo.HashBytes(infoBytes),
+ Storage: badStorage{},
})
require.NoError(t, err)
assert.True(t, _new)
defer tt.Drop()
- cn := &connection{
- t: tt,
- }
+ cn := &PeerConn{Peer: Peer{
+ t: tt,
+ callbacks: &cfg.Callbacks,
+ }}
+ tt.conns[cn] = struct{}{}
+ cn.peerImpl = cn
+ cl.lock()
+ defer cl.unlock()
assert.NoError(t, cn.peerSentHave(0))
assert.Error(t, cn.peerSentHave(1))
}
func TestPieceCompletedInStorageButNotClient(t *testing.T) {
greetingTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
defer os.RemoveAll(greetingTempDir)
- cfg := TestingConfig
+ cfg := TestingConfig(t)
cfg.DataDir = greetingTempDir
- seeder, err := NewClient(&TestingConfig)
+ seeder, err := NewClient(TestingConfig(t))
require.NoError(t, err)
+ defer seeder.Close()
seeder.AddTorrentSpec(&TorrentSpec{
- Info: &greetingMetainfo.Info,
+ InfoBytes: greetingMetainfo.InfoBytes,
+ })
+}
+
+// Check that when the listen port is 0, all the protocols listened on have
+// the same port, and it isn't zero.
+func TestClientDynamicListenPortAllProtocols(t *testing.T) {
+ cl, err := NewClient(TestingConfig(t))
+ require.NoError(t, err)
+ defer cl.Close()
+ port := cl.LocalPort()
+ assert.NotEqual(t, 0, port)
+ cl.eachListener(func(s Listener) bool {
+ assert.Equal(t, port, missinggo.AddrPort(s.Addr()))
+ return true
})
}
+
+func TestClientDynamicListenTCPOnly(t *testing.T) {
+ cfg := TestingConfig(t)
+ cfg.DisableUTP = true
+ cfg.DisableTCP = false
+ cl, err := NewClient(cfg)
+ require.NoError(t, err)
+ defer cl.Close()
+ assert.NotEqual(t, 0, cl.LocalPort())
+}
+
+func TestClientDynamicListenUTPOnly(t *testing.T) {
+ cfg := TestingConfig(t)
+ cfg.DisableTCP = true
+ cfg.DisableUTP = false
+ cl, err := NewClient(cfg)
+ require.NoError(t, err)
+ defer cl.Close()
+ assert.NotEqual(t, 0, cl.LocalPort())
+}
+
+func totalConns(tts []*Torrent) (ret int) {
+ for _, tt := range tts {
+ tt.cl.lock()
+ ret += len(tt.conns)
+ tt.cl.unlock()
+ }
+ return
+}
+
+func TestSetMaxEstablishedConn(t *testing.T) {
+ var tts []*Torrent
+ ih := testutil.GreetingMetaInfo().HashInfoBytes()
+ cfg := TestingConfig(t)
+ cfg.DisableAcceptRateLimiting = true
+ cfg.DropDuplicatePeerIds = true
+ for i := 0; i < 3; i += 1 {
+ cl, err := NewClient(cfg)
+ require.NoError(t, err)
+ defer cl.Close()
+ tt, _ := cl.AddTorrentInfoHash(ih)
+ tt.SetMaxEstablishedConns(2)
+ defer testutil.ExportStatusWriter(cl, fmt.Sprintf("%d", i), t)()
+ tts = append(tts, tt)
+ }
+ addPeers := func() {
+ for _, tt := range tts {
+ for _, _tt := range tts {
+ // if tt != _tt {
+ tt.AddClientPeer(_tt.cl)
+ // }
+ }
+ }
+ }
+ waitTotalConns := func(num int) {
+ for totalConns(tts) != num {
+ addPeers()
+ time.Sleep(time.Millisecond)
+ }
+ }
+ addPeers()
+ waitTotalConns(6)
+ tts[0].SetMaxEstablishedConns(1)
+ waitTotalConns(4)
+ tts[0].SetMaxEstablishedConns(0)
+ waitTotalConns(2)
+ tts[0].SetMaxEstablishedConns(1)
+ addPeers()
+ waitTotalConns(4)
+ tts[0].SetMaxEstablishedConns(2)
+ addPeers()
+ waitTotalConns(6)
+}
+
+// Creates a file containing its own name as data. Make a metainfo from that, adds it to the given
+// client, and returns a magnet link.
+func makeMagnet(t *testing.T, cl *Client, dir, name string) string {
+ os.MkdirAll(dir, 0o770)
+ file, err := os.Create(filepath.Join(dir, name))
+ require.NoError(t, err)
+ file.Write([]byte(name))
+ file.Close()
+ mi := metainfo.MetaInfo{}
+ mi.SetDefaults()
+ info := metainfo.Info{PieceLength: 256 * 1024}
+ err = info.BuildFromFilePath(filepath.Join(dir, name))
+ require.NoError(t, err)
+ mi.InfoBytes, err = bencode.Marshal(info)
+ require.NoError(t, err)
+ magnet := mi.Magnet(nil, &info).String()
+ tr, err := cl.AddTorrent(&mi)
+ require.NoError(t, err)
+ require.True(t, tr.Seeding())
+ tr.VerifyData()
+ return magnet
+}
+
+// https://github.com/anacrolix/torrent/issues/114
+func TestMultipleTorrentsWithEncryption(t *testing.T) {
+ testSeederLeecherPair(
+ t,
+ func(cfg *ClientConfig) {
+ cfg.HeaderObfuscationPolicy.Preferred = true
+ cfg.HeaderObfuscationPolicy.RequirePreferred = true
+ },
+ func(cfg *ClientConfig) {
+ cfg.HeaderObfuscationPolicy.RequirePreferred = false
+ },
+ )
+}
+
+// Test that the leecher can download a torrent in its entirety from the seeder. Note that the
+// seeder config is done first.
+func testSeederLeecherPair(t *testing.T, seeder, leecher func(*ClientConfig)) {
+ cfg := TestingConfig(t)
+ cfg.Seed = true
+ cfg.DataDir = filepath.Join(cfg.DataDir, "server")
+ os.Mkdir(cfg.DataDir, 0o755)
+ seeder(cfg)
+ server, err := NewClient(cfg)
+ require.NoError(t, err)
+ defer server.Close()
+ defer testutil.ExportStatusWriter(server, "s", t)()
+ magnet1 := makeMagnet(t, server, cfg.DataDir, "test1")
+ // Extra torrents are added to test the seeder having to match incoming obfuscated headers
+ // against more than one torrent. See issue #114
+ makeMagnet(t, server, cfg.DataDir, "test2")
+ for i := 0; i < 100; i++ {
+ makeMagnet(t, server, cfg.DataDir, fmt.Sprintf("test%d", i+2))
+ }
+ cfg = TestingConfig(t)
+ cfg.DataDir = filepath.Join(cfg.DataDir, "client")
+ leecher(cfg)
+ client, err := NewClient(cfg)
+ require.NoError(t, err)
+ defer client.Close()
+ defer testutil.ExportStatusWriter(client, "c", t)()
+ tr, err := client.AddMagnet(magnet1)
+ require.NoError(t, err)
+ tr.AddClientPeer(server)
+ <-tr.GotInfo()
+ tr.DownloadAll()
+ client.WaitAll()
+}
+
+// This appears to be the situation with the S3 BitTorrent client.
+func TestObfuscatedHeaderFallbackSeederDisallowsLeecherPrefers(t *testing.T) {
+ // Leecher prefers obfuscation, but the seeder does not allow it.
+ testSeederLeecherPair(
+ t,
+ func(cfg *ClientConfig) {
+ cfg.HeaderObfuscationPolicy.Preferred = false
+ cfg.HeaderObfuscationPolicy.RequirePreferred = true
+ },
+ func(cfg *ClientConfig) {
+ cfg.HeaderObfuscationPolicy.Preferred = true
+ cfg.HeaderObfuscationPolicy.RequirePreferred = false
+ },
+ )
+}
+
+func TestObfuscatedHeaderFallbackSeederRequiresLeecherPrefersNot(t *testing.T) {
+ // Leecher prefers no obfuscation, but the seeder enforces it.
+ testSeederLeecherPair(
+ t,
+ func(cfg *ClientConfig) {
+ cfg.HeaderObfuscationPolicy.Preferred = true
+ cfg.HeaderObfuscationPolicy.RequirePreferred = true
+ },
+ func(cfg *ClientConfig) {
+ cfg.HeaderObfuscationPolicy.Preferred = false
+ cfg.HeaderObfuscationPolicy.RequirePreferred = false
+ },
+ )
+}
+
+func TestClientAddressInUse(t *testing.T) {
+ s, _ := NewUtpSocket("udp", "localhost:50007", nil, log.Default)
+ if s != nil {
+ defer s.Close()
+ }
+ cfg := TestingConfig(t).SetListenAddr("localhost:50007")
+ cfg.DisableUTP = false
+ cl, err := NewClient(cfg)
+ if err == nil {
+ assert.Nil(t, cl.Close())
+ }
+ require.Error(t, err)
+ require.Nil(t, cl)
+}
+
+func TestClientHasDhtServersWhenUtpDisabled(t *testing.T) {
+ cc := TestingConfig(t)
+ cc.DisableUTP = true
+ cc.NoDHT = false
+ cl, err := NewClient(cc)
+ require.NoError(t, err)
+ defer cl.Close()
+ assert.NotEmpty(t, cl.DhtServers())
+}
+
+func TestClientDisabledImplicitNetworksButDhtEnabled(t *testing.T) {
+ cfg := TestingConfig(t)
+ cfg.DisableTCP = true
+ cfg.DisableUTP = true
+ cfg.NoDHT = false
+ cl, err := NewClient(cfg)
+ require.NoError(t, err)
+ defer cl.Close()
+ assert.Empty(t, cl.listeners)
+ assert.NotEmpty(t, cl.DhtServers())
+}
+
+func TestBadPeerIpPort(t *testing.T) {
+ for _, tc := range []struct {
+ title string
+ ip net.IP
+ port int
+ expectedOk bool
+ setup func(*Client)
+ }{
+ {"empty both", nil, 0, true, func(*Client) {}},
+ {"empty/nil ip", nil, 6666, true, func(*Client) {}},
+ {
+ "empty port",
+ net.ParseIP("127.0.0.1/32"),
+ 0, true,
+ func(*Client) {},
+ },
+ {
+ "in doppleganger addresses",
+ net.ParseIP("127.0.0.1/32"),
+ 2322,
+ true,
+ func(cl *Client) {
+ cl.dopplegangerAddrs["10.0.0.1:2322"] = struct{}{}
+ },
+ },
+ {
+ "in IP block list",
+ net.ParseIP("10.0.0.1"),
+ 2322,
+ true,
+ func(cl *Client) {
+ cl.ipBlockList = iplist.New([]iplist.Range{
+ {First: net.ParseIP("10.0.0.1"), Last: net.ParseIP("10.0.0.255")},
+ })
+ },
+ },
+ {
+ "in bad peer IPs",
+ net.ParseIP("10.0.0.1"),
+ 2322,
+ true,
+ func(cl *Client) {
+ ipAddr, ok := netip.AddrFromSlice(net.ParseIP("10.0.0.1"))
+ require.True(t, ok)
+ cl.badPeerIPs = map[netip.Addr]struct{}{}
+ cl.badPeerIPs[ipAddr] = struct{}{}
+ },
+ },
+ {
+ "good",
+ net.ParseIP("10.0.0.1"),
+ 2322,
+ false,
+ func(cl *Client) {},
+ },
+ } {
+ t.Run(tc.title, func(t *testing.T) {
+ cfg := TestingConfig(t)
+ cfg.DisableTCP = true
+ cfg.DisableUTP = true
+ cfg.NoDHT = false
+ cl, err := NewClient(cfg)
+ require.NoError(t, err)
+ defer cl.Close()
+
+ tc.setup(cl)
+ require.Equal(t, tc.expectedOk, cl.badPeerIPPort(tc.ip, tc.port))
+ })
+ }
+}
+
+// https://github.com/anacrolix/torrent/issues/837
+func TestClientConfigSetHandlerNotIgnored(t *testing.T) {
+ cfg := TestingConfig(t)
+ cfg.Logger.SetHandlers(log.DiscardHandler)
+ c := qt.New(t)
+ cl, err := NewClient(cfg)
+ c.Assert(err, qt.IsNil)
+ defer cl.Close()
+ c.Assert(cl.logger.Handlers, qt.HasLen, 1)
+ h := cl.logger.Handlers[0].(log.StreamHandler)
+ c.Check(h.W, qt.Equals, io.Discard)
+}