Skip to content

Commit

Permalink
Don't display .pyc and .pyo files in "missing files" conda doctor…
Browse files Browse the repository at this point in the history
… health check (#13931)

Co-authored-by: Ken Odegard <kodegard@anaconda.com>
Co-authored-by: Jannis Leidel <jannis@leidel.info>
Co-authored-by: Bianca Henderson <beeankha@gmail.com>
  • Loading branch information
4 people committed May 31, 2024
1 parent 7814522 commit cef2e58
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 44 deletions.
10 changes: 9 additions & 1 deletion conda/plugins/subcommands/doctor/health_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,22 @@ def samefile(path1: Path, path2: Path) -> bool:
return False


def excluded_files_check(filename: str) -> bool:
excluded_extensions = (".pyc", ".pyo")
return filename.endswith(excluded_extensions)


def find_packages_with_missing_files(prefix: str | Path) -> dict[str, list[str]]:
"""Finds packages listed in conda-meta which have missing files."""
packages_with_missing_files = {}
prefix = Path(prefix)
for file in (prefix / "conda-meta").glob("*.json"):
for file_name in json.loads(file.read_text()).get("files", []):
# Add warnings if json file has missing "files"
if not (prefix / file_name).exists():
if (
not excluded_files_check(file_name)
and not (prefix / file_name).exists()
):
packages_with_missing_files.setdefault(file.stem, []).append(file_name)
return packages_with_missing_files

Expand Down
19 changes: 19 additions & 0 deletions news/13931-no-add-pyc-doctor
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
### Enhancements

* Skip checking for `.pyc` and `.pyo` files in the `conda doctor` "missing files" health check. (#13370 via #13931)

### Bug fixes

* <news item>

### Deprecations

* <news item>

### Docs

* <news item>

### Other

* <news item>
117 changes: 74 additions & 43 deletions tests/plugins/subcommands/doctor/test_health_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@
from pytest_mock import MockerFixture


@pytest.fixture
def env_ok(tmp_path: Path) -> Iterable[tuple[Path, str, str, str]]:
@pytest.fixture(params=[".pyo", ".pyc"])
def env_ok(tmp_path: Path, request) -> Iterable[tuple[Path, str, str, str, str]]:
"""Fixture that returns a testing environment with no missing files"""
package = uuid.uuid4().hex

(tmp_path / "bin").mkdir(parents=True, exist_ok=True)
(tmp_path / "lib").mkdir(parents=True, exist_ok=True)
(tmp_path / "pycache").mkdir(parents=True, exist_ok=True)
(tmp_path / "conda-meta").mkdir(parents=True, exist_ok=True)

bin_doctor = f"bin/{package}"
Expand All @@ -44,12 +45,16 @@ def env_ok(tmp_path: Path) -> Iterable[tuple[Path, str, str, str]]:
lib_doctor = f"lib/{package}.py"
(tmp_path / lib_doctor).touch()

ignored_doctor = f"pycache/{package}.{request.param}"
(tmp_path / ignored_doctor).touch()

# A template json file mimicking a json file in conda-meta
# the "sha256" and "sha256_in_prefix" values are sha256 checksum generated for an empty file
PACKAGE_JSON = {
"files": [
bin_doctor,
lib_doctor,
ignored_doctor,
],
"paths_data": {
"paths": [
Expand All @@ -63,28 +68,39 @@ def env_ok(tmp_path: Path) -> Iterable[tuple[Path, str, str, str]]:
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"sha256_in_prefix": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
},
{
"_path": ignored_doctor,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"sha256_in_prefix": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
},
],
"paths_version": 1,
},
}

(tmp_path / "conda-meta" / f"{package}.json").write_text(json.dumps(PACKAGE_JSON))

yield tmp_path, bin_doctor, lib_doctor, package
yield tmp_path, bin_doctor, lib_doctor, ignored_doctor, package


@pytest.fixture
def env_missing_files(env_ok: tuple[Path, str, str, str]) -> tuple[Path, str, str, str]:
def env_missing_files(
env_ok: tuple[Path, str, str, str, str],
) -> tuple[Path, str, str, str, str]:
"""Fixture that returns a testing environment with missing files"""
prefix, bin_doctor, _, _ = env_ok
prefix, bin_doctor, _, ignored_doctor, _ = env_ok
(prefix / bin_doctor).unlink() # file bin_doctor becomes "missing"
(prefix / ignored_doctor).unlink() # file ignored_doctor becomes "missing"

return env_ok


@pytest.fixture
def env_altered_files(env_ok: tuple[Path, str, str, str]) -> tuple[Path, str, str, str]:
def env_altered_files(
env_ok: tuple[Path, str, str, str, str],
) -> tuple[Path, str, str, str, str]:
"""Fixture that returns a testing environment with altered files"""
prefix, _, lib_doctor, _ = env_ok
prefix, _, lib_doctor, _, _ = env_ok
# Altering the lib_doctor.py file so that it's sha256 checksum will change
with open(prefix / lib_doctor, "w") as f:
f.write("print('Hello, World!')")
Expand All @@ -93,10 +109,10 @@ def env_altered_files(env_ok: tuple[Path, str, str, str]) -> tuple[Path, str, st


def test_listed_on_envs_txt_file(
tmp_path: Path, mocker: MockerFixture, env_ok: tuple[Path, str, str, str]
tmp_path: Path, mocker: MockerFixture, env_ok: tuple[Path, str, str, str, str]
):
"""Test that runs for the case when the env is listed on the environments.txt file"""
prefix, _, _, _ = env_ok
prefix, _, _, _, _ = env_ok
tmp_envs_txt_file = tmp_path / "envs.txt"
tmp_envs_txt_file.write_text(f"{prefix}")

Expand All @@ -108,10 +124,10 @@ def test_listed_on_envs_txt_file(


def test_not_listed_on_envs_txt_file(
tmp_path: Path, mocker: MockerFixture, env_ok: tuple[Path, str, str, str]
tmp_path: Path, mocker: MockerFixture, env_ok: tuple[Path, str, str, str, str]
):
"""Test that runs for the case when the env is not listed on the environments.txt file"""
prefix, _, _, _ = env_ok
prefix, _, _, _, _ = env_ok
tmp_envs_txt_file = tmp_path / "envs.txt"
tmp_envs_txt_file.write_text("Not environment name")

Expand All @@ -122,54 +138,57 @@ def test_not_listed_on_envs_txt_file(
assert not check_envs_txt_file(prefix)


def test_no_missing_files(env_ok: tuple[Path, str, str, str]):
def test_no_missing_files(env_ok: tuple[Path, str, str, str, str]):
"""Test that runs for the case with no missing files"""
prefix, _, _, _ = env_ok
prefix, _, _, _, _ = env_ok
assert find_packages_with_missing_files(prefix) == {}


def test_missing_files(env_missing_files: tuple[Path, str, str, str]):
prefix, bin_doctor, _, package = env_missing_files
def test_missing_files(env_missing_files: tuple[Path, str, str, str, str]):
prefix, bin_doctor, _, ignored_doctor, package = env_missing_files
assert find_packages_with_missing_files(prefix) == {package: [bin_doctor]}


def test_no_altered_files(env_ok: tuple[Path, str, str, str]):
def test_no_altered_files(env_ok: tuple[Path, str, str, str, str]):
"""Test that runs for the case with no altered files"""
prefix, _, _, _ = env_ok
prefix, _, _, _, _ = env_ok
assert find_altered_packages(prefix) == {}


def test_altered_files(env_altered_files: tuple[Path, str, str, str]):
prefix, _, lib_doctor, package = env_altered_files
def test_altered_files(env_altered_files: tuple[Path, str, str, str, str]):
prefix, _, lib_doctor, _, package = env_altered_files
assert find_altered_packages(prefix) == {package: [lib_doctor]}


@pytest.mark.parametrize("verbose", [True, False])
def test_missing_files_action(
env_missing_files: tuple[Path, str, str, str], capsys, verbose
env_missing_files: tuple[Path, str, str, str, str], capsys, verbose
):
prefix, bin_doctor, _, package = env_missing_files
prefix, bin_doctor, _, ignored_doctor, package = env_missing_files
missing_files(prefix, verbose=verbose)
captured = capsys.readouterr()
if verbose:
assert str(bin_doctor) in captured.out
assert str(ignored_doctor) not in captured.out
else:
assert f"{package}: 1" in captured.out


@pytest.mark.parametrize("verbose", [True, False])
def test_no_missing_files_action(env_ok: tuple[Path, str, str, str], capsys, verbose):
prefix, _, _, _ = env_ok
def test_no_missing_files_action(
env_ok: tuple[Path, str, str, str, str], capsys, verbose
):
prefix, _, _, _, _ = env_ok
missing_files(prefix, verbose=verbose)
captured = capsys.readouterr()
assert "There are no packages with missing files." in captured.out


@pytest.mark.parametrize("verbose", [True, False])
def test_altered_files_action(
env_altered_files: tuple[Path, str, str, str], capsys, verbose
env_altered_files: tuple[Path, str, str, str, str], capsys, verbose
):
prefix, _, lib_doctor, package = env_altered_files
prefix, _, lib_doctor, _, package = env_altered_files
altered_files(prefix, verbose=verbose)
captured = capsys.readouterr()
if verbose:
Expand All @@ -179,17 +198,22 @@ def test_altered_files_action(


@pytest.mark.parametrize("verbose", [True, False])
def test_no_altered_files_action(env_ok: tuple[Path, str, str, str], capsys, verbose):
prefix, _, _, _ = env_ok
def test_no_altered_files_action(
env_ok: tuple[Path, str, str, str, str], capsys, verbose
):
prefix, _, _, _, _ = env_ok
altered_files(prefix, verbose=verbose)
captured = capsys.readouterr()
assert "There are no packages with altered files." in captured.out


def test_env_txt_check_action(
tmp_path: Path, mocker: MockerFixture, env_ok: tuple[Path, str, str, str], capsys
tmp_path: Path,
mocker: MockerFixture,
env_ok: tuple[Path, str, str, str, str],
capsys,
):
prefix, _, _, _ = env_ok
prefix, _, _, _, _ = env_ok
tmp_envs_txt_file = tmp_path / "envs.txt"
tmp_envs_txt_file.write_text(f"{prefix}")

Expand All @@ -203,9 +227,12 @@ def test_env_txt_check_action(


def test_not_env_txt_check_action(
tmp_path: Path, mocker: MockerFixture, env_ok: tuple[Path, str, str, str], capsys
tmp_path: Path,
mocker: MockerFixture,
env_ok: tuple[Path, str, str, str, str],
capsys,
):
prefix, _, _, _ = env_ok
prefix, _, _, _, _ = env_ok
tmp_envs_txt_file = tmp_path / "envs.txt"
tmp_envs_txt_file.write_text("Not environment name")

Expand All @@ -218,9 +245,9 @@ def test_not_env_txt_check_action(
assert X_MARK in captured.out


def test_json_keys_missing(env_ok: tuple[Path, str, str, str], capsys):
def test_json_keys_missing(env_ok: tuple[Path, str, str, str, str], capsys):
"""Test that runs for the case with empty json"""
prefix, _, _, package = env_ok
prefix, _, _, _, package = env_ok
file = prefix / "conda-meta" / f"{package}.json"
with open(file) as f:
data = json.load(f)
Expand All @@ -231,9 +258,9 @@ def test_json_keys_missing(env_ok: tuple[Path, str, str, str], capsys):
assert find_altered_packages(prefix) == {}


def test_wrong_path_version(env_ok: tuple[Path, str, str, str]):
def test_wrong_path_version(env_ok: tuple[Path, str, str, str, str]):
"""Test that runs for the case when path_version is not equal to 1"""
prefix, _, _, package = env_ok
prefix, _, _, _, package = env_ok
file = prefix / "conda-meta" / f"{package}.json"
with open(file) as f:
data = json.load(f)
Expand All @@ -244,19 +271,22 @@ def test_wrong_path_version(env_ok: tuple[Path, str, str, str]):
assert find_altered_packages(prefix) == {}


def test_json_cannot_be_loaded(env_ok: tuple[Path, str, str, str]):
def test_json_cannot_be_loaded(env_ok: tuple[Path, str, str, str, str]):
"""Test that runs for the case when json file is missing"""
prefix, _, _, package = env_ok
prefix, _, _, _, package = env_ok
# passing a None type to json.loads() so that it fails
assert find_altered_packages(prefix) == {}


@pytest.mark.parametrize("verbose", [True, False])
def test_display_health_checks(
env_ok: tuple[Path, str, str, str], verbose: bool, capsys, monkeypatch: MonkeyPatch
env_ok: tuple[Path, str, str, str, str],
verbose: bool,
capsys,
monkeypatch: MonkeyPatch,
):
"""Test that runs display_health_checks without missing or altered files."""
prefix, bin_doctor, lib_doctor, package = env_ok
prefix, bin_doctor, lib_doctor, ignored_doctor, package = env_ok
monkeypatch.setenv("CONDA_PREFIX", str(prefix))
reset_context()
display_health_checks(prefix, verbose=verbose)
Expand All @@ -267,32 +297,33 @@ def test_display_health_checks(

@pytest.mark.parametrize("verbose", [True, False])
def test_display_health_checks_missing_files(
env_missing_files: tuple[Path, str, str, str],
env_missing_files: tuple[Path, str, str, str, str],
verbose: bool,
capsys,
monkeypatch: MonkeyPatch,
):
"""Test that runs display_health_checks with missing files"""
prefix, bin_doctor, _, package = env_missing_files
prefix, bin_doctor, _, ignored_doctor, package = env_missing_files
monkeypatch.setenv("CONDA_PREFIX", str(prefix))
reset_context()
display_health_checks(prefix, verbose=verbose)
captured = capsys.readouterr()
if verbose:
assert str(bin_doctor) in captured.out
assert str(ignored_doctor) not in captured.out
else:
assert f"{package}: 1" in captured.out


@pytest.mark.parametrize("verbose", [True, False])
def test_display_health_checks_altered_files(
env_altered_files: tuple[Path, str, str, str],
env_altered_files: tuple[Path, str, str, str, str],
verbose: bool,
capsys,
monkeypatch: MonkeyPatch,
):
"""Test that runs display_health_checks with altered files"""
prefix, _, lib_doctor, package = env_altered_files
prefix, _, lib_doctor, _, package = env_altered_files
monkeypatch.setenv("CONDA_PREFIX", str(prefix))
reset_context()
display_health_checks(prefix, verbose=verbose)
Expand Down

0 comments on commit cef2e58

Please sign in to comment.