diff --git a/python-nox.changes b/python-nox.changes index 97aa566..d82f577 100644 --- a/python-nox.changes +++ b/python-nox.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Fri Jun 16 03:03:49 UTC 2023 - Steve Kowalik + +- Add patch support-tox-4.patch: + * Support tox 4 + ------------------------------------------------------------------- Tue Apr 25 02:05:35 UTC 2023 - John Vandenberg diff --git a/python-nox.spec b/python-nox.spec index bda2318..530ea24 100644 --- a/python-nox.spec +++ b/python-nox.spec @@ -22,9 +22,10 @@ Version: 2023.4.22 Release: 0 Summary: Flexible test automation License: Apache-2.0 -Group: Development/Languages/Python URL: https://nox.thea.codes -Source: https://github.com/theacodes/nox/archive/%{version}.tar.gz#/nox-%{version}.tar.gz +Source: https://github.com/wntrblm/nox/archive/%{version}.tar.gz#/nox-%{version}.tar.gz +# PATCH-FIX-UPSTREAM gh#wntrblm/nox#687 +Patch0: support-tox-4.patch BuildRequires: %{python_module base >= 3.5} BuildRequires: %{python_module hatchling} BuildRequires: %{python_module pip} @@ -56,7 +57,7 @@ BuildRequires: %{python_module virtualenv >= 14.0.0} Flexible test automation. %prep -%setup -q -n nox-%{version} +%autosetup -p1 -n nox-%{version} %build %pyproject_wheel diff --git a/support-tox-4.patch b/support-tox-4.patch new file mode 100644 index 0000000..7c3eec9 --- /dev/null +++ b/support-tox-4.patch @@ -0,0 +1,608 @@ +From 41387eda390183ed390f5a9e612cb0d47f68628e Mon Sep 17 00:00:00 2001 +From: Lumir Balhar +Date: Mon, 20 Feb 2023 15:26:06 +0100 +Subject: [PATCH 1/2] to_to_nox implementation for tox 4 + +--- + nox/tox4_to_nox.jinja2 | 33 +++++++++++++++++ + nox/tox_to_nox.py | 84 ++++++++++++++++++++++++++++++++++++++---- + pyproject.toml | 2 +- + 3 files changed, 110 insertions(+), 9 deletions(-) + create mode 100644 nox/tox4_to_nox.jinja2 + +diff --git a/nox/tox4_to_nox.jinja2 b/nox/tox4_to_nox.jinja2 +new file mode 100644 +index 00000000..e5a67d9b +--- /dev/null ++++ b/nox/tox4_to_nox.jinja2 +@@ -0,0 +1,33 @@ ++import nox ++ ++{% for envname, envconfig in config.items()|sort: %} ++@nox.session({%- if envconfig.base_python %}python='{{envconfig.base_python}}'{%- endif %}) ++def {{fixname(envname)}}(session): ++ {%- if envconfig.description != '' %} ++ """{{envconfig.description}}""" ++ {%- endif %} ++ {%- set envs = envconfig.get('set_env', {}) -%} ++ {%- for key, value in envs.items()|sort: %} ++ session.env['{{key}}'] = '{{value}}' ++ {%- endfor %} ++ ++ {%- if envconfig.deps %} ++ session.install({{envconfig.deps}}) ++ {%- endif %} ++ ++ {%- if not envconfig.skip_install %} ++ {%- if envconfig.use_develop %} ++ session.install('-e', '.') ++ {%- else %} ++ session.install('.') ++ {%- endif -%} ++ {%- endif %} ++ ++ {%- if envconfig.change_dir %} ++ session.chdir('{{envconfig.change_dir}}') ++ {%- endif %} ++ ++ {%- for command in envconfig.commands %} ++ session.run({{command}}) ++ {%- endfor %} ++{% endfor %} +diff --git a/nox/tox_to_nox.py b/nox/tox_to_nox.py +index a6591b4b..26b0146c 100644 +--- a/nox/tox_to_nox.py ++++ b/nox/tox_to_nox.py +@@ -17,24 +17,38 @@ + from __future__ import annotations + + import argparse ++import os + import pkgutil +-from typing import Any, Iterator ++import re ++from configparser import ConfigParser ++from pathlib import Path ++from subprocess import check_output ++from typing import Any, Iterable + + import jinja2 + import tox.config ++from tox import __version__ as TOX_VERSION + +-_TEMPLATE = jinja2.Template( +- pkgutil.get_data(__name__, "tox_to_nox.jinja2").decode("utf-8"), # type: ignore[union-attr] +- extensions=["jinja2.ext.do"], +-) ++TOX4 = TOX_VERSION[0] == "4" + ++if TOX4: ++ _TEMPLATE = jinja2.Template( ++ pkgutil.get_data(__name__, "tox4_to_nox.jinja2").decode("utf-8"), # type: ignore[union-attr] ++ extensions=["jinja2.ext.do"], ++ ) ++else: ++ _TEMPLATE = jinja2.Template( ++ pkgutil.get_data(__name__, "tox_to_nox.jinja2").decode("utf-8"), # type: ignore[union-attr] ++ extensions=["jinja2.ext.do"], ++ ) + +-def wrapjoin(seq: Iterator[Any]) -> str: ++ ++def wrapjoin(seq: Iterable[Any]) -> str: + return ", ".join([f"'{item}'" for item in seq]) + + + def fixname(envname: str) -> str: +- envname = envname.replace("-", "_") ++ envname = envname.replace("-", "_").replace("testenv:", "") + if not envname.isidentifier(): + print( + f"Environment {envname!r} is not a valid nox session name.\n" +@@ -49,7 +63,61 @@ def main() -> None: + + args = parser.parse_args() + +- config = tox.config.parseconfig([]) ++ if TOX4: ++ output = check_output(["tox", "config"], text=True) ++ original_config = ConfigParser() ++ original_config.read_string(output) ++ config: dict[str, dict[str, Any]] = {} ++ ++ for name, section in original_config.items(): ++ if name == "DEFAULT": ++ continue ++ ++ config[name] = dict(section) ++ # Convert set_env from string to dict ++ set_env = {} ++ for var in section.get("set_env", "").strip().splitlines(): ++ k, v = var.split("=") ++ if k not in ( ++ "PYTHONHASHSEED", ++ "PIP_DISABLE_PIP_VERSION_CHECK", ++ "PYTHONIOENCODING", ++ ): ++ set_env[k] = v ++ ++ config[name]["set_env"] = set_env ++ ++ config[name]["commands"] = [ ++ wrapjoin(c.split()) for c in section["commands"].strip().splitlines() ++ ] ++ ++ config[name]["deps"] = wrapjoin(section["deps"].strip().splitlines()) ++ ++ for option in "skip_install", "use_develop": ++ if section.get(option): ++ if section[option] == "False": ++ config[name][option] = False ++ else: ++ config[name][option] = True ++ ++ if os.path.isabs(section["base_python"]) or re.match( ++ r"py\d+", section["base_python"] ++ ): ++ impl = ( ++ "python" if section["py_impl"] == "cpython" else section["py_impl"] ++ ) ++ config[name]["base_python"] = impl + section["py_dot_ver"] ++ ++ change_dir = Path(section.get("change_dir")) ++ rel_to_cwd = change_dir.relative_to(Path.cwd()) ++ if str(rel_to_cwd) == ".": ++ config[name]["change_dir"] = None ++ else: ++ config[name]["change_dir"] = rel_to_cwd ++ ++ else: ++ config = tox.config.parseconfig([]) ++ + output = _TEMPLATE.render(config=config, wrapjoin=wrapjoin, fixname=fixname) + + with open(args.output, "w") as outfile: +diff --git a/pyproject.toml b/pyproject.toml +index c234bf89..b027d299 100644 +--- a/pyproject.toml ++++ b/pyproject.toml +@@ -50,7 +50,7 @@ dependencies = [ + [project.optional-dependencies] + tox_to_nox = [ + "jinja2", +- "tox<4", ++ "tox", + ] + + [project.urls] + +From 0a317823ed4cfe9c73855bcd21e178187703b292 Mon Sep 17 00:00:00 2001 +From: Lumir Balhar +Date: Mon, 20 Feb 2023 15:28:02 +0100 +Subject: [PATCH 2/2] Testing and CI with multiple versions of tox + +--- + .github/workflows/ci.yml | 29 ++++++++++++- + noxfile.py | 26 ++++++++++-- + pyproject.toml | 1 + + tests/test_tox_to_nox.py | 92 +++++++++++++++++++++------------------- + 4 files changed, 100 insertions(+), 48 deletions(-) + +diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml +index 9d2476ec..4b034de5 100644 +--- a/.github/workflows/ci.yml ++++ b/.github/workflows/ci.yml +@@ -18,6 +18,7 @@ jobs: + matrix: + os: [ubuntu-20.04, windows-latest, macos-latest] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] ++ tox-version: ["latest", "<4"] + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} +@@ -35,7 +36,33 @@ jobs: + run: | + python -m pip install --disable-pip-version-check . + - name: Run tests on ${{ matrix.os }} +- run: nox --non-interactive --error-on-missing-interpreter --session "tests-${{ matrix.python-version }}" -- --full-trace ++ run: nox --non-interactive --error-on-missing-interpreter --session "tests(python='${{ matrix.python-version }}', tox_version='${{ matrix.tox-version }}')" -- --full-trace ++ - name: Save coverage report ++ uses: actions/upload-artifact@v3 ++ with: ++ name: coverage ++ path: .coverage.* ++ ++ coverage: ++ needs: build ++ runs-on: ubuntu-latest ++ steps: ++ - uses: actions/checkout@v3 ++ - name: Set up Python 3.11 ++ uses: actions/setup-python@v4 ++ with: ++ python-version: "3.11" ++ - name: Install Nox-under-test ++ run: | ++ python -m pip install --disable-pip-version-check . ++ - name: Download individual coverage reports ++ uses: actions/download-artifact@v3 ++ with: ++ name: coverage ++ - name: Display structure of downloaded files ++ run: ls -aR ++ - name: Run coverage ++ run: nox --non-interactive --session "cover" + + lint: + runs-on: ubuntu-latest +diff --git a/noxfile.py b/noxfile.py +index dc23b51d..c6e5d43d 100644 +--- a/noxfile.py ++++ b/noxfile.py +@@ -31,12 +31,29 @@ + nox.options.sessions.append("conda_tests") + + +-@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11"]) +-def tests(session: nox.Session) -> None: ++@nox.session ++@nox.parametrize( ++ "python, tox_version", ++ [ ++ (python, tox_version) ++ for python in ("3.7", "3.8", "3.9", "3.10", "3.11") ++ for tox_version in ("latest", "<4") ++ ], ++) ++def tests(session: nox.Session, tox_version: str) -> None: + """Run test suite with pytest.""" ++ # Because there is a dependency conflict between ++ # argcomplete and the latest tox (both depend on ++ # a different version of importlibmetadata for Py 3.7) ++ # pip installs tox 3 as the latest one for Py 3.7. ++ if session.python == "3.7" and tox_version == "latest": ++ return ++ + session.create_tmp() # Fixes permission errors on Windows + session.install("-r", "requirements-test.txt") + session.install("-e", ".[tox_to_nox]") ++ if tox_version != "latest": ++ session.install(f"tox{tox_version}") + session.run( + "pytest", + "--cov=nox", +@@ -44,9 +61,10 @@ def tests(session: nox.Session) -> None: + "pyproject.toml", + "--cov-report=", + *session.posargs, +- env={"COVERAGE_FILE": f".coverage.{session.python}"}, ++ env={ ++ "COVERAGE_FILE": f".coverage.{session.python}.tox.{tox_version.lstrip('<')}" ++ }, + ) +- session.notify("cover") + + + @nox.session(python=["3.7", "3.8", "3.9", "3.10"], venv_backend="conda") +diff --git a/pyproject.toml b/pyproject.toml +index b027d299..a0945d2a 100644 +--- a/pyproject.toml ++++ b/pyproject.toml +@@ -73,6 +73,7 @@ profile = "black" + + [tool.coverage.run] + branch = true ++relative_files = true + omit = [ "nox/_typing.py" ] + + [tool.coverage.report] +diff --git a/tests/test_tox_to_nox.py b/tests/test_tox_to_nox.py +index c7b54850..c4d86b60 100644 +--- a/tests/test_tox_to_nox.py ++++ b/tests/test_tox_to_nox.py +@@ -18,9 +18,14 @@ + import textwrap + + import pytest ++from tox import __version__ as TOX_VERSION + + from nox import tox_to_nox + ++TOX4 = TOX_VERSION[0] == "4" ++PYTHON_VERSION = f"{sys.version_info.major}.{sys.version_info.minor}" ++PYTHON_VERSION_NODOT = PYTHON_VERSION.replace(".", "") ++ + + @pytest.fixture + def makeconfig(tmpdir): +@@ -40,9 +45,9 @@ def makeconfig(toxini_content): + def test_trivial(makeconfig): + result = makeconfig( + textwrap.dedent( +- """ ++ f""" + [tox] +- envlist = py27 ++ envlist = py{PYTHON_VERSION_NODOT} + """ + ) + ) +@@ -50,12 +55,12 @@ def test_trivial(makeconfig): + assert ( + result + == textwrap.dedent( +- """ ++ f""" + import nox + + +- @nox.session(python='python2.7') +- def py27(session): ++ @nox.session(python='python{PYTHON_VERSION}') ++ def py{PYTHON_VERSION_NODOT}(session): + session.install('.') + """ + ).lstrip() +@@ -65,9 +70,9 @@ def py27(session): + def test_skipinstall(makeconfig): + result = makeconfig( + textwrap.dedent( +- """ ++ f""" + [tox] +- envlist = py27 ++ envlist = py{PYTHON_VERSION_NODOT} + + [testenv] + skip_install = True +@@ -78,12 +83,12 @@ def test_skipinstall(makeconfig): + assert ( + result + == textwrap.dedent( +- """ ++ f""" + import nox + + +- @nox.session(python='python2.7') +- def py27(session): ++ @nox.session(python='python{PYTHON_VERSION}') ++ def py{PYTHON_VERSION_NODOT}(session): + """ + ).lstrip() + ) +@@ -92,9 +97,9 @@ def py27(session): + def test_usedevelop(makeconfig): + result = makeconfig( + textwrap.dedent( +- """ ++ f""" + [tox] +- envlist = py27 ++ envlist = py{PYTHON_VERSION_NODOT} + + [testenv] + usedevelop = True +@@ -105,12 +110,12 @@ def test_usedevelop(makeconfig): + assert ( + result + == textwrap.dedent( +- """ ++ f""" + import nox + + +- @nox.session(python='python2.7') +- def py27(session): ++ @nox.session(python='python{PYTHON_VERSION}') ++ def py{PYTHON_VERSION_NODOT}(session): + session.install('-e', '.') + """ + ).lstrip() +@@ -120,12 +125,12 @@ def py27(session): + def test_commands(makeconfig): + result = makeconfig( + textwrap.dedent( +- """ ++ f""" + [tox] + envlist = lint + + [testenv:lint] +- basepython = python2.7 ++ basepython = python{PYTHON_VERSION} + commands = + python setup.py check --metadata --restructuredtext --strict + flake8 \\ +@@ -138,11 +143,11 @@ def test_commands(makeconfig): + assert ( + result + == textwrap.dedent( +- """ ++ f""" + import nox + + +- @nox.session(python='python2.7') ++ @nox.session(python='python{PYTHON_VERSION}') + def lint(session): + session.install('.') + session.run('python', 'setup.py', 'check', '--metadata', \ +@@ -156,12 +161,12 @@ def lint(session): + def test_deps(makeconfig): + result = makeconfig( + textwrap.dedent( +- """ ++ f""" + [tox] + envlist = lint + + [testenv:lint] +- basepython = python2.7 ++ basepython = python{PYTHON_VERSION} + deps = + flake8 + gcp-devrel-py-tools>=0.0.3 +@@ -172,11 +177,11 @@ def test_deps(makeconfig): + assert ( + result + == textwrap.dedent( +- """ ++ f""" + import nox + + +- @nox.session(python='python2.7') ++ @nox.session(python='python{PYTHON_VERSION}') + def lint(session): + session.install('flake8', 'gcp-devrel-py-tools>=0.0.3') + session.install('.') +@@ -188,12 +193,12 @@ def lint(session): + def test_env(makeconfig): + result = makeconfig( + textwrap.dedent( +- """ ++ f""" + [tox] + envlist = lint + + [testenv:lint] +- basepython = python2.7 ++ basepython = python{PYTHON_VERSION} + setenv = + SPHINX_APIDOC_OPTIONS=members,inherited-members,show-inheritance + TEST=meep +@@ -204,11 +209,11 @@ def test_env(makeconfig): + assert ( + result + == textwrap.dedent( +- """ ++ f""" + import nox + + +- @nox.session(python='python2.7') ++ @nox.session(python='python{PYTHON_VERSION}') + def lint(session): + session.env['SPHINX_APIDOC_OPTIONS'] = \ + 'members,inherited-members,show-inheritance' +@@ -222,12 +227,12 @@ def lint(session): + def test_chdir(makeconfig): + result = makeconfig( + textwrap.dedent( +- """ ++ f""" + [tox] + envlist = lint + + [testenv:lint] +- basepython = python2.7 ++ basepython = python{PYTHON_VERSION} + changedir = docs + """ + ) +@@ -236,11 +241,11 @@ def test_chdir(makeconfig): + assert ( + result + == textwrap.dedent( +- """ ++ f""" + import nox + + +- @nox.session(python='python2.7') ++ @nox.session(python='python{PYTHON_VERSION}') + def lint(session): + session.install('.') + session.chdir('docs') +@@ -252,12 +257,12 @@ def lint(session): + def test_dash_in_envname(makeconfig): + result = makeconfig( + textwrap.dedent( +- """ ++ f""" + [tox] + envlist = test-with-dash + + [testenv:test-with-dash] +- basepython = python2.7 ++ basepython = python{PYTHON_VERSION} + """ + ) + ) +@@ -265,11 +270,11 @@ def test_dash_in_envname(makeconfig): + assert ( + result + == textwrap.dedent( +- """ ++ f""" + import nox + + +- @nox.session(python='python2.7') ++ @nox.session(python='python{PYTHON_VERSION}') + def test_with_dash(session): + session.install('.') + """ +@@ -277,15 +282,16 @@ def test_with_dash(session): + ) + + ++@pytest.mark.skipif(TOX4, reason="Not supported in tox 4.") + def test_non_identifier_in_envname(makeconfig, capfd): + result = makeconfig( + textwrap.dedent( +- """ ++ f""" + [tox] + envlist = test-with-& + + [testenv:test-with-&] +- basepython = python2.7 ++ basepython = python{PYTHON_VERSION} + """ + ) + ) +@@ -293,11 +299,11 @@ def test_non_identifier_in_envname(makeconfig, capfd): + assert ( + result + == textwrap.dedent( +- """ ++ f""" + import nox + + +- @nox.session(python='python2.7') ++ @nox.session(python='python{PYTHON_VERSION}') + def test_with_&(session): + session.install('.') + """ +@@ -316,12 +322,12 @@ def test_with_&(session): + def test_descriptions_into_docstrings(makeconfig): + result = makeconfig( + textwrap.dedent( +- """ ++ f""" + [tox] + envlist = lint + + [testenv:lint] +- basepython = python2.7 ++ basepython = python{PYTHON_VERSION} + description = + runs the lint action + now with an unnecessary second line +@@ -332,11 +338,11 @@ def test_descriptions_into_docstrings(makeconfig): + assert ( + result + == textwrap.dedent( +- """ ++ f""" + import nox + + +- @nox.session(python='python2.7') ++ @nox.session(python='python{PYTHON_VERSION}') + def lint(session): + \"\"\"runs the lint action now with an unnecessary second line\"\"\" + session.install('.')