1+ import json
12import os
3+ import re
24import shutil
35from glob import glob
46from typing import Callable
@@ -256,7 +258,7 @@ def test_cache_purge_with_empty_cache(script: PipTestEnvironment) -> None:
256258 and exit without an error code."""
257259 result = script .pip ("cache" , "purge" , allow_stderr_warning = True )
258260 assert result .stderr == "WARNING: No matching packages\n "
259- assert result .stdout == "Files removed: 0 (0 bytes)\n "
261+ assert result .stdout == "Files removed: 0 (0 bytes)\n Directories removed: 0 \ n "
260262
261263
262264@pytest .mark .usefixtures ("populate_wheel_cache" )
@@ -265,7 +267,7 @@ def test_cache_remove_with_bad_pattern(script: PipTestEnvironment) -> None:
265267 and exit without an error code."""
266268 result = script .pip ("cache" , "remove" , "aaa" , allow_stderr_warning = True )
267269 assert result .stderr == 'WARNING: No matching packages for pattern "aaa"\n '
268- assert result .stdout == "Files removed: 0 (0 bytes)\n "
270+ assert result .stdout == "Files removed: 0 (0 bytes)\n Directories removed: 0 \ n "
269271
270272
271273def test_cache_list_too_many_args (script : PipTestEnvironment ) -> None :
@@ -413,3 +415,109 @@ def test_cache_abort_when_no_cache_dir(
413415 "ERROR: pip cache commands can not function"
414416 " since cache is disabled." in result .stderr .splitlines ()
415417 )
418+
419+
420+ @pytest .fixture
421+ def populate_wheel_cache_with_empty_dirs (wheel_cache_dir : str ) -> None :
422+ metadata_dir = os .path .join (wheel_cache_dir , "metadata_only" )
423+ os .makedirs (metadata_dir )
424+ with open (os .path .join (metadata_dir , "metadata.json" ), "w" ):
425+ pass
426+
427+ empty_dir = os .path .join (wheel_cache_dir , "completely_empty" )
428+ os .makedirs (empty_dir )
429+
430+ nested_empty = os .path .join (wheel_cache_dir , "nested" , "empty" , "dirs" )
431+ os .makedirs (nested_empty )
432+
433+
434+ @pytest .fixture
435+ def populate_http_cache_with_empty_dirs (cache_dir : str ) -> None :
436+ http_cache_dir = os .path .join (cache_dir , "http" )
437+ empty1 = os .path .join (http_cache_dir , "empty1" )
438+ empty2 = os .path .join (http_cache_dir , "empty2" , "nested" )
439+
440+ os .makedirs (empty1 )
441+ os .makedirs (empty2 )
442+
443+
444+ @pytest .fixture
445+ def create_selfcheck_json (cache_dir : str ) -> None :
446+ selfcheck_path = os .path .join (cache_dir , "selfcheck.json" )
447+ with open (selfcheck_path , "w" ) as statefile :
448+ json .dump (
449+ {
450+ "/some/prefix" : {
451+ "last_check" : "2020-01-01T00:00:00" ,
452+ "pypi_version" : "20.0.1" ,
453+ }
454+ },
455+ statefile ,
456+ )
457+
458+
459+ @pytest .mark .usefixtures (
460+ "populate_wheel_cache_with_empty_dirs" ,
461+ "populate_http_cache_with_empty_dirs" ,
462+ "create_selfcheck_json" ,
463+ )
464+ def test_cache_purge_removes_empty_dirs_and_legacy_files (
465+ script : PipTestEnvironment ,
466+ cache_dir : str ,
467+ wheel_cache_dir : str ,
468+ ) -> None :
469+ """Test pip cache purge/remove with empty dirs and legacy files.
470+
471+ Verifies purge removes:
472+ - Wheel cache directories without .whl files
473+ - HTTP cache empty directories
474+ - Legacy selfcheck.json file
475+ - Reports correct directory counts
476+ Also tests that 'cache remove' works similarly.
477+ """
478+ selfcheck_path = os .path .join (cache_dir , "selfcheck.json" )
479+ http_cache_dir = os .path .join (cache_dir , "http" )
480+ metadata_dir = os .path .join (wheel_cache_dir , "metadata_only" )
481+
482+ # Verify setup
483+ assert os .path .exists (selfcheck_path )
484+ assert os .path .exists (metadata_dir )
485+ assert os .path .exists (os .path .join (http_cache_dir , "empty1" ))
486+
487+ result = script .pip ("cache" , "purge" , "--verbose" , allow_stderr_warning = True )
488+
489+ # Verify all cleanup happened
490+ assert not os .path .exists (selfcheck_path )
491+ assert "Removed legacy selfcheck.json file" in result .stdout
492+ assert not os .path .exists (metadata_dir )
493+ assert not os .path .exists (os .path .join (wheel_cache_dir , "completely_empty" ))
494+ assert not os .path .exists (os .path .join (http_cache_dir , "empty1" ))
495+ assert "Directories removed:" in result .stdout
496+
497+ # Verify directory count is positive
498+ dir_count = int (re .findall (r"Directories removed: (\d+)" , result .stdout )[0 ])
499+ assert dir_count > 0
500+
501+
502+ def test_cache_purge_with_mixed_content (
503+ script : PipTestEnvironment ,
504+ populate_wheel_cache : list [tuple [str , str ]],
505+ wheel_cache_dir : str ,
506+ ) -> None :
507+ """Test purge removes both wheel files and empty directories."""
508+ # Add an empty directory alongside the wheels
509+ empty_dir = os .path .join (wheel_cache_dir , "empty_subdir" )
510+ os .makedirs (empty_dir )
511+
512+ result = script .pip ("cache" , "purge" , "--verbose" )
513+
514+ # Verify wheels and empty directory were removed
515+ for _name , filepath in populate_wheel_cache :
516+ assert not os .path .exists (filepath )
517+ assert not os .path .exists (empty_dir )
518+
519+ # Verify counts in output
520+ assert "Files removed:" in result .stdout
521+ assert "Directories removed:" in result .stdout
522+ files_removed = int (re .findall (r"Files removed: (\d+)" , result .stdout )[0 ])
523+ assert files_removed == 4
0 commit comments