From 4fd7d5484b73d1b06931105682277e270d9c1afd Mon Sep 17 00:00:00 2001 From: Bianca Henderson Date: Tue, 9 Jul 2024 13:26:38 -0400 Subject: [PATCH 1/3] Replace `sys.exit` calls in `conda_build/inspect_pkg.py` (#5393) * Replace sys.exit calls in conda_build/inspect_pkg.py * Update exceptions in unit tests * use alternate method to capture logs in test_menuinst_validation_fails_bad_json --------- Co-authored-by: Daniel Holth --- conda_build/inspect_pkg.py | 15 ++++++++++---- tests/cli/test_main_inspect.py | 5 +++-- tests/test_inspect.py | 5 +++-- tests/test_inspect_pkg.py | 28 +++++++++++++++++++++++-- tests/test_post.py | 38 +++++++++++++++++++++++++++------- 5 files changed, 74 insertions(+), 17 deletions(-) diff --git a/conda_build/inspect_pkg.py b/conda_build/inspect_pkg.py index 5747f3a7b8..54ec2c6f28 100644 --- a/conda_build/inspect_pkg.py +++ b/conda_build/inspect_pkg.py @@ -20,6 +20,7 @@ from conda.core.prefix_data import PrefixData from conda.models.records import PrefixRecord +from .exceptions import CondaBuildUserError from .os_utils.ldd import ( get_linkages, get_package_obj_files, @@ -219,9 +220,13 @@ def inspect_linkages( sysroot: str = "", ) -> str: if not packages and not untracked and not all_packages: - sys.exit("At least one package or --untracked or --all must be provided") + raise CondaBuildUserError( + "At least one package or --untracked or --all must be provided" + ) elif on_win: - sys.exit("Error: conda inspect linkages is only implemented in Linux and OS X") + raise CondaBuildUserError( + "`conda inspect linkages` is only implemented on Linux and macOS" + ) prefix = Path(prefix) installed = {prec.name: prec for prec in PrefixData(str(prefix)).iter_records()} @@ -237,7 +242,7 @@ def inspect_linkages( if name == untracked_package: obj_files = get_untracked_obj_files(prefix) elif name not in installed: - sys.exit(f"Package {name} is not installed in {prefix}") + raise CondaBuildUserError(f"Package {name} is not installed in {prefix}") else: obj_files = get_package_obj_files(installed[name], prefix) @@ -308,7 +313,9 @@ def inspect_objects( groupby: str = "package", ): if not on_mac: - sys.exit("Error: conda inspect objects is only implemented in OS X") + raise CondaBuildUserError( + "`conda inspect objects` is only implemented on macOS" + ) prefix = Path(prefix) installed = {prec.name: prec for prec in PrefixData(str(prefix)).iter_records()} diff --git a/tests/cli/test_main_inspect.py b/tests/cli/test_main_inspect.py index b8931b5220..83859bf441 100644 --- a/tests/cli/test_main_inspect.py +++ b/tests/cli/test_main_inspect.py @@ -9,6 +9,7 @@ from conda_build import api from conda_build.cli import main_inspect +from conda_build.exceptions import CondaBuildUserError from conda_build.utils import on_win from ..utils import metadata_dir @@ -23,7 +24,7 @@ def test_inspect_linkages(testing_workdir, capfd): # get a package that has known object output args = ["linkages", "python"] if on_win: - with pytest.raises(SystemExit) as exc: + with pytest.raises(CondaBuildUserError) as exc: main_inspect.execute(args) assert "conda inspect linkages is only implemented in Linux and OS X" in exc else: @@ -36,7 +37,7 @@ def test_inspect_objects(testing_workdir, capfd): # get a package that has known object output args = ["objects", "python"] if sys.platform != "darwin": - with pytest.raises(SystemExit) as exc: + with pytest.raises(CondaBuildUserError) as exc: main_inspect.execute(args) assert "conda inspect objects is only implemented in OS X" in exc else: diff --git a/tests/test_inspect.py b/tests/test_inspect.py index cd90ba98ae..04acf2728b 100644 --- a/tests/test_inspect.py +++ b/tests/test_inspect.py @@ -6,11 +6,12 @@ import pytest from conda_build import api +from conda_build.exceptions import CondaBuildUserError def test_inspect_linkages(): if sys.platform == "win32": - with pytest.raises(SystemExit) as exc: + with pytest.raises(CondaBuildUserError) as exc: out_string = api.inspect_linkages("python") assert "conda inspect linkages is only implemented in Linux and OS X" in exc else: @@ -20,7 +21,7 @@ def test_inspect_linkages(): def test_inspect_objects(): if sys.platform != "darwin": - with pytest.raises(SystemExit) as exc: + with pytest.raises(CondaBuildUserError) as exc: out_string = api.inspect_objects("python") assert "conda inspect objects is only implemented in OS X" in exc else: diff --git a/tests/test_inspect_pkg.py b/tests/test_inspect_pkg.py index dae6d7f6ca..4f20b85105 100644 --- a/tests/test_inspect_pkg.py +++ b/tests/test_inspect_pkg.py @@ -10,8 +10,9 @@ import pytest from conda.core.prefix_data import PrefixData -from conda_build.inspect_pkg import which_package -from conda_build.utils import on_win +from conda_build.exceptions import CondaBuildUserError +from conda_build.inspect_pkg import inspect_linkages, inspect_objects, which_package +from conda_build.utils import on_mac, on_win def test_which_package(tmp_path: Path): @@ -271,3 +272,26 @@ def test_which_package_battery(tmp_path: Path): # missing files should return no packages assert not len(list(which_package(tmp_path / "missing", tmp_path))) + + +def test_inspect_linkages_no_packages(): + with pytest.raises(CondaBuildUserError): + inspect_linkages([]) + + +@pytest.mark.skipif(not on_win, reason="inspect_linkages is available") +def test_inspect_linkages_on_win(): + with pytest.raises(CondaBuildUserError): + inspect_linkages(["packages"]) + + +@pytest.mark.skipif(on_win, reason="inspect_linkages is not available") +def test_inspect_linkages_not_installed(): + with pytest.raises(CondaBuildUserError): + inspect_linkages(["not_installed_pkg"]) + + +@pytest.mark.skipif(on_mac, reason="inspect_objects is only available on macOS") +def test_inspect_objects_not_on_mac(): + with pytest.raises(CondaBuildUserError): + inspect_objects([]) diff --git a/tests/test_post.py b/tests/test_post.py index eb2672218a..e0eb59237f 100644 --- a/tests/test_post.py +++ b/tests/test_post.py @@ -9,6 +9,7 @@ import pytest +import conda_build.utils from conda_build import api, post from conda_build.utils import ( get_site_packages, @@ -138,7 +139,7 @@ def test_menuinst_validation_fails_bad_schema(testing_config, caplog, tmp_path): assert "ValidationError" in captured_text -def test_menuinst_validation_fails_bad_json(testing_config, caplog, tmp_path): +def test_menuinst_validation_fails_bad_json(testing_config, monkeypatch, tmp_path): "3rd check - non-parsable JSON fails validation" recipe = Path(metadata_dir, "_menu_json_validation") recipe_tmp = tmp_path / "_menu_json_validation" @@ -147,13 +148,36 @@ def test_menuinst_validation_fails_bad_json(testing_config, caplog, tmp_path): menu_json_contents = menu_json.read_text() menu_json.write_text(menu_json_contents + "Make this an invalid JSON") - with caplog.at_level(logging.WARNING): - api.build(str(recipe_tmp), config=testing_config, notest=True) + # suspect caplog fixture may fail; use monkeypatch instead. + records = [] - captured_text = caplog.text - assert "Found 'Menu/*.json' files but couldn't validate:" not in captured_text - assert "not a valid menuinst JSON document" in captured_text - assert "JSONDecodeError" in captured_text + class MonkeyLogger: + def __getattr__(self, name): + return self.warning + + def warning(self, *args, **kwargs): + records.append((*args, kwargs)) + + monkeylogger = MonkeyLogger() + + def get_monkey_logger(*args, **kwargs): + return monkeylogger + + # For some reason it uses get_logger in the individual functions, instead of + # a module-level global that we could easily patch. + monkeypatch.setattr(conda_build.utils, "get_logger", get_monkey_logger) + + api.build(str(recipe_tmp), config=testing_config, notest=True) + + # without %s substitution + messages = [record[0] for record in records] + + assert "Found 'Menu/*.json' files but couldn't validate: %s" not in messages + assert "'%s' is not a valid menuinst JSON document!" in messages + assert any( + isinstance(record[-1].get("exc_info"), json.JSONDecodeError) + for record in records + ) def test_file_hash(testing_config, caplog, tmp_path): From fe6b8303a393997b5a5404af7c43c258584d2a99 Mon Sep 17 00:00:00 2001 From: Ken Odegard Date: Tue, 9 Jul 2024 21:05:30 +0200 Subject: [PATCH 2/3] Update `conda build --test recipe/` deprecation (#5352) * Update deprecation * Add test * Update news/3192-deprecate-testing-recipes --------- Co-authored-by: Bianca Henderson Co-authored-by: Bianca Henderson --- conda_build/build.py | 14 +++++++++----- conda_build/deprecations.py | 13 +++++++++++-- news/3192-deprecate-testing-recipes | 19 +++++++++++++++++++ tests/test_build.py | 13 ++++++++++++- 4 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 news/3192-deprecate-testing-recipes diff --git a/conda_build/build.py b/conda_build/build.py index 9bce326305..b759f84967 100644 --- a/conda_build/build.py +++ b/conda_build/build.py @@ -2886,6 +2886,15 @@ def warn_on_use_of_SRC_DIR(metadata): ) +@deprecated( + "3.16.0", + "24.7.0", + addendum=( + "Test built packages instead, not recipes " + "(e.g., `conda build --test package` instead of `conda build --test recipe/`)." + ), + deprecation_type=FutureWarning, # we need to warn users, not developers +) def _construct_metadata_for_test_from_recipe(recipe_dir, config): config.need_cleanup = False config.recipe_dir = None @@ -2893,11 +2902,6 @@ def _construct_metadata_for_test_from_recipe(recipe_dir, config): metadata = expand_outputs( render_recipe(recipe_dir, config=config, reset_build_id=False) )[0][1] - log = utils.get_logger(__name__) - log.warning( - "Testing based on recipes is deprecated as of conda-build 3.16.0. Please adjust " - "your code to pass your desired conda package to test instead." - ) utils.rm_rf(metadata.config.test_dir) diff --git a/conda_build/deprecations.py b/conda_build/deprecations.py index f691b5192d..cf87b4f25c 100644 --- a/conda_build/deprecations.py +++ b/conda_build/deprecations.py @@ -86,6 +86,7 @@ def __call__( *, addendum: str | None = None, stack: int = 0, + deprecation_type: type[Warning] = DeprecationWarning, ) -> Callable[[Callable[P, T]], Callable[P, T]]: """Deprecation decorator for functions, methods, & classes. @@ -102,6 +103,7 @@ def deprecated_decorator(func: Callable[P, T]) -> Callable[P, T]: remove_in=remove_in, prefix=f"{func.__module__}.{func.__qualname__}", addendum=addendum, + deprecation_type=deprecation_type, ) # alert developer that it's time to remove something @@ -128,6 +130,7 @@ def argument( rename: str | None = None, addendum: str | None = None, stack: int = 0, + deprecation_type: type[Warning] = DeprecationWarning, ) -> Callable[[Callable[P, T]], Callable[P, T]]: """Deprecation decorator for keyword arguments. @@ -149,6 +152,7 @@ def deprecated_decorator(func: Callable[P, T]) -> Callable[P, T]: addendum=( f"Use '{rename}' instead." if rename and not addendum else addendum ), + deprecation_type=deprecation_type, ) # alert developer that it's time to remove something @@ -181,6 +185,7 @@ def action( *, addendum: str | None = None, stack: int = 0, + deprecation_type: type[Warning] = FutureWarning, ) -> ActionType: """Wraps any argparse.Action to issue a deprecation warning.""" @@ -203,7 +208,7 @@ def __init__(inner_self: Self, *args: Any, **kwargs: Any) -> None: else f"`{inner_self.dest}`" ), addendum=addendum, - deprecation_type=FutureWarning, + deprecation_type=deprecation_type, ) # alert developer that it's time to remove something @@ -263,6 +268,7 @@ def constant( *, addendum: str | None = None, stack: int = 0, + deprecation_type: type[Warning] = DeprecationWarning, ) -> None: """Deprecation function for module constant/global. @@ -281,6 +287,7 @@ def constant( remove_in=remove_in, prefix=f"{fullname}.{constant}", addendum=addendum, + deprecation_type=deprecation_type, ) # alert developer that it's time to remove something @@ -310,6 +317,7 @@ def topic( topic: str, addendum: str | None = None, stack: int = 0, + deprecation_type: type[Warning] = DeprecationWarning, ) -> None: """Deprecation function for a topic. @@ -325,6 +333,7 @@ def topic( remove_in=remove_in, prefix=topic, addendum=addendum, + deprecation_type=deprecation_type, ) # alert developer that it's time to remove something @@ -379,7 +388,7 @@ def _generate_message( prefix: str, addendum: str | None, *, - deprecation_type: type[Warning] = DeprecationWarning, + deprecation_type: type[Warning], ) -> tuple[type[Warning] | None, str]: """Generate the standardized deprecation message and determine whether the deprecation is pending, active, or past. diff --git a/news/3192-deprecate-testing-recipes b/news/3192-deprecate-testing-recipes new file mode 100644 index 0000000000..1ea0d425ad --- /dev/null +++ b/news/3192-deprecate-testing-recipes @@ -0,0 +1,19 @@ +### Enhancements + +* + +### Bug fixes + +* + +### Deprecations + +* Mark `conda_build.build._construct_metadata_for_test_from_recipe` as deprecated. Test built packages instead, not recipes (e.g., `conda build --test package` instead of `conda build --test recipe/`). (#3192 via #5352) + +### Docs + +* + +### Other + +* diff --git a/tests/test_build.py b/tests/test_build.py index f7c3f2ba8c..d94df2dd93 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -20,7 +20,10 @@ from conda_build import api, build from conda_build.exceptions import CondaBuildUserError -from .utils import get_noarch_python_meta, metadata_dir +from .utils import get_noarch_python_meta, metadata_dir, metadata_path + +if TYPE_CHECKING: + from conda_build.config import Config if TYPE_CHECKING: from pytest_mock import MockerFixture @@ -351,6 +354,14 @@ def test_copy_readme(testing_metadata: MetaData, readme: str): assert Path(testing_metadata.config.info_dir, readme).exists() +def test_construct_metadata_for_test_from_recipe(testing_config: Config) -> None: + with pytest.warns(FutureWarning): + build._construct_metadata_for_test_from_recipe( + str(metadata_path / "test_source_files"), + testing_config, + ) + + @pytest.mark.skipif(not on_win, reason="WSL is only on Windows") def test_wsl_unsupported( testing_metadata: MetaData, From 0c136f5000bed769924faefdbbc045b8ffe90218 Mon Sep 17 00:00:00 2001 From: conda-bot <18747875+conda-bot@users.noreply.github.com> Date: Tue, 9 Jul 2024 14:13:04 -0500 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=94=84=20synced=20file(s)=20with=20co?= =?UTF-8?q?nda/infrastructure=20(#5401)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Conda Bot --- .github/workflows/labels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 0189478992..dec559a105 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -19,7 +19,7 @@ jobs: GLOBAL: https://raw.githubusercontent.com/conda/infra/main/.github/global.yml LOCAL: .github/labels.yml steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - id: has_local uses: andstor/file-existence-action@076e0072799f4942c8bc574a82233e1e4d13e9d6 # v3.0.0 with: