"encoding/binary"
"fmt"
"io"
- "io/ioutil"
+ "net"
+ "net/netip"
"os"
"path/filepath"
"reflect"
- "sync"
"testing"
+ "testing/iotest"
"time"
- "github.com/anacrolix/dht"
- _ "github.com/anacrolix/envpprof"
- "github.com/anacrolix/missinggo"
- "github.com/anacrolix/missinggo/filecache"
- "github.com/bradfitz/iter"
+ "github.com/frankban/quicktest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- "golang.org/x/time/rate"
+
+ "github.com/anacrolix/log"
+
+ "github.com/anacrolix/dht/v2"
+ "github.com/anacrolix/missinggo/v2"
+ "github.com/anacrolix/missinggo/v2/filecache"
"github.com/anacrolix/torrent/bencode"
"github.com/anacrolix/torrent/internal/testutil"
"github.com/anacrolix/torrent/storage"
)
-func TestingConfig() *ClientConfig {
- cfg := NewDefaultClientConfig()
- cfg.ListenHost = LoopbackListenHost
- cfg.NoDHT = true
- cfg.DataDir = tempDir()
- cfg.DisableTrackers = true
- cfg.NoDefaultPortForwarding = true
- cfg.DisableAcceptRateLimiting = true
- return cfg
-}
-
func TestClientDefault(t *testing.T) {
- cl, err := NewClient(TestingConfig())
+ cl, err := NewClient(TestingConfig(t))
require.NoError(t, err)
- cl.Close()
+ require.Empty(t, cl.Close())
}
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()
-}
-
-func TestBoltPieceCompletionClosedWhenClientClosed(t *testing.T) {
- cfg := TestingConfig()
- pc, err := storage.NewBoltPieceCompletion(cfg.DataDir)
- require.NoError(t, err)
- ci := storage.NewFileWithCompletion(cfg.DataDir, pc)
- defer ci.Close()
- cfg.DefaultStorage = ci
- cl, err := NewClient(cfg)
- require.NoError(t, err)
- cl.Close()
- // And again, https://github.com/anacrolix/torrent/issues/158
- cl, err = NewClient(cfg)
- 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()
func TestTorrentInitialState(t *testing.T) {
dir, mi := testutil.GreetingTestTorrent()
defer os.RemoveAll(dir)
- cl := &Client{
- config: &ClientConfig{},
- }
+ var cl Client
+ cl.init(TestingConfig(t))
cl.initLogger()
tor := cl.newTorrent(
mi.HashInfoBytes(),
- storage.NewFileWithCompletion(tempDir(), storage.NewMapPieceCompletion()),
+ storage.NewFileWithCompletion(t.TempDir(), storage.NewMapPieceCompletion()),
)
tor.setChunkSize(2)
- tor.cl.mu.Lock()
- err := tor.setInfoBytes(mi.InfoBytes)
- tor.cl.mu.Unlock()
+ 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) {
}
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)
}
}
-type FileCacheClientStorageFactoryParams struct {
- Capacity int64
- SetCapacity bool
- Wrapper func(*filecache.Cache) storage.ClientImpl
-}
-
-func NewFileCacheClientStorageFactory(ps FileCacheClientStorageFactoryParams) storageFactory {
- return func(dataDir string) storage.ClientImpl {
- fc, err := filecache.NewCache(dataDir)
- if err != nil {
- panic(err)
- }
- if ps.SetCapacity {
- fc.SetCapacity(ps.Capacity)
- }
- return ps.Wrapper(fc)
- }
-}
-
-type storageFactory func(string) storage.ClientImpl
-
-func TestClientTransferDefault(t *testing.T) {
- testClientTransfer(t, testClientTransferParams{
- ExportClientStatus: true,
- LeecherStorage: NewFileCacheClientStorageFactory(FileCacheClientStorageFactoryParams{
- Wrapper: fileCachePieceResourceStorage,
- }),
- })
-}
-
-func TestClientTransferRateLimitedUpload(t *testing.T) {
- started := time.Now()
- testClientTransfer(t, testClientTransferParams{
- // We are uploading 13 bytes (the length of the greeting torrent). The
- // chunks are 2 bytes in length. Then the smallest burst we can run
- // with is 2. Time taken is (13-burst)/rate.
- SeederUploadRateLimiter: rate.NewLimiter(11, 2),
- ExportClientStatus: true,
- })
- require.True(t, time.Since(started) > time.Second)
-}
-
-func TestClientTransferRateLimitedDownload(t *testing.T) {
- testClientTransfer(t, testClientTransferParams{
- LeecherDownloadRateLimiter: rate.NewLimiter(512, 512),
- })
-}
-
func fileCachePieceResourceStorage(fc *filecache.Cache) storage.ClientImpl {
- return storage.NewResourcePieces(fc.AsResourceProvider())
-}
-
-func TestClientTransferSmallCache(t *testing.T) {
- testClientTransfer(t, testClientTransferParams{
- LeecherStorage: NewFileCacheClientStorageFactory(FileCacheClientStorageFactoryParams{
- SetCapacity: true,
- // Going below the piece length means it can't complete a piece so
- // that it can be hashed.
- Capacity: 5,
- Wrapper: fileCachePieceResourceStorage,
- }),
- 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) {
- // Leecher storage
- for _, ls := range []storageFactory{
- NewFileCacheClientStorageFactory(FileCacheClientStorageFactoryParams{
- Wrapper: fileCachePieceResourceStorage,
- }),
- storage.NewBoltDB,
- } {
- // Seeder storage
- for _, ss := range []func(string) storage.ClientImpl{
- storage.NewFile,
- storage.NewMMap,
- } {
- for _, responsive := range []bool{false, true} {
- testClientTransfer(t, testClientTransferParams{
- Responsive: responsive,
- SeederStorage: ss,
- LeecherStorage: ls,
- })
- 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,
- LeecherStorage: ls,
- })
- }
- }
- }
- }
-}
-
-type testClientTransferParams struct {
- Responsive bool
- Readahead int64
- SetReadahead bool
- ExportClientStatus bool
- LeecherStorage func(string) storage.ClientImpl
- SeederStorage func(string) storage.ClientImpl
- SeederUploadRateLimiter *rate.Limiter
- LeecherDownloadRateLimiter *rate.Limiter
-}
-
-// Creates a seeder and a leecher, and ensures the data transfers when a read
-// is attempted on the leecher.
-func testClientTransfer(t *testing.T, ps testClientTransferParams) {
- greetingTempDir, mi := testutil.GreetingTestTorrent()
- defer os.RemoveAll(greetingTempDir)
- // Create seeder and a Torrent.
- cfg := TestingConfig()
- cfg.Seed = true
- if ps.SeederUploadRateLimiter != nil {
- cfg.UploadRateLimiter = ps.SeederUploadRateLimiter
- }
- // cfg.ListenAddr = "localhost:4000"
- if ps.SeederStorage != nil {
- cfg.DefaultStorage = ps.SeederStorage(greetingTempDir)
- defer cfg.DefaultStorage.Close()
- } else {
- cfg.DataDir = greetingTempDir
- }
- seeder, err := NewClient(cfg)
- require.NoError(t, err)
- if ps.ExportClientStatus {
- testutil.ExportStatusWriter(seeder, "s")
- }
- seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
- // Run a Stats right after Closing the Client. This will trigger the Stats
- // panic in #214 caused by RemoteAddr on Closed uTP sockets.
- defer seederTorrent.Stats()
- defer seeder.Close()
- seederTorrent.VerifyData()
- // Create leecher and a Torrent.
- leecherDataDir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(leecherDataDir)
- cfg = TestingConfig()
- if ps.LeecherStorage == nil {
- cfg.DataDir = leecherDataDir
- } else {
- cfg.DefaultStorage = ps.LeecherStorage(leecherDataDir)
- }
- if ps.LeecherDownloadRateLimiter != nil {
- cfg.DownloadRateLimiter = ps.LeecherDownloadRateLimiter
- }
- cfg.Seed = false
- leecher, err := NewClient(cfg)
- require.NoError(t, err)
- defer leecher.Close()
- if ps.ExportClientStatus {
- testutil.ExportStatusWriter(leecher, "l")
- }
- leecherTorrent, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
- ret = TorrentSpecFromMetaInfo(mi)
- ret.ChunkSize = 2
- return
- }())
- require.NoError(t, err)
- assert.True(t, new)
- // Now do some things with leecher and seeder.
- leecherTorrent.AddClientPeer(seeder)
- // The Torrent should not be interested in obtaining peers, so the one we
- // just added should be the only one.
- assert.False(t, leecherTorrent.Seeding())
- assert.EqualValues(t, 1, leecherTorrent.Stats().PendingPeers)
- r := leecherTorrent.NewReader()
- defer r.Close()
- if ps.Responsive {
- r.SetResponsive()
- }
- if ps.SetReadahead {
- r.SetReadahead(ps.Readahead)
- }
- assertReadAllGreeting(t, r)
-
- seederStats := seederTorrent.Stats()
- assert.True(t, 13 <= seederStats.BytesWrittenData.Int64())
- assert.True(t, 8 <= seederStats.ChunksWritten.Int64())
-
- leecherStats := leecherTorrent.Stats()
- assert.True(t, 13 <= leecherStats.BytesReadData.Int64())
- assert.True(t, 8 <= leecherStats.ChunksRead.Int64())
-
- // Try reading through again for the cases where the torrent data size
- // exceeds the size of the cache.
- assertReadAllGreeting(t, r)
-}
-
-func assertReadAllGreeting(t *testing.T, r io.ReadSeeker) {
- pos, err := r.Seek(0, io.SeekStart)
- 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")
- seederTorrent, ok, err := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
- require.NoError(t, err)
- assert.True(t, ok)
- seederTorrent.VerifyData()
-
- cfg = TestingConfig()
- cfg.Seed = true
- 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 = TestingConfig()
- cfg.Seed = false
- cfg.DataDir, err = ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(cfg.DataDir)
- leecherLeecher, _ := NewClient(cfg)
- require.NoError(t, err)
- defer leecherLeecher.Close()
- testutil.ExportStatusWriter(leecherLeecher, "ll")
- leecherGreeting, ok, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
- ret = TorrentSpecFromMetaInfo(mi)
- ret.ChunkSize = 2
- return
- }())
- require.NoError(t, err)
- assert.True(t, ok)
- llg, ok, err := leecherLeecher.AddTorrentSpec(func() (ret *TorrentSpec) {
- ret = TorrentSpecFromMetaInfo(mi)
- ret.ChunkSize = 3
- return
- }())
- require.NoError(t, err)
- assert.True(t, ok)
- // 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)
- }()
- done := make(chan struct{})
- defer close(done)
- go leecherGreeting.AddClientPeer(seeder)
- go leecherGreeting.AddClientPeer(leecherLeecher)
- wg.Add(1)
- go func() {
- defer wg.Done()
- leecherGreeting.DownloadAll()
- leecher.WaitAll()
- }()
- wg.Wait()
+ return storage.NewResourcePiecesOpts(
+ fc.AsResourceProvider(),
+ storage.ResourcePiecesOpts{
+ LeaveIncompleteChunks: true,
+ },
+ )
}
func TestMergingTrackersByAddingSpecs(t *testing.T) {
- cl, err := NewClient(TestingConfig())
+ cl, err := NewClient(TestingConfig(t))
require.NoError(t, err)
defer cl.Close()
spec := TorrentSpec{}
// 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, err := NewClient(cfg)
require.NoError(t, err)
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.ListenHost = func(string) string { return "redonk" }
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)
defer seeder.Close()
seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
seederTorrent.VerifyData()
- leecherDataDir, err := ioutil.TempDir("", "")
- require.Nil(t, err)
- defer os.RemoveAll(leecherDataDir)
- cfg = TestingConfig()
+ leecherDataDir := t.TempDir()
+ cfg = TestingConfig(t)
cfg.DataDir = leecherDataDir
leecher, err := NewClient(cfg)
require.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)
defer seeder.Close()
seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
seederTorrent.VerifyData()
- leecherDataDir, err := ioutil.TempDir("", "")
- require.Nil(t, err)
- defer os.RemoveAll(leecherDataDir)
- cfg = TestingConfig()
+ leecherDataDir := t.TempDir()
+ cfg = TestingConfig(t)
cfg.DataDir = leecherDataDir
leecher, err := NewClient(cfg)
require.Nil(t, err)
_, err = io.ReadFull(reader, b)
assert.Nil(t, err)
assert.EqualValues(t, "lo", string(b))
- go leecherTorrent.Drop()
_, 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)
require.NoError(t, err)
defer cl.Close()
numServers := 0
- cl.eachDhtServer(func(s *dht.Server) {
- assert.Equal(t, ipl, s.IPBlocklist())
+ 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()
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.HashInfoBytes(),
}
func writeTorrentData(ts *storage.Torrent, info metainfo.Info, b []byte) {
- for i := range iter.N(info.NumPieces()) {
+ 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, csf func(*filecache.Cache) storage.ClientImpl) {
- fileCacheDir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(fileCacheDir)
+ fileCacheDir := t.TempDir()
fileCache, err := filecache.NewCache(fileCacheDir)
require.NoError(t, err)
greetingDataTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
defer os.RemoveAll(greetingDataTempDir)
filePieceStore := csf(fileCache)
- defer filePieceStore.Close()
info, err := greetingMetainfo.UnmarshalInfo()
require.NoError(t, err)
ih := greetingMetainfo.HashInfoBytes()
require.NoError(t, greetingData.Piece(p).MarkComplete())
}
}
- cfg := TestingConfig()
+ cfg := TestingConfig(t)
// TODO: Disable network option?
cfg.DisableTCP = true
cfg.DisableUTP = true
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 TestAddMetainfoWithNodes(t *testing.T) {
- cfg := TestingConfig()
+ cfg := TestingConfig(t)
cfg.ListenHost = func(string) string { return "" }
cfg.NoDHT = false
- cfg.DhtStartingNodes = 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.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()
sum := func() (ret int64) {
- cl.eachDhtServer(func(s *dht.Server) {
- ret += s.Stats().OutboundQueriesAttempted
+ cl.eachDhtServer(func(s DhtServer) {
+ ret += s.Stats().(dht.ServerStats).OutboundQueriesAttempted
})
return
}
// 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.
- assert.EqualValues(t, 6*len(cl.dhtServers), sum())
+ for sum() != int64(6*len(cl.dhtServers)) {
+ time.Sleep(time.Millisecond)
+ }
}
type testDownloadCancelParams struct {
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)
require.NoError(t, err)
defer seeder.Close()
- testutil.ExportStatusWriter(seeder, "s")
+ defer testutil.ExportStatusWriter(seeder, "s", t)()
seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
seederTorrent.VerifyData()
- leecherDataDir, err := ioutil.TempDir("", "")
- require.NoError(t, err)
- defer os.RemoveAll(leecherDataDir)
+ leecherDataDir := t.TempDir()
fc, err := filecache.NewCache(leecherDataDir)
require.NoError(t, err)
if ps.SetLeecherStorageCapacity {
}
cfg.DefaultStorage = storage.NewResourcePieces(fc.AsResourceProvider())
cfg.DataDir = leecherDataDir
- leecher, _ := NewClient(cfg)
+ leecher, err := NewClient(cfg)
+ require.NoError(t, err)
defer leecher.Close()
- testutil.ExportStatusWriter(leecher, "l")
+ defer testutil.ExportStatusWriter(leecher, "l", t)()
leecherGreeting, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
ret = TorrentSpecFromMetaInfo(mi)
ret.ChunkSize = 2
psc := leecherGreeting.SubscribePieceStateChanges()
defer psc.Close()
- leecherGreeting.cl.mu.Lock()
+ leecherGreeting.cl.lock()
leecherGreeting.downloadPiecesLocked(0, leecherGreeting.numPieces())
if ps.Cancel {
- leecherGreeting.cancelPiecesLocked(0, leecherGreeting.NumPieces())
+ leecherGreeting.cancelPiecesLocked(0, leecherGreeting.NumPieces(), "")
}
- leecherGreeting.cl.mu.Unlock()
+ leecherGreeting.cl.unlock()
done := make(chan struct{})
defer close(done)
go leecherGreeting.AddClientPeer(seeder)
}
}()
for !reflect.DeepEqual(completes, expected) {
- select {
- case _v := <-psc.Values:
- v := _v.(PieceStateChange)
- completes[v.Index] = v.Complete
- }
+ v := <-psc.Values
+ completes[v.Index] = v.Complete
}
}
// 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()
info := metainfo.Info{
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)
seeder.AddTorrentSpec(&TorrentSpec{
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())
+ cl, err := NewClient(TestingConfig(t))
require.NoError(t, err)
defer cl.Close()
port := cl.LocalPort()
assert.NotEqual(t, 0, port)
- cl.eachListener(func(s socket) bool {
+ cl.eachListener(func(s Listener) bool {
assert.Equal(t, port, missinggo.AddrPort(s.Addr()))
return true
})
}
func TestClientDynamicListenTCPOnly(t *testing.T) {
- cfg := TestingConfig()
+ 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())
- cl.eachListener(func(s socket) bool {
- assert.True(t, isTcpNetwork(s.Addr().Network()))
- return true
- })
}
func TestClientDynamicListenUTPOnly(t *testing.T) {
- cfg := TestingConfig()
+ 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())
- cl.eachListener(func(s socket) bool {
- assert.True(t, isUtpNetwork(s.Addr().Network()))
- return true
- })
-}
-
-func TestClientDynamicListenPortNoProtocols(t *testing.T) {
- cfg := TestingConfig()
- cfg.DisableTCP = true
- cfg.DisableUTP = true
- cl, err := NewClient(cfg)
- require.NoError(t, err)
- defer cl.Close()
- assert.Equal(t, 0, cl.LocalPort())
}
func totalConns(tts []*Torrent) (ret int) {
for _, tt := range tts {
- tt.cl.mu.Lock()
+ tt.cl.lock()
ret += len(tt.conns)
- tt.cl.mu.Unlock()
+ tt.cl.unlock()
}
return
}
func TestSetMaxEstablishedConn(t *testing.T) {
- ss := testutil.NewStatusServer(t)
- defer ss.Close()
var tts []*Torrent
ih := testutil.GreetingMetaInfo().HashInfoBytes()
- cfg := TestingConfig()
+ cfg := TestingConfig(t)
cfg.DisableAcceptRateLimiting = true
- cfg.dropDuplicatePeerIds = true
- for i := range iter.N(3) {
+ 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)
- ss.HandleStatusWriter(cl, fmt.Sprintf("/%d", i))
+ defer testutil.ExportStatusWriter(cl, fmt.Sprintf("%d", i), t)()
tts = append(tts, tt)
}
addPeers := func() {
waitTotalConns(6)
}
-func makeMagnet(t *testing.T, cl *Client, dir string, name string) string {
- os.MkdirAll(dir, 0770)
+// 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))
require.NoError(t, err)
mi.InfoBytes, err = bencode.Marshal(info)
require.NoError(t, err)
- magnet := mi.Magnet(name, mi.HashInfoBytes()).String()
+ magnet := mi.Magnet(nil, &info).String()
tr, err := cl.AddTorrent(&mi)
require.NoError(t, err)
require.True(t, tr.Seeding())
// https://github.com/anacrolix/torrent/issues/114
func TestMultipleTorrentsWithEncryption(t *testing.T) {
- cfg := TestingConfig()
- cfg.DisableUTP = true
+ 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")
- cfg.ForceEncryption = true
- os.Mkdir(cfg.DataDir, 0755)
+ os.Mkdir(cfg.DataDir, 0o755)
+ seeder(cfg)
server, err := NewClient(cfg)
require.NoError(t, err)
defer server.Close()
- testutil.ExportStatusWriter(server, "s")
+ 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")
- cfg = TestingConfig()
- cfg.DisableUTP = true
+ 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")
- cfg.ForceEncryption = true
+ leecher(cfg)
client, err := NewClient(cfg)
require.NoError(t, err)
defer client.Close()
- testutil.ExportStatusWriter(client, "c")
+ defer testutil.ExportStatusWriter(client, "c", t)()
tr, err := client.AddMagnet(magnet1)
require.NoError(t, err)
tr.AddClientPeer(server)
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", ":50007")
+ s, _ := NewUtpSocket("udp", ":50007", nil, log.Default)
if s != nil {
defer s.Close()
}
- cfg := TestingConfig().SetListenAddr(":50007")
+ cfg := TestingConfig(t).SetListenAddr(":50007")
cl, err := NewClient(cfg)
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{
+ 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))
+ })
+ }
+
+}