github.com/anacrolix/torrent@v1.61.0/client-peerconn_test.go (about)

     1  package torrent
     2  
     3  import (
     4  	"cmp"
     5  	"io"
     6  	"os"
     7  	"testing"
     8  	"testing/iotest"
     9  
    10  	"github.com/anacrolix/chansync"
    11  	"github.com/anacrolix/missinggo/v2"
    12  	"github.com/anacrolix/missinggo/v2/bitmap"
    13  	"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/internal/testutil"
    19  )
    20  
    21  func TestPeerConnEstablished(t *testing.T) {
    22  	var expectedPeerId PeerID
    23  	missinggo.CopyExact(&expectedPeerId, "12345123451234512345")
    24  
    25  	gotPeerConnectedEvt := false
    26  	// Disconnect occurs asynchronously to Client/Torrent lifetime.
    27  	var gotPeerDisconnectedEvt chansync.SetOnce
    28  
    29  	ps := testClientTransferParams{
    30  		ConfigureSeeder: ConfigureClient{
    31  			Config: func(cfg *ClientConfig) {
    32  				cfg.PeerID = "12345123451234512345"
    33  			},
    34  		},
    35  		ConfigureLeecher: ConfigureClient{
    36  			Config: func(cfg *ClientConfig) {
    37  				// cfg.DisableUTP = true
    38  				cfg.DisableTCP = true
    39  				cfg.Debug = false
    40  				cfg.DisableTrackers = true
    41  				cfg.EstablishedConnsPerTorrent = 1
    42  				cfg.Callbacks.StatusUpdated = append(
    43  					cfg.Callbacks.StatusUpdated,
    44  					func(e StatusUpdatedEvent) {
    45  						if e.Event == PeerConnected {
    46  							gotPeerConnectedEvt = true
    47  							require.Equal(t, expectedPeerId, e.PeerId)
    48  							require.NoError(t, e.Error)
    49  						}
    50  					},
    51  					func(e StatusUpdatedEvent) {
    52  						if e.Event == PeerDisconnected {
    53  							require.Equal(t, expectedPeerId, e.PeerId)
    54  							require.NoError(t, e.Error)
    55  							// Signal after checking the values.
    56  							gotPeerDisconnectedEvt.Set()
    57  						}
    58  					},
    59  				)
    60  			},
    61  		},
    62  	}
    63  
    64  	testClientTransfer(t, ps)
    65  	// double check that the callbacks were called
    66  	require.True(t, gotPeerConnectedEvt)
    67  	<-gotPeerDisconnectedEvt.Done()
    68  }
    69  
    70  type ConfigureClient struct {
    71  	Config func(cfg *ClientConfig)
    72  	Client func(cl *Client)
    73  }
    74  
    75  type testClientTransferParams struct {
    76  	SeederUploadRateLimiter    *rate.Limiter
    77  	LeecherDownloadRateLimiter *rate.Limiter
    78  	ConfigureSeeder            ConfigureClient
    79  	ConfigureLeecher           ConfigureClient
    80  
    81  	LeecherStartsWithoutMetadata bool
    82  }
    83  
    84  // Simplified version of testClientTransfer found in test/leecher-storage.go.
    85  // Could not import and reuse that function due to circular dependencies between modules.
    86  func testClientTransfer(t *testing.T, ps testClientTransferParams) {
    87  	greetingTempDir, mi := testutil.GreetingTestTorrent()
    88  	defer os.RemoveAll(greetingTempDir)
    89  	// Create seeder and a Torrent.
    90  	cfg := TestingConfig(t)
    91  	cfg.Seed = true
    92  	// Some test instances don't like this being on, even when there's no cache involved.
    93  	cfg.DropMutuallyCompletePeers = false
    94  	cfg.UploadRateLimiter = cmp.Or(ps.SeederUploadRateLimiter, cfg.UploadRateLimiter)
    95  	cfg.DataDir = greetingTempDir
    96  	if ps.ConfigureSeeder.Config != nil {
    97  		ps.ConfigureSeeder.Config(cfg)
    98  	}
    99  	seeder, err := NewClient(cfg)
   100  	require.NoError(t, err)
   101  	if ps.ConfigureSeeder.Client != nil {
   102  		ps.ConfigureSeeder.Client(seeder)
   103  	}
   104  	seederTorrent, _, _ := seeder.AddTorrentSpec(TorrentSpecFromMetaInfo(mi))
   105  	defer seeder.Close()
   106  	<-seederTorrent.Complete().On()
   107  
   108  	// Create leecher and a Torrent.
   109  	leecherDataDir := t.TempDir()
   110  	cfg = TestingConfig(t)
   111  	// See the seeder client config comment.
   112  	cfg.DropMutuallyCompletePeers = false
   113  	cfg.DataDir = leecherDataDir
   114  	if ps.LeecherDownloadRateLimiter != nil {
   115  		cfg.DownloadRateLimiter = ps.LeecherDownloadRateLimiter
   116  	}
   117  	cfg.Seed = false
   118  	if ps.ConfigureLeecher.Config != nil {
   119  		ps.ConfigureLeecher.Config(cfg)
   120  	}
   121  	leecher, err := NewClient(cfg)
   122  	require.NoError(t, err)
   123  	defer leecher.Close()
   124  	if ps.ConfigureLeecher.Client != nil {
   125  		ps.ConfigureLeecher.Client(leecher)
   126  	}
   127  	leecherTorrent, new, err := leecher.AddTorrentSpec(func() (ret *TorrentSpec) {
   128  		ret = TorrentSpecFromMetaInfo(mi)
   129  		ret.ChunkSize = 2
   130  		if ps.LeecherStartsWithoutMetadata {
   131  			ret.InfoBytes = nil
   132  		}
   133  		return
   134  	}())
   135  	require.NoError(t, err)
   136  	assert.False(t, leecherTorrent.Complete().Bool())
   137  	assert.True(t, new)
   138  
   139  	added := leecherTorrent.AddClientPeer(seeder)
   140  	assert.False(t, leecherTorrent.Seeding())
   141  	// The leecher will use peers immediately if it doesn't have the metadata. Otherwise, they
   142  	// should be sitting idle until we demand data.
   143  	if !ps.LeecherStartsWithoutMetadata {
   144  		assert.EqualValues(t, added, leecherTorrent.Stats().PendingPeers)
   145  	}
   146  	if ps.LeecherStartsWithoutMetadata {
   147  		<-leecherTorrent.GotInfo()
   148  	}
   149  	r := leecherTorrent.NewReader()
   150  	defer r.Close()
   151  	go leecherTorrent.SetInfoBytes(mi.InfoBytes)
   152  
   153  	assertReadAllGreeting(t, r)
   154  	<-leecherTorrent.Complete().On()
   155  	assert.NotEmpty(t, seederTorrent.PeerConns())
   156  	leecherPeerConns := leecherTorrent.PeerConns()
   157  	if cfg.DropMutuallyCompletePeers {
   158  		// I don't think we can assume it will be empty already, due to timing.
   159  		// assert.Empty(t, leecherPeerConns)
   160  	} else {
   161  		assert.NotEmpty(t, leecherPeerConns)
   162  	}
   163  	foundSeeder := false
   164  	for _, pc := range leecherPeerConns {
   165  		completed := pc.PeerPieces().GetCardinality()
   166  		t.Logf("peer conn %v has %v completed pieces", pc, completed)
   167  		if completed == bitmap.BitRange(leecherTorrent.Info().NumPieces()) {
   168  			foundSeeder = true
   169  		}
   170  	}
   171  	if !foundSeeder {
   172  		t.Errorf("didn't find seeder amongst leecher peer conns")
   173  	}
   174  
   175  	seederStats := seederTorrent.Stats()
   176  	assert.True(t, 13 <= seederStats.BytesWrittenData.Int64())
   177  	assert.True(t, 8 <= seederStats.ChunksWritten.Int64())
   178  
   179  	leecherStats := leecherTorrent.Stats()
   180  	assert.True(t, 13 <= leecherStats.BytesReadData.Int64())
   181  	assert.True(t, 8 <= leecherStats.ChunksRead.Int64())
   182  
   183  	// Try reading through again for the cases where the torrent data size
   184  	// exceeds the size of the cache.
   185  	assertReadAllGreeting(t, r)
   186  }
   187  
   188  func assertReadAllGreeting(t *testing.T, r io.ReadSeeker) {
   189  	pos, err := r.Seek(0, io.SeekStart)
   190  	assert.NoError(t, err)
   191  	assert.EqualValues(t, 0, pos)
   192  	qt.Check(t, qt.IsNil(iotest.TestReader(r, []byte(testutil.GreetingFileContents))))
   193  }