"encoding/binary"
"fmt"
"io"
- "io/ioutil"
+ "net"
+ "net/netip"
"os"
"path/filepath"
"reflect"
"testing"
+ "testing/iotest"
"time"
- "github.com/bradfitz/iter"
+ "github.com/frankban/quicktest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
+ "github.com/anacrolix/log"
+
"github.com/anacrolix/dht/v2"
- _ "github.com/anacrolix/envpprof"
- "github.com/anacrolix/missinggo"
+ "github.com/anacrolix/missinggo/v2"
"github.com/anacrolix/missinggo/v2/filecache"
"github.com/anacrolix/torrent/bencode"
)
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: TestingConfig(),
- }
+ var cl Client
+ cl.init(TestingConfig(t))
cl.initLogger()
tor := cl.newTorrent(
mi.HashInfoBytes(),
- storage.NewFileWithCompletion(TestingTempDir.NewSub(), storage.NewMapPieceCompletion()),
+ storage.NewFileWithCompletion(t.TempDir(), storage.NewMapPieceCompletion()),
)
tor.setChunkSize(2)
tor.cl.lock()
- err := tor.setInfoBytes(mi.InfoBytes)
+ err := tor.setInfoBytesLocked(mi.InfoBytes)
tor.cl.unlock()
require.NoError(t, err)
require.Len(t, tor.pieces, 3)
tor.cl.lock()
assert.EqualValues(t, 3, tor.pieceNumPendingChunks(0))
tor.cl.unlock()
- assert.EqualValues(t, chunkSpec{4, 1}, chunkIndexSpec(2, tor.pieceLength(0), tor.chunkSize))
+ 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)
}
func fileCachePieceResourceStorage(fc *filecache.Cache) storage.ClientImpl {
- return storage.NewResourcePieces(fc.AsResourceProvider())
+ 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
cl, err := NewClient(cfg)
require.NoError(b, err)
defer cl.Close()
b.ReportAllocs()
- for range iter.N(b.N) {
+ 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
}
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()
- defer 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 {
leecher, err := NewClient(cfg)
require.NoError(t, err)
defer leecher.Close()
- defer testutil.ExportStatusWriter(leecher, "l")()
+ defer testutil.ExportStatusWriter(leecher, "l", t)()
leecherGreeting, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
ret = TorrentSpecFromMetaInfo(mi)
ret.ChunkSize = 2
leecherGreeting.cl.lock()
leecherGreeting.downloadPiecesLocked(0, leecherGreeting.numPieces())
if ps.Cancel {
- leecherGreeting.cancelPiecesLocked(0, leecherGreeting.NumPieces())
+ leecherGreeting.cancelPiecesLocked(0, leecherGreeting.NumPieces(), "")
}
leecherGreeting.cl.unlock()
done := make(chan struct{})
}
}()
for !reflect.DeepEqual(completes, expected) {
- _v := <-psc.Values
- v := _v.(PieceStateChange)
+ 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()
}
func TestClientDynamicListenTCPOnly(t *testing.T) {
- cfg := TestingConfig()
+ cfg := TestingConfig(t)
cfg.DisableUTP = true
cfg.DisableTCP = false
cl, err := NewClient(cfg)
}
func TestClientDynamicListenUTPOnly(t *testing.T) {
- cfg := TestingConfig()
+ cfg := TestingConfig(t)
cfg.DisableTCP = true
cfg.DisableUTP = false
cl, err := NewClient(cfg)
func TestSetMaxEstablishedConn(t *testing.T) {
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)
- defer testutil.ExportStatusWriter(cl, fmt.Sprintf("%d", i))()
+ defer testutil.ExportStatusWriter(cl, fmt.Sprintf("%d", i), t)()
tts = append(tts, tt)
}
addPeers := func() {
// 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 string, name string) string {
- os.MkdirAll(dir, 0770)
+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())
// 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 func(*ClientConfig), leecher func(*ClientConfig)) {
- cfg := TestingConfig()
+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, 0755)
+ os.Mkdir(cfg.DataDir, 0o755)
seeder(cfg)
server, err := NewClient(cfg)
require.NoError(t, err)
defer server.Close()
- defer 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
for i := 0; i < 100; i++ {
makeMagnet(t, server, cfg.DataDir, fmt.Sprintf("test%d", i+2))
}
- cfg = TestingConfig()
+ 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")()
+ defer testutil.ExportStatusWriter(client, "c", t)()
tr, err := client.AddMagnet(magnet1)
require.NoError(t, err)
tr.AddClientPeer(server)
}
func TestClientAddressInUse(t *testing.T) {
- s, _ := NewUtpSocket("udp", ":50007", nil)
+ 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()
+ cc := TestingConfig(t)
cc.DisableUTP = true
cc.NoDHT = false
cl, err := NewClient(cc)
assert.NotEmpty(t, cl.DhtServers())
}
-func TestIssue335(t *testing.T) {
- dir, mi := testutil.GreetingTestTorrent()
- defer os.RemoveAll(dir)
- cfg := TestingConfig()
- cfg.Seed = false
- cfg.Debug = true
- cfg.DataDir = dir
- comp, err := storage.NewBoltPieceCompletion(dir)
- require.NoError(t, err)
- defer comp.Close()
- cfg.DefaultStorage = storage.NewMMapWithCompletion(dir, comp)
+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()
- tor, new, err := cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
- require.NoError(t, err)
- assert.True(t, new)
- require.True(t, cl.WaitAll())
- tor.Drop()
- _, new, err = cl.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
- require.NoError(t, err)
- assert.True(t, new)
- require.True(t, cl.WaitAll())
+ 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))
+ })
+ }
+
}