@@ -121,72 +121,6 @@ async def test_tracker_metadata_exchange_still_runs_with_low_active_count() -> N
121121 session .handle_magnet_metadata_exchange .assert_awaited_once ()
122122
123123
124- @pytest .mark .asyncio
125- async def test_tracker_metadata_exchange_uses_productive_summary_not_raw_active_count () -> None :
126- """Non-productive active connections must not suppress standalone tracker metadata fetch."""
127- from ccbt .session .announce import AnnounceLoop
128- from ccbt .session .session import AsyncTorrentSession
129-
130- td = {
131- "name" : "magnet-test" ,
132- "info_hash" : b"1" * 20 ,
133- "announce" : "http://tracker.example.com/announce" ,
134- "_metadata_incomplete" : True ,
135- "pieces_info" : None ,
136- "file_info" : None ,
137- }
138- session = AsyncTorrentSession (td , "." )
139- session .handle_magnet_metadata_exchange = AsyncMock (return_value = True )
140-
141- loop = AnnounceLoop (session )
142- await loop ._maybe_trigger_tracker_metadata_exchange (
143- [{"ip" : "192.0.2.1" , "port" : 6881 , "peer_source" : "tracker" }],
144- active_count = 25 ,
145- connection_summary = {
146- "active_connections" : 25 ,
147- "productive_connections" : 0 ,
148- "metadata_capable_connections" : 0 ,
149- "metadata_exchange_active" : 0 ,
150- "peers_with_piece_info" : 0 ,
151- },
152- )
153-
154- session .handle_magnet_metadata_exchange .assert_awaited_once ()
155-
156-
157- @pytest .mark .asyncio
158- async def test_tracker_metadata_exchange_skips_when_live_exchange_already_active () -> None :
159- """Standalone tracker metadata fetch should not duplicate an already-running live exchange."""
160- from ccbt .session .announce import AnnounceLoop
161- from ccbt .session .session import AsyncTorrentSession
162-
163- td = {
164- "name" : "magnet-test" ,
165- "info_hash" : b"1" * 20 ,
166- "announce" : "http://tracker.example.com/announce" ,
167- "_metadata_incomplete" : True ,
168- "pieces_info" : None ,
169- "file_info" : None ,
170- }
171- session = AsyncTorrentSession (td , "." )
172- session .handle_magnet_metadata_exchange = AsyncMock (return_value = True )
173-
174- loop = AnnounceLoop (session )
175- await loop ._maybe_trigger_tracker_metadata_exchange (
176- [{"ip" : "192.0.2.1" , "port" : 6881 , "peer_source" : "tracker" }],
177- active_count = 1 ,
178- connection_summary = {
179- "active_connections" : 1 ,
180- "productive_connections" : 1 ,
181- "metadata_capable_connections" : 1 ,
182- "metadata_exchange_active" : 1 ,
183- "peers_with_piece_info" : 0 ,
184- },
185- )
186-
187- session .handle_magnet_metadata_exchange .assert_not_awaited ()
188-
189-
190124@pytest .mark .asyncio
191125async def test_immediate_tracker_connection_schedules_metadata_fallback (
192126 monkeypatch ,
@@ -372,66 +306,13 @@ async def test_magnet_bitfield_does_not_promote_incomplete_metadata() -> None:
372306 assert len (piece_manager .peer_availability ["198.51.100.10:6881" ].pieces ) == 8
373307
374308
375- @pytest .mark .asyncio
376- async def test_magnet_checkpoint_restore_waits_for_metadata_geometry () -> None :
377- """Checkpoint piece states should wait for final metadata before rebuilding layout."""
378- torrent_data = {
379- "info_hash" : b"m" * 20 ,
380- "name" : "magnet-checkpoint-test" ,
381- "announce" : "http://tracker.example.com/announce" ,
382- "_metadata_incomplete" : True ,
383- "file_info" : None ,
384- "pieces_info" : None ,
385- }
386-
387- piece_manager = AsyncPieceManager (torrent_data )
388- checkpoint = TorrentCheckpoint (
389- info_hash = b"m" * 20 ,
390- torrent_name = "magnet-checkpoint-test" ,
391- total_pieces = 2 ,
392- piece_length = 16384 ,
393- total_length = 32768 ,
394- verified_pieces = [0 ],
395- piece_states = {0 : CheckpointPieceState .VERIFIED },
396- download_stats = DownloadStats (bytes_downloaded = 16384 ),
397- output_dir = "." ,
398- )
399-
400- await piece_manager .restore_from_checkpoint (checkpoint )
401-
402- assert piece_manager ._deferred_checkpoint is not None
403- assert piece_manager .pieces == []
404-
405- await piece_manager .update_from_metadata (
406- {
407- "info_hash" : b"m" * 20 ,
408- "name" : "magnet-checkpoint-test" ,
409- "announce" : "http://tracker.example.com/announce" ,
410- "_metadata_incomplete" : False ,
411- "file_info" : {
412- "name" : "magnet-checkpoint-test" ,
413- "type" : "single" ,
414- "total_length" : 524288 ,
415- },
416- "pieces_info" : {
417- "num_pieces" : 2 ,
418- "piece_length" : 262144 ,
419- "piece_hashes" : [b"a" * 20 , b"b" * 20 ],
420- "total_length" : 524288 ,
421- },
422- }
423- )
424-
425- assert piece_manager ._deferred_checkpoint is None
426- assert piece_manager .pieces [0 ].length == 262144
427- assert piece_manager .pieces [0 ].state == PieceState .MISSING
428-
429-
430309@pytest .mark .asyncio
431310async def test_selector_premarked_piece_still_issues_initial_request (
432311 monkeypatch ,
433312) -> None :
434313 """Selector pre-marking must not suppress the first real piece request."""
314+ import time
315+
435316 from ccbt .piece .async_piece_manager import AsyncPieceManager , PieceState
436317
437318 torrent_data = {
@@ -453,7 +334,7 @@ async def test_selector_premarked_piece_still_issues_initial_request(
453334 piece = piece_manager .pieces [0 ]
454335 piece .state = PieceState .REQUESTED
455336 piece .request_count = 1
456- piece .last_request_time = 0.0
337+ piece .last_request_time = time . time ()
457338 piece_manager ._pending_piece_requests .add (0 )
458339 piece_manager .peer_availability ["198.51.100.10:6881" ] = SimpleNamespace (
459340 pieces = {0 }
@@ -471,10 +352,9 @@ async def fake_request_blocks_normal(
471352 missing_blocks : list [object ],
472353 available_peers : list [object ],
473354 _peer_manager : object ,
474- ) -> int :
355+ ) -> None :
475356 _ = missing_blocks , _peer_manager
476357 request_calls .append ((piece_index , len (available_peers )))
477- return 1
478358
479359 monkeypatch .setattr (piece_manager , "_get_peers_for_piece" , fake_get_peers_for_piece )
480360 monkeypatch .setattr (
@@ -488,7 +368,6 @@ async def fake_request_blocks_normal(
488368 assert request_calls == [(0 , 1 )]
489369 assert 0 not in piece_manager ._pending_piece_requests
490370 assert piece_manager .pieces [0 ].state == PieceState .DOWNLOADING
491- assert piece_manager .pieces [0 ].last_request_time > 0.0
492371
493372
494373@pytest .mark .asyncio
@@ -531,88 +410,3 @@ async def test_emergency_tracker_path_attempts_metadata_exchange() -> None:
531410
532411 session .handle_magnet_metadata_exchange .assert_awaited ()
533412 peer_manager .connect_to_peers .assert_awaited ()
534-
535-
536- @pytest .mark .asyncio
537- async def test_piece_stays_requested_when_no_block_requests_are_sent (monkeypatch ) -> None :
538- """Pieces should remain retryable when peer selection produced no actual block requests."""
539- from ccbt .piece .async_piece_manager import AsyncPieceManager , PieceState
540-
541- torrent_data = {
542- "info_hash" : b"6" * 20 ,
543- "name" : "no-op-request-test" ,
544- "announce" : "http://tracker.example.com/announce" ,
545- "file_info" : {"type" : "single" , "length" : 16384 , "name" : "x" , "total_length" : 16384 },
546- "pieces_info" : {
547- "piece_length" : 16384 ,
548- "num_pieces" : 1 ,
549- "piece_hashes" : [b"x" * 20 ],
550- "total_length" : 16384 ,
551- },
552- }
553-
554- piece_manager = AsyncPieceManager (torrent_data )
555- await piece_manager .update_from_metadata (torrent_data )
556- piece_manager .peer_availability ["198.51.100.10:6881" ] = SimpleNamespace (pieces = {0 })
557-
558- async def fake_get_peers_for_piece (piece_index : int , _peer_manager : object ):
559- _ = piece_index , _peer_manager
560- return [SimpleNamespace ()]
561-
562- async def fake_request_blocks_normal (
563- piece_index : int ,
564- missing_blocks : list [object ],
565- available_peers : list [object ],
566- _peer_manager : object ,
567- ) -> int :
568- _ = piece_index , missing_blocks , available_peers , _peer_manager
569- return 0
570-
571- monkeypatch .setattr (piece_manager , "_get_peers_for_piece" , fake_get_peers_for_piece )
572- monkeypatch .setattr (
573- piece_manager ,
574- "_request_blocks_normal" ,
575- fake_request_blocks_normal ,
576- )
577-
578- await piece_manager .request_piece_from_peers (0 , SimpleNamespace (get_active_peers = lambda : []))
579-
580- assert piece_manager .pieces [0 ].state == PieceState .REQUESTED
581- assert piece_manager .pieces [0 ].last_request_time == 0.0
582-
583-
584- @pytest .mark .asyncio
585- async def test_handle_peer_choked_requeues_piece_without_inflight_requests () -> None :
586- """Choked peers should return inert pieces to a retryable state immediately."""
587- from ccbt .piece .async_piece_manager import AsyncPieceManager , PieceState
588-
589- torrent_data = {
590- "info_hash" : b"7" * 20 ,
591- "name" : "choke-requeue-test" ,
592- "announce" : "http://tracker.example.com/announce" ,
593- "file_info" : {"type" : "single" , "length" : 16384 , "name" : "x" , "total_length" : 16384 },
594- "pieces_info" : {
595- "piece_length" : 16384 ,
596- "num_pieces" : 1 ,
597- "piece_hashes" : [b"x" * 20 ],
598- "total_length" : 16384 ,
599- },
600- }
601-
602- piece_manager = AsyncPieceManager (torrent_data )
603- await piece_manager .update_from_metadata (torrent_data )
604- piece = piece_manager .pieces [0 ]
605- piece .state = PieceState .DOWNLOADING
606- piece .last_request_time = 123.0
607- piece .blocks [0 ].requested_from .add ("198.51.100.10:6881" )
608- piece_manager ._requested_pieces_per_peer ["198.51.100.10:6881" ] = {0 }
609- piece_manager ._active_block_requests [0 ] = {"198.51.100.10:6881" : [(0 , 16384 , 100.0 )]}
610-
611- peer = SimpleNamespace (peer_info = SimpleNamespace (ip = "198.51.100.10" , port = 6881 ))
612- await piece_manager .handle_peer_choked (peer )
613-
614- assert piece .state == PieceState .MISSING
615- assert piece .last_request_time == 0.0
616- assert piece .blocks [0 ].requested_from == set ()
617- assert "198.51.100.10:6881" not in piece_manager ._requested_pieces_per_peer
618- assert 0 not in piece_manager ._active_block_requests
0 commit comments