diff --git a/docs/source/plugins.rst b/docs/source/plugins.rst index cce65d9..7ecf19c 100644 --- a/docs/source/plugins.rst +++ b/docs/source/plugins.rst @@ -133,24 +133,17 @@ creation. ) LOG.info(f"Successfully installed conda packages for {envname}") -``edgetest-conda/setup.cfg`` +``edgetest-conda/pyproject.toml`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. code-block:: ini +.. code-block:: toml :emphasize-lines: 11-13 - [metadata] + [project] name = edgetest-conda ... + dependencies = [ "edgetest>=2021.11.0" ] - [options] - packages = find: - ... - install_requires = - edgetest>=2021.11.0 - - [options.entry_points] - edgetest = - conda = edgetest_conda.plugin + entry-points."edgetest"."conda" = "edgetest_conda.plugin" The ``options.entry_points`` section here creates the link between ``edgetest`` and your plugin. diff --git a/docs/source/quickstart.rst b/docs/source/quickstart.rst index b62bf36..e4ccf22 100644 --- a/docs/source/quickstart.rst +++ b/docs/source/quickstart.rst @@ -22,9 +22,8 @@ Project support type ``edgetest`` works with the following types of projects: -- ``pyproject.toml`` -- ``setup.cfg`` -- and ``requirements.txt`` +- ``pyproject.toml``, and +- ``requirements.txt`` Usage diff --git a/docs/source/usage.rst b/docs/source/usage.rst index 7890026..d40ff52 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -7,17 +7,17 @@ Configuration file Suppose you want to test upgrades for a subset of your dependencies. Instead of letting ``edgetest`` parse your requirements file, you can create a configuration file: -.. code-block:: ini +.. code-block:: toml - [edgetest.envs.pandas] - upgrade = - pandas + [[tool.edgetest.env]] + name = "pandas" + upgrade = [ "pandas" ] Then run .. code-block:: console - $ edgetest -c edgetest.cfg + $ edgetest -c edgetest.toml This command will @@ -29,26 +29,16 @@ This command will .. important:: - This configuration file can be a standalone file which is distinct from the ``setup.cfg`` or ``pyproject.toml`` - configurations. Just note it must have a ``edgetest.envs`` otherwise it expects a PEP 517-style ``setup.cfg`` + This configuration file can be a standalone file, distinct from ``pyproject.toml``. - -``setup.cfg`` and ``pyproject.toml`` Configuration +``pyproject.toml`` Configuration -------------------------------------------------- -You can also specify your testing environment through ``setup.cfg`` or ``pyproject.toml``: +You can also specify your testing environment through ``pyproject.toml``: .. tabs:: - .. tab:: .cfg - - .. code-block:: ini - - [edgetest.envs.pandas] - upgrade = - pandas - .. tab:: .toml (legacy) .. code-block:: toml @@ -72,14 +62,6 @@ your requirements might look like this: .. tabs:: - .. tab:: .cfg - - .. code-block:: ini - - [options] - install_requires = - pandas - .. tab:: .toml .. code-block:: toml @@ -89,16 +71,10 @@ your requirements might look like this: "pandas", ] -To point to ``setup.cfg`` or ``pyproject.toml``, supply the location of the file as your ``--config``: +To point to ``pyproject.toml``, supply the location of the file as your ``--config``: .. tabs:: - .. tab:: .cfg - - .. code-block:: console - - $ edgetest -c path/to/setup.cfg - .. tab:: .toml .. code-block:: console @@ -107,7 +83,7 @@ To point to ``setup.cfg`` or ``pyproject.toml``, supply the location of the file .. important:: - Using ``setup.cfg`` or ``pyproject.toml`` will allow you to upgrade optional installations. + Using ``pyproject.toml`` will allow you to upgrade optional installations. Installing extras @@ -118,19 +94,7 @@ call as follows: .. tabs:: - .. tab:: .cfg - - Add an ``extras`` list to your environment: - - .. code-block:: ini - - [edgetest.envs.pandas] - upgrade = - pandas - extras = - tests - - .. tab:: .toml (legacy) + .. tab:: .toml (legacy) Add an ``extras`` list to your environment: @@ -168,17 +132,6 @@ To specify the python version for your virtual environments, modify your configu .. tabs:: - .. tab:: .cfg - - Add ``python_version`` to your environment: - - .. code-block:: ini - - [edgetest.envs.pandas] - upgrade = - pandas - python_version = "3.13" - .. tab:: .toml (legacy) Add ``python_version`` to your environment: @@ -207,20 +160,6 @@ To customize your test command, modify the configuration or CLI call as follows: .. tabs:: - .. tab:: .cfg - - Add a ``command`` key to your environment: - - .. code-block:: ini - - [edgetest.envs.pandas] - upgrade = - pandas - extras = - tests - command = - pytest tests -m 'not integration' - .. tab:: .toml (legacy) Add a ``command`` key to your environment: @@ -266,22 +205,6 @@ To specify additional ``pip`` dependencies, modify as follows: .. tabs:: - .. tab:: .cfg - - Add a ``deps`` list: - - .. code-block:: ini - - [edgetest.envs.pandas] - upgrade = - pandas - extras = - tests - command = - pytest tests -m "not integration" - deps = - scikit-learn - .. tab:: .toml (legacy) Add a ``deps`` list: @@ -334,57 +257,6 @@ you can specify those under the ``edgetest`` section of your configuration: .. tabs:: - .. tab:: .cfg - - .. code-block:: ini - - [edgetest] - extras = - tests - command = - pytest tests -m 'not integration' - - [edgetest.envs.pandas] - upgrade = - pandas - - [edgetest.envs.numpy] - upgrade = - numpy - - .. important:: - - You can combine your configuration file with ``requirements.txt``. If you have the following - configuration file: - - .. code-block:: ini - - [options] - install_requires = - - [edgetest] - extras = - tests - command = - pytest tests -m 'not integration' - - and the following requirements file: - - .. code-block:: text - - pandas>=0.25.1,<=1.0.0 - scikit-learn>=0.23.0,<=0.24.2 - - - the following CLI call - - .. code-block:: console - - $ edgetest -c edgetest.cfg -r requirements.txt - - will apply the default arguments to each environment. - - .. tab:: .toml (legacy) .. code-block:: toml @@ -423,26 +295,6 @@ in your testing project directory: .. tabs:: - .. tab:: .cfg - - .. code-block:: ini - - [edgetest.envs.pandas] - package_dir = ../mypackage - upgrade = - pandas - - [edgetest.envs.numpy] - package_dir = ../myotherpackage - upgrade = - numpy - - After running - - .. code-block:: console - - $ edgetest -c path/to/edgetest.cfg - .. tab:: .toml (legacy) .. code-block:: toml @@ -507,16 +359,6 @@ in the ``uv`` documentation `here =2.2.8,<2.3.9`` might be repl .. tabs:: - .. tab:: Configuration file (cfg/toml) + .. tab:: Configuration file (toml) Include the correct ``--requirements`` filepath and use ``--export``: .. code-block:: console $ edgetest \ - -c path/to/edgetest.cfg \ + -c path/to/edgetest.toml \ --requirements requirements.txt \ --export @@ -629,18 +462,10 @@ Testing lower bounds of packages This feature is experimental and may change in future releases. Limited functionality is available. Suppose you want to test if a new feature is compatible with dependencies' lower bounds. -You can use an edgetest environment to test the lower bounds of dependencies in your ``setup.cfg`` or ``pyproject.toml`` as follows: +You can use an edgetest environment to test the lower bounds of dependencies in your ``pyproject.toml`` as follows: .. tabs:: - .. tab:: .cfg - - .. code-block:: ini - - [edgetest.envs.pandas_lower] - lower = - pandas - .. tab:: .toml (legacy) .. code-block:: toml @@ -669,18 +494,6 @@ For example, if your project configuration looks like this: .. tabs:: - .. tab:: .cfg - - .. code-block:: ini - - [options] - install_requires = - pandas>=0.24.3 - - [edgetest.envs.pandas_lower] - lower = - pandas - .. tab:: .toml (legacy) .. code-block:: toml diff --git a/edgetest/core.py b/edgetest/core.py index 23e08c7..f1fe6a7 100644 --- a/edgetest/core.py +++ b/edgetest/core.py @@ -4,9 +4,9 @@ import shlex from pathlib import Path from subprocess import Popen -from typing import Dict, List from pluggy._hooks import _HookRelay +from uv import find_uv_bin from edgetest.logger import get_logger from edgetest.utils import _isin_case_dashhyphen_ins, _run_command, pushd @@ -47,8 +47,8 @@ def __init__( self, hook: _HookRelay, envname: str, - upgrade: List[str] | None = None, - lower: List[str] | None = None, + upgrade: list[str] | None = None, + lower: list[str] | None = None, package_dir: str | None = None, ): """Init method.""" @@ -93,8 +93,8 @@ def python_path(self) -> str: def setup( self, - extras: List[str] | None = None, - deps: List[str] | None = None, + extras: list[str] | None = None, + deps: list[str] | None = None, skip: bool = False, **options, ) -> None: @@ -140,9 +140,15 @@ def setup( # Install the local package with pushd(self.package_dir): - pkg = "." + uv_ = find_uv_bin() + base_cmd_ = [ + uv_, + "sync", + "--inexact", + f"--python={self.python_path}", + ] if extras: - pkg += f"[{', '.join(extras)}]" + base_cmd_ += [f"--extra={e}" for e in extras] if deps: LOG.info( "Installing specified additional dependencies into %s: %s", @@ -152,7 +158,7 @@ def setup( split = [shlex.split(dep) for dep in deps] try: _run_command( - "uv", + uv_, "pip", "install", f"--python={self.python_path}", @@ -172,7 +178,10 @@ def setup( LOG.info(f"Installing the local package into {self.envname}...") try: _run_command( - "uv", "pip", "install", f"--python={self.python_path}", pkg + *base_cmd_, + env={ + "UV_PROJECT_ENVIRONMENT": str(Path(self.basedir, self.envname)) + }, ) LOG.info( f"Successfully installed the local package into {self.envname}..." @@ -227,7 +236,7 @@ def setup( f"Successfully installed lower bounds of packages in {self.envname}" ) - def upgraded_packages(self) -> List[Dict[str, str]]: + def upgraded_packages(self) -> list[dict[str, str]]: """Get the list of upgraded packages for the test environment. Parameters @@ -243,8 +252,9 @@ def upgraded_packages(self) -> List[Dict[str, str]]: if self.upgrade is None: return [] # Get the version for the upgraded package(s) + uv_ = find_uv_bin() out, _ = _run_command( - "uv", "pip", "list", f"--python={self.python_path}", "--format", "json" + uv_, "pip", "list", f"--python={self.python_path}", "--format", "json" ) outjson = json.loads(out) @@ -255,7 +265,7 @@ def upgraded_packages(self) -> List[Dict[str, str]]: if _isin_case_dashhyphen_ins(pkg.get("name", ""), upgrade_wo_extras) ] - def lowered_packages(self) -> List[Dict[str, str]]: + def lowered_packages(self) -> list[dict[str, str]]: """Get list of lowered packages for the test environment. Returns diff --git a/edgetest/hookspecs.py b/edgetest/hookspecs.py index 990a343..300236e 100644 --- a/edgetest/hookspecs.py +++ b/edgetest/hookspecs.py @@ -1,7 +1,5 @@ """Hook specifications for edgetest.""" -from typing import Dict, List - import pluggy from edgetest.schema import Schema @@ -19,12 +17,12 @@ def addoption(schema: Schema): @hookspec -def pre_run_hook(conf: Dict): +def pre_run_hook(conf: dict): """Pre-setup and test hook. Parameters ---------- - conf : Dict + conf : dict The entire configuration dictionary. """ @@ -50,7 +48,7 @@ def path_to_python(basedir: str, envname: str) -> str: @hookspec(firstresult=True) -def create_environment(basedir: str, envname: str, conf: Dict): +def create_environment(basedir: str, envname: str, conf: dict): """Create the virtual environment for testing. Parameters @@ -72,7 +70,7 @@ def create_environment(basedir: str, envname: str, conf: Dict): @hookspec(firstresult=True) -def run_update(basedir: str, envname: str, upgrade: List, conf: Dict): +def run_update(basedir: str, envname: str, upgrade: list, conf: dict): """Update packages from upgrade list. Parameters @@ -96,7 +94,7 @@ def run_update(basedir: str, envname: str, upgrade: List, conf: Dict): @hookspec(firstresult=True) -def run_install_lower(basedir: str, envname: str, lower: Dict[str, str], conf: Dict): +def run_install_lower(basedir: str, envname: str, lower: dict[str, str], conf: dict): """Install lower bounds of packages provided. Parameters @@ -107,7 +105,7 @@ def run_install_lower(basedir: str, envname: str, lower: Dict[str, str], conf: D Environment to install into. lower_bounds : Dict[str, str] Lower bounds of packages to install. - conf : Dict + conf : dict The configuration dictionary for the environment. This is useful if you want to add configuration arguments for additional dependencies that can only be installed through the environment manager (e.g. Conda). @@ -115,7 +113,7 @@ def run_install_lower(basedir: str, envname: str, lower: Dict[str, str], conf: D @hookspec -def post_run_hook(testers: List, conf: Dict): +def post_run_hook(testers: list, conf: dict): """Post testing hook. For executing code after the environment set up and testing. diff --git a/edgetest/interface.py b/edgetest/interface.py index f672ebf..da22f34 100644 --- a/edgetest/interface.py +++ b/edgetest/interface.py @@ -1,7 +1,6 @@ """Command-line interface.""" from pathlib import Path -from typing import List import click import pluggy @@ -15,11 +14,9 @@ from edgetest.schema import EdgetestValidator, Schema from edgetest.utils import ( gen_requirements_config, - parse_cfg, parse_toml, upgrade_pyproject_toml, upgrade_requirements, - upgrade_setup_cfg, ) LOG = get_logger(__name__) @@ -124,9 +121,7 @@ def cli( """ # Get the hooks pm = get_plugin_manager() - if config and Path(config).suffix == ".cfg": - conf = parse_cfg(filename=config, requirements=requirements) - elif config and Path(config).suffix == ".toml": + if config and Path(config).suffix == ".toml": conf = parse_toml(filename=config, requirements=requirements) else: # Find the path to the local directory using the requirements file @@ -146,12 +141,11 @@ def cli( raise ValueError("Unable to validate configuration file.") conf = validator.document - if environment: - conf["envs"] = [env for env in conf["envs"] if env["name"] == environment] - # Run the pre-test hook pm.hook.pre_run_hook(conf=conf) - testers: List[TestPackage] = [] + if environment: + conf["envs"] = [env for env in conf["envs"] if env["name"] == environment] + testers: list[TestPackage] = [] for env in conf["envs"]: testers.append( TestPackage( @@ -178,25 +172,7 @@ def cli( click.echo(f"\n\n{report}") if export and testers[-1].status: - if config is not None and Path(config).name == "setup.cfg": - parser = upgrade_setup_cfg( - upgraded_packages=testers[-1].upgraded_packages(), - filename=config, - ) - with open(config, "w") as outfile: - parser.write(outfile) - if "options" not in parser or not parser.get("options", "install_requires"): - click.echo( - "No PEP-517 style requirements in ``setup.cfg`` to update. Updating " - f"{requirements}" - ) - upgraded = upgrade_requirements( - fname_or_buf=requirements, - upgraded_packages=testers[-1].upgraded_packages(), - ) - with open(requirements, "w") as outfile: - outfile.write(upgraded) - elif config is not None and Path(config).name == "pyproject.toml": + if config is not None and Path(config).name == "pyproject.toml": parser = upgrade_pyproject_toml( upgraded_packages=testers[-1].upgraded_packages(), filename=config, diff --git a/edgetest/lib.py b/edgetest/lib.py index 2c54a63..8a7729c 100644 --- a/edgetest/lib.py +++ b/edgetest/lib.py @@ -3,10 +3,10 @@ import logging import platform from pathlib import Path -from typing import Dict, List import click import pluggy +from uv import find_uv_bin from edgetest.utils import _run_command @@ -25,7 +25,7 @@ def path_to_python(basedir: str, envname: str) -> str: @hookimpl(trylast=True) -def create_environment(basedir: str, envname: str, conf: Dict): +def create_environment(basedir: str, envname: str, conf: dict): """Create the virtual environment for testing. Creates an environment using ``uv``. @@ -45,7 +45,8 @@ def create_environment(basedir: str, envname: str, conf: Dict): Error raised if the environment cannot be created. """ try: - callargs_ = ["uv", "venv", str(Path(basedir, envname))] + uv_ = find_uv_bin() + callargs_ = [uv_, "venv", str(Path(basedir, envname))] if (py_version := conf.get("python_version")) is not None: callargs_.append(f"--python={py_version}") if Path(basedir, envname).is_dir(): @@ -56,7 +57,7 @@ def create_environment(basedir: str, envname: str, conf: Dict): @hookimpl(trylast=True) -def run_update(basedir: str, envname: str, upgrade: List, conf: Dict): +def run_update(basedir: str, envname: str, upgrade: list, conf: dict): """Update packages from upgrade list. Parameters @@ -77,8 +78,9 @@ def run_update(basedir: str, envname: str, upgrade: List, conf: Dict): Error raised if the packages cannot be updated. """ python_path = path_to_python(basedir, envname) + uv_ = find_uv_bin() callargs_ = [ - "uv", + uv_, "pip", "install", f"--python={python_path}", @@ -97,7 +99,7 @@ def run_update(basedir: str, envname: str, upgrade: List, conf: Dict): @hookimpl(trylast=True) -def run_install_lower(basedir: str, envname: str, lower: List[str], conf: Dict): +def run_install_lower(basedir: str, envname: str, lower: list[str], conf: dict): """Install lower bounds of packages provided. Parameters @@ -106,22 +108,23 @@ def run_install_lower(basedir: str, envname: str, lower: List[str], conf: Dict): The base directory location for the environment. envname : str Environment to install into. - lower : List[str] + lower : list[str] Lower bounds of packages to install. - conf : Dict + conf : dict The configuration dictionary for the environment. This is useful if you want to add configuration arguments for additional dependencies that can only be installed through the environment manager (e.g. Conda). """ python_path = path_to_python(basedir, envname) try: - _run_command("uv", "pip", "install", f"--python={python_path}", *lower) + uv_ = find_uv_bin() + _run_command(uv_, "pip", "install", f"--python={python_path}", *lower) except Exception as err: raise RuntimeError(f"Unable to pip install: {lower}") from err @hookimpl(tryfirst=True) -def post_run_hook(testers: List, conf: Dict): +def post_run_hook(testers: list, conf: dict): """Refresh ``uv.lock`` based on the test output.""" ctx = click.get_current_context() if not ctx.params["export"]: @@ -133,7 +136,8 @@ def post_run_hook(testers: List, conf: Dict): ): # uv.lock exists already and the last tester passed try: - _run_command("uv", "lock", "--upgrade") + uv_ = find_uv_bin() + _run_command(uv_, "lock", "--upgrade") except RuntimeError: LOG.info("Unable to update the ``uv.lock`` file.") else: diff --git a/edgetest/report.py b/edgetest/report.py index dbce615..917ad61 100644 --- a/edgetest/report.py +++ b/edgetest/report.py @@ -1,6 +1,6 @@ """Generate rST reports.""" -from typing import Any, List +from typing import Any from tabulate import tabulate @@ -9,7 +9,7 @@ VALID_OUTPUTS = ["rst", "github"] -def gen_report(testers: List[TestPackage], output_type: str = "rst") -> Any: +def gen_report(testers: list[TestPackage], output_type: str = "rst") -> Any: """Generate a rST report. Parameters @@ -36,7 +36,7 @@ def gen_report(testers: List[TestPackage], output_type: str = "rst") -> Any: "Lowered packages", "Package version", ] - rows: List[List] = [] + rows: list[list] = [] for env in testers: upgraded = env.upgraded_packages() lowered = env.lowered_packages() diff --git a/edgetest/schema.py b/edgetest/schema.py index a0fe973..2873037 100644 --- a/edgetest/schema.py +++ b/edgetest/schema.py @@ -1,7 +1,5 @@ """Define the Cerberus schema for the testing configuration.""" -from typing import Dict, List - from cerberus import Validator BASE_SCHEMA = { @@ -66,7 +64,7 @@ class Schema: schema = BASE_SCHEMA - def add_envoption(self, option: str, schema: Dict): + def add_envoption(self, option: str, schema: dict): """Add an environment-level option. Parameters @@ -82,7 +80,7 @@ def add_envoption(self, option: str, schema: Dict): """ self.schema["envs"]["schema"]["schema"][option] = schema # type: ignore - def add_globaloption(self, option: str, schema: Dict): + def add_globaloption(self, option: str, schema: dict): """Add a global option. Parameters @@ -102,7 +100,7 @@ def add_globaloption(self, option: str, schema: Dict): class EdgetestValidator(Validator): """Custom validator for coercing lists from ``.ini`` style files.""" - def _normalize_coerce_listify(self, value: str) -> List: + def _normalize_coerce_listify(self, value: str) -> list: """Coerce a value into a list. Parameters diff --git a/edgetest/utils.py b/edgetest/utils.py index d00d81b..d656b0b 100644 --- a/edgetest/utils.py +++ b/edgetest/utils.py @@ -2,11 +2,9 @@ import os import warnings -from configparser import ConfigParser from contextlib import contextmanager from pathlib import Path from subprocess import PIPE, Popen -from typing import Dict, List, Tuple import tomlkit from packaging.requirements import Requirement @@ -18,13 +16,15 @@ LOG = get_logger(__name__) -def _run_command(*args) -> Tuple[str, int]: +def _run_command(*args, env: dict[str, str] | None = None) -> tuple[str, int]: """Run a command using ``subprocess.Popen``. Parameters ---------- *args Arguments for the command. + env : dict, optional (default None) + Environment variables for the subprocess call. Returns ------- @@ -39,7 +39,7 @@ def _run_command(*args) -> Tuple[str, int]: Error raised when the command is not successfully executed. """ LOG.debug(f"Running the following command: \n\n {' '.join(args)}") - popen = Popen(args, stdout=PIPE, stderr=PIPE, universal_newlines=True) + popen = Popen(args, stdout=PIPE, stderr=PIPE, env=env, universal_newlines=True) out, err = popen.communicate() if popen.returncode: raise RuntimeError( @@ -68,7 +68,7 @@ def pushd(new_dir: str): os.chdir(curr_dir) -def convert_requirements(requirements: str, conf: Dict | None = None) -> Dict: +def convert_requirements(requirements: str, conf: dict | None = None) -> dict: """Generate environments for a newline-separate list of package requirements. This function will generate one environment per entry with an additional environment @@ -83,7 +83,7 @@ def convert_requirements(requirements: str, conf: Dict | None = None) -> Dict: Returns ------- - Dict + dict A configuration dictionary. """ conf = {"envs": []} if conf is None else conf @@ -104,7 +104,7 @@ def convert_requirements(requirements: str, conf: Dict | None = None) -> Dict: return conf -def gen_requirements_config(fname_or_buf: str, **options) -> Dict: +def gen_requirements_config(fname_or_buf: str, **options) -> dict: """Generate a configuration file from package requirements. This function will convert the package installation requirements to a configuration @@ -120,7 +120,7 @@ def gen_requirements_config(fname_or_buf: str, **options) -> Dict: Returns ------- - Dict + dict The configuration file. """ # First, get the requirements @@ -136,114 +136,9 @@ def gen_requirements_config(fname_or_buf: str, **options) -> Dict: return output -def parse_cfg(filename: str = "setup.cfg", requirements: str | None = None) -> Dict: - """Generate a configuration from a ``.ini`` style file. - - This function can operate in two ways. First, it can look for sections that - start with ``edgetest`` and build a configuration. Suppose - you have ``setup.cfg`` as follows: - - .. code-block:: ini - - [edgetest.envs.pandas] - upgrade = - pandas - - This will result in a configuration that has one testing environment, named - ``pandas``, that upgrades the ``pandas`` package. - - If you don't have any sections that start with ``edgetest.envs``, we will look for - the PEP 517-style ``setup.cfg`` install requirements (the ``install_requires`` key - within the ``options`` section). To set global defaults for you environments, use - the ``edgetest`` section: - - .. code-block:: ini - - [edgetest] - extras = - tests - command = - pytest tests -m "not integration" - - [edgetest.envs.pandas] - upgrade = - pandas - - For this single environment file, the above configuration is equivalent to - - .. code-block:: ini - - [edgetest.envs.pandas] - extras = - tests - command = - pytest tests -m "not integration" - upgrade = - pandas - - Parameters - ---------- - filename : str, optional (default "setup.cfg") - The name of the configuration file to read. Defaults to ``setup.cfg``. - requirements : str, optional (default None) - An optional path to the requirements text file. If there are no PEP-517 - style dependencies or coded environments in the edgetest configuration, this - function will look for dependencies in the requirements file. - - Returns - ------- - Dict - A configuration dictionary for ``edgetest``. - """ - # Read in the configuration file - config = ConfigParser() - config.read(filename) - # Parse - output: Dict = {"envs": []} - # Get any global options if necessary - options = dict(config["edgetest"]) if "edgetest" in config else {} - # Next, create the sections - for section in config.sections(): - if not section.startswith("edgetest."): - continue - # Look for the special ``envs`` key - section_name = section.split(".") - if section_name[1] == "envs": - output["envs"].append(dict(config[section])) - output["envs"][-1]["name"] = section_name[2] - if ( - "lower" in output["envs"][-1] - and "options" in config - and "install_requires" in config["options"] - ): - output["envs"][-1]["lower"] = get_lower_bounds( - config.get("options", "install_requires"), - output["envs"][-1]["lower"], - ) - else: - output[section_name[1]] = dict(config[section]) - if len(output["envs"]) == 0: - if config.get("options", "install_requires"): - output = convert_requirements( - requirements=config["options"]["install_requires"], conf=output - ) - elif requirements: - req_conf = gen_requirements_config(fname_or_buf=requirements) - output["envs"] = req_conf["envs"] - else: - raise ValueError("Please supply a valid list of environments to create.") - # Apply global environment options (without overwriting) - for idx in range(len(output["envs"])): - output["envs"][idx] = dict( - list(options.items()) + list(output["envs"][idx].items()) - ) - - return output - - def parse_toml( filename: str = "pyproject.toml", requirements: str | None = None -) -> Dict: +) -> dict: """Generate a configuration from a ``.toml`` style file. This function will look for a table that starts with either ``edgetest`` @@ -335,7 +230,7 @@ def parse_toml( Returns ------- - Dict + dict A configuration dictionary for ``edgetest``. """ options: dict @@ -395,7 +290,7 @@ def parse_toml( return output -def _parse_toml_classic(config: Table) -> Tuple[Dict, Dict]: +def _parse_toml_classic(config: Table) -> tuple[dict, dict]: """Generate a configuration from a ``.toml`` style file. This function is used to parse the classic ``edgetest`` table format. @@ -407,9 +302,9 @@ def _parse_toml_classic(config: Table) -> Tuple[Dict, Dict]: Returns ------- - Dict + dict A configuration dictionary for ``edgetest``. - Dict + dict Global configuration options for ``edgetest``. """ options = { @@ -418,7 +313,7 @@ def _parse_toml_classic(config: Table) -> Tuple[Dict, Dict]: if not isinstance(value, Table) } - output: Dict = {"envs": []} + output: dict = {"envs": []} for section in config: if section == "envs": for name, env in config.get("envs", tomlkit.table()).unwrap().items(): @@ -429,7 +324,7 @@ def _parse_toml_classic(config: Table) -> Tuple[Dict, Dict]: return output, options -def _parse_toml_tool(config: Table) -> Tuple[Dict, Dict]: +def _parse_toml_tool(config: Table) -> tuple[dict, dict]: """Generate a configuration from a ``.toml`` style configuration. This function is used to parse the newer ``tool.edgetest`` table format. @@ -453,7 +348,7 @@ def _parse_toml_tool(config: Table) -> Tuple[Dict, Dict]: if key != "env" and not isinstance(value, Table) } - output: Dict = {"envs": []} + output: dict = {"envs": []} for section in config: if section == "env": output["envs"] = config["env"].unwrap() @@ -464,7 +359,7 @@ def _parse_toml_tool(config: Table) -> Tuple[Dict, Dict]: def upgrade_requirements( - fname_or_buf: str, upgraded_packages: List[Dict[str, str]] + fname_or_buf: str, upgraded_packages: list[dict[str, str]] ) -> str: """Create an upgraded requirements file. @@ -521,52 +416,14 @@ def upgrade_requirements( return "\n".join(str(pkg) for pkg in pkgs) -def upgrade_setup_cfg( - upgraded_packages: List[Dict[str, str]], filename: str = "setup.cfg" -) -> ConfigParser: - """Upgrade the ``setup.cfg`` file. - - Parameters - ---------- - upgraded_packages : List[Dict[str, str]] - A list of packages upgraded in the testing procedure. - filename : str, optional (default "setup.cfg") - The name of the configuration file to read. Defaults to ``setup.cfg``. - - Returns - ------- - ConfigParser - The updated configuration file. - """ - parser = ConfigParser() - parser.read(filename) - if "options" in parser and parser.get("options", "install_requires"): - LOG.info(f"Updating the requirements in {filename}") - upgraded = upgrade_requirements( - fname_or_buf=parser["options"]["install_requires"].lstrip(), - upgraded_packages=upgraded_packages, - ) - parser["options"]["install_requires"] = "\n" + upgraded - # Update the extras, if necessary - if "options.extras_require" in parser: - for extra, dependencies in parser.items("options.extras_require"): - upgraded = upgrade_requirements( - fname_or_buf=dependencies, - upgraded_packages=upgraded_packages, - ) - parser["options.extras_require"][extra] = "\n" + upgraded - - return parser - - def upgrade_pyproject_toml( - upgraded_packages: List[Dict[str, str]], filename: str = "pyproject.toml" + upgraded_packages: list[dict[str, str]], filename: str = "pyproject.toml" ) -> tomlkit.TOMLDocument: """Upgrade the ``pyproject.toml`` file. Parameters ---------- - upgraded_packages : List[Dict[str, str]] + upgraded_packages : list[dict[str, str]] A list of packages upgraded in the testing procedure. filename : str, optional (default "pyproject.toml") The name of the configuration file to read. Defaults to ``pyproject.toml``. @@ -597,7 +454,7 @@ def upgrade_pyproject_toml( return parser -def _isin_case_dashhyphen_ins(a: str, vals: List[str]) -> bool: +def _isin_case_dashhyphen_ins(a: str, vals: list[str]) -> bool: """Run isin check that is case and dash/hyphen insensitive. Paramaters @@ -615,7 +472,7 @@ def _isin_case_dashhyphen_ins(a: str, vals: List[str]) -> bool: return any(a.replace("_", "-").lower() == b.replace("_", "-").lower() for b in vals) -def get_lower_bounds(requirements: str | List[str], lower: str | List[str]) -> str: +def get_lower_bounds(requirements: str | list[str], lower: str | list[str]) -> str: r"""Get lower bounds of requested packages from installation requirements. Parses through the project ``requirements`` and the newline-delimited @@ -626,7 +483,7 @@ def get_lower_bounds(requirements: str | List[str], lower: str | List[str]) -> s requirements : str or list Project setup requirements, e.g. ``"pandas>=1.5.1,<=1.4.2\nnumpy>=1.22.1,<=1.25.4"`` - lower : str | List[str] + lower : str | list[str] Newline-delimited packages requested, e.g. ``"pandas\nnumpy"``. @@ -643,7 +500,7 @@ def get_lower_bounds(requirements: str | List[str], lower: str | List[str]) -> s ] elif isinstance(requirements, list): pkgs = [Requirement(val) for val in requirements] - all_lower_bounds: Dict[str, str] = {} + all_lower_bounds: dict[str, str] = {} for pkg in pkgs: full_name = pkg.name + (f"[{','.join(pkg.extras)}]" if pkg.extras else "") for spec in pkg.specifier: diff --git a/pyproject.toml b/pyproject.toml index 14106bb..214cfad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ optional-dependencies.qa = [ "mypy", "pre-commit", "pre-commit-uv", - "ruff~=0.5", + "ruff", "types-click", "types-setuptools", "types-tabulate", diff --git a/tests/test_core.py b/tests/test_core.py index 18d317f..b4da63d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -5,6 +5,7 @@ from unittest.mock import PropertyMock, call, patch import pytest +from uv import find_uv_bin from edgetest.core import TestPackage @@ -68,11 +69,13 @@ def test_basic_setup(mock_popen, mock_path, tmpdir, plugin_manager): else: py_loc = env_loc / "bin" / "python" + uv_ = find_uv_bin() assert mock_popen.call_args_list == [ call( - ("uv", "pip", "install", f"--python={py_loc!s}", "."), + (uv_, "sync", "--inexact", f"--python={py_loc!s}"), stdout=-1, stderr=-1, + env={"UV_PROJECT_ENVIRONMENT": str(env_loc)}, universal_newlines=True, ), ] @@ -171,11 +174,20 @@ def test_setup_extras(mock_popen, mock_path, tmpdir, plugin_manager): else: py_loc = Path(env_loc) / "bin" / "python" + uv_ = find_uv_bin() assert mock_popen.call_args_list == [ call( - ("uv", "pip", "install", f"--python={py_loc!s}", ".[tests, complete]"), + ( + uv_, + "sync", + "--inexact", + f"--python={py_loc!s}", + "--extra=tests", + "--extra=complete", + ), stdout=-1, stderr=-1, + env={"UV_PROJECT_ENVIRONMENT": str(env_loc)}, universal_newlines=True, ), ] @@ -207,10 +219,11 @@ def test_setup_pip_deps(mock_popen, mock_path, tmpdir, plugin_manager): else: py_loc = Path(env_loc) / "bin" / "python" + uv_ = find_uv_bin() assert mock_popen.call_args_list == [ call( ( - "uv", + uv_, "pip", "install", f"--python={py_loc!s}", @@ -220,12 +233,14 @@ def test_setup_pip_deps(mock_popen, mock_path, tmpdir, plugin_manager): ), stdout=-1, stderr=-1, + env=None, universal_newlines=True, ), call( - ("uv", "pip", "install", f"--python={py_loc}", "."), + (uv_, "sync", "--inexact", f"--python={py_loc!s}"), stdout=-1, stderr=-1, + env={"UV_PROJECT_ENVIRONMENT": str(env_loc)}, universal_newlines=True, ), ] @@ -257,10 +272,11 @@ def test_setup_pip_deps_error(mock_popen, mock_path, tmpdir, plugin_manager): else: py_loc = Path(env_loc) / "bin" / "python" + uv_ = find_uv_bin() assert mock_popen.call_args_list == [ call( ( - "uv", + uv_, "pip", "install", f"--python={py_loc!s}", @@ -270,6 +286,7 @@ def test_setup_pip_deps_error(mock_popen, mock_path, tmpdir, plugin_manager): ), stdout=-1, stderr=-1, + env=None, universal_newlines=True, ), ] diff --git a/tests/test_integration_cfg.py b/tests/test_integration_cfg.py deleted file mode 100644 index 0020e2c..0000000 --- a/tests/test_integration_cfg.py +++ /dev/null @@ -1,244 +0,0 @@ -"""Create a fake package and test.""" - -import configparser -import sys -from pathlib import Path - -import pytest -from click.testing import CliRunner - -from edgetest.interface import cli - -SETUP_CFG = """ -[metadata] -name = toy_package -version = 0.1.0 -description = Fake description -python_requires = - >=3.10.0 - -[options] -zip_safe = False -include_package_data = True -packages = find: -install_requires = - polars<=2.0.0 - -[options.extras_require] -tests = - pytest - -[edgetest] -extras = - tests -""" - -SETUP_CFG_LOWER = """ -[metadata] -name = toy_package -version = 0.1.0 -description = Fake description -python_requires = - >=3.10.0 - -[options] -zip_safe = False -include_package_data = True -packages = find: -install_requires = - polars>=1.30.0,<=1.35.0 - -[options.extras_require] -tests = - pytest - -[edgetest] -extras = - tests - -[edgetest.envs.lower_env] -lower = - polars -""" - - -SETUP_CFG_EXTRAS = """ -[metadata] -name = toy_package -version = 0.1.0 -description = Fake description -python_requires = - >=3.10.0 - -[options] -zip_safe = False -include_package_data = True -packages = find: -install_requires = - scikit-learn>=1.7.0,<=1.7.2 - polars[pyarrow]>=1.30.0,<=1.35.0 - -[options.extras_require] -tests = - pytest - -[edgetest.envs.core] -extras = - tests -upgrade = - scikit-learn - polars[pyarrow] - -[edgetest.envs.lower_env] -extras = - tests -lower = - scikit-learn - polars[pyarrow] -""" - - -SETUP_PY = """ -from setuptools import setup - -setup() -""" - -MODULE_CODE = """ -def main() -> str: - print("Hello world") -""" - -TEST_CODE = """ -from toy_package import main - -def test_func(): - main() -""" - -PY_VER = f"python{sys.version_info.major}.{sys.version_info.minor}" - - -@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows") -@pytest.mark.integration -def test_toy_package(): - """Test using edgetest with a toy package.""" - runner = CliRunner() - - with runner.isolated_filesystem() as loc: - with open("setup.cfg", "w") as outfile: - outfile.write(SETUP_CFG) - with open("setup.py", "w") as outfile: - outfile.write(SETUP_PY) - # Make a directory for the module - Path(loc, "toy_package").mkdir() - with open(Path(loc, "toy_package", "__init__.py"), "w") as outfile: - outfile.write(MODULE_CODE) - # Make a directory for the tests - Path(loc, "tests").mkdir() - with open(Path(loc, "tests", "test_main.py"), "w") as outfile: - outfile.write(TEST_CODE) - - # Run the CLI - result = runner.invoke(cli, ["--config=setup.cfg"]) - - assert result.exit_code == 0 - assert Path(loc, ".edgetest").is_dir() - assert Path(loc, ".edgetest", "polars").is_dir() - assert "polars" in result.stdout - - if sys.platform != "win32": - assert Path( - loc, ".edgetest", "polars", "lib", PY_VER, "site-packages", "polars" - ).is_dir() - - -@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows") -@pytest.mark.integration -def test_toy_package_lower(): - """Test lower bounds with a toy package.""" - runner = CliRunner() - - with runner.isolated_filesystem() as loc: - with open("setup.cfg", "w") as outfile: - outfile.write(SETUP_CFG_LOWER) - with open("setup.py", "w") as outfile: - outfile.write(SETUP_PY) - # Make a directory for the module - Path(loc, "toy_package").mkdir() - with open(Path(loc, "toy_package", "__init__.py"), "w") as outfile: - outfile.write(MODULE_CODE) - # Make a directory for the tests - Path(loc, "tests").mkdir() - with open(Path(loc, "tests", "test_main.py"), "w") as outfile: - outfile.write(TEST_CODE) - - # Run the CLI - result = runner.invoke(cli, ["--config=setup.cfg"]) - - assert result.exit_code == 0 - assert Path(loc, ".edgetest").is_dir() - assert Path(loc, ".edgetest", "lower_env").is_dir() - assert "polars" in result.stdout - - if sys.platform != "win32": - assert Path( - loc, ".edgetest", "lower_env", "lib", PY_VER, "site-packages", "polars" - ).is_dir() - - -@pytest.mark.integration -def test_toy_package_extras(): - """Test using edgetest with a toy package.""" - runner = CliRunner() - - with runner.isolated_filesystem() as loc: - with open("setup.cfg", "w") as outfile: - outfile.write(SETUP_CFG_EXTRAS) - with open("setup.py", "w") as outfile: - outfile.write(SETUP_PY) - # Make a directory for the module - Path(loc, "toy_package").mkdir() - with open(Path(loc, "toy_package", "__init__.py"), "w") as outfile: - outfile.write(MODULE_CODE) - # Make a directory for the tests - Path(loc, "tests").mkdir() - with open(Path(loc, "tests", "test_main.py"), "w") as outfile: - outfile.write(TEST_CODE) - - # Run the CLI - result = runner.invoke(cli, ["--config=setup.cfg", "--export"]) - - assert result.exit_code == 0 - assert Path(loc, ".edgetest").is_dir() - - for envname in ("core", "lower_env"): - assert Path(loc, ".edgetest", envname).is_dir() - - if sys.platform != "win32": - assert Path( - loc, ".edgetest", envname, "lib", PY_VER, "site-packages", "polars" - ).is_dir() - assert Path( - loc, ".edgetest", envname, "lib", PY_VER, "site-packages", "pyarrow" - ).is_dir() - assert Path( - loc, ".edgetest", envname, "lib", PY_VER, "site-packages", "sklearn" - ).is_dir() - - config = configparser.ConfigParser() - config.read("setup.cfg") - - assert "scikit-learn" in config["options"]["install_requires"] - assert "polars[pyarrow]" in config["options"]["install_requires"] - assert config["edgetest.envs.core"]["extras"] == "\ntests" - assert ( - config["edgetest.envs.core"]["upgrade"] == "\nscikit-learn\npolars[pyarrow]" - ) - assert config["edgetest.envs.lower_env"]["extras"] == "\ntests" - assert ( - config["edgetest.envs.lower_env"]["lower"] - == "\nscikit-learn\npolars[pyarrow]" - ) - assert "polars" in result.stdout - assert "scikit-learn" in result.stdout diff --git a/tests/test_interface_cfg.py b/tests/test_interface_cfg.py deleted file mode 100644 index 757b2a1..0000000 --- a/tests/test_interface_cfg.py +++ /dev/null @@ -1,715 +0,0 @@ -"""Test the CLI.""" - -import platform -from pathlib import Path -from unittest.mock import PropertyMock, call, patch - -import pytest -from click.testing import CliRunner - -from edgetest.interface import cli - -CURR_DIR = Path(__file__).resolve().parent - -REQS = """ -myupgrade -""" - -SETUP_CFG = """ -[edgetest.envs.myenv] -upgrade = - myupgrade -command = - pytest tests -m 'not integration' -""" - - -SETUP_CFG_COOLDOWN = """[edgetest] -exclude_newer = - 3 days - -[edgetest.envs.myenv] -upgrade = - myupgrade -command = - pytest tests -m 'not integration' -""" - -SETUP_CFG_LOWER = """ -[options] -install_requires = - myupgrade - mylower<=0.1.0,>=0.0.1 - -[edgetest.envs.myenv_lower] -lower = - mylower -command = - pytest tests -m 'not integration' -""" - -SETUP_CFG_REQS = """ -[options] -install_requires = - myupgrade<=0.1.5 -""" - -SETUP_CFG_REQS_UPGRADE = "[options]\ninstall_requires = \n\tmyupgrade<=0.2.0\n\n" - -SETUP_CFG_EXTRAS = """ -[options.extras_require] -myextra = - myupgrade<=0.1.5 - -[edgetest.envs.myenv] -upgrade = - myupgrade -extras = - myextra -command = - pytest tests -m 'not integration' -""" - -SETUP_CFG_EXTRAS_UPGRADE = """[options.extras_require] -myextra = \n\tmyupgrade<=0.2.0 - -[edgetest.envs.myenv] -upgrade = \n\tmyupgrade -extras = \n\tmyextra -command = \n\tpytest tests -m 'not integration' - -""" - -PIP_LIST = """ -[{"name": "myupgrade", "version": "0.2.0"}] -""" - -TABLE_OUTPUT = """ - -============= ================== =============== =================== ================== ================= -Environment Setup successful Passing tests Upgraded packages Lowered packages Package version -============= ================== =============== =================== ================== ================= -myenv True True myupgrade 0.2.0 -============= ================== =============== =================== ================== ================= -""" - -TABLE_OUTPUT_LOWER = """ - -============= ================== =============== =================== ================== ================= -Environment Setup successful Passing tests Upgraded packages Lowered packages Package version -============= ================== =============== =================== ================== ================= -myenv_lower True True mylower 0.0.1 -============= ================== =============== =================== ================== ================= -""" - -TABLE_OUTPUT_NOTEST = """ - -============= ================== =============== =================== ================== ================= -Environment Setup successful Passing tests Upgraded packages Lowered packages Package version -============= ================== =============== =================== ================== ================= -myenv True False myupgrade 0.2.0 -============= ================== =============== =================== ================== ================= -""" - -TABLE_OUTPUT_NOTEST_LOWER = """ - -============= ================== =============== =================== ================== ================= -Environment Setup successful Passing tests Upgraded packages Lowered packages Package version -============= ================== =============== =================== ================== ================= -myenv_lower True False mylower 0.0.1 -============= ================== =============== =================== ================== ================= -""" - - -TABLE_OUTPUT_REQS = """ - -================ ================== =============== =================== ================== ================= -Environment Setup successful Passing tests Upgraded packages Lowered packages Package version -================ ================== =============== =================== ================== ================= -myupgrade True True myupgrade 0.2.0 -all-requirements True True myupgrade 0.2.0 -================ ================== =============== =================== ================== ================= -""" - - -@patch("edgetest.core.Popen", autospec=True) -@patch("edgetest.utils.Popen", autospec=True) -def test_cli_basic(mock_popen, mock_cpopen): - """Test creating a basic environment.""" - mock_popen.return_value.communicate.return_value = (PIP_LIST, "error") - type(mock_popen.return_value).returncode = PropertyMock(return_value=0) - mock_cpopen.return_value.communicate.return_value = ("output", "error") - type(mock_cpopen.return_value).returncode = PropertyMock(return_value=0) - - runner = CliRunner() - - with runner.isolated_filesystem() as loc: - with open("setup.cfg", "w") as outfile: - outfile.write(SETUP_CFG) - - result = runner.invoke(cli, ["--config=setup.cfg"]) - - assert result.exit_code == 0 - - env_loc = Path(loc) / ".edgetest" / "myenv" - if platform.system() == "Windows": - py_loc = env_loc / "Scripts" / "python.exe" - else: - py_loc = env_loc / "bin" / "python" - - assert mock_popen.call_args_list == [ - call( - ("uv", "venv", str(env_loc)), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - call( - ("uv", "pip", "install", f"--python={py_loc!s}", "."), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - call( - ("uv", "pip", "install", f"--python={py_loc!s}", "myupgrade", "--upgrade"), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - call( - ("uv", "pip", "list", f"--python={py_loc!s}", "--format", "json"), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - ] - assert mock_cpopen.call_args_list == [ - call( - ( - f"{py_loc!s}", - "-m", - "pytest", - "tests", - "-m", - "not integration", - ), - universal_newlines=True, - ) - ] - - assert result.output == TABLE_OUTPUT - - -@pytest.mark.parametrize("ignore_cooldown", [True, False]) -@patch("edgetest.core.Popen", autospec=True) -@patch("edgetest.utils.Popen", autospec=True) -def test_cli_basic_cooldown(mock_popen, mock_cpopen, ignore_cooldown): - """Test creating a basic environment with dependency cooldowns.""" - mock_popen.return_value.communicate.return_value = (PIP_LIST, "error") - type(mock_popen.return_value).returncode = PropertyMock(return_value=0) - mock_cpopen.return_value.communicate.return_value = ("output", "error") - type(mock_cpopen.return_value).returncode = PropertyMock(return_value=0) - - runner = CliRunner() - - with runner.isolated_filesystem() as loc: - with open("setup.cfg", "w") as outfile: - outfile.write(SETUP_CFG_COOLDOWN) - - if ignore_cooldown: - result = runner.invoke(cli, ["--config=setup.cfg", "--ignore-cooldown"]) - else: - result = runner.invoke(cli, ["--config=setup.cfg"]) - - assert result.exit_code == 0 - - env_loc = Path(loc) / ".edgetest" / "myenv" - if platform.system() == "Windows": - py_loc = env_loc / "Scripts" / "python.exe" - else: - py_loc = env_loc / "bin" / "python" - - # Upgrade installation argument depends on the parameterization - upgrade_callargs_ = [ - "uv", - "pip", - "install", - f"--python={py_loc!s}", - "myupgrade", - "--upgrade", - ] - if not ignore_cooldown: - upgrade_callargs_.append("--exclude-newer=3 days") - - assert mock_popen.call_args_list == [ - call( - ("uv", "venv", str(env_loc)), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - call( - ("uv", "pip", "install", f"--python={py_loc!s}", "."), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - call( - tuple(upgrade_callargs_), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - call( - ("uv", "pip", "list", f"--python={py_loc!s}", "--format", "json"), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - ] - assert mock_cpopen.call_args_list == [ - call( - ( - f"{py_loc!s}", - "-m", - "pytest", - "tests", - "-m", - "not integration", - ), - universal_newlines=True, - ) - ] - - assert result.output == TABLE_OUTPUT - - -@patch("edgetest.core.Popen", autospec=True) -@patch("edgetest.utils.Popen", autospec=True) -def test_cli_basic_lower(mock_popen, mock_cpopen): - """Test creating a basic environment.""" - mock_popen.return_value.communicate.return_value = (PIP_LIST, "error") - type(mock_popen.return_value).returncode = PropertyMock(return_value=0) - mock_cpopen.return_value.communicate.return_value = ("output", "error") - type(mock_cpopen.return_value).returncode = PropertyMock(return_value=0) - - runner = CliRunner() - - with runner.isolated_filesystem() as loc: - with open("setup.cfg", "w") as outfile: - outfile.write(SETUP_CFG_LOWER) - - result = runner.invoke(cli, ["--config=setup.cfg"]) - - assert result.exit_code == 0 - - env_loc = Path(loc) / ".edgetest" / "myenv_lower" - if platform.system() == "Windows": - py_loc = env_loc / "Scripts" / "python.exe" - else: - py_loc = env_loc / "bin" / "python" - - assert mock_popen.call_args_list == [ - call( - ("uv", "venv", str(env_loc)), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - call( - ("uv", "pip", "install", f"--python={py_loc!s}", "."), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - call( - ("uv", "pip", "install", f"--python={py_loc!s}", "mylower==0.0.1"), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - ] - assert mock_cpopen.call_args_list == [ - call( - ( - f"{py_loc!s}", - "-m", - "pytest", - "tests", - "-m", - "not integration", - ), - universal_newlines=True, - ) - ] - - assert result.output == TABLE_OUTPUT_LOWER - - -@patch("edgetest.core.Popen", autospec=True) -@patch("edgetest.utils.Popen", autospec=True) -def test_cli_reqs(mock_popen, mock_cpopen): - """Test running tests based on the requirements file.""" - mock_popen.return_value.communicate.return_value = (PIP_LIST, "error") - type(mock_popen.return_value).returncode = PropertyMock(return_value=0) - mock_cpopen.return_value.communicate.return_value = ("output", "error") - type(mock_cpopen.return_value).returncode = PropertyMock(return_value=0) - - runner = CliRunner() - - with runner.isolated_filesystem() as loc: - with open("requirements.txt", "w") as outfile: - outfile.write(REQS) - - result = runner.invoke(cli) - - if platform.system() == "Windows": - py_myupgrade_loc = ( - Path(loc) / ".edgetest" / "myupgrade" / "Scripts" / "python.exe" - ) - py_allreq_loc = ( - Path(loc) / ".edgetest" / "all-requirements" / "Scripts" / "python.exe" - ) - else: - py_myupgrade_loc = Path(loc) / ".edgetest" / "myupgrade" / "bin" / "python" - py_allreq_loc = Path(loc) / ".edgetest" / "all-requirements" / "bin" / "python" - - assert result.exit_code == 0 - - assert mock_popen.call_args_list == [ - call( - ("uv", "venv", str(Path(loc) / ".edgetest" / "myupgrade")), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - call( - ("uv", "pip", "install", f"--python={py_myupgrade_loc!s}", "."), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - call( - ( - "uv", - "pip", - "install", - f"--python={py_myupgrade_loc!s}", - "myupgrade", - "--upgrade", - ), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - call( - ("uv", "venv", str(Path(loc) / ".edgetest" / "all-requirements")), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - call( - ("uv", "pip", "install", f"--python={py_allreq_loc!s}", "."), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - call( - ( - "uv", - "pip", - "install", - f"--python={py_allreq_loc!s}", - "myupgrade", - "--upgrade", - ), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - call( - ( - "uv", - "pip", - "list", - f"--python={py_myupgrade_loc!s}", - "--format", - "json", - ), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - call( - ( - "uv", - "pip", - "list", - f"--python={py_allreq_loc!s}", - "--format", - "json", - ), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - ] - assert mock_cpopen.call_args_list == [ - call( - (f"{py_myupgrade_loc!s}", "-m", "pytest"), - universal_newlines=True, - ), - call( - (f"{py_allreq_loc!s}", "-m", "pytest"), - universal_newlines=True, - ), - ] - - assert result.output == TABLE_OUTPUT_REQS - - -@patch("edgetest.core.Popen", autospec=True) -@patch("edgetest.utils.Popen", autospec=True) -def test_cli_setup_reqs_update(mock_popen, mock_cpopen): - """Test running tests and updating requirements in a ``setup.cfg`` file.""" - mock_popen.return_value.communicate.return_value = (PIP_LIST, "error") - type(mock_popen.return_value).returncode = PropertyMock(return_value=0) - mock_cpopen.return_value.communicate.return_value = ("output", "error") - type(mock_cpopen.return_value).returncode = PropertyMock(return_value=0) - - runner = CliRunner() - - with runner.isolated_filesystem() as loc: - with open("setup.cfg", "w") as outfile: - outfile.write(SETUP_CFG_REQS) - - result = runner.invoke(cli, ["--config=setup.cfg", "--export"]) - - with open("setup.cfg") as infile: - out = infile.read() - - assert result.exit_code == 0 - - assert out == SETUP_CFG_REQS_UPGRADE - - -@patch("edgetest.core.Popen", autospec=True) -@patch("edgetest.utils.Popen", autospec=True) -def test_cli_setup_extras_update(mock_popen, mock_cpopen): - """Test running tests and updating extra installation requirements in a ``setup.cfg`` file.""" - mock_popen.return_value.communicate.return_value = (PIP_LIST, "error") - type(mock_popen.return_value).returncode = PropertyMock(return_value=0) - mock_cpopen.return_value.communicate.return_value = ("output", "error") - type(mock_cpopen.return_value).returncode = PropertyMock(return_value=0) - - runner = CliRunner() - - with runner.isolated_filesystem() as loc: - with open("setup.cfg", "w") as outfile: - outfile.write(SETUP_CFG_EXTRAS) - - result = runner.invoke(cli, ["--config=setup.cfg", "--export"]) - - with open("setup.cfg") as infile: - out = infile.read() - - assert result.exit_code == 0 - - assert out == SETUP_CFG_EXTRAS_UPGRADE - - -@patch("edgetest.core.Popen", autospec=True) -@patch("edgetest.utils.Popen", autospec=True) -def test_cli_nosetup(mock_popen, mock_cpopen): - """Test creating a basic environment.""" - mock_popen.return_value.communicate.return_value = (PIP_LIST, "error") - type(mock_popen.return_value).returncode = PropertyMock(return_value=0) - mock_cpopen.return_value.communicate.return_value = ("output", "error") - type(mock_cpopen.return_value).returncode = PropertyMock(return_value=0) - - runner = CliRunner() - - with runner.isolated_filesystem() as loc: - with open("setup.cfg", "w") as outfile: - outfile.write(SETUP_CFG) - - result = runner.invoke(cli, ["--config=setup.cfg", "--nosetup"]) - - assert result.exit_code == 0 - - env_loc = str(Path(loc) / ".edgetest" / "myenv") - if platform.system() == "Windows": - py_loc = Path(env_loc) / "Scripts" / "python.exe" - else: - py_loc = Path(env_loc) / "bin" / "python" - - assert mock_popen.call_args_list == [ - call( - ("uv", "pip", "list", f"--python={py_loc}", "--format", "json"), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - ] - assert mock_cpopen.call_args_list == [ - call( - (f"{py_loc}", "-m", "pytest", "tests", "-m", "not integration"), - universal_newlines=True, - ) - ] - - assert ( - result.output == f"""Using existing environment for myenv...\n{TABLE_OUTPUT}""" - ) - - -@patch("edgetest.core.Popen", autospec=True) -@patch("edgetest.utils.Popen", autospec=True) -def test_cli_nosetup_lower(mock_popen, mock_cpopen): - """Test creating a basic environment.""" - mock_popen.return_value.communicate.return_value = (PIP_LIST, "error") - type(mock_popen.return_value).returncode = PropertyMock(return_value=0) - mock_cpopen.return_value.communicate.return_value = ("output", "error") - type(mock_cpopen.return_value).returncode = PropertyMock(return_value=0) - - runner = CliRunner() - - with runner.isolated_filesystem() as loc: - with open("setup.cfg", "w") as outfile: - outfile.write(SETUP_CFG_LOWER) - - result = runner.invoke(cli, ["--config=setup.cfg", "--nosetup"]) - - assert result.exit_code == 0 - - env_loc = str(Path(loc) / ".edgetest" / "myenv_lower") - if platform.system() == "Windows": - py_loc = Path(env_loc) / "Scripts" / "python.exe" - else: - py_loc = Path(env_loc) / "bin" / "python" - - assert mock_cpopen.call_args_list == [ - call( - (f"{py_loc}", "-m", "pytest", "tests", "-m", "not integration"), - universal_newlines=True, - ) - ] - - assert ( - result.output - == f"""Using existing environment for myenv_lower...\n{TABLE_OUTPUT_LOWER}""" - ) - - -@patch("edgetest.utils.Popen", autospec=True) -def test_cli_notest(mock_popen): - """Test creating a basic environment.""" - mock_popen.return_value.communicate.return_value = (PIP_LIST, "error") - type(mock_popen.return_value).returncode = PropertyMock(return_value=0) - - runner = CliRunner() - - with runner.isolated_filesystem() as loc: - with open("setup.cfg", "w") as outfile: - outfile.write(SETUP_CFG) - - result = runner.invoke(cli, ["--config=setup.cfg", "--notest"]) - - assert result.exit_code == 0 - - env_loc = Path(loc) / ".edgetest" / "myenv" - if platform.system() == "Windows": - py_loc = env_loc / "Scripts" / "python.exe" - else: - py_loc = env_loc / "bin" / "python" - - assert mock_popen.call_args_list == [ - call( - ("uv", "venv", str(env_loc)), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - call( - ("uv", "pip", "install", f"--python={py_loc!s}", "."), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - call( - ( - "uv", - "pip", - "install", - f"--python={py_loc!s}", - "myupgrade", - "--upgrade", - ), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - call( - ("uv", "pip", "list", f"--python={py_loc!s}", "--format", "json"), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - ] - - assert result.output == f"""Skipping tests for myenv\n{TABLE_OUTPUT_NOTEST}""" - - -@patch("edgetest.utils.Popen", autospec=True) -def test_cli_notest_lower(mock_popen): - """Test creating a basic environment.""" - mock_popen.return_value.communicate.return_value = (PIP_LIST, "error") - type(mock_popen.return_value).returncode = PropertyMock(return_value=0) - - runner = CliRunner() - - with runner.isolated_filesystem() as loc: - with open("setup.cfg", "w") as outfile: - outfile.write(SETUP_CFG_LOWER) - - result = runner.invoke(cli, ["--config=setup.cfg", "--notest"]) - - assert result.exit_code == 0 - - env_loc = Path(loc) / ".edgetest" / "myenv_lower" - if platform.system() == "Windows": - py_loc = env_loc / "Scripts" / "python.exe" - else: - py_loc = env_loc / "bin" / "python" - - assert mock_popen.call_args_list == [ - call( - ("uv", "venv", str(env_loc)), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - call( - ("uv", "pip", "install", f"--python={py_loc!s}", "."), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - call( - ( - "uv", - "pip", - "install", - f"--python={py_loc!s}", - "mylower==0.0.1", - ), - stdout=-1, - stderr=-1, - universal_newlines=True, - ), - ] - - assert ( - result.output - == f"""Skipping tests for myenv_lower\n{TABLE_OUTPUT_NOTEST_LOWER}""" - ) diff --git a/tests/test_interface_toml.py b/tests/test_interface_toml.py index f2707a4..8c2defb 100644 --- a/tests/test_interface_toml.py +++ b/tests/test_interface_toml.py @@ -6,6 +6,7 @@ import pytest from click.testing import CliRunner +from uv import find_uv_bin from edgetest.interface import cli @@ -184,22 +185,25 @@ def test_cli_basic(mock_popen, mock_cpopen, toml_source): else: py_loc = env_loc / "bin" / "python" + uv_ = find_uv_bin() assert mock_popen.call_args_list == [ call( - ("uv", "venv", str(env_loc)), + (uv_, "venv", str(env_loc)), stdout=-1, stderr=-1, + env=None, universal_newlines=True, ), call( - ("uv", "pip", "install", f"--python={py_loc!s}", "."), + (uv_, "sync", "--inexact", f"--python={py_loc!s}"), stdout=-1, stderr=-1, + env={"UV_PROJECT_ENVIRONMENT": str(env_loc)}, universal_newlines=True, ), call( ( - "uv", + uv_, "pip", "install", f"--python={py_loc!s}", @@ -208,12 +212,14 @@ def test_cli_basic(mock_popen, mock_cpopen, toml_source): ), stdout=-1, stderr=-1, + env=None, universal_newlines=True, ), call( - ("uv", "pip", "list", f"--python={py_loc!s}", "--format", "json"), + (uv_, "pip", "list", f"--python={py_loc!s}", "--format", "json"), stdout=-1, stderr=-1, + env=None, universal_newlines=True, ), ] @@ -266,8 +272,9 @@ def test_cli_basic_cooldown(mock_popen, mock_cpopen, ignore_cooldown): py_loc = env_loc / "bin" / "python" # Upgrade installation argument depends on the parameterization + uv_ = find_uv_bin() upgrade_callargs_ = [ - "uv", + uv_, "pip", "install", f"--python={py_loc!s}", @@ -279,27 +286,31 @@ def test_cli_basic_cooldown(mock_popen, mock_cpopen, ignore_cooldown): assert mock_popen.call_args_list == [ call( - ("uv", "venv", str(env_loc)), + (uv_, "venv", str(env_loc)), stdout=-1, stderr=-1, + env=None, universal_newlines=True, ), call( - ("uv", "pip", "install", f"--python={py_loc!s}", "."), + (uv_, "sync", "--inexact", f"--python={py_loc!s}"), stdout=-1, stderr=-1, + env={"UV_PROJECT_ENVIRONMENT": str(env_loc)}, universal_newlines=True, ), call( tuple(upgrade_callargs_), stdout=-1, stderr=-1, + env=None, universal_newlines=True, ), call( - ("uv", "pip", "list", f"--python={py_loc!s}", "--format", "json"), + (uv_, "pip", "list", f"--python={py_loc!s}", "--format", "json"), stdout=-1, stderr=-1, + env=None, universal_newlines=True, ), ] @@ -346,22 +357,25 @@ def test_cli_basic_lower(mock_popen, mock_cpopen, toml_source): else: py_loc = env_loc / "bin" / "python" + uv_ = find_uv_bin() assert mock_popen.call_args_list == [ call( - ("uv", "venv", str(env_loc)), + (uv_, "venv", str(env_loc)), stdout=-1, stderr=-1, + env=None, universal_newlines=True, ), call( - ("uv", "pip", "install", f"--python={py_loc!s}", "."), + (uv_, "sync", "--inexact", f"--python={py_loc!s}"), stdout=-1, stderr=-1, + env={"UV_PROJECT_ENVIRONMENT": str(env_loc)}, universal_newlines=True, ), call( ( - "uv", + uv_, "pip", "install", f"--python={py_loc!s}", @@ -369,6 +383,7 @@ def test_cli_basic_lower(mock_popen, mock_cpopen, toml_source): ), stdout=-1, stderr=-1, + env=None, universal_newlines=True, ), ] @@ -419,22 +434,25 @@ def test_cli_reqs(mock_popen, mock_cpopen): assert result.exit_code == 0 + uv_ = find_uv_bin() assert mock_popen.call_args_list == [ call( - ("uv", "venv", str(Path(loc) / ".edgetest" / "myupgrade")), + (uv_, "venv", str(Path(loc) / ".edgetest" / "myupgrade")), stdout=-1, stderr=-1, + env=None, universal_newlines=True, ), call( - ("uv", "pip", "install", f"--python={py_myupgrade_loc!s}", "."), + (uv_, "sync", "--inexact", f"--python={py_myupgrade_loc!s}"), stdout=-1, stderr=-1, + env={"UV_PROJECT_ENVIRONMENT": str(Path(loc) / ".edgetest" / "myupgrade")}, universal_newlines=True, ), call( ( - "uv", + uv_, "pip", "install", f"--python={py_myupgrade_loc!s}", @@ -443,29 +461,35 @@ def test_cli_reqs(mock_popen, mock_cpopen): ), stdout=-1, stderr=-1, + env=None, universal_newlines=True, ), call( - ("uv", "venv", str(Path(loc) / ".edgetest" / "all-requirements")), + (uv_, "venv", str(Path(loc) / ".edgetest" / "all-requirements")), stdout=-1, stderr=-1, + env=None, universal_newlines=True, ), call( ( - "uv", - "pip", - "install", + uv_, + "sync", + "--inexact", f"--python={py_allreq_loc!s}", - ".", ), stdout=-1, stderr=-1, + env={ + "UV_PROJECT_ENVIRONMENT": str( + Path(loc) / ".edgetest" / "all-requirements" + ) + }, universal_newlines=True, ), call( ( - "uv", + uv_, "pip", "install", f"--python={py_allreq_loc!s}", @@ -474,11 +498,12 @@ def test_cli_reqs(mock_popen, mock_cpopen): ), stdout=-1, stderr=-1, + env=None, universal_newlines=True, ), call( ( - "uv", + uv_, "pip", "list", f"--python={py_myupgrade_loc!s}", @@ -487,11 +512,12 @@ def test_cli_reqs(mock_popen, mock_cpopen): ), stdout=-1, stderr=-1, + env=None, universal_newlines=True, ), call( ( - "uv", + uv_, "pip", "list", f"--python={py_allreq_loc!s}", @@ -500,6 +526,7 @@ def test_cli_reqs(mock_popen, mock_cpopen): ), stdout=-1, stderr=-1, + env=None, universal_newlines=True, ), ] @@ -599,11 +626,13 @@ def test_cli_nosetup(mock_popen, mock_cpopen): else: py_loc = Path(env_loc) / "bin" / "python" + uv_ = find_uv_bin() assert mock_popen.call_args_list == [ call( - ("uv", "pip", "list", f"--python={py_loc}", "--format", "json"), + (uv_, "pip", "list", f"--python={py_loc}", "--format", "json"), stdout=-1, stderr=-1, + env=None, universal_newlines=True, ), ] @@ -679,22 +708,25 @@ def test_cli_notest(mock_popen): else: py_loc = env_loc / "bin" / "python" + uv_ = find_uv_bin() assert mock_popen.call_args_list == [ call( - ("uv", "venv", str(env_loc)), + (uv_, "venv", str(env_loc)), stdout=-1, stderr=-1, + env=None, universal_newlines=True, ), call( - ("uv", "pip", "install", f"--python={py_loc!s}", "."), + (uv_, "sync", "--inexact", f"--python={py_loc!s}"), stdout=-1, stderr=-1, + env={"UV_PROJECT_ENVIRONMENT": str(env_loc)}, universal_newlines=True, ), call( ( - "uv", + uv_, "pip", "install", f"--python={py_loc!s}", @@ -703,12 +735,14 @@ def test_cli_notest(mock_popen): ), stdout=-1, stderr=-1, + env=None, universal_newlines=True, ), call( - ("uv", "pip", "list", f"--python={py_loc!s}", "--format", "json"), + (uv_, "pip", "list", f"--python={py_loc!s}", "--format", "json"), stdout=-1, stderr=-1, + env=None, universal_newlines=True, ), ] @@ -738,22 +772,25 @@ def test_cli_notest_lower(mock_popen): else: py_loc = env_loc / "bin" / "python" + uv_ = find_uv_bin() assert mock_popen.call_args_list == [ call( - ("uv", "venv", str(env_loc)), + (uv_, "venv", str(env_loc)), stdout=-1, stderr=-1, + env=None, universal_newlines=True, ), call( - ("uv", "pip", "install", f"--python={py_loc!s}", "."), + (uv_, "sync", "--inexact", f"--python={py_loc!s}"), stdout=-1, stderr=-1, + env={"UV_PROJECT_ENVIRONMENT": str(env_loc)}, universal_newlines=True, ), call( ( - "uv", + uv_, "pip", "install", f"--python={py_loc!s}", @@ -761,6 +798,7 @@ def test_cli_notest_lower(mock_popen): ), stdout=-1, stderr=-1, + env=None, universal_newlines=True, ), ] diff --git a/tests/test_lib.py b/tests/test_lib.py index fab442b..d52a6e6 100644 --- a/tests/test_lib.py +++ b/tests/test_lib.py @@ -3,6 +3,7 @@ from unittest.mock import patch import pytest +from uv import find_uv_bin from edgetest.lib import ( create_environment, @@ -32,12 +33,12 @@ def test_path_to_python(mock_platform): @patch("edgetest.lib._run_command", autospec=True) def test_create_environment(mock_run): create_environment("test", "test", {}) - mock_run.assert_called_with("uv", "venv", str(Path("test", "test"))) + uv_ = find_uv_bin() + mock_run.assert_called_with(uv_, "venv", str(Path("test", "test"))) create_environment("test", "test", {"python_version": "3.10"}) - mock_run.assert_called_with( - "uv", "venv", str(Path("test", "test")), "--python=3.10" - ) + + mock_run.assert_called_with(uv_, "venv", str(Path("test", "test")), "--python=3.10") mock_run.side_effect = RuntimeError() with pytest.raises(RuntimeError): @@ -52,13 +53,14 @@ def test_run_update(mock_run, mock_ctx, ignore_cooldown): python_path = path_to_python("test", "test") run_update("test", "test", ["1", "2"], {"exclude_newer": "3 days"}) + uv_ = find_uv_bin() if ignore_cooldown: mock_run.assert_called_with( - "uv", "pip", "install", f"--python={python_path}", "1", "2", "--upgrade" + uv_, "pip", "install", f"--python={python_path}", "1", "2", "--upgrade" ) else: mock_run.assert_called_with( - "uv", + uv_, "pip", "install", f"--python={python_path}", @@ -77,8 +79,10 @@ def test_run_update(mock_run, mock_ctx, ignore_cooldown): def test_run_install_lower(mock_run): python_path = path_to_python("test", "test") run_install_lower("test", "test", ["package1==1", "package2==2"], {"test": "test"}) + + uv_ = find_uv_bin() mock_run.assert_called_with( - "uv", + uv_, "pip", "install", f"--python={python_path}", diff --git a/tests/test_utils.py b/tests/test_utils.py index 03b2b57..8993415 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -10,10 +10,8 @@ _isin_case_dashhyphen_ins, gen_requirements_config, get_lower_bounds, - parse_cfg, parse_toml, upgrade_pyproject_toml, - upgrade_setup_cfg, ) REQS = """ @@ -21,77 +19,6 @@ mydep2<=0.10.0 """ -CFG_NOREQS = """ -[edgetest.envs.myenv] -upgrade = - myupgrade -command = - pytest tests -m 'not integration' - -[edgetest.envs.myenv_lower] -lower = - mylower -command = - pytest tests -m 'not integration' -""" - -CFG_REQS = """ -[options] -install_requires = - myupgrade -""" - -CFG_REQS_DEFAULTS = """ -[options] -install_requires = - myupgrade - -[edgetest] -extras = - tests -command = - pytest tests -m 'not integration' -""" - -CFG_NOREQS_DEFAULTS = """ -[edgetest] -extras = - tests -command = - pytest tests -m 'not integration' - -[edgetest.envs.myenv] -upgrade = - myupgrade -command = - pytest tests - -[edgetest.envs.myenv_lower] -lower = - mylower -command = - pytest tests -""" - -CFG_CUSTOM = """ -[edgetest] -extras = - tests -command = - pytest tests -m 'not integration' - -[edgetest.custom] -mycustom = mykey - -[edgetest.envs.myenv] -upgrade = - myupgrade - -[edgetest.envs.myenv_lower] -lower = - mylower -""" - TOML_NOREQS = """ [edgetest.envs.myenv] @@ -236,17 +163,6 @@ """ -CFG_REQS_UPGRADE = """ -[options] -install_requires = - pandas<=1.0.0,>=1.0.0 - numpy<=1.0.0,>=1.0.0 - -[options.extras_require] -tests = - pytest<=1.0.0,>=1.0.0 -""" - TOML_REQS_UPGRADE = """ [project] dependencies = [ @@ -295,155 +211,6 @@ def test_parse_reqs(mock_pathlib): assert validator.validate(cfg) -def test_parse_cfg(tmpdir): - """Test parsing a config with no install requirements.""" - location = tmpdir.mkdir("mylocation") - conf_loc = Path(str(location), "myconfig.ini") - with open(conf_loc, "w") as outfile: - outfile.write(CFG_NOREQS) - - cfg = parse_cfg(filename=conf_loc) - - assert cfg == { - "envs": [ - { - "name": "myenv", - "upgrade": "\nmyupgrade", - "command": "\npytest tests -m 'not integration'", - }, - { - "name": "myenv_lower", - "lower": "\nmylower", - "command": "\npytest tests -m 'not integration'", - }, - ] - } - - validator = EdgetestValidator(schema=BASE_SCHEMA) - - assert validator.validate(cfg) - - -def test_parse_cfg_default(tmpdir): - """Test parsing a config with no install requirements and defaults.""" - location = tmpdir.mkdir("mylocation") - conf_loc = Path(str(location), "myconfig.ini") - with open(conf_loc, "w") as outfile: - outfile.write(CFG_NOREQS_DEFAULTS) - - cfg = parse_cfg(filename=conf_loc) - - assert cfg == { - "envs": [ - { - "name": "myenv", - "upgrade": "\nmyupgrade", - "extras": "\ntests", - "command": "\npytest tests", - }, - { - "name": "myenv_lower", - "lower": "\nmylower", - "extras": "\ntests", - "command": "\npytest tests", - }, - ] - } - - validator = EdgetestValidator(schema=BASE_SCHEMA) - - assert validator.validate(cfg) - - -def test_parse_cfg_reqs(tmpdir): - """Test parsing a PEP-517 style config.""" - location = tmpdir.mkdir("mylocation") - conf_loc = Path(str(location), "setup.cfg") - with open(conf_loc, "w") as outfile: - outfile.write(CFG_REQS) - - cfg = parse_cfg(filename=conf_loc) - - assert cfg == { - "envs": [ - {"name": "myupgrade", "upgrade": "myupgrade"}, - {"name": "all-requirements", "upgrade": "myupgrade"}, - ] - } - - validator = EdgetestValidator(schema=BASE_SCHEMA) - - assert validator.validate(cfg) - - -def test_parse_cfg_reqs_default(tmpdir): - """Test parsing a PEP-517 style config with default arguments.""" - location = tmpdir.mkdir("mylocation") - conf_loc = Path(str(location), "setup.cfg") - with open(conf_loc, "w") as outfile: - outfile.write(CFG_REQS_DEFAULTS) - - cfg = parse_cfg(filename=conf_loc) - - assert cfg == { - "envs": [ - { - "name": "myupgrade", - "upgrade": "myupgrade", - "extras": "\ntests", - "command": "\npytest tests -m 'not integration'", - }, - { - "name": "all-requirements", - "upgrade": "myupgrade", - "extras": "\ntests", - "command": "\npytest tests -m 'not integration'", - }, - ] - } - - validator = EdgetestValidator(schema=BASE_SCHEMA) - - assert validator.validate(cfg) - - -def test_parse_custom_cfg(tmpdir): - """Test parsing a custom configuration.""" - location = tmpdir.mkdir("mylocation") - conf_loc = Path(str(location), "setup.cfg") - with open(conf_loc, "w") as outfile: - outfile.write(CFG_CUSTOM) - - cfg = parse_cfg(filename=conf_loc) - - assert cfg == { - "custom": {"mycustom": "mykey"}, - "envs": [ - { - "name": "myenv", - "upgrade": "\nmyupgrade", - "extras": "\ntests", - "command": "\npytest tests -m 'not integration'", - }, - { - "name": "myenv_lower", - "lower": "\nmylower", - "extras": "\ntests", - "command": "\npytest tests -m 'not integration'", - }, - ], - } - - schema = Schema() - schema.add_globaloption( - "custom", {"type": "dict", "schema": {"mycustom": {"type": "string"}}} - ) - - validator = EdgetestValidator(schema=schema.schema) - - assert validator.validate(cfg) - - @pytest.mark.parametrize("toml_source", [TOML_NOREQS, TOML_NOREQS_TOOL]) def test_parse_toml(tmpdir, toml_source): """Test parsing a config with no install requirements.""" @@ -629,28 +396,6 @@ def test_parse_custom_toml(tmpdir, toml_source): assert validator.validate(toml) -def test_upgrade_setup_cfg(tmpdir): - location = tmpdir.mkdir("mylocation") - conf_loc = Path(str(location), "setup.cfg") - with open(conf_loc, "w") as outfile: - outfile.write(CFG_REQS_UPGRADE) - - upgrade_cfg = upgrade_setup_cfg( - upgraded_packages=[ - {"name": "pandas", "version": "2.0.0"}, - {"name": "numpy", "version": "3.0.0"}, - {"name": "pytest", "version": "4.0.0"}, - ], - filename=conf_loc, - ) - - assert ( - upgrade_cfg["options"]["install_requires"] - == "\npandas<=2.0.0,>=1.0.0\nnumpy<=3.0.0,>=1.0.0" - ) - assert upgrade_cfg["options.extras_require"]["tests"] == "\npytest<=4.0.0,>=1.0.0" - - def test_upgrade_pyproject_toml(tmpdir): location = tmpdir.mkdir("mylocation") conf_loc = Path(str(location), "pyproject.toml") diff --git a/uv.lock b/uv.lock index 682f9f0..e0bb915 100644 --- a/uv.lock +++ b/uv.lock @@ -622,7 +622,7 @@ requires-dist = [ { name = "pytest", marker = "extra == 'tests'" }, { name = "pytest-cov", marker = "extra == 'tests'" }, { name = "recommonmark", marker = "extra == 'docs'" }, - { name = "ruff", marker = "extra == 'qa'", specifier = "~=0.5" }, + { name = "ruff", marker = "extra == 'qa'" }, { name = "sphinx", marker = "extra == 'docs'" }, { name = "sphinx-copybutton", marker = "extra == 'docs'" }, { name = "sphinx-tabs", marker = "extra == 'docs'" },