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: 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/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/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/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_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, 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):