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  }