summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Pentchev <roam@debian.org>2023-07-02 01:13:43 +0300
committerPeter Pentchev <roam@debian.org>2023-07-02 01:13:43 +0300
commit650896668a3087cd41c133eea58acca6fe3b969e (patch)
tree52dab7f7f030b3bbd7f3723bcdaa190198903fc4
parent596f3697c0be357af52bb460bd8194139de25e16 (diff)
parentf3b1b1f32401d544e6e42d8dd95aa011ae2796cd (diff)
Update upstream source from tag 'upstream/0.1.2'
Update to upstream version '0.1.2' with Debian dir 3d41674951f84e8a75ec1129c00bf64294c6a03f
-rw-r--r--.config/ruff-all/pyproject.toml3
-rw-r--r--.config/ruff-base/pyproject.toml32
-rw-r--r--.editorconfig3
-rw-r--r--.gitignore9
-rw-r--r--.readthedocs.yaml11
-rw-r--r--.reuse/dep59
-rw-r--r--CHANGELOG.md25
-rw-r--r--LICENSES/BSD-2-Clause.txt9
-rw-r--r--LICENSES/BSD-3-Clause.txt11
-rw-r--r--MANIFEST.in7
-rw-r--r--PKG-INFO28
-rw-r--r--README.md9
-rw-r--r--config/ruff-all/pyproject.toml6
-rw-r--r--config/ruff-base/pyproject.toml38
-rw-r--r--config/ruff-most/pyproject.toml55
-rw-r--r--docs/changes.md118
-rw-r--r--docs/cmd/index.md8
-rw-r--r--docs/cmd/tox-stages.md133
-rw-r--r--docs/index.md63
-rw-r--r--docs/man/tox-stages.1172
-rw-r--r--mkdocs.yml51
-rw-r--r--pyproject.toml42
-rw-r--r--requirements/docs.txt7
-rw-r--r--requirements/install.txt9
-rw-r--r--requirements/selftest.txt7
-rw-r--r--requirements/test.txt4
-rw-r--r--setup.cfg12
-rw-r--r--src/selftest/__init__.py3
-rw-r--r--src/selftest/__main__.py159
-rw-r--r--src/test_stages.egg-info/PKG-INFO81
-rw-r--r--src/test_stages.egg-info/SOURCES.txt31
-rw-r--r--src/test_stages.egg-info/dependency_links.txt1
-rw-r--r--src/test_stages.egg-info/entry_points.txt5
-rw-r--r--src/test_stages.egg-info/requires.txt13
-rw-r--r--src/test_stages.egg-info/top_level.txt2
-rw-r--r--src/test_stages.egg-info/zip-safe1
-rw-r--r--src/test_stages/__init__.py28
-rw-r--r--src/test_stages/cmd.py108
-rw-r--r--src/test_stages/tox_stages/__init__.py2
-rw-r--r--src/test_stages/tox_stages/__main__.py74
-rw-r--r--src/tox_trivtags/__init__.py95
-rw-r--r--src/tox_trivtags/parse.py61
-rw-r--r--stubs/pyproject_hooks.pyi9
-rw-r--r--stubs/tox/__init__.pyi3
-rw-r--r--stubs/tox/config.pyi3
-rw-r--r--tox.ini132
-rw-r--r--unit_tests/__init__.py2
-rw-r--r--unit_tests/test_functional.py102
48 files changed, 1266 insertions, 530 deletions
diff --git a/.config/ruff-all/pyproject.toml b/.config/ruff-all/pyproject.toml
deleted file mode 100644
index 3823c31..0000000
--- a/.config/ruff-all/pyproject.toml
+++ /dev/null
@@ -1,3 +0,0 @@
-[tool.ruff]
-extend = "../ruff-base/pyproject.toml"
-select = ["ALL"]
diff --git a/.config/ruff-base/pyproject.toml b/.config/ruff-base/pyproject.toml
deleted file mode 100644
index 97d6cf1..0000000
--- a/.config/ruff-base/pyproject.toml
+++ /dev/null
@@ -1,32 +0,0 @@
-[tool.ruff]
-target-version = "py38"
-line-length = 100
-select = []
-ignore = [
- # We know what "self" is... I hope
- "ANN101",
-
- # We let the "black" tool take care of most of the formatting
- "COM812",
-
- # No blank lines before the class docstring, TYVM
- "D203",
-
- # The multi-line docstring summary starts on the same line
- "D213",
-
- # Our exceptions are simple enough
- "EM",
-
- # ruff does not seem to like the empty line before "from typing import ..."
- "I",
-
- # The Tagged and TaggedFrozen classes need to be typedload-compatible
- "TCH",
-
- # We are fine with relative imports
- "TID",
-
- # Much too restrictive
- "TRY",
-]
diff --git a/.editorconfig b/.editorconfig
index 5d9a133..b11eb0b 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,3 +1,6 @@
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
+#
# https://editorconfig.org/
root = true
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..99b3215
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
+
+.mypy_cache
+.ruff_cache
+site/
+.tox
+
+**/__pycache__/
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000..8a5d014
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
+
+version: 2
+
+mkdocs:
+ configuration: mkdocs.yml
+
+python:
+ install:
+ - requirements: requirements/docs.txt
diff --git a/.reuse/dep5 b/.reuse/dep5
new file mode 100644
index 0000000..e63b66c
--- /dev/null
+++ b/.reuse/dep5
@@ -0,0 +1,9 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: test-stages
+Upstream-Contact: Peter Pentchev <roam@ringlet.net>
+Source: https://gitlab.com/ppentchev/test-stages
+
+Files:
+ stubs/contextlib_chdir.pyi
+Copyright: Álvaro Mondéjar Rubio <mondejar1994@gmail.com>
+License: BSD-3-Clause
diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
index 0cc69fd..0000000
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,25 +0,0 @@
-# Changelog
-
-All notable changes to the test-stages project will be documented in this file.
-
-The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-
-## [Unreleased]
-
-## [0.1.1] - 2023-02-07
-
-### Fixes
-
-- Include the changelog file and the `.config/ruff-*/pyproject.toml` files in
- the PyPI source distribution tarball.
-
-## [0.1.0] - 2023-02-07
-
-### Started
-
-- First public release.
-
-[Unreleased]: https://gitlab.com/ppentchev/test-stages/-/compare/release%2F0.1.1...main
-[0.1.1]: https://gitlab.com/ppentchev/test-stages/-/compare/release%2F0.1.0...release%2F0.1.1
-[0.1.0]: https://gitlab.com/ppentchev/test-stages/-/tags/release%2F0.1.0
diff --git a/LICENSES/BSD-2-Clause.txt b/LICENSES/BSD-2-Clause.txt
new file mode 100644
index 0000000..5f662b3
--- /dev/null
+++ b/LICENSES/BSD-2-Clause.txt
@@ -0,0 +1,9 @@
+Copyright (c) <year> <owner>
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/LICENSES/BSD-3-Clause.txt b/LICENSES/BSD-3-Clause.txt
new file mode 100644
index 0000000..ea890af
--- /dev/null
+++ b/LICENSES/BSD-3-Clause.txt
@@ -0,0 +1,11 @@
+Copyright (c) <year> <owner>.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index d10c2e6..0000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,7 +0,0 @@
-include CHANGELOG.md
-recursive-include .config pyproject.toml
-include .editorconfig
-recursive-include requirements *.txt
-recursive-include stubs *.pyi
-include tox.ini
-recursive-include unit_tests *.py
diff --git a/PKG-INFO b/PKG-INFO
index 7dc71f5..93cc538 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,12 +1,13 @@
Metadata-Version: 2.1
Name: test_stages
-Version: 0.1.1
+Version: 0.1.2
Summary: Group Tox, Nox, etc environments into stages, run them in parallel
-Author-email: Peter Pentchev <roam@ringlet.net>
-Project-URL: Homepage, https://gitlab.com/ppentchev/test-stages
-Project-URL: Changes, https://gitlab.com/ppentchev/test-stages/-/blob/main/CHANGELOG.md
+Project-URL: Homepage, https://devel.ringlet.net/devel/test-stages
+Project-URL: Changes, https://devel.ringlet.net/devel/test-stages/changes/
Project-URL: Issue Tracker, https://gitlab.com/ppentchev/test-stages/-/issues
Project-URL: Source Code, https://gitlab.com/ppentchev/test-stages
+Author-email: Peter Pentchev <roam@ringlet.net>
+License: BSD-2-Clause
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Framework :: tox
@@ -28,9 +29,22 @@ Classifier: Topic :: Software Development
Classifier: Topic :: Software Development :: Testing
Classifier: Topic :: Software Development :: Testing :: Unit
Classifier: Topic :: Utilities
+Classifier: Typing :: Typed
Requires-Python: >=3.8
-Description-Content-Type: text/markdown
+Requires-Dist: click<9,>=8
+Requires-Dist: distlib<0.4,>=0.3.6
+Requires-Dist: parse-stages<0.2,>=0.1.4
+Requires-Dist: pyparsing<4,>=3
+Requires-Dist: tomli<3,>=2; python_version < '3.11'
+Requires-Dist: utf8-locale<2,>=1
Provides-Extra: tox
+Requires-Dist: tox<4,>=3; extra == 'tox'
+Description-Content-Type: text/markdown
+
+<!--
+SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+SPDX-License-Identifier: BSD-2-Clause
+-->
# Run Tox tests in groups, stopping on errors
@@ -65,8 +79,6 @@ section of the `pyproject.toml` file:
[tool.test-stages]
stages = ["ruff and not @manual", "@check", "@tests"]
-Note that the `tox-stages` tool only supports Tox version 3 for the present.
-
## Author
The `test-stages` library is developed by [Peter Pentchev][roam] in
@@ -75,7 +87,7 @@ The `test-stages` library is developed by [Peter Pentchev][roam] in
[flake8]: https://github.com/pycqa/flake8 "The flake8 Python syntax and style checker"
[gitlab]: https://gitlab.com/ppentchev/test-stages "The test-stages GitLab repository"
[nox]: https://nox.thea.codes/ "The Nox test runner"
-[parse-stages]: https://gitlab.com/ppentchev/parse-stages "Parse a mini-language for selecting objects by tag or name"
+[parse-stages]: https://devel.ringlet.net/devel/parse-stages "Parse a mini-language for selecting objects by tag or name"
[roam]: mailto:roam@ringlet.net "Peter Pentchev"
[ruff]: https://github.com/charliermarsh/ruff "Ruff, the extremely fast Python linter"
[tox]: https://tox.wiki/ "The Tox automation project"
diff --git a/README.md b/README.md
index b29b544..f568f5d 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,8 @@
+<!--
+SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+SPDX-License-Identifier: BSD-2-Clause
+-->
+
# Run Tox tests in groups, stopping on errors
The `test-stages` library provides command-line tools that wrap
@@ -31,8 +36,6 @@ section of the `pyproject.toml` file:
[tool.test-stages]
stages = ["ruff and not @manual", "@check", "@tests"]
-Note that the `tox-stages` tool only supports Tox version 3 for the present.
-
## Author
The `test-stages` library is developed by [Peter Pentchev][roam] in
@@ -41,7 +44,7 @@ The `test-stages` library is developed by [Peter Pentchev][roam] in
[flake8]: https://github.com/pycqa/flake8 "The flake8 Python syntax and style checker"
[gitlab]: https://gitlab.com/ppentchev/test-stages "The test-stages GitLab repository"
[nox]: https://nox.thea.codes/ "The Nox test runner"
-[parse-stages]: https://gitlab.com/ppentchev/parse-stages "Parse a mini-language for selecting objects by tag or name"
+[parse-stages]: https://devel.ringlet.net/devel/parse-stages "Parse a mini-language for selecting objects by tag or name"
[roam]: mailto:roam@ringlet.net "Peter Pentchev"
[ruff]: https://github.com/charliermarsh/ruff "Ruff, the extremely fast Python linter"
[tox]: https://tox.wiki/ "The Tox automation project"
diff --git a/config/ruff-all/pyproject.toml b/config/ruff-all/pyproject.toml
new file mode 100644
index 0000000..d59603f
--- /dev/null
+++ b/config/ruff-all/pyproject.toml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
+
+[tool.ruff]
+extend = "../ruff-base/pyproject.toml"
+select = ["ALL"]
diff --git a/config/ruff-base/pyproject.toml b/config/ruff-base/pyproject.toml
new file mode 100644
index 0000000..bba46dd
--- /dev/null
+++ b/config/ruff-base/pyproject.toml
@@ -0,0 +1,38 @@
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
+
+[tool.ruff]
+target-version = "py38"
+line-length = 100
+select = []
+ignore = [
+ # We know what "self" is... I hope
+ "ANN101",
+
+ # We let the "black" tool take care of most of the formatting
+ "COM812",
+
+ # No blank lines before the class docstring, TYVM
+ "D203",
+
+ # The multi-line docstring summary starts on the same line
+ "D213",
+]
+
+[tool.ruff.isort]
+force-single-line = true
+known-first-party = ["test_stages", "tox_trivtags"]
+lines-after-imports = 2
+single-line-exclusions = ["collections.abc", "typing"]
+
+[tool.ruff.per-file-ignores]
+# The self-test tool uses subprocess responsibly.
+"src/selftest/__main__.py" = ["S603", "S607"]
+
+# mypy needs a couple of assertions, and some of them will be a bit unwieldy if
+# broken down into multiple statements.
+"src/tox_trivtags/parse.py" = ["PT018", "S101"]
+
+# This is a unit test suite, it can output diagnostic messages.
+# Also, we try to use subprocess responsibly.
+"unit_tests/**.py" = ["S101", "S603", "S607", "T201"]
diff --git a/config/ruff-most/pyproject.toml b/config/ruff-most/pyproject.toml
new file mode 100644
index 0000000..e92af07
--- /dev/null
+++ b/config/ruff-most/pyproject.toml
@@ -0,0 +1,55 @@
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
+
+[tool.ruff]
+extend = "../ruff-base/pyproject.toml"
+# These are all the linters in Ruff 0.0.265 except for those we ignore completely.
+select = [
+ "A",
+ "ANN",
+ "ARG",
+ "B",
+ "BLE",
+ "C4",
+ "C90",
+ "COM",
+ "D",
+ "DJ",
+ "DTZ",
+ "E",
+ "EM",
+ "ERA",
+ "EXE",
+ "F",
+ "FBT",
+ "G",
+ "I",
+ "ICN",
+ "INP",
+ "INT",
+ "ISC",
+ "N",
+ "NPY",
+ "PD",
+ "PGH",
+ "PIE",
+ "PL",
+ "PT",
+ "PTH",
+ "PYI",
+ "Q",
+ "RET",
+ "RSE",
+ "RUF",
+ "S",
+ "SIM",
+ "SLF",
+ "T10",
+ "T20",
+ "TCH",
+ "TID",
+ "TRY",
+ "UP",
+ "W",
+ "YTT",
+]
diff --git a/docs/changes.md b/docs/changes.md
new file mode 100644
index 0000000..1fd5142
--- /dev/null
+++ b/docs/changes.md
@@ -0,0 +1,118 @@
+<!--
+SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+SPDX-License-Identifier: BSD-2-Clause
+-->
+
+# Changelog
+
+All notable changes to the test-stages project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+## [0.1.2] - 2023-03-13
+
+### Incompatible changes
+
+- tox-trivtags:
+ - drop the `tox_trivtags.parse.parse_config()` function, running
+ `tox --showconfig` is the only supported method now
+
+### Fixes
+
+- tox-stages:
+ - minor refactoring and fixes suggested by Ruff
+- tox-trivtags:
+ - use the correct way to ignore a specific Ruff check for the whole
+ file instead of telling Ruff to skip that file entirely!
+ - minor fixes suggested by Ruff
+- testing framework:
+ - correct the `tox.envlist` list in the `tox.ini` file
+
+### Additions
+
+- add the beginnings of [MkDocs-based][tool-mkdocs] documentation, hosted
+ [at the Ringlet test-stages webpage][ringlet-test-stages]
+ for the latest release and [at ReadTheDocs][readthedocs] for
+ the latest version from the Git repository
+- add manual page for the `tox-stages` tool in the mdoc format
+- add a `.gitignore` file, mainly so that the `reuse` tool can be run even
+ in the presence of some test-related files and directories
+- add a `selftest` module (not installed in the wheel) that runs
+ the `tox-stages` tool itself on a copy of the source tree
+- build system:
+ - add the "Typing :: Typed" PyPI trove classifier
+ - specify the project's two-clause BSD license
+- tox-stages:
+ - add support for Tox 4.x
+ - allow the `tox-stages` command-line tool to be invoked via `python3 -m`
+ - add the `--arg` / `-A` option to pass additional arguments to Tox
+ - add the `--parallel` / `-p` option to specify which stages should run
+ their tests in parallel
+- testing framework:
+ - add the `reuse` Tox test environment for checking the SPDX tags manually
+
+### Other changes
+
+- use SPDX license tags
+- move the changelog file into the MkDocs-managed `docs/` directory
+- point to the Ringlet homepage in the package metadata and the README file
+- tox-stages:
+ - reformat the import statements using Ruff's isort implementation
+ - use `tox run-parallel` when running with Tox 4.x
+- tox-trivtags:
+ - reformat the import statements using Ruff's isort implementation
+ - use `tox config` when running with Tox 4.x
+- build system:
+ - switch to hatch/hatchling for the PEP517 build
+ - move the `contextlib-chdir` module from the installation requirements to
+ the test ones, since we do not use it in the installed library
+ - bump the `parse-stages` dependency version to 0.1.4 so that an empty
+ set may be specified as an argument to the `--parallel` option
+- testing framework:
+ - Ruff:
+ - move the Ruff configuration files from `.config/` to `config/`
+ - run `ruff check ...` explicitly
+ - enable all of the Ruff checks in the default (`ruff`) test environment
+ - use ruff 0.0.265 and ignore some subprocess checks: we do check
+ - remove them `EM` checks override, we do not raise any exceptions
+ - Formatting:
+ - rename the `black` and `black-reformat` Tox environments to
+ `format` and `reformat` respectively and invoke Ruff's isort
+ implementation in both
+ - specify Python 3.8 as the target version
+ - Pylint:
+ - remove the `empty-comment` plugin override, the SPDX license tags
+ no longer cause it to complain
+ - specify Python 3.8 as the target version
+ - use pylint 2.17.x with no changes
+ - update the `tox.ini` file for Tox 4.x (mostly a multiline list) and
+ make the unit tests that run Tox 3.x revert those adaptations
+ - use the `@manual` tag for Tox test environments that should only be
+ run manually with care
+ - drop the Tox environment that runs `flake8` and `pycodestyle`,
+ we depend on Ruff for that
+
+## [0.1.1] - 2023-02-07
+
+### Fixes
+
+- Include the changelog file and the `.config/ruff-*/pyproject.toml` files in
+ the PyPI source distribution tarball.
+
+## [0.1.0] - 2023-02-07
+
+### Started
+
+- First public release.
+
+[readthedocs]: https://test-stages.readthedocs.io/en/latest/
+[ringlet-test-stages]: https://devel.ringlet.net/devel/test-stages/ "The Ringlet test-stages homepage"
+[tool-mkdocs]: https://www.mkdocs.org/ "Project documentation with Markdown"
+
+[Unreleased]: https://gitlab.com/ppentchev/test-stages/-/compare/release%2F0.1.2...main
+[0.1.2]: https://gitlab.com/ppentchev/test-stages/-/compare/release%2F0.1.1...release%2F0.1.2
+[0.1.1]: https://gitlab.com/ppentchev/test-stages/-/compare/release%2F0.1.0...release%2F0.1.1
+[0.1.0]: https://gitlab.com/ppentchev/test-stages/-/tags/release%2F0.1.0
diff --git a/docs/cmd/index.md b/docs/cmd/index.md
new file mode 100644
index 0000000..20529d6
--- /dev/null
+++ b/docs/cmd/index.md
@@ -0,0 +1,8 @@
+<!--
+SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+SPDX-License-Identifier: BSD-2-Clause
+-->
+
+# The command-line tools in the test-stages package
+
+- [tox-stages](tox-stages.md)
diff --git a/docs/cmd/tox-stages.md b/docs/cmd/tox-stages.md
new file mode 100644
index 0000000..e6773c3
--- /dev/null
+++ b/docs/cmd/tox-stages.md
@@ -0,0 +1,133 @@
+<!--
+SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+SPDX-License-Identifier: BSD-2-Clause
+-->
+
+# tox-stages - run Tox environments in groups, stop on failure
+
+## Synopsis
+
+``` sh
+tox-stages [-f filename] available
+tox-stages [-f filename] run [-A arg...] [-p spec] stage...
+```
+
+## Description
+
+The `tox-stages` tool is used to run Tox test environments in several
+stages, one or more environments running in parallel at each stage.
+If any of the test environments run at some stage shoud fail,
+`tox-stages` will stop, not run anything further, and exit with
+a non-zero exit code.
+This allows quick static check tools like e.g. `ruff` to stop
+the testing process early, and also allows scenarios like running
+all the static check tools before the package's unit or functional
+tests to avoid unnecessary failures on simple errors.
+
+## Tagging Tox test environments
+
+The `tox-stages` tool expects to be able to invoke an installation of
+Tox that will load the `tox_trivtags` plugin module distributed as part of
+the `test-stages` library.
+This module will add a "tags" list of strings to the definition of each
+Tox environment; those tags can be specified in the `tox.ini` file as follows:
+
+``` ini
+[testenv:format]
+skip_install = True
+tags =
+ check
+ format
+deps =
+ ...
+```
+
+## Subcommands
+
+### available - can the tox-stages tool be run on this system
+
+The `tox-stages available` subcommand exits with a code of zero
+(indicating success) if there is a suitable version of Tox installed in
+the same Python execution environment as the `tox-stages` tool itself.
+
+### run - run some Tox environments in stages
+
+The `tox-stages run` subcommand starts the process of running Tox test
+environments, grouped in stages.
+If any of the test environments run at some stage shoud fail,
+`tox-stages` will stop, not run anything further, and exit with
+a non-zero exit code.
+
+The `run` subcommand accepts the following options:
+
+- `--arg argument` / `-A argument` <br/>
+ Pass an additional command-line argument to each Tox invocation.
+ This option may be specified more than once, and the arguments will be
+ passed in the order given.
+- `--parallel spec` / `-p spec` <br/>
+ Specify which stages to run in parallel.
+ The `spec` parameter is a list of stage indices (1, 2, etc.) or
+ ranges (4-6); the tests in the specified stages will be run in
+ parallel, while the tests in the rest of the stages will not.
+ By default, all tests are run in parallel.
+ The special values "" (an empty string), "0" (a single character,
+ the digit zero), or "none" will be treated as an empty set, and
+ no tests will be run in parallel.
+- `stage...` <br/>
+ The positional arguments to the `run` subcommand are interpreted as
+ test stage specifications as described in
+ [the parse-stages library's documentation][ringlet-parse-stages].
+ If no stage specifications are given on the command line,
+ `tox-stages` will read the `pyproject.toml` file in the same
+ directory as the `tox.ini` file, and will look for a
+ `tool.test-stages.stages` list of strings to use.
+
+## Files
+
+If no stage specifications are given to the `run` subcommand,
+the `pyproject.toml` file is read and its `tool.test-stages.stages`
+variable (expected to be a list of strings) is used instead.
+
+## Examples
+
+Run all the stages as defined in the `pyproject.toml` file's
+`tool.test-stages.stages` parameter:
+
+``` sh
+tox-stages run
+```
+
+Run a specific set of stages, passing `-- -k slug` as additional
+Tox arguments so that e.g. a `pytest` environment that uses the Tox
+`{posargs}` variable may only run a selected subset of tests:
+
+``` sh
+tox-stages -A -- -A -k -A slug @check unit-tests
+```
+
+Execute a somewhat more complicated recipe:
+
+- first, run all test environments with names containing "ruff" in parallel
+- then, run the rest of the test environments marked with the "check" tag,
+ but not marked with the "manual" tag, one by one
+- then, run all test environments with names containing "unit" in parallel
+- finally, run the rest of the test environments marked with the "tests" tag,
+ but not marked with the "manual" tag, in parallel
+
+``` sh
+tox-stages -p 1,3-4 ruff '@check and not @manual' unit '@tests and not @manual'
+```
+
+## Author
+
+The `tox-stages` tool, along with its documentation, is developed as part of
+the `test-stages` library by [Peter Pentchev][roam] in
+[a GitLab repository][gitlab].
+This documentation is hosted at [Ringlet][ringlet-test-stages] with
+a copy at [ReadTheDocs][readthedocs].
+
+[gitlab]: https://gitlab.com/ppentchev/test-stages "The test-stages GitLab repository"
+[roam]: mailto:roam@ringlet.net "Peter Pentchev"
+[readthedocs]: https://test-stages.readthedocs.io/en/latest/
+[ringlet-parse-stages]: https://devel.ringlet.net/devel/parse-stages "Parse a mini-language for selecting objects by tag or name"
+[ringlet-test-stages]: https://devel.ringlet.net/devel/test-stages/ "The Ringlet test-stages homepage"
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..9ea1fba
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,63 @@
+<!--
+SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+SPDX-License-Identifier: BSD-2-Clause
+-->
+
+# Run Tox tests in groups, stopping on errors
+
+\[[Home][ringlet-test-stages] | [GitLab][gitlab] | [PyPI][pypi] | [ReadTheDocs][readthedocs]\]
+
+The `test-stages` library provides command-line tools that wrap
+Python test environment runners such as [Tox][tox] or [Nox][nox],
+invoking them so as the various tests are run in parallel, in groups,
+as specified on the command line.
+This allows the fastest tests to be run first, and the slower ones to
+only be started if it makes sense (e.g. if tools like [ruff] or [flake8]
+did not uncover any trivial syntax errors).
+
+The `tox-stages` tool runs Tox with the specified groups of test
+environments, stopping if any of the tests in a group should fail.
+This allows quick static check tools like e.g. `ruff` to stop
+the testing process early, and also allows scenarios like running
+all the static check tools before the package's unit or functional
+tests to avoid unnecessary failures on simple errors.
+
+The syntax for grouping the test environments to be run is described in
+the [parse-stages] library's documentation.
+
+[flake8]: https://github.com/pycqa/flake8 "The flake8 Python syntax and style checker"
+[nox]: https://nox.thea.codes/ "The Nox test runner"
+[parse-stages]: https://devel.ringlet.net/devel/parse-stages "Parse a mini-language for selecting objects by tag or name"
+[ruff]: https://github.com/charliermarsh/ruff "Ruff, the extremely fast Python linter"
+[tox]: https://tox.wiki/ "The Tox automation project"
+
+## Running Tox tests in groups
+
+The `tox-stages` tool may be invoked with a list of stages specified on
+the command line:
+
+``` sh
+ tox-stages run @check @tests
+```
+
+If the `tox-stages run` command is invoked without any stage specifications,
+the tool looks for the `stages` list of strings in the `[tool.test-stages]`
+section of the `pyproject.toml` file:
+
+``` toml
+ [tool.test-stages]
+ stages = ["ruff and not @manual", "@check", "@tests"]
+```
+
+## Author
+
+The `test-stages` library is developed by [Peter Pentchev][roam] in
+[a GitLab repository][gitlab].
+This documentation is hosted at [Ringlet][ringlet-test-stages] with
+a copy at [ReadTheDocs][readthedocs].
+
+[gitlab]: https://gitlab.com/ppentchev/test-stages "The test-stages GitLab repository"
+[pypi]: https://pypi.org/project/test-stages/ "The test-stages Python Package Index page"
+[roam]: mailto:roam@ringlet.net "Peter Pentchev"
+[readthedocs]: https://test-stages.readthedocs.io/en/latest/
+[ringlet-test-stages]: https://devel.ringlet.net/devel/test-stages/ "The Ringlet test-stages homepage"
diff --git a/docs/man/tox-stages.1 b/docs/man/tox-stages.1
new file mode 100644
index 0000000..e680999
--- /dev/null
+++ b/docs/man/tox-stages.1
@@ -0,0 +1,172 @@
+.\" SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+.\" SPDX-License-Identifier: BSD-2-Clause
+.Dd May 13, 2023
+.Dt TOX-STAGES 1
+.Os
+.Sh NAME
+.Nm tox-stages
+.Nd run Tox environments in groups, stop on failure
+.Sh SYNOPSIS
+.Nm
+.Op Fl f Ar filename
+.Cm available
+.Nm
+.Op Fl f Ar filename
+.Cm run
+.Op Fl -arg Ar arg... | Fl A Ar arg...
+.Op Fl -parallel Ar spec | Fl p Ar spec
+.Op Ar stage...
+.Sh DESCRIPTION
+The
+.Nm
+tool is used to run Tox test environments in several
+stages, one or more environments running in parallel at each stage.
+If any of the test environments run at some stage shoud fail,
+.Nm
+will stop, not run anything further, and exit with
+a non-zero exit code.
+This allows quick static check tools like e.g.
+.Cm ruff
+to stop the testing process early, and also allows scenarios like running
+all the static check tools before the package's unit or functional
+tests to avoid unnecessary failures on simple errors.
+.Sh TAGGING TOX TEST ENVIRONMENTS
+The
+.Nm
+tool expects to be able to invoke an installation of
+Tox that will load the
+.Cm tox_trivtags
+plugin module distributed as part of
+the
+.Nm test-stages
+library.
+This module will add a
+.Va tags
+list of strings to the definition of each
+Tox environment; those tags can be specified in the
+.Pa tox.ini
+file as follows:
+.Pp
+.Dl [testenv:format]
+.Dl skip_install = True
+.Dl tags =
+.Dl " check"
+.Dl " format"
+.Dl deps =
+.Dl " ..."
+.Sh SUBCOMMANDS
+.Ss available - can the tox-stages tool be run on this system
+The
+.Nm
+.Cm available
+subcommand exits with a code of zero (indicating success) if there is
+a suitable version of Tox installed in the same Python execution environment
+as the
+.Nm
+tool itself.
+.Ss run - run some Tox environments in stages
+The
+.Nm
+.Cm run
+subcommand starts the process of running Tox test
+environments, grouped in stages.
+If any of the test environments run at some stage shoud fail,
+.Nm
+will stop, not run anything further, and exit with
+a non-zero exit code.
+.Pp
+The
+.Cm run
+subcommand accepts the following options:
+.Bl -tag -width indent
+.It Fl -arg Ar argument | Fl A Ar argument
+Pass an additional command-line argument to each Tox invocation.
+This option may be specified more than once, and the arguments will be
+passed in the order given.
+.It Fl -parallel Ar spec | Fl p Ar spec
+Specify which stages to run in parallel.
+The
+.Ar spec
+parameter is a list of stage indices (1, 2, etc.) or
+ranges (4-6); the tests in the specified stages will be run in
+parallel, while the tests in the rest of the stages will not.
+By default, all tests are run in parallel.
+The special values
+.Dq ""
+(an empty string),
+.Dq 0
+(a single character, the digit zero), or
+.Dq none
+will be treated as an empty set, and
+no tests will be run in parallel.
+.El
+.Pp
+The positional arguments to the
+.Cm run
+subcommand are interpreted as
+test stage specifications as described in
+the parse-stages library's documentation.
+If no stage specifications are given on the command line,
+.Nm
+will read the
+.Pa pyproject.toml
+file in the same
+directory as the
+.Pa tox.ini
+file, and will look for a
+.Va tool.test-stages.stages
+list of strings to use.
+.Sh FILES
+If no stage specifications are given on the command line,
+.Nm
+will read the
+.Pa pyproject.toml
+file in the same
+directory as the
+.Pa tox.ini
+file, and will look for a
+.Va tool.test-stages.stages
+list of strings to use.
+.Sh EXAMPLES
+Run all the stages as defined in the
+.Pa pyproject.toml
+ file's
+.Va tool.test-stages.stages
+parameter:
+.Pp
+.Dl tox-stages run
+.Pp
+Run a specific set of stages, passing
+.Ar -- -k slug
+as additional
+Tox arguments so that e.g. a
+.Cm pytest
+environment that uses the Tox
+.Va {posargs}
+variable may only run a selected subset of tests:
+.Pp
+.Dl tox-stages -A -- -A -k -A slug @check unit-tests
+.Pp
+Execute a somewhat more complicated recipe:
+.Bl -tag -width \-
+.It -
+first, run all test environments with names containing "ruff" in parallel
+.It -
+then, run the rest of the test environments marked with the "check" tag,
+but not marked with the "manual" tag, one by one
+.It -
+then, run all test environments with names containing "unit" in parallel
+.It -
+finally, run the rest of the test environments marked with the "tests" tag,
+but not marked with the "manual" tag, in parallel
+.El
+.Pp
+.Dl tox-stages -p 1,3-4 ruff '@check and not @manual' unit '@tests and not @manual'
+.Sh AUTHORS
+The
+.Nm
+tool, along with its documentation, is developed as part of
+the
+.Nm test-stages library by
+.An Peter Pentchev
+.Aq roam@ringlet.net .
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 0000000..2bef9e8
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,51 @@
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
+
+theme:
+ name: material
+ features:
+ - navigation.instant
+ - navigation.tracking
+ - toc.integrate
+ - toc.follow
+ - content.code.copy
+ palette:
+ - media: "(prefers-color-scheme: light)"
+ scheme: default
+ toggle:
+ icon: material/weather-sunny
+ name: Switch to dark mode
+ - media: "(prefers-color-scheme: dark)"
+ scheme: slate
+ toggle:
+ icon: material/weather-night
+ name: Switch to light mode
+site_name: test-stages
+repo_url: https://gitlab.com/ppentchev/test-stages
+repo_name: test-stages
+site_author: ppentchev
+site_url: https://devel.ringlet.net/devel/test-stages/
+site_dir: site/docs
+nav:
+ - 'index.md'
+ - 'Changelog': 'changes.md'
+ - 'Command-line tools': 'cmd/index.md'
+ - 'The tox-stages tool': 'cmd/tox-stages.md'
+markdown_extensions:
+ - toc:
+ - pymdownx.highlight:
+ anchor_linenums: true
+ - pymdownx.inlinehilite:
+ - pymdownx.superfences:
+plugins:
+ - mkdocstrings:
+ handlers:
+ python:
+ paths: [src]
+ options:
+ heading_level: 3
+ show_root_heading: true
+ - search
+watch:
+ - 'src/test_stages'
+ - 'src/tox_trivtags'
diff --git a/pyproject.toml b/pyproject.toml
index 434d27e..3d72dc4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,11 +1,18 @@
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
+
[build-system]
-requires = ["setuptools >= 61", "wheel"]
-build-backend = "setuptools.build_meta"
+requires = [
+ "hatchling >= 1.8, < 2",
+ "hatch-requirements-txt >= 0.3, < 0.5",
+]
+build-backend = "hatchling.build"
[project]
name = "test_stages"
description = "Group Tox, Nox, etc environments into stages, run them in parallel"
readme = "README.md"
+license = {text = "BSD-2-Clause"}
requires-python = ">= 3.8"
classifiers = [
"Development Status :: 4 - Beta",
@@ -29,6 +36,7 @@ classifiers = [
"Topic :: Software Development :: Testing",
"Topic :: Software Development :: Testing :: Unit",
"Topic :: Utilities",
+ "Typing :: Typed",
]
dynamic = ["dependencies", "version"]
@@ -46,25 +54,22 @@ tox = ["tox >= 3, < 4"]
tox-stages = "test_stages.tox_stages.__main__:main"
[project.urls]
-Homepage = "https://gitlab.com/ppentchev/test-stages"
-Changes = "https://gitlab.com/ppentchev/test-stages/-/blob/main/CHANGELOG.md"
+Homepage = "https://devel.ringlet.net/devel/test-stages"
+Changes = "https://devel.ringlet.net/devel/test-stages/changes/"
"Issue Tracker" = "https://gitlab.com/ppentchev/test-stages/-/issues"
"Source Code" = "https://gitlab.com/ppentchev/test-stages"
-[tool.setuptools]
-zip-safe = true
-package-dir = {"" = "src"}
-packages = ["test_stages", "test_stages.tox_stages", "tox_trivtags"]
+[tool.hatch.build.targets.wheel]
+packages = ["src/test_stages", "src/tox_trivtags"]
-[tool.setuptools.package-data]
-test_stages = ["py.typed"]
-tox_trivtags = ["py.typed"]
+[tool.hatch.metadata.hooks.requirements_txt]
+files = ["requirements/install.txt"]
-[tool.setuptools.dynamic]
-dependencies = {file = "requirements/install.txt"}
-version = {attr = "test_stages.VERSION"}
+[tool.hatch.version]
+path = "src/test_stages/__init__.py"
[tool.black]
+target-version = ["py38", "py39", "py310", "py311"]
line-length = 100
[tool.mypy]
@@ -73,6 +78,7 @@ python_version = "3.8"
# This is the list of the Pylint 2.16.1 default plugins.
[tool.pylint]
+py-version = "3.8"
load-plugins = [
"pylint.extensions.bad_builtin",
"pylint.extensions.broad_try_clause",
@@ -87,7 +93,7 @@ load-plugins = [
"pylint.extensions.docparams",
"pylint.extensions.docstyle",
"pylint.extensions.dunder",
- # "pylint.extensions.empty_comment", # the copyright notices trigger that one
+ "pylint.extensions.empty_comment",
"pylint.extensions.emptystring",
"pylint.extensions.eq_without_hash",
"pylint.extensions.for_any_all",
@@ -108,9 +114,5 @@ disable = [
"consider-using-assignment-expr",
]
-[tool.ruff]
-extend = ".config/ruff-base/pyproject.toml"
-select = ["E", "F"]
-
[tool.test-stages]
-stages = ["ruff", "@check", "@tests"]
+stages = ["ruff and not @manual", "@check and not @manual", "@tests and not @manual"]
diff --git a/requirements/docs.txt b/requirements/docs.txt
new file mode 100644
index 0000000..d9ca115
--- /dev/null
+++ b/requirements/docs.txt
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
+
+mkdocs >= 1.4.2, < 2
+mkdocs-material >= 9.1.2, < 10
+mkdocstrings >= 0.21.2, < 0.22
+mkdocstrings-python >= 0.10.1, < 0.11
diff --git a/requirements/install.txt b/requirements/install.txt
index b7d8d3f..4231c99 100644
--- a/requirements/install.txt
+++ b/requirements/install.txt
@@ -1,8 +1,9 @@
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
+
click >= 8, < 9
-contextlib-chdir >= 1, < 2; python_version < '3.11'
-packaging >= 17, < 24
-parse-stages >= 0.1, < 0.2
+distlib >= 0.3.6, < 0.4
+parse-stages >= 0.1.4, < 0.2
pyparsing >= 3, < 4
-setuptools
tomli >= 2, < 3; python_version < '3.11'
utf8-locale >= 1, < 2
diff --git a/requirements/selftest.txt b/requirements/selftest.txt
new file mode 100644
index 0000000..e5c75b6
--- /dev/null
+++ b/requirements/selftest.txt
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
+
+contextlib-chdir >= 1, < 2; python_version < '3.11'
+pyproject_hooks >= 1, < 2
+tomli-w >= 1, < 2
+tox >= 4, < 5
diff --git a/requirements/test.txt b/requirements/test.txt
index 422bfeb..f21c3a5 100644
--- a/requirements/test.txt
+++ b/requirements/test.txt
@@ -1 +1,5 @@
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
+
+contextlib-chdir >= 1, < 2; python_version < '3.11'
pytest >= 6, < 8
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index 31a7fbd..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,12 +0,0 @@
-[flake8]
-extend_ignore = C812
-max_line_length = 100
-inline_quotes = double
-
-[pycodestyle]
-max-line-length = 100
-
-[egg_info]
-tag_build =
-tag_date = 0
-
diff --git a/src/selftest/__init__.py b/src/selftest/__init__.py
new file mode 100644
index 0000000..459f02f
--- /dev/null
+++ b/src/selftest/__init__.py
@@ -0,0 +1,3 @@
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
+"""A self-test for the `test-stages` library's `tox-stages` runner."""
diff --git a/src/selftest/__main__.py b/src/selftest/__main__.py
new file mode 100644
index 0000000..5b793f1
--- /dev/null
+++ b/src/selftest/__main__.py
@@ -0,0 +1,159 @@
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
+"""Run a test for the `test-stages` library's `tox-stages` runner."""
+
+from __future__ import annotations
+
+import os
+import pathlib
+import subprocess
+import sys
+import tarfile
+import tempfile
+import typing
+
+import pyproject_hooks
+import tomli_w
+import utf8_locale
+
+
+if sys.version_info >= (3, 11):
+ import contextlib as contextlib_chdir # pylint: disable=reimported
+
+ import tomllib
+else:
+ import contextlib_chdir
+ import tomli as tomllib
+
+
+if typing.TYPE_CHECKING:
+ from typing import Final
+
+
+def validate_srcdir(srcdir: pathlib.Path) -> None:
+ """Make sure we can find a couple of files in the source directory."""
+ for relpath in (
+ "requirements/install.txt",
+ "src/test_stages/tox_stages/__main__.py",
+ "unit_tests/test_functional.py",
+ ):
+ path = srcdir / relpath
+ if not path.is_file():
+ sys.exit(f"Expected to find {relpath} in {srcdir}, but {path} is not a regular file")
+
+
+def build_sdist(srcdir: pathlib.Path, tempd: pathlib.Path) -> pathlib.Path:
+ """Build a source distribution tarball."""
+ with contextlib_chdir.chdir(tempd):
+ distdir: Final = tempd / "dist"
+ distdir.mkdir(mode=0o755)
+
+ backend: Final = tomllib.loads((srcdir / "pyproject.toml").read_text(encoding="UTF-8"))[
+ "build-system"
+ ]["build-backend"]
+
+ caller: Final = pyproject_hooks.BuildBackendHookCaller(srcdir, backend)
+ fname: Final = caller.build_sdist(distdir)
+ sdist: Final = distdir / fname
+ if sdist.parent != distdir:
+ sys.exit(f"The PEP517 build returned {fname} which does not seem to be a pure filename")
+ return sdist
+
+
+def extract_sdist(sdist: pathlib.Path, tempd: pathlib.Path) -> pathlib.Path:
+ """Extract the sdist tarball."""
+ if not sdist.name.endswith(".tar.gz"):
+ sys.exit(f"The PEP517 build generated a non-.tar.gz file: {sdist}")
+
+ topdir: Final = tempd / "src"
+ topdir.mkdir(mode=0o755)
+
+ with tarfile.open(sdist, mode="r") as star:
+ star.extractall(topdir)
+ entries: Final = sorted(path for path in topdir.iterdir())
+ if len(entries) != 1:
+ sys.exit(f"Expected {sdist} to contain a single directory, got {entries!r}")
+
+ testdir: Final = entries[0]
+ if not testdir.is_dir() or not testdir.name.startswith(("test_stages-", "test-stages-")):
+ sys.exit(f"Expected {sdist} to contain a single `test-stages-*` directory, got {testdir}")
+ return testdir
+
+
+def adapt_pyproject(testdir: pathlib.Path) -> None:
+ """Disable this selftest to avoid infinite recursion."""
+ projfile: Final = testdir / "pyproject.toml"
+ projdata: Final = tomllib.loads(projfile.read_text(encoding="UTF-8"))
+
+ stages: Final = projdata["tool"]["test-stages"]["stages"]
+ if not stages[-1].startswith("@tests"):
+ sys.exit(f"Expected a `@tests...` test-stages entry, got {stages!r}")
+ stages[-1] = f"{stages[-1]} and not selftest"
+
+ projfile.write_text(tomli_w.dumps(projdata), encoding="UTF-8")
+
+
+def run_tox(testdir: pathlib.Path) -> None:
+ """Clean up the environment a bit, then run Tox."""
+ env: Final = dict(item for item in os.environ.items() if not item[0].startswith("TOX"))
+ subprocess.check_call(["pwd"], cwd=testdir, env=env)
+ subprocess.check_call(["cat", "pyproject.toml"], cwd=testdir, env=env)
+
+ subprocess.check_call(["tox-stages", "available"], cwd=testdir, env=env)
+
+ marker: Final = testdir / "selftest-marker.txt"
+ if marker.is_symlink() or marker.exists():
+ sys.exit(f"Did not expect {marker} to exist")
+ subprocess.check_call(
+ ["python3", "-m", "test_stages.tox_stages", "run", "@selftest"],
+ cwd=testdir,
+ env=env,
+ )
+ if not marker.is_file():
+ sys.exit(f"`tox-stages run @selftest` did not create {marker}")
+
+ marker.unlink()
+ subprocess.check_call(
+ ["python3", "-m", "test_stages.tox_stages", "run", "--arg", "--notest", "@selftest"],
+ cwd=testdir,
+ env=env,
+ )
+ if marker.is_symlink() or marker.exists():
+ sys.exit(f"A `--notest` run still created {marker}")
+
+ utf8_env = dict(env)
+ utf8_env.update(utf8_locale.UTF8Detect().detect().env_vars)
+ blurb = "import pathlib"
+ if blurb in subprocess.check_output(
+ ["tox-stages", "run", "@selftest"], cwd=testdir, encoding="UTF-8", env=utf8_env
+ ):
+ sys.exit("A run without any -p option output {blurb!r}")
+
+ if blurb in subprocess.check_output(
+ ["tox-stages", "run", "@selftest", "-p", "1"], cwd=testdir, encoding="UTF-8", env=utf8_env
+ ):
+ sys.exit("A `-p 1` run did not output {blurb!r}")
+
+ if blurb not in subprocess.check_output(
+ ["tox-stages", "run", "@selftest", "-p", "7"], cwd=testdir, encoding="UTF-8", env=utf8_env
+ ):
+ sys.exit("A `-p 7` run output {blurb!r}")
+
+ subprocess.check_call(["tox-stages", "run"], cwd=testdir, env=env)
+
+
+def main() -> None:
+ """Build a source distribution, extract it, run some tests."""
+ srcdir: Final = pathlib.Path(".").resolve()
+ validate_srcdir(srcdir)
+
+ with tempfile.TemporaryDirectory() as tempd_name:
+ tempd: Final = pathlib.Path(tempd_name)
+ sdist: Final = build_sdist(srcdir, tempd)
+ testdir: Final = extract_sdist(sdist, tempd)
+ adapt_pyproject(testdir)
+ run_tox(testdir)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/test_stages.egg-info/PKG-INFO b/src/test_stages.egg-info/PKG-INFO
deleted file mode 100644
index 973d0ec..0000000
--- a/src/test_stages.egg-info/PKG-INFO
+++ /dev/null
@@ -1,81 +0,0 @@
-Metadata-Version: 2.1
-Name: test-stages
-Version: 0.1.1
-Summary: Group Tox, Nox, etc environments into stages, run them in parallel
-Author-email: Peter Pentchev <roam@ringlet.net>
-Project-URL: Homepage, https://gitlab.com/ppentchev/test-stages
-Project-URL: Changes, https://gitlab.com/ppentchev/test-stages/-/blob/main/CHANGELOG.md
-Project-URL: Issue Tracker, https://gitlab.com/ppentchev/test-stages/-/issues
-Project-URL: Source Code, https://gitlab.com/ppentchev/test-stages
-Classifier: Development Status :: 4 - Beta
-Classifier: Environment :: Console
-Classifier: Framework :: tox
-Classifier: Intended Audience :: Developers
-Classifier: License :: DFSG approved
-Classifier: License :: Freely Distributable
-Classifier: License :: OSI Approved
-Classifier: License :: OSI Approved :: BSD License
-Classifier: Operating System :: POSIX
-Classifier: Operating System :: Unix
-Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: 3.8
-Classifier: Programming Language :: Python :: 3.9
-Classifier: Programming Language :: Python :: 3.10
-Classifier: Programming Language :: Python :: 3.11
-Classifier: Topic :: Software Development
-Classifier: Topic :: Software Development :: Testing
-Classifier: Topic :: Software Development :: Testing :: Unit
-Classifier: Topic :: Utilities
-Requires-Python: >=3.8
-Description-Content-Type: text/markdown
-Provides-Extra: tox
-
-# Run Tox tests in groups, stopping on errors
-
-The `test-stages` library provides command-line tools that wrap
-Python test environment runners such as [Tox][tox] or [Nox][nox],
-invoking them so as the various tests are run in parallel, in groups,
-as specified on the command line. This allows the fastest tests to be run
-first, and the slower ones to only be started if it makes sense (e.g. if
-tools like [ruff] or [flake8] did not uncover any trivial syntax errors).
-
-The `tox-stages` tool runs Tox with the specified groups of test
-environments, stopping if any of the tests in a group should fail.
-This allows quick static check tools like e.g. `ruff` to stop
-the testing process early, and also allows scenarios like running
-all the static check tools before the package's unit or functional
-tests to avoid unnecessary failures on simple errors.
-
-The syntax for grouping the test environments to be run is described in
-the [parse-stages] library's documentation.
-
-## Running Tox tests in groups
-
-The `tox-stages` tool may be invoked with a list of stages specified on
-the command line:
-
- tox-stages run @check @tests
-
-If the `tox-stages run` command is invoked without any stage specifications,
-the tool looks for the `stages` list of strings in the `[tool.test-stages]`
-section of the `pyproject.toml` file:
-
- [tool.test-stages]
- stages = ["ruff and not @manual", "@check", "@tests"]
-
-Note that the `tox-stages` tool only supports Tox version 3 for the present.
-
-## Author
-
-The `test-stages` library is developed by [Peter Pentchev][roam] in
-[a GitLab repository][gitlab].
-
-[flake8]: https://github.com/pycqa/flake8 "The flake8 Python syntax and style checker"
-[gitlab]: https://gitlab.com/ppentchev/test-stages "The test-stages GitLab repository"
-[nox]: https://nox.thea.codes/ "The Nox test runner"
-[parse-stages]: https://gitlab.com/ppentchev/parse-stages "Parse a mini-language for selecting objects by tag or name"
-[roam]: mailto:roam@ringlet.net "Peter Pentchev"
-[ruff]: https://github.com/charliermarsh/ruff "Ruff, the extremely fast Python linter"
-[tox]: https://tox.wiki/ "The Tox automation project"
diff --git a/src/test_stages.egg-info/SOURCES.txt b/src/test_stages.egg-info/SOURCES.txt
deleted file mode 100644
index d3b0266..0000000
--- a/src/test_stages.egg-info/SOURCES.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-.editorconfig
-CHANGELOG.md
-MANIFEST.in
-README.md
-pyproject.toml
-setup.cfg
-tox.ini
-.config/ruff-all/pyproject.toml
-.config/ruff-base/pyproject.toml
-requirements/install.txt
-requirements/test.txt
-src/test_stages/__init__.py
-src/test_stages/cmd.py
-src/test_stages/py.typed
-src/test_stages.egg-info/PKG-INFO
-src/test_stages.egg-info/SOURCES.txt
-src/test_stages.egg-info/dependency_links.txt
-src/test_stages.egg-info/entry_points.txt
-src/test_stages.egg-info/requires.txt
-src/test_stages.egg-info/top_level.txt
-src/test_stages.egg-info/zip-safe
-src/test_stages/tox_stages/__init__.py
-src/test_stages/tox_stages/__main__.py
-src/tox_trivtags/__init__.py
-src/tox_trivtags/parse.py
-src/tox_trivtags/py.typed
-stubs/contextlib_chdir.pyi
-stubs/tox/__init__.pyi
-stubs/tox/config.pyi
-unit_tests/__init__.py
-unit_tests/test_functional.py \ No newline at end of file
diff --git a/src/test_stages.egg-info/dependency_links.txt b/src/test_stages.egg-info/dependency_links.txt
deleted file mode 100644
index 8b13789..0000000
--- a/src/test_stages.egg-info/dependency_links.txt
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/test_stages.egg-info/entry_points.txt b/src/test_stages.egg-info/entry_points.txt
deleted file mode 100644
index 6761724..0000000
--- a/src/test_stages.egg-info/entry_points.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-[console_scripts]
-tox-stages = test_stages.tox_stages.__main__:main
-
-[tox]
-trivtags = tox_trivtags
diff --git a/src/test_stages.egg-info/requires.txt b/src/test_stages.egg-info/requires.txt
deleted file mode 100644
index 0504ff4..0000000
--- a/src/test_stages.egg-info/requires.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-click<9,>=8
-packaging<24,>=17
-parse-stages<0.2,>=0.1
-pyparsing<4,>=3
-setuptools
-utf8-locale<2,>=1
-
-[:python_version < "3.11"]
-contextlib-chdir<2,>=1
-tomli<3,>=2
-
-[tox]
-tox<4,>=3
diff --git a/src/test_stages.egg-info/top_level.txt b/src/test_stages.egg-info/top_level.txt
deleted file mode 100644
index a7bff51..0000000
--- a/src/test_stages.egg-info/top_level.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-test_stages
-tox_trivtags
diff --git a/src/test_stages.egg-info/zip-safe b/src/test_stages.egg-info/zip-safe
deleted file mode 100644
index 8b13789..0000000
--- a/src/test_stages.egg-info/zip-safe
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/test_stages/__init__.py b/src/test_stages/__init__.py
index 3136379..a833b8e 100644
--- a/src/test_stages/__init__.py
+++ b/src/test_stages/__init__.py
@@ -1,27 +1,5 @@
-# Copyright (c) Peter Pentchev <roam@ringlet.net>
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-# 1. Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
-# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
-# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-# SUCH DAMAGE.
-#
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
"""Run `tox` on several groups of environments, stopping on errors."""
-VERSION = "0.1.1"
+VERSION = "0.1.2"
diff --git a/src/test_stages/cmd.py b/src/test_stages/cmd.py
index b917f91..6e8378a 100644
--- a/src/test_stages/cmd.py
+++ b/src/test_stages/cmd.py
@@ -1,27 +1,5 @@
-# Copyright (c) Peter Pentchev <roam@ringlet.net>
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-# 1. Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
-# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
-# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-# SUCH DAMAGE.
-#
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
"""Command-line tool helpers for the various test-stages implementations."""
from __future__ import annotations
@@ -30,14 +8,20 @@ import dataclasses
import functools
import pathlib
import sys
-
-from collections.abc import Callable
-from typing import Any, Final, NamedTuple, TypeVar
+from typing import TYPE_CHECKING, NamedTuple
import click
import parse_stages as parse
import utf8_locale
+
+if TYPE_CHECKING:
+ from collections.abc import Callable
+ from typing import Any, Final, TypeVar
+
+ # pylint: disable-next=invalid-name
+ _T = TypeVar("_T")
+
if sys.version_info >= (3, 11):
import tomllib
else:
@@ -52,6 +36,30 @@ class Stage(NamedTuple):
spec: str
expr: parse.BoolExpr
+ parallel: bool
+
+
+class TestStage(NamedTuple):
+ """A final representation of a test stage: the environments to run and some attributes."""
+
+ envlist: list[TestEnv]
+ """The list of environments to run at this stage."""
+
+ parallel: bool
+ """Run the environments in parallel."""
+
+
+class StagesList:
+ """Parse the `--parallel` command-line option argument into a set."""
+
+ # pylint: disable=too-few-public-methods
+
+ stages: set[int]
+ """The selected stages, 1-based."""
+
+ def __init__(self, value: str) -> None:
+ """Record the selected set of stages."""
+ self.stages = set(parse.parse_stage_ids(value))
@dataclasses.dataclass(frozen=True)
@@ -73,10 +81,6 @@ class ConfigHolder:
cfg: Config | None = None
-# pylint: disable-next=invalid-name
-_T = TypeVar("_T")
-
-
def _split_by(current: list[_T], func: Callable[[_T], bool]) -> tuple[list[_T], list[_T]]:
"""Split an ordered list of items in two by the given predicate."""
res: Final[tuple[list[_T], list[_T]]] = ([], [])
@@ -88,12 +92,12 @@ def _split_by(current: list[_T], func: Callable[[_T], bool]) -> tuple[list[_T],
return res
-def select_stages(cfg: Config, all_stages: list[TestEnv]) -> list[list[TestEnv]]:
+def select_stages(cfg: Config, all_stages: list[TestEnv]) -> list[TestStage]:
"""Group the stages as specified."""
def process_stage(
- acc: tuple[list[list[TestEnv]], list[TestEnv]], stage: Stage
- ) -> tuple[list[list[TestEnv]], list[TestEnv]]:
+ acc: tuple[list[TestStage], list[TestEnv]], stage: Stage
+ ) -> tuple[list[TestStage], list[TestEnv]]:
"""Stash the environments matched by a stage specification."""
res, current = acc
if not current:
@@ -101,10 +105,10 @@ def select_stages(cfg: Config, all_stages: list[TestEnv]) -> list[list[TestEnv]]
left, matched = _split_by(current, stage.expr.evaluate)
if not matched:
sys.exit(f"No test environments matched by {stage.spec}")
- res.append(matched)
+ res.append(TestStage(envlist=matched, parallel=stage.parallel))
return res, left
- res_init: Final[list[list[TestEnv]]] = []
+ res_init: Final[list[TestStage]] = []
return functools.reduce(process_stage, cfg.stages, (res_init, list(all_stages)))[0]
@@ -156,16 +160,34 @@ def click_available() -> Callable[[Callable[[Config], bool]], click.Command]:
return inner
-def click_run() -> Callable[[Callable[[Config, list[list[TestEnv]]], None]], click.Command]:
+def click_run() -> Callable[[Callable[[Config, list[TestStage], list[str]], None]], click.Command]:
"""Wrap a run() function, preparing the configuration."""
- def inner(handler: Callable[[Config, list[list[TestEnv]]], None]) -> click.Command:
+ def inner(handler: Callable[[Config, list[TestStage], list[str]], None]) -> click.Command:
"""Wrap the run function."""
@click.command(name="run")
+ @click.option(
+ "-A",
+ "--arg",
+ type=str,
+ multiple=True,
+ help=(
+ "an additional argument to pass to the test runner; "
+ "may be specified multiple times"
+ ),
+ )
+ @click.option(
+ "-p",
+ "--parallel",
+ type=StagesList,
+ help="specify which stages to run in parallel (e.g. '1,4-6')",
+ )
@click.argument("stages_spec", nargs=-1, required=False, type=str)
@click.pass_context
- def real_run(ctx: click.Context, stages_spec: list[str]) -> None:
+ def real_run(
+ ctx: click.Context, arg: list[str], parallel: StagesList | None, stages_spec: list[str]
+ ) -> None:
"""Run the test environments in stages."""
cfg_base: Final = extract_cfg(ctx)
if not stages_spec:
@@ -174,13 +196,17 @@ def click_run() -> Callable[[Callable[[Config, list[list[TestEnv]]], None]], cli
if not stages_spec:
sys.exit("No stages specified either on the command line or in pyproject.toml")
+ pstages: Final = set(range(len(stages_spec))) if parallel is None else parallel.stages
cfg: Final = dataclasses.replace(
cfg_base,
- stages=[Stage(spec, parse.parse_spec(spec)) for spec in stages_spec],
+ stages=[
+ Stage(spec, parse.parse_spec(spec), idx in pstages)
+ for idx, spec in enumerate(stages_spec)
+ ],
)
ctx.obj.cfg = cfg
- handler(cfg, select_stages(cfg, cfg.get_all_envs(cfg)))
+ handler(cfg, select_stages(cfg, cfg.get_all_envs(cfg)), arg)
return real_run
diff --git a/src/test_stages/tox_stages/__init__.py b/src/test_stages/tox_stages/__init__.py
index 451db5d..f67e155 100644
--- a/src/test_stages/tox_stages/__init__.py
+++ b/src/test_stages/tox_stages/__init__.py
@@ -1,3 +1,5 @@
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
"""A `test-stages` implementation for the Tox test runner.
This module contains the configuration parsing and runtime glue to
diff --git a/src/test_stages/tox_stages/__main__.py b/src/test_stages/tox_stages/__main__.py
index 1ac1587..e61df38 100644
--- a/src/test_stages/tox_stages/__main__.py
+++ b/src/test_stages/tox_stages/__main__.py
@@ -1,48 +1,25 @@
-# Copyright (c) Peter Pentchev <roam@ringlet.net>
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-# 1. Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
-# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
-# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-# SUCH DAMAGE.
-#
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
"""The main tox-stages command-line executable."""
-# This is a command-line tool, output is part of its job.
-# flake8: noqa: T201
-
from __future__ import annotations
import dataclasses
-import pathlib
import subprocess
import sys
-
-from typing import Final
+from typing import TYPE_CHECKING
import tox_trivtags
+from test_stages import cmd
-from .. import cmd
-if tox_trivtags.HAVE_MOD_TOX_3:
+if tox_trivtags.HAVE_MOD_TOX_3 or tox_trivtags.HAVE_MOD_TOX_4:
from tox_trivtags import parse as ttt_parse
+if TYPE_CHECKING:
+ import pathlib
+ from typing import Final
+
@dataclasses.dataclass(frozen=True)
class Config(cmd.Config):
@@ -57,42 +34,48 @@ def _cmd_available(cfg: cmd.Config) -> bool:
Currently the only supported way is `tox --showconfig`.
"""
- assert isinstance(cfg, Config)
+ assert isinstance(cfg, Config) # noqa: S101 # mypy needs this
return cfg.tox_program is not None
@cmd.click_run()
-def _cmd_run(cfg: cmd.Config, stages: list[list[cmd.TestEnv]]) -> None:
+def _cmd_run(cfg: cmd.Config, stages: list[cmd.TestStage], extra_args: list[str]) -> None:
"""Run the Tox environments in groups."""
toxdir = cfg.filename.parent
- def run_group(group: list[cmd.TestEnv]) -> None:
+ def run_group(group: list[cmd.TestEnv], *, parallel: bool) -> None:
"""Run the stages in a single group."""
if not isinstance(cfg, Config) or cfg.tox_program is None:
# _tox_get_envs() really should have taken care of that
sys.exit(f"Internal error: tox-stages run_group: Config? {cfg!r}")
names: Final = ",".join(env.name for env in group)
- print(f"\n=== Running Tox environments: {names}\n")
+ print(f"\n=== Running Tox environments: {names}\n") # noqa: T201
+ run_parallel = (
+ (["-p", "all"] if parallel else [])
+ if tox_trivtags.HAVE_MOD_TOX_3
+ else (["run-parallel"] if parallel else ["run"])
+ )
+ tox_cmd: Final = [*cfg.tox_program, *run_parallel, "-e", names, *extra_args]
res: Final = subprocess.run(
- cfg.tox_program + ["-p", "all", "-e", names],
+ tox_cmd,
check=False,
cwd=toxdir,
env=cfg.utf8_env,
- shell=False,
+ shell=False, # noqa: S603
)
if res.returncode != 0:
sys.exit(f"Tox failed for the {names} environments")
- for group in stages:
- run_group(group)
+ for desc in stages:
+ run_group(desc.envlist, parallel=desc.parallel)
- print("\n=== All Tox environment groups passed!")
+ print("\n=== All Tox environment groups passed!") # noqa: T201
def _tox_get_envs(cfg: cmd.Config) -> list[cmd.TestEnv]:
"""Get all the Tox environments from the config file."""
- assert isinstance(cfg, Config)
+ assert isinstance(cfg, Config) # noqa: S101 # mypy needs this
if cfg.tox_program is None:
sys.exit("No tox program found or specified")
tcfg: Final = ttt_parse.parse_showconfig(
@@ -110,7 +93,7 @@ def _find_tox_program() -> list[str | pathlib.Path] | None:
Also, we only support Tox 3.x for the present.
"""
- if not tox_trivtags.HAVE_MOD_TOX_3:
+ if not tox_trivtags.HAVE_MOD_TOX_3 and not tox_trivtags.HAVE_MOD_TOX_4:
return None
return [sys.executable, "-m", "tox"]
@@ -130,3 +113,8 @@ def main(cfg: cmd.Config) -> cmd.Config:
main.add_command(_cmd_available)
main.add_command(_cmd_run)
+
+
+if __name__ == "__main__":
+ # pylint: disable-next=no-value-for-parameter
+ main()
diff --git a/src/tox_trivtags/__init__.py b/src/tox_trivtags/__init__.py
index c53e709..7ae7811 100644
--- a/src/tox_trivtags/__init__.py
+++ b/src/tox_trivtags/__init__.py
@@ -1,43 +1,53 @@
-# Copyright (c) Peter Pentchev <roam@ringlet.net>
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-# 1. Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
-# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
-# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-# SUCH DAMAGE.
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
"""Parse a list of tags in the Tox configuration.
Inspired by https://github.com/tox-dev/tox-tags
"""
-import packaging.version
-import pkg_resources
+from __future__ import annotations
+import logging
+import typing
+
+import distlib.database as dist_database
+import distlib.version as dist_version
+
+
+if typing.TYPE_CHECKING:
+ from typing import Final
+
+
+def _get_tox_distribution() -> dist_database.InstalledDistribution | None:
+ """Figure out whether Tox is installed."""
+
+ def no_messages(_evt: logging.LogRecord) -> bool:
+ """Do not output any logging messages, none at all."""
+ return False
+
+ logging.getLogger("distlib.database").addFilter(no_messages)
+ logging.getLogger("distlib.metadata").addFilter(no_messages)
+ dist: Final = dist_database.DistributionPath(include_egg=True).get_distribution("tox")
+ logging.getLogger("distlib.database").removeFilter(no_messages)
+ logging.getLogger("distlib.metadata").removeFilter(no_messages)
+ return dist
+
+
+_TOX_DIST: Final = _get_tox_distribution()
+
+if _TOX_DIST is None:
+ HAVE_MOD_TOX_3 = False
+ HAVE_MOD_TOX_4 = False
+else:
+ _TOX_VERSION: Final = dist_version.NormalizedVersion(_TOX_DIST.version)
-try:
HAVE_MOD_TOX_3 = (
- packaging.version.Version("3")
- <= packaging.version.Version(pkg_resources.get_distribution("tox").version)
- < packaging.version.Version("4")
+ dist_version.NormalizedVersion("3") <= _TOX_VERSION < dist_version.NormalizedVersion("4")
+ )
+
+ HAVE_MOD_TOX_4 = (
+ dist_version.NormalizedVersion("4") <= _TOX_VERSION < dist_version.NormalizedVersion("5")
)
-except pkg_resources.DistributionNotFound:
- HAVE_MOD_TOX_3 = False
if HAVE_MOD_TOX_3:
@@ -50,3 +60,26 @@ if HAVE_MOD_TOX_3:
parser.add_testenv_attribute(
"tags", "line-list", "A list of tags describing this test environment", default=[]
)
+
+
+if HAVE_MOD_TOX_4:
+ from typing import List
+
+ import tox.plugin as t_plugin
+
+ if typing.TYPE_CHECKING:
+ import tox.config.sets as t_sets
+ import tox.session.state as t_state
+
+ @t_plugin.impl
+ def tox_add_env_config(
+ env_conf: t_sets.EnvConfigSet,
+ state: t_state.State, # noqa: ARG001
+ ) -> None:
+ """Parse a testenv's "tags" attribute as a list of lines."""
+ env_conf.add_config(
+ keys=["tags"],
+ of_type=List[str],
+ default=[],
+ desc="A list of tags describing this test environment",
+ )
diff --git a/src/tox_trivtags/parse.py b/src/tox_trivtags/parse.py
index 70488f3..a61c8ba 100644
--- a/src/tox_trivtags/parse.py
+++ b/src/tox_trivtags/parse.py
@@ -1,30 +1,8 @@
-# Copyright (c) Peter Pentchev <roam@ringlet.net>
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-# 1. Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
-# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
-# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-# SUCH DAMAGE.
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
"""Query Tox for the tags defined in the specified file."""
# mypy needs these assertions, and they are better expressed in a compact manner
-# flake8: noqa: PT018
from __future__ import annotations
@@ -33,10 +11,9 @@ import configparser
import pathlib
import subprocess
import sys
-
from typing import Final, NamedTuple
-import tox.config
+from tox_trivtags import HAVE_MOD_TOX_3
DEFAULT_FILENAME = pathlib.Path("tox.ini")
@@ -50,15 +27,6 @@ class TestenvTags(NamedTuple):
tags: list[str]
-def parse_config(filename: pathlib.Path = DEFAULT_FILENAME) -> dict[str, TestenvTags]:
- """Use `tox.config.parseconfig()` to parse the Tox config file."""
- tox_cfg: Final = tox.config.parseconfig(["-c", str(filename)])
- return {
- name: TestenvTags(cfg_name=f"testenv:{name}", name=name, tags=env.tags)
- for name, env in tox_cfg.envconfigs.items()
- }
-
-
def _validate_parsed_bool(value: ast.expr) -> bool:
"""Make sure a boolean value is indeed a boolean value."""
assert isinstance(value, ast.Constant) and isinstance(value.value, bool)
@@ -91,6 +59,11 @@ def _parse_strlist(value: str) -> list[str]:
return _validate_parsed_strlist(a_body[0].value)
+def _parse_lines(value: str) -> list[str]:
+ """Parse a list of text lines as Tox 4 wants to output the tags."""
+ return [line for line in value.splitlines() if line]
+
+
def remove_prefix(value: str, prefix: str) -> str:
"""Remove a string's prefix if it is there.
@@ -109,12 +82,20 @@ def parse_showconfig(
"""Run `tox --showconfig` and look for tags in its output."""
if tox_invoke is None:
tox_invoke = [sys.executable, "-u", "-m", "tox"]
+ tox_cmd: Final = [
+ *tox_invoke,
+ "--showconfig" if HAVE_MOD_TOX_3 else "config",
+ "-c",
+ filename,
+ "-e",
+ "ALL",
+ ]
contents: Final = subprocess.run(
- tox_invoke + ["--showconfig", "-c", filename],
+ tox_cmd,
check=True,
encoding="UTF-8",
env=env,
- shell=False,
+ shell=False, # noqa: S603
stdout=subprocess.PIPE,
).stdout
assert isinstance(contents, str)
@@ -123,7 +104,11 @@ def parse_showconfig(
cfgp.read_string(contents)
return {
- name: TestenvTags(cfg_name=cfg_name, name=name, tags=_parse_strlist(tags))
+ name: TestenvTags(
+ cfg_name=cfg_name,
+ name=name,
+ tags=_parse_strlist(tags) if HAVE_MOD_TOX_3 else _parse_lines(tags),
+ )
for cfg_name, name, tags in (
(cfg_name, name, env["tags"])
for cfg_name, name, env in (
diff --git a/stubs/pyproject_hooks.pyi b/stubs/pyproject_hooks.pyi
new file mode 100644
index 0000000..d8cb317
--- /dev/null
+++ b/stubs/pyproject_hooks.pyi
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
+
+import pathlib
+
+
+class BuildBackendHookCaller:
+ def __init__(self, source_dir: pathlib.Path, build_backend: str) -> None: ...
+ def build_sdist(self, distdir: pathlib.Path) -> str: ...
diff --git a/stubs/tox/__init__.pyi b/stubs/tox/__init__.pyi
index b982201..a6a64c3 100644
--- a/stubs/tox/__init__.pyi
+++ b/stubs/tox/__init__.pyi
@@ -1,3 +1,6 @@
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
+
from collections.abc import Callable
from typing import TypeVar
diff --git a/stubs/tox/config.pyi b/stubs/tox/config.pyi
index 798feb7..75b0a61 100644
--- a/stubs/tox/config.pyi
+++ b/stubs/tox/config.pyi
@@ -1,3 +1,6 @@
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
+
from collections.abc import Iterable
from typing import Any, Dict, List
diff --git a/tox.ini b/tox.ini
index 807c9f3..ef4c99b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,104 +1,88 @@
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
+
[tox]
+minversion = 4.1
envlist =
ruff
- black
- pep8
+ ruff-all
+ format
mypy
pylint
- unit_tests-no-tox
- unit_tests-tox-3
- unit_tests-tox-4
+ unit-tests-no-tox
+ unit-tests-tox-3
+ unit-tests-tox-4
+ selftest
isolated_build = True
[defs]
+pyfiles_mypy =
+ src/selftest \
+ src/test_stages \
+ unit_tests
+
pyfiles =
- src/test_stages
+ {[defs]pyfiles_mypy} \
src/tox_trivtags
- unit_tests
[testenv:ruff]
skip_install = True
tags =
check
deps =
- ruff >= 0.0.243, < 0.1
+ ruff >= 0.0.265, < 0.1
commands =
- ruff -- {[defs]pyfiles}
+ ruff check --config config/ruff-most/pyproject.toml -- {[defs]pyfiles}
[testenv:ruff-all]
skip_install = True
tags =
check
deps =
- ruff == 0.0.243
+ ruff == 0.0.265
commands =
- ruff --config .config/ruff-all/pyproject.toml -- {[defs]pyfiles}
+ ruff check --config config/ruff-all/pyproject.toml -- {[defs]pyfiles}
-[testenv:black]
+[testenv:format]
skip_install = True
tags =
check
deps =
black >= 23, < 24
+ ruff >= 0.0.265, < 0.1
commands =
+ ruff check --config config/ruff-base/pyproject.toml --select=I --diff -- {[defs]pyfiles}
black --check {[defs]pyfiles}
-[testenv:black-reformat]
+[testenv:reformat]
skip_install = True
tags =
format
+ manual
deps =
black >= 23, < 24
+ ruff >= 0.0.265, < 0.1
commands =
+ ruff check --config config/ruff-base/pyproject.toml --select=I --fix -- {[defs]pyfiles}
black {[defs]pyfiles}
-[testenv:pep8]
-skip_install = True
-tags =
- check
-deps =
- flake8 >= 6, < 7
- flake8-2020 >= 1, < 2
- flake8-annotations >= 3, < 4
- flake8-blind-except >= 0.2, < 0.3
- flake8-bugbear >= 23, < 24
- flake8-builtins >= 2, < 3
- flake8-commas >= 2, < 3
- flake8-comprehensions >= 3, < 4
- flake8-datetimez >= 20, < 21
- flake8-debugger >= 4, < 5
- flake8-executable >= 2, < 3
- flake8-implicit-str-concat >= 0.3, < 0.4
- flake8-no-pep420 >= 2, < 3
- flake8-pie >= 0.16, < 0.17
- flake8-print >= 5, < 6
- flake8-pytest-style >= 1, < 2
- flake8-quotes >= 3, < 4
- flake8-return >= 1, < 2
- flake8-simplify >= 0.19, < 0.20
- flake8-use-pathlib >= 0.3, < 0.4
- mccabe >= 0.7, < 0.8
- pep8-naming >= 0.13, < 0.14
- pycodestyle >= 2.10, < 3
-commands =
- flake8 {[defs]pyfiles}
- pycodestyle {[defs]pyfiles}
-
[testenv:mypy]
skip_install = True
tags =
check
deps =
-r requirements/install.txt
+ -r requirements/selftest.txt
-r requirements/test.txt
- mypy >= 0.942
+ mypy >= 1, < 2
tomli >= 2, < 3
- tox >= 3, < 4
+
+ # pytest still needs this one...
types-setuptools >= 20
setenv =
MYPYPATH = {toxinidir}/stubs
commands =
- mypy {[defs]pyfiles}
+ mypy --follow-imports silent --exclude tox_trivtags {[defs]pyfiles_mypy}
[testenv:pylint]
skip_install = True
@@ -106,11 +90,12 @@ tags =
check
deps =
-r requirements/install.txt
+ -r requirements/selftest.txt
-r requirements/test.txt
- pylint >= 2.16, < 2.17
- tox >= 3, < 4
+ pylint >= 2.17, < 2.18
+ tox >= 3, < 5
commands =
- pylint {[defs]pyfiles}
+ pylint --ignore tox_trivtags {[defs]pyfiles_mypy}
[testenv:unit-tests-no-tox]
tags =
@@ -147,13 +132,28 @@ allowlist_externals =
sh
commands =
tox-stages --help
- sh -c 'if tox-stages available; then echo Waat; exit 1; else echo Not available; fi'
+ tox-stages available
+ pytest {posargs} unit_tests
+
+[testenv:selftest]
+tags =
+ tests
+deps =
+ -r requirements/install.txt
+ -r requirements/selftest.txt
+ hatchling >= 1.14.1, < 2
+ hatch-requirements-txt >= 0.4, < 0.5
+setenv =
+ PYTHONPATH = {toxinidir}/src
+commands =
+ python3 -m selftest
# The pyupgrade tool does not seem to have a "check only" mode
[testenv:pyupgrade]
skip_install = True
tags =
- check-later
+ check
+ manual
deps =
pyupgrade >= 3, < 4
allowlist_externals =
@@ -161,6 +161,26 @@ allowlist_externals =
commands =
sh -c 'pyupgrade --py38-plus src/test_stages/*.py src/test_stages/tox_stages/*.py src/tox_trivtags/*.py unit_tests/*.py'
+[testenv:reuse]
+skip_install = True
+tags =
+ check
+ manual
+deps =
+ reuse >= 1, < 2
+commands =
+ reuse lint
+
+[testenv:docs]
+skip_install = True
+tags =
+ docs
+ manual
+deps =
+ -r requirements/docs.txt
+commands =
+ mkdocs build
+
[testenv:t-single]
tags =
something
@@ -184,3 +204,9 @@ tags =
'eh"?
commands =
python3 -c 'raise NotImplementedError()'
+
+[testenv:t-selftest-marker]
+tags =
+ selftest
+commands =
+ python3 -c 'import pathlib; pathlib.Path("selftest-marker.txt").write_text("", encoding="UTF-8")'
diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py
index 867982a..f32c1ef 100644
--- a/unit_tests/__init__.py
+++ b/unit_tests/__init__.py
@@ -1 +1,3 @@
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
"""Unit tests for the `test-stages` library and its runner implementations."""
diff --git a/unit_tests/test_functional.py b/unit_tests/test_functional.py
index f509e46..d7c0f18 100644
--- a/unit_tests/test_functional.py
+++ b/unit_tests/test_functional.py
@@ -1,65 +1,82 @@
-# Copyright (c) Peter Pentchev <roam@ringlet.net>
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-# 1. Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
-# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
-# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-# SUCH DAMAGE.
+# SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
+# SPDX-License-Identifier: BSD-2-Clause
"""Load the Tox configuration, look for our tags thing."""
-# This is a test suite.
-# flake8: noqa: T201
-
from __future__ import annotations
import contextlib
import pathlib
+import shutil
+import subprocess
import sys
import tempfile
-
-from collections.abc import Callable, Iterator
-from contextlib import AbstractContextManager
-from typing import Final
+from typing import TYPE_CHECKING
import pytest
import utf8_locale
+import tox_trivtags
import tox_trivtags.parse as ttt_parse
+
if sys.version_info >= (3, 11):
import contextlib as contextlib_chdir # pylint: disable=reimported
else:
import contextlib_chdir
+if TYPE_CHECKING:
+ from collections.abc import Callable, Iterator
+ from contextlib import AbstractContextManager
+ from typing import Final
+
+
_EXPECTED: Final[dict[str, list[str]]] = {
- "black": ["check"],
- "black-reformat": ["format"],
+ "format": ["check"],
+ "reformat": ["format", "manual"],
"unit-tests-no-tox": ["tests"],
"unit-tests-tox-3": ["tests"],
"unit-tests-tox-4": ["tests"],
- ".package": [],
+ (".package" if tox_trivtags.HAVE_MOD_TOX_3 else ".pkg"): [],
"t-single": ["something"],
"t-several": ["all", "the", "things"],
"t-special": ["So,", "how many", "$tags", 'is "too many",', "'eh\"?"],
}
+def copy_and_adapt(srcdir: pathlib.Path, dstdir: pathlib.Path) -> None:
+ """Copy some files over, adapt the tox.ini file."""
+ src_tox = srcdir / "tox.ini"
+ dst_tox = dstdir / "tox.ini"
+ lines: Final = src_tox.read_text(encoding="UTF-8").splitlines()
+ adapted: Final = [
+ line.replace(" \\", "") for line in lines if not line.startswith("minversion")
+ ]
+ dst_tox.write_text("".join(line + "\n" for line in adapted), encoding="UTF-8")
+ res: Final = subprocess.run(["diff", "-u", "--", src_tox, dst_tox], check=False)
+ assert res.returncode != 0
+
+ shutil.copytree(srcdir / "config", dstdir / "config")
+ shutil.copytree(srcdir / "requirements", dstdir / "requirements")
+ shutil.copy2(srcdir / "pyproject.toml", dstdir / "pyproject.toml")
+
+
+@contextlib.contextmanager
+def adapt_tox_cwd() -> Iterator[pathlib.Path]:
+ """If using Tox 3.x, copy the files over and adapt them."""
+ cwd: Final = pathlib.Path("").absolute()
+ if not tox_trivtags.HAVE_MOD_TOX_3:
+ print("Not using Tox 3.x, no need to copy or adapt any files")
+ yield cwd
+ return
+
+ with tempfile.TemporaryDirectory() as toxd_name:
+ toxd = pathlib.Path(toxd_name).absolute()
+ print(f"Adapting files for Tox 3.x from {cwd} to {toxd}")
+ copy_and_adapt(cwd, toxd)
+ with contextlib_chdir.chdir(toxd):
+ yield toxd
+
+
@contextlib.contextmanager
def _cfg_filename_cwd() -> Iterator[pathlib.Path]:
"""No arguments, parse the tox.ini file in the current directory."""
@@ -92,22 +109,5 @@ def _do_test_run_showconfig(filename: pathlib.Path) -> None:
def test_run_showconfig(cfg_filename: Callable[[], AbstractContextManager[pathlib.Path]]) -> None:
"""Run `tox --showconfig` expecting tox.ini to be in the specified directory."""
print()
- with cfg_filename() as filename:
+ with adapt_tox_cwd(), cfg_filename() as filename:
_do_test_run_showconfig(filename)
-
-
-def _do_test_call_tox_config(filename: pathlib.Path) -> None:
- """Invoke tox.config.Config() to parse the Tox configuration."""
- envs: Final = ttt_parse.parse_config(filename)
- print(f"Got some Tox environments: {' '.join(sorted(envs))}")
- for envname, expected in _EXPECTED.items():
- print(f"- envname {envname!r} expected {expected!r}")
- assert envs[envname].tags == expected
-
-
-@pytest.mark.parametrize("cfg_filename", [_cfg_filename_cwd, _cfg_filename_tempdir])
-def test_call_tox_config(cfg_filename: Callable[[], AbstractContextManager[pathlib.Path]]) -> None:
- """Parse the tox.ini file in the specified directory."""
- print()
- with cfg_filename() as filename:
- _do_test_call_tox_config(filename)