@@ -98,3 +98,272 @@ pub enum TorrentError {
98
98
CouldNotSendResponse ,
99
99
InvalidInfoHash ,
100
100
}
101
+
102
+ #[ cfg( test) ]
103
+ mod tests {
104
+ use std:: {
105
+ net:: { IpAddr , Ipv4Addr , SocketAddr } ,
106
+ ops:: Sub ,
107
+ time:: Duration ,
108
+ } ;
109
+
110
+ use aquatic_udp_protocol:: { AnnounceEvent , NumberOfBytes } ;
111
+
112
+ use crate :: {
113
+ peer:: TorrentPeer ,
114
+ protocol:: clock:: clock:: { DefaultClock , SinceUnixEpoch , StoppedClock , StoppedTime , Time , WorkingClock } ,
115
+ torrent:: TorrentEntry ,
116
+ PeerId ,
117
+ } ;
118
+
119
+ struct TorrentPeerBuilder {
120
+ peer : TorrentPeer ,
121
+ }
122
+
123
+ impl TorrentPeerBuilder {
124
+ pub fn default ( ) -> TorrentPeerBuilder {
125
+ let default_peer = TorrentPeer {
126
+ peer_id : PeerId ( [ 0u8 ; 20 ] ) ,
127
+ peer_addr : SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 127 , 0 , 0 , 1 ) ) , 8080 ) ,
128
+ updated : DefaultClock :: now ( ) ,
129
+ uploaded : NumberOfBytes ( 0 ) ,
130
+ downloaded : NumberOfBytes ( 0 ) ,
131
+ left : NumberOfBytes ( 0 ) ,
132
+ event : AnnounceEvent :: Started ,
133
+ } ;
134
+ TorrentPeerBuilder { peer : default_peer }
135
+ }
136
+
137
+ pub fn with_event_completed ( mut self ) -> Self {
138
+ self . peer . event = AnnounceEvent :: Completed ;
139
+ self
140
+ }
141
+
142
+ pub fn with_peer_address ( mut self , peer_addr : SocketAddr ) -> Self {
143
+ self . peer . peer_addr = peer_addr;
144
+ self
145
+ }
146
+
147
+ pub fn with_peer_id ( mut self , peer_id : PeerId ) -> Self {
148
+ self . peer . peer_id = peer_id;
149
+ self
150
+ }
151
+
152
+ pub fn with_number_of_bytes_left ( mut self , left : i64 ) -> Self {
153
+ self . peer . left = NumberOfBytes ( left) ;
154
+ self
155
+ }
156
+
157
+ pub fn updated_at ( mut self , updated : SinceUnixEpoch ) -> Self {
158
+ self . peer . updated = updated;
159
+ self
160
+ }
161
+
162
+ pub fn into ( self ) -> TorrentPeer {
163
+ self . peer
164
+ }
165
+ }
166
+
167
+ /// A torrent seeder is a peer with 0 bytes left to download which
168
+ /// has not announced it has stopped
169
+ fn a_torrent_seeder ( ) -> TorrentPeer {
170
+ TorrentPeerBuilder :: default ( )
171
+ . with_number_of_bytes_left ( 0 )
172
+ . with_event_completed ( )
173
+ . into ( )
174
+ }
175
+
176
+ /// A torrent leecher is a peer that is not a seeder.
177
+ /// Leecher: left > 0 OR event = Stopped
178
+ fn a_torrent_leecher ( ) -> TorrentPeer {
179
+ TorrentPeerBuilder :: default ( )
180
+ . with_number_of_bytes_left ( 1 )
181
+ . with_event_completed ( )
182
+ . into ( )
183
+ }
184
+
185
+ #[ test]
186
+ fn the_default_torrent_entry_should_contain_an_empty_list_of_peers ( ) {
187
+ let torrent_entry = TorrentEntry :: new ( ) ;
188
+
189
+ assert_eq ! ( torrent_entry. get_peers( None ) . len( ) , 0 ) ;
190
+ }
191
+
192
+ #[ test]
193
+ fn a_new_peer_can_be_added_to_a_torrent_entry ( ) {
194
+ let mut torrent_entry = TorrentEntry :: new ( ) ;
195
+ let torrent_peer = TorrentPeerBuilder :: default ( ) . into ( ) ;
196
+
197
+ torrent_entry. update_peer ( & torrent_peer) ; // Add the peer
198
+
199
+ assert_eq ! ( * torrent_entry. get_peers( None ) [ 0 ] , torrent_peer) ;
200
+ assert_eq ! ( torrent_entry. get_peers( None ) . len( ) , 1 ) ;
201
+ }
202
+
203
+ #[ test]
204
+ fn a_torrent_entry_should_contain_the_list_of_peers_that_were_added_to_the_torrent ( ) {
205
+ let mut torrent_entry = TorrentEntry :: new ( ) ;
206
+ let torrent_peer = TorrentPeerBuilder :: default ( ) . into ( ) ;
207
+
208
+ torrent_entry. update_peer ( & torrent_peer) ; // Add the peer
209
+
210
+ assert_eq ! ( torrent_entry. get_peers( None ) , vec![ & torrent_peer] ) ;
211
+ }
212
+
213
+ #[ test]
214
+ fn a_peer_can_be_updated_in_a_torrent_entry ( ) {
215
+ let mut torrent_entry = TorrentEntry :: new ( ) ;
216
+ let mut torrent_peer = TorrentPeerBuilder :: default ( ) . into ( ) ;
217
+ torrent_entry. update_peer ( & torrent_peer) ; // Add the peer
218
+
219
+ torrent_peer. event = AnnounceEvent :: Completed ; // Update the peer
220
+ torrent_entry. update_peer ( & torrent_peer) ; // Update the peer in the torrent entry
221
+
222
+ assert_eq ! ( torrent_entry. get_peers( None ) [ 0 ] . event, AnnounceEvent :: Completed ) ;
223
+ }
224
+
225
+ #[ test]
226
+ fn a_peer_should_be_removed_from_a_torrent_entry_when_the_peer_announces_it_has_stopped ( ) {
227
+ let mut torrent_entry = TorrentEntry :: new ( ) ;
228
+ let mut torrent_peer = TorrentPeerBuilder :: default ( ) . into ( ) ;
229
+ torrent_entry. update_peer ( & torrent_peer) ; // Add the peer
230
+
231
+ torrent_peer. event = AnnounceEvent :: Stopped ; // Update the peer
232
+ torrent_entry. update_peer ( & torrent_peer) ; // Update the peer in the torrent entry
233
+
234
+ assert_eq ! ( torrent_entry. get_peers( None ) . len( ) , 0 ) ;
235
+ }
236
+
237
+ #[ test]
238
+ fn torrent_stats_change_when_a_previously_known_peer_announces_it_has_completed_the_torrent ( ) {
239
+ let mut torrent_entry = TorrentEntry :: new ( ) ;
240
+ let mut torrent_peer = TorrentPeerBuilder :: default ( ) . into ( ) ;
241
+
242
+ torrent_entry. update_peer ( & torrent_peer) ; // Add the peer
243
+
244
+ torrent_peer. event = AnnounceEvent :: Completed ; // Update the peer
245
+ let stats_have_changed = torrent_entry. update_peer ( & torrent_peer) ; // Update the peer in the torrent entry
246
+
247
+ assert ! ( stats_have_changed) ;
248
+ }
249
+
250
+ #[ test]
251
+ fn torrent_stats_should_not_change_when_a_peer_announces_it_has_completed_the_torrent_if_it_is_the_first_announce_from_the_peer (
252
+ ) {
253
+ let mut torrent_entry = TorrentEntry :: new ( ) ;
254
+ let torrent_peer_announcing_complete_event = TorrentPeerBuilder :: default ( ) . with_event_completed ( ) . into ( ) ;
255
+
256
+ // Add a peer that did not exist before in the entry
257
+ let torrent_stats_have_not_changed = !torrent_entry. update_peer ( & torrent_peer_announcing_complete_event) ;
258
+
259
+ assert ! ( torrent_stats_have_not_changed) ;
260
+ }
261
+
262
+ #[ test]
263
+ fn a_torrent_entry_could_filter_out_peers_with_a_given_socket_address ( ) {
264
+ let mut torrent_entry = TorrentEntry :: new ( ) ;
265
+ let peer_socket_address = SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 127 , 0 , 0 , 1 ) ) , 8080 ) ;
266
+ let torrent_peer = TorrentPeerBuilder :: default ( ) . with_peer_address ( peer_socket_address) . into ( ) ;
267
+ torrent_entry. update_peer ( & torrent_peer) ; // Add peer
268
+
269
+ // Get peers excluding the one we have just added
270
+ let peers = torrent_entry. get_peers ( Some ( & peer_socket_address) ) ;
271
+
272
+ assert_eq ! ( peers. len( ) , 0 ) ;
273
+ }
274
+
275
+ fn peer_id_from_i32 ( number : i32 ) -> PeerId {
276
+ let peer_id = number. to_le_bytes ( ) ;
277
+ PeerId ( [
278
+ 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , 0u8 , peer_id[ 0 ] , peer_id[ 1 ] , peer_id[ 2 ] ,
279
+ peer_id[ 3 ] ,
280
+ ] )
281
+ }
282
+
283
+ #[ test]
284
+ fn the_tracker_should_limit_the_list_of_peers_to_74_when_clients_scrape_torrents ( ) {
285
+ let mut torrent_entry = TorrentEntry :: new ( ) ;
286
+
287
+ // We add one more peer than the scrape limit
288
+ for peer_number in 1 ..=74 + 1 {
289
+ let torrent_peer = TorrentPeerBuilder :: default ( )
290
+ . with_peer_id ( peer_id_from_i32 ( peer_number) )
291
+ . into ( ) ;
292
+ torrent_entry. update_peer ( & torrent_peer) ;
293
+ }
294
+
295
+ let peers = torrent_entry. get_peers ( None ) ;
296
+
297
+ assert_eq ! ( peers. len( ) , 74 )
298
+ }
299
+
300
+ #[ test]
301
+ fn torrent_stats_should_have_the_number_of_seeders_for_a_torrent ( ) {
302
+ let mut torrent_entry = TorrentEntry :: new ( ) ;
303
+ let torrent_seeder = a_torrent_seeder ( ) ;
304
+
305
+ torrent_entry. update_peer ( & torrent_seeder) ; // Add seeder
306
+
307
+ assert_eq ! ( torrent_entry. get_stats( ) . 0 , 1 ) ;
308
+ }
309
+
310
+ #[ test]
311
+ fn torrent_stats_should_have_the_number_of_leechers_for_a_torrent ( ) {
312
+ let mut torrent_entry = TorrentEntry :: new ( ) ;
313
+ let torrent_leecher = a_torrent_leecher ( ) ;
314
+
315
+ torrent_entry. update_peer ( & torrent_leecher) ; // Add leecher
316
+
317
+ assert_eq ! ( torrent_entry. get_stats( ) . 2 , 1 ) ;
318
+ }
319
+
320
+ #[ test]
321
+ fn torrent_stats_should_have_the_number_of_peers_that_having_announced_at_least_two_events_the_latest_one_is_the_completed_event (
322
+ ) {
323
+ let mut torrent_entry = TorrentEntry :: new ( ) ;
324
+ let mut torrent_peer = TorrentPeerBuilder :: default ( ) . into ( ) ;
325
+ torrent_entry. update_peer ( & torrent_peer) ; // Add the peer
326
+
327
+ // Announce "Completed" torrent download event.
328
+ torrent_peer. event = AnnounceEvent :: Completed ;
329
+ torrent_entry. update_peer ( & torrent_peer) ; // Update the peer
330
+
331
+ let number_of_previously_known_peers_with_completed_torrent = torrent_entry. get_stats ( ) . 1 ;
332
+
333
+ assert_eq ! ( number_of_previously_known_peers_with_completed_torrent, 1 ) ;
334
+ }
335
+
336
+ #[ test]
337
+ fn torrent_stats_should_not_include_a_peer_in_the_completed_counter_if_the_peer_has_announced_only_one_event ( ) {
338
+ let mut torrent_entry = TorrentEntry :: new ( ) ;
339
+ let torrent_peer_announcing_complete_event = TorrentPeerBuilder :: default ( ) . with_event_completed ( ) . into ( ) ;
340
+
341
+ // Announce "Completed" torrent download event.
342
+ // It's the first event announced from this peer.
343
+ torrent_entry. update_peer ( & torrent_peer_announcing_complete_event) ; // Add the peer
344
+
345
+ let number_of_peers_with_completed_torrent = torrent_entry. get_stats ( ) . 1 ;
346
+
347
+ assert_eq ! ( number_of_peers_with_completed_torrent, 0 ) ;
348
+ }
349
+
350
+ #[ test]
351
+ fn a_torrent_entry_should_remove_a_peer_not_updated_after_a_timeout_in_seconds ( ) {
352
+ let mut torrent_entry = TorrentEntry :: new ( ) ;
353
+
354
+ let timeout = 120u32 ;
355
+
356
+ let now = WorkingClock :: now ( ) ;
357
+ StoppedClock :: local_set ( & now) ;
358
+
359
+ let timeout_seconds_before_now = now. sub ( Duration :: from_secs ( timeout as u64 ) ) ;
360
+ let inactive_peer = TorrentPeerBuilder :: default ( )
361
+ . updated_at ( timeout_seconds_before_now. sub ( Duration :: from_secs ( 1 ) ) )
362
+ . into ( ) ;
363
+ torrent_entry. update_peer ( & inactive_peer) ; // Add the peer
364
+
365
+ torrent_entry. remove_inactive_peers ( timeout) ;
366
+
367
+ assert_eq ! ( torrent_entry. peers. len( ) , 0 ) ;
368
+ }
369
+ }
0 commit comments