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 }