github.com/anacrolix/torrent@v1.61.0/test/transfer_test.go (about) 1 package test 2 3 import ( 4 "io" 5 "os" 6 "sync" 7 "testing" 8 "testing/iotest" 9 "time" 10 11 "github.com/anacrolix/log" 12 "github.com/anacrolix/missinggo/v2/filecache" 13 qt "github.com/go-quicktest/qt" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 "golang.org/x/time/rate" 17 18 "github.com/anacrolix/torrent" 19 "github.com/anacrolix/torrent/internal/testutil" 20 "github.com/anacrolix/torrent/storage" 21 ) 22 23 type fileCacheClientStorageFactoryParams struct { 24 Capacity int64 25 SetCapacity bool 26 } 27 28 func newFileCacheClientStorageFactory(ps fileCacheClientStorageFactoryParams) StorageFactory { 29 return func(dataDir string) storage.ClientImplCloser { 30 fc, err := filecache.NewCache(dataDir) 31 if err != nil { 32 panic(err) 33 } 34 var capFuncPtr storage.TorrentCapacity 35 if ps.SetCapacity { 36 fc.SetCapacity(ps.Capacity) 37 f := func() (cap int64, capped bool) { 38 return ps.Capacity, ps.SetCapacity 39 } 40 capFuncPtr = &f 41 } 42 43 return struct { 44 storage.ClientImpl 45 io.Closer 46 }{ 47 storage.NewResourcePiecesOpts( 48 fc.AsResourceProvider(), 49 storage.ResourcePiecesOpts{ 50 Capacity: capFuncPtr, 51 }), 52 io.NopCloser(nil), 53 } 54 } 55 } 56 57 func TestClientTransferDefault(t *testing.T) { 58 testClientTransfer(t, testClientTransferParams{ 59 LeecherStorage: newFileCacheClientStorageFactory(fileCacheClientStorageFactoryParams{}), 60 }) 61 } 62 63 func TestClientTransferDefaultNoMetadata(t *testing.T) { 64 testClientTransfer(t, testClientTransferParams{ 65 LeecherStorage: newFileCacheClientStorageFactory(fileCacheClientStorageFactoryParams{}), 66 LeecherStartsWithoutMetadata: true, 67 }) 68 } 69 70 func TestClientTransferRateLimitedUpload(t *testing.T) { 71 started := time.Now() 72 testClientTransfer(t, testClientTransferParams{ 73 // We are uploading 13 bytes (the length of the greeting torrent). The 74 // chunks are 2 bytes in length. Then the smallest burst we can run 75 // with is 2. Time taken is (13-burst)/rate. 76 SeederUploadRateLimiter: rate.NewLimiter(11, 2), 77 }) 78 require.True(t, time.Since(started) > time.Second) 79 } 80 81 func TestClientTransferRateLimitedDownload(t *testing.T) { 82 testClientTransfer(t, testClientTransferParams{ 83 LeecherDownloadRateLimiter: rate.NewLimiter(512, 512), 84 ConfigureSeeder: ConfigureClient{ 85 Config: func(cfg *torrent.ClientConfig) { 86 // If we send too many keep alives, we consume all the leechers available download 87 // rate. The default isn't exposed, but a minute is pretty reasonable. 88 cfg.KeepAliveTimeout = time.Minute 89 }, 90 }, 91 }) 92 } 93 94 func testClientTransferSmallCache(t *testing.T, setReadahead bool, readahead int64) { 95 testClientTransfer(t, testClientTransferParams{ 96 LeecherStorage: newFileCacheClientStorageFactory(fileCacheClientStorageFactoryParams{ 97 SetCapacity: true, 98 // Going below the piece length means it can't complete a piece so 99 // that it can be hashed. 100 Capacity: 5, 101 }), 102 LeecherStorageCapacity: 5, 103 SetReadahead: setReadahead, 104 // Can't readahead too far or the cache will thrash and drop data we 105 // thought we had. 106 Readahead: readahead, 107 108 // These tests don't work well with more than 1 connection to the seeder. 109 ConfigureLeecher: ConfigureClient{ 110 Config: func(cfg *torrent.ClientConfig) { 111 cfg.DropDuplicatePeerIds = true 112 // cfg.DisableIPv6 = true 113 // cfg.DisableUTP = true 114 }, 115 }, 116 }) 117 } 118 119 func TestClientTransferSmallCachePieceSizedReadahead(t *testing.T) { 120 testClientTransferSmallCache(t, true, 5) 121 } 122 123 func TestClientTransferSmallCacheLargeReadahead(t *testing.T) { 124 testClientTransferSmallCache(t, true, 15) 125 } 126 127 func TestClientTransferSmallCacheDefaultReadahead(t *testing.T) { 128 testClientTransferSmallCache(t, false, -1) 129 } 130 131 func TestFilecacheClientTransferVarious(t *testing.T) { 132 TestLeecherStorage(t, LeecherStorageTestCase{ 133 "Filecache", newFileCacheClientStorageFactory(fileCacheClientStorageFactoryParams{}), 0, 134 }) 135 } 136 137 // Check that after completing leeching, a leecher transitions to a seeding 138 // correctly. Connected in a chain like so: Seeder <-> Leecher <-> LeecherLeecher. 139 func testSeedAfterDownloading(t *testing.T, disableUtp bool) { 140 greetingTempDir, mi := testutil.GreetingTestTorrent() 141 defer os.RemoveAll(greetingTempDir) 142 143 cfg := torrent.TestingConfig(t) 144 cfg.Seed = true 145 cfg.MaxAllocPeerRequestDataPerConn = 4 146 cfg.DataDir = greetingTempDir 147 cfg.DisableUTP = disableUtp 148 seeder, err := torrent.NewClient(cfg) 149 require.NoError(t, err) 150 defer seeder.Close() 151 defer testutil.ExportStatusWriter(seeder, "s", t)() 152 seederTorrent, ok, err := seeder.AddTorrentSpec(torrent.TorrentSpecFromMetaInfo(mi)) 153 require.NoError(t, err) 154 assert.True(t, ok) 155 seederTorrent.VerifyData() 156 157 cfg = torrent.TestingConfig(t) 158 cfg.Seed = true 159 cfg.DataDir = t.TempDir() 160 cfg.DisableUTP = disableUtp 161 // Make sure the leecher-leecher doesn't connect directly to the seeder. This is because I 162 // wanted to see if having the higher chunk-sized leecher-leecher would cause the leecher to 163 // error decoding. However it shouldn't because a client should only be receiving pieces sized 164 // to the chunk size it expects. 165 cfg.DisablePEX = true 166 //cfg.Debug = true 167 cfg.Logger = log.Default.WithContextText("leecher") 168 leecher, err := torrent.NewClient(cfg) 169 require.NoError(t, err) 170 defer leecher.Close() 171 defer testutil.ExportStatusWriter(leecher, "l", t)() 172 173 cfg = torrent.TestingConfig(t) 174 cfg.DisableUTP = disableUtp 175 cfg.Seed = false 176 cfg.DataDir = t.TempDir() 177 cfg.MaxAllocPeerRequestDataPerConn = 4 178 cfg.Logger = log.Default.WithContextText("leecher-leecher") 179 cfg.Debug = true 180 leecherLeecher, _ := torrent.NewClient(cfg) 181 require.NoError(t, err) 182 defer leecherLeecher.Close() 183 defer testutil.ExportStatusWriter(leecherLeecher, "ll", t)() 184 leecherGreeting, ok, err := leecher.AddTorrentSpec(func() (ret *torrent.TorrentSpec) { 185 ret = torrent.TorrentSpecFromMetaInfo(mi) 186 ret.ChunkSize = 2 187 return 188 }()) 189 require.NoError(t, err) 190 assert.True(t, ok) 191 llg, ok, err := leecherLeecher.AddTorrentSpec(func() (ret *torrent.TorrentSpec) { 192 ret = torrent.TorrentSpecFromMetaInfo(mi) 193 ret.ChunkSize = 3 194 return 195 }()) 196 require.NoError(t, err) 197 assert.True(t, ok) 198 // Simultaneously DownloadAll in Leecher, and read the contents 199 // consecutively in LeecherLeecher. This non-deterministically triggered a 200 // case where the leecher wouldn't unchoke the LeecherLeecher. 201 var wg sync.WaitGroup 202 { 203 // Prioritize a region, and ensure it's been hashed, so we want connections. 204 r := llg.NewReader() 205 llg.VerifyData() 206 wg.Add(1) 207 go func() { 208 defer wg.Done() 209 defer r.Close() 210 qt.Check(t, qt.IsNil(iotest.TestReader(r, []byte(testutil.GreetingFileContents)))) 211 }() 212 } 213 go leecherGreeting.AddClientPeer(seeder) 214 go leecherGreeting.AddClientPeer(leecherLeecher) 215 wg.Add(1) 216 go func() { 217 defer wg.Done() 218 leecherGreeting.DownloadAll() 219 leecher.WaitAll() 220 }() 221 wg.Wait() 222 } 223 224 func TestSeedAfterDownloadingDisableUtp(t *testing.T) { 225 testSeedAfterDownloading(t, true) 226 } 227 228 func TestSeedAfterDownloadingAllowUtp(t *testing.T) { 229 testSeedAfterDownloading(t, false) 230 }