12 "github.com/anacrolix/log"
13 "github.com/anacrolix/missinggo/v2/filecache"
14 qt "github.com/frankban/quicktest"
15 "github.com/stretchr/testify/assert"
16 "github.com/stretchr/testify/require"
17 "golang.org/x/time/rate"
19 "github.com/anacrolix/torrent"
20 "github.com/anacrolix/torrent/internal/testutil"
21 "github.com/anacrolix/torrent/storage"
24 type fileCacheClientStorageFactoryParams struct {
29 func newFileCacheClientStorageFactory(ps fileCacheClientStorageFactoryParams) StorageFactory {
30 return func(dataDir string) storage.ClientImplCloser {
31 fc, err := filecache.NewCache(dataDir)
35 var sharedCapacity *int64
37 sharedCapacity = &ps.Capacity
38 fc.SetCapacity(ps.Capacity)
44 storage.NewResourcePiecesOpts(
45 fc.AsResourceProvider(),
46 storage.ResourcePiecesOpts{
47 Capacity: sharedCapacity,
49 ioutil.NopCloser(nil),
54 func TestClientTransferDefault(t *testing.T) {
55 testClientTransfer(t, testClientTransferParams{
56 LeecherStorage: newFileCacheClientStorageFactory(fileCacheClientStorageFactoryParams{}),
60 func TestClientTransferDefaultNoMetadata(t *testing.T) {
61 testClientTransfer(t, testClientTransferParams{
62 LeecherStorage: newFileCacheClientStorageFactory(fileCacheClientStorageFactoryParams{}),
63 LeecherStartsWithoutMetadata: true,
67 func TestClientTransferRateLimitedUpload(t *testing.T) {
69 testClientTransfer(t, testClientTransferParams{
70 // We are uploading 13 bytes (the length of the greeting torrent). The
71 // chunks are 2 bytes in length. Then the smallest burst we can run
72 // with is 2. Time taken is (13-burst)/rate.
73 SeederUploadRateLimiter: rate.NewLimiter(11, 2),
75 require.True(t, time.Since(started) > time.Second)
78 func TestClientTransferRateLimitedDownload(t *testing.T) {
79 testClientTransfer(t, testClientTransferParams{
80 LeecherDownloadRateLimiter: rate.NewLimiter(512, 512),
81 ConfigureSeeder: ConfigureClient{
82 Config: func(cfg *torrent.ClientConfig) {
83 // If we send too many keep alives, we consume all the leechers available download
84 // rate. The default isn't exposed, but a minute is pretty reasonable.
85 cfg.KeepAliveTimeout = time.Minute
91 func testClientTransferSmallCache(t *testing.T, setReadahead bool, readahead int64) {
92 testClientTransfer(t, testClientTransferParams{
93 LeecherStorage: newFileCacheClientStorageFactory(fileCacheClientStorageFactoryParams{
95 // Going below the piece length means it can't complete a piece so
96 // that it can be hashed.
99 LeecherStorageCapacity: 5,
100 SetReadahead: setReadahead,
101 // Can't readahead too far or the cache will thrash and drop data we
103 Readahead: readahead,
105 // These tests don't work well with more than 1 connection to the seeder.
106 ConfigureLeecher: ConfigureClient{
107 Config: func(cfg *torrent.ClientConfig) {
108 cfg.DropDuplicatePeerIds = true
109 // cfg.DisableIPv6 = true
110 // cfg.DisableUTP = true
116 func TestClientTransferSmallCachePieceSizedReadahead(t *testing.T) {
117 testClientTransferSmallCache(t, true, 5)
120 func TestClientTransferSmallCacheLargeReadahead(t *testing.T) {
121 testClientTransferSmallCache(t, true, 15)
124 func TestClientTransferSmallCacheDefaultReadahead(t *testing.T) {
125 testClientTransferSmallCache(t, false, -1)
128 func TestFilecacheClientTransferVarious(t *testing.T) {
129 TestLeecherStorage(t, LeecherStorageTestCase{
130 "Filecache", newFileCacheClientStorageFactory(fileCacheClientStorageFactoryParams{}), 0,
134 // Check that after completing leeching, a leecher transitions to a seeding
135 // correctly. Connected in a chain like so: Seeder <-> Leecher <-> LeecherLeecher.
136 func testSeedAfterDownloading(t *testing.T, disableUtp bool) {
137 greetingTempDir, mi := testutil.GreetingTestTorrent()
138 defer os.RemoveAll(greetingTempDir)
140 cfg := torrent.TestingConfig(t)
142 cfg.MaxAllocPeerRequestDataPerConn = 4
143 cfg.DataDir = greetingTempDir
144 cfg.DisableUTP = disableUtp
145 seeder, err := torrent.NewClient(cfg)
146 require.NoError(t, err)
148 defer testutil.ExportStatusWriter(seeder, "s", t)()
149 seederTorrent, ok, err := seeder.AddTorrentSpec(torrent.TorrentSpecFromMetaInfo(mi))
150 require.NoError(t, err)
152 seederTorrent.VerifyData()
154 cfg = torrent.TestingConfig(t)
156 cfg.DataDir = t.TempDir()
157 cfg.DisableUTP = disableUtp
158 // Make sure the leecher-leecher doesn't connect directly to the seeder. This is because I
159 // wanted to see if having the higher chunk-sized leecher-leecher would cause the leecher to
160 // error decoding. However it shouldn't because a client should only be receiving pieces sized
161 // to the chunk size it expects.
162 cfg.DisablePEX = true
164 cfg.Logger = log.Default.WithContextText("leecher")
165 leecher, err := torrent.NewClient(cfg)
166 require.NoError(t, err)
167 defer leecher.Close()
168 defer testutil.ExportStatusWriter(leecher, "l", t)()
170 cfg = torrent.TestingConfig(t)
171 cfg.DisableUTP = disableUtp
173 cfg.DataDir = t.TempDir()
174 cfg.MaxAllocPeerRequestDataPerConn = 4
175 cfg.Logger = log.Default.WithContextText("leecher-leecher")
177 leecherLeecher, _ := torrent.NewClient(cfg)
178 require.NoError(t, err)
179 defer leecherLeecher.Close()
180 defer testutil.ExportStatusWriter(leecherLeecher, "ll", t)()
181 leecherGreeting, ok, err := leecher.AddTorrentSpec(func() (ret *torrent.TorrentSpec) {
182 ret = torrent.TorrentSpecFromMetaInfo(mi)
186 require.NoError(t, err)
188 llg, ok, err := leecherLeecher.AddTorrentSpec(func() (ret *torrent.TorrentSpec) {
189 ret = torrent.TorrentSpecFromMetaInfo(mi)
193 require.NoError(t, err)
195 // Simultaneously DownloadAll in Leecher, and read the contents
196 // consecutively in LeecherLeecher. This non-deterministically triggered a
197 // case where the leecher wouldn't unchoke the LeecherLeecher.
198 var wg sync.WaitGroup
200 // Prioritize a region, and ensure it's been hashed, so we want connections.
207 qt.Check(t, iotest.TestReader(r, []byte(testutil.GreetingFileContents)), qt.IsNil)
210 go leecherGreeting.AddClientPeer(seeder)
211 go leecherGreeting.AddClientPeer(leecherLeecher)
215 leecherGreeting.DownloadAll()
221 func TestSeedAfterDownloadingDisableUtp(t *testing.T) {
222 testSeedAfterDownloading(t, true)
225 func TestSeedAfterDownloadingAllowUtp(t *testing.T) {
226 testSeedAfterDownloading(t, false)