From 7680ef513253f9eb97bc1557a3ea9f3ad33257bd Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 13 Jun 2025 10:53:22 -0300 Subject: [PATCH 01/12] Drop old Python versions and add Python 3.13 --- setup.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 76a34b7..53245bf 100644 --- a/setup.py +++ b/setup.py @@ -27,9 +27,9 @@ def read(fname): 'random_order', ], include_package_data=True, - python_requires=">=3.5.0", + python_requires=">=3.9", install_requires=[ - 'pytest>=3.0.0', + 'pytest', ], classifiers=[ 'Development Status :: 5 - Production/Stable', @@ -38,14 +38,11 @@ def read(fname): 'Topic :: Software Development :: Testing', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', 'License :: OSI Approved :: MIT License', ], keywords='pytest random test order shuffle', From 44106f44b0280ba7372f086cb0834a7f30335222 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 13 Jun 2025 10:54:04 -0300 Subject: [PATCH 02/12] Update build-package.yml --- .github/workflows/build-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-package.yml b/.github/workflows/build-package.yml index c4ef3fa..2a3efb3 100644 --- a/.github/workflows/build-package.yml +++ b/.github/workflows/build-package.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v3 From ad7753dd108916d756f73ee92f2677197f5bdbd6 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 13 Jun 2025 10:56:47 -0300 Subject: [PATCH 03/12] Update README.rst --- README.rst | 52 ---------------------------------------------------- 1 file changed, 52 deletions(-) diff --git a/README.rst b/README.rst index d964379..98f92c4 100644 --- a/README.rst +++ b/README.rst @@ -228,58 +228,6 @@ pass undeservedly, you can disable it: Note that randomisation is disabled by default. By passing ``-p no:random_order`` you are stopping the plugin from being registered so its hooks won't be registered and its command line options won't appear in ``--help``. --------------- -Changelog --------------- - -v1.1.1 (2024-01-20) -+++++++++++++++++++ - - * Fixes #54 - ``AttributeError`` when cacheprovider plugin disabled. Thanks @jhanm12 - - -v1.1.0 (2022-12-03) -+++++++++++++++++++ - - * Fixes xdist support (thanks @matejsp) - - -v1.0.4 (2018-11-30) -+++++++++++++++++++ - -* Fixes issues with doctests reported in #36 - ``class``, ``package`` and ``module`` didn't work - because ``DoctestItem`` doesn't have ``cls`` or ``module`` attributes. Thanks @tobywf. -* Deprecate ``none`` bucket type. **Update**: this was a mistake, it will be kept for backwards compatibility. -* With tox, run tests of pytest-random-order with both pytest 3 and 4. - -v1.0.3 (2018-11-16) -+++++++++++++++++++ - -* Fixes compatibility issues with pytest 4.0.0, works with pytest 3.0+ as before. -* Tests included in the source distribution. - -v1.0.0 (2018-10-20) -+++++++++++++++++++ - -* Plugin no longer alters the test order by default. You will have to either 1) pass ``--random-order``, - or ``--random-order-bucket=``, or ``--random-order-seed=``, or - 2) edit your pytest configuration file and add one of these options - there under ``addopts``, or 3) specify these flags in environment variable ``PYTEST_ADDOPTS``. -* Python 3.5+ is required. If you want to use this plugin with Python 2.7, use v0.8.0 which is stable and fine - if you are happy with it randomising the test order by default. -* The name under which the plugin registers itself is changed from ``random-order`` (hyphen) to ``random_order`` - (underscore). This addresses the issue of consistency when disabling or enabling this plugin via the standard - ``-p`` flag. Previously, the plugin could be disabled by passing ``-p no:random-order`` yet re-enabled - only by passing ``-p pytest_random_order.plugin``. Now they are ``-p no:random_order`` - to disable and ``-p random_order.plugin`` to enable (The ``.plugin`` bit, I think, is required because - pytest probably thinks it's an unrelated thing to ``random_order`` and import it, yet without it it's the - same thing so doesn't import it). - - -v0.8.0 -++++++ - -* pytest cache plugin's ``--failed-first`` works now. ------- Credits From c13e8b6e9969e21338a427183d415cb564597ede Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 13 Jun 2025 10:57:06 -0300 Subject: [PATCH 04/12] Create CHANGELOG.rst --- CHANGELOG.rst | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 CHANGELOG.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000..13a7fc4 --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,53 @@ + +-------------- +Changelog +-------------- + +v1.1.1 (2024-01-20) ++++++++++++++++++++ + + * Fixes #54 - ``AttributeError`` when cacheprovider plugin disabled. Thanks @jhanm12 + + +v1.1.0 (2022-12-03) ++++++++++++++++++++ + + * Fixes xdist support (thanks @matejsp) + + +v1.0.4 (2018-11-30) ++++++++++++++++++++ + +* Fixes issues with doctests reported in #36 - ``class``, ``package`` and ``module`` didn't work + because ``DoctestItem`` doesn't have ``cls`` or ``module`` attributes. Thanks @tobywf. +* Deprecate ``none`` bucket type. **Update**: this was a mistake, it will be kept for backwards compatibility. +* With tox, run tests of pytest-random-order with both pytest 3 and 4. + +v1.0.3 (2018-11-16) ++++++++++++++++++++ + +* Fixes compatibility issues with pytest 4.0.0, works with pytest 3.0+ as before. +* Tests included in the source distribution. + +v1.0.0 (2018-10-20) ++++++++++++++++++++ + +* Plugin no longer alters the test order by default. You will have to either 1) pass ``--random-order``, + or ``--random-order-bucket=``, or ``--random-order-seed=``, or + 2) edit your pytest configuration file and add one of these options + there under ``addopts``, or 3) specify these flags in environment variable ``PYTEST_ADDOPTS``. +* Python 3.5+ is required. If you want to use this plugin with Python 2.7, use v0.8.0 which is stable and fine + if you are happy with it randomising the test order by default. +* The name under which the plugin registers itself is changed from ``random-order`` (hyphen) to ``random_order`` + (underscore). This addresses the issue of consistency when disabling or enabling this plugin via the standard + ``-p`` flag. Previously, the plugin could be disabled by passing ``-p no:random-order`` yet re-enabled + only by passing ``-p pytest_random_order.plugin``. Now they are ``-p no:random_order`` + to disable and ``-p random_order.plugin`` to enable (The ``.plugin`` bit, I think, is required because + pytest probably thinks it's an unrelated thing to ``random_order`` and import it, yet without it it's the + same thing so doesn't import it). + + +v0.8.0 +++++++ + +* pytest cache plugin's ``--failed-first`` works now. From e6daddbcfe04a9ced5eb5466bcd2ff34de203427 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 13 Jun 2025 10:57:53 -0300 Subject: [PATCH 05/12] Update CHANGELOG.rst --- CHANGELOG.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 13a7fc4..7957e3b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,11 @@ Changelog -------------- +UNRELEASED (UNRELEASED) ++++++++++++++++++++++++ + +* Dropped support for EOL Python versions and added support for Python 3.13. + v1.1.1 (2024-01-20) +++++++++++++++++++ From 6ab17fca8509901d9c919fccd9f7835290710d8b Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 13 Jun 2025 10:58:05 -0300 Subject: [PATCH 06/12] Update CHANGELOG.rst --- CHANGELOG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7957e3b..fcaf611 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,8 +3,8 @@ Changelog -------------- -UNRELEASED (UNRELEASED) -+++++++++++++++++++++++ +UNRELEASED +++++++++++ * Dropped support for EOL Python versions and added support for Python 3.13. From e70354c1f53f3604237a9e89d2529c238489dc4f Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 22 Jun 2025 10:50:10 -0300 Subject: [PATCH 07/12] Modernize workflows and settings --- .github/dependabot.yml | 13 +++ .github/workflows/build-package.yml | 42 ------- .github/workflows/deploy.yml | 56 ++++++++++ .github/workflows/publish-package.yml | 35 ------ .github/workflows/test.yml | 59 ++++++++++ .pre-commit-config.yaml | 16 +++ RELEASING.rst | 12 ++ pyproject.toml | 17 +++ random_order/bucket_types.py | 17 ++- random_order/cache.py | 10 +- random_order/config.py | 20 ++-- random_order/plugin.py | 56 +++++----- random_order/shuffler.py | 10 +- random_order/xdist.py | 5 +- requirements.txt | 5 - setup.cfg | 15 --- setup.py | 55 +++++---- tests/conftest.py | 28 ++--- tests/test_actual_test_runs.py | 154 ++++++++++++++------------ tests/test_cli.py | 24 ++-- tests/test_doctests.py | 40 ++++--- tests/test_markers.py | 23 ++-- tests/test_plugin_failure.py | 16 +-- tests/test_shuffle.py | 87 +++++++++++---- tests/test_xdist.py | 3 +- tox.ini | 10 ++ 26 files changed, 488 insertions(+), 340 deletions(-) create mode 100644 .github/dependabot.yml delete mode 100644 .github/workflows/build-package.yml create mode 100644 .github/workflows/deploy.yml delete mode 100644 .github/workflows/publish-package.yml create mode 100644 .github/workflows/test.yml create mode 100644 .pre-commit-config.yaml create mode 100644 RELEASING.rst create mode 100644 pyproject.toml delete mode 100644 requirements.txt delete mode 100644 setup.cfg create mode 100644 tox.ini diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..be006de --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +# Keep GitHub Actions up to date with GitHub's Dependabot... +# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + groups: + github-actions: + patterns: + - "*" # Group all Actions updates into a single larger pull request + schedule: + interval: weekly diff --git a/.github/workflows/build-package.yml b/.github/workflows/build-package.yml deleted file mode 100644 index 2a3efb3..0000000 --- a/.github/workflows/build-package.yml +++ /dev/null @@ -1,42 +0,0 @@ -# This workflow will install Python dependencies, run tests and lint with a variety of Python versions -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python - -name: Build package - -on: - [push] - -jobs: - build: - - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] - - steps: - - uses: actions/checkout@v3 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install flake8 pytest - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - pip install . - - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - - name: Test with pytest - run: | - pytest diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..e009732 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,56 @@ +name: deploy + +on: + workflow_dispatch: + inputs: + version: + description: 'Release version' + required: true + default: '1.2.3' + +jobs: + + package: + runs-on: ubuntu-latest + env: + SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.event.inputs.version }} + + steps: + - uses: actions/checkout@v3 + + - name: Build and Check Package + uses: hynek/build-and-inspect-python-package@v1.5 + + deploy: + needs: package + runs-on: ubuntu-latest + environment: deploy + permissions: + id-token: write # For PyPI trusted publishers. + contents: write # For tag and release notes. + + steps: + - uses: actions/checkout@v3 + + - name: Download Package + uses: actions/download-artifact@v3 + with: + name: Packages + path: dist + + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@v1.8.5 + + - name: Push tag + run: | + git config user.name "pytest bot" + git config user.email "pytestbot@gmail.com" + git tag --annotate --message=v${{ github.event.inputs.version }} v${{ github.event.inputs.version }} ${{ github.sha }} + git push origin v${{ github.event.inputs.version }} + + - name: GitHub Release + uses: softprops/action-gh-release@v1 + with: + body_path: scripts/latest-release-notes.md + files: dist/* + tag_name: v${{ github.event.inputs.version }} diff --git a/.github/workflows/publish-package.yml b/.github/workflows/publish-package.yml deleted file mode 100644 index e9e92a0..0000000 --- a/.github/workflows/publish-package.yml +++ /dev/null @@ -1,35 +0,0 @@ -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python - -name: Publish package - -on: - release: - types: [published] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - - uses: actions/checkout@v3 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: '3.x' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build - - - name: Build package - run: python -m build - - - name: Publish a Python distribution to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..926a590 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,59 @@ +name: test + +on: + push: + branches: + - main + - "test-me-*" + + pull_request: + + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + + package: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Build and Check Package + uses: hynek/build-and-inspect-python-package@v1.5 + + test: + + needs: [package] + + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + python: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + os: [ubuntu-latest, windows-latest] + + steps: + - uses: actions/checkout@v3 + + - name: Download Package + uses: actions/download-artifact@v3 + with: + name: Packages + path: dist + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + + - name: Install tox + run: | + python -m pip install --upgrade pip + pip install tox + + - name: Test + shell: bash + run: | + tox run -e py --installpkg `find dist/*.tar.gz` diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..4206469 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +exclude: '^($|.*\.bin)' +repos: + - repo: local + hooks: + - id: rst + name: rst + entry: rst-lint --encoding utf-8 + files: ^(CHANGELOG.rst|README.rst|HOWTORELEASE.rst)$ + language: python + additional_dependencies: [pygments, restructuredtext_lint] + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.3.3 + hooks: + - id: ruff + args: ["--fix"] + - id: ruff-format diff --git a/RELEASING.rst b/RELEASING.rst new file mode 100644 index 0000000..2c734f1 --- /dev/null +++ b/RELEASING.rst @@ -0,0 +1,12 @@ +Here are the steps on how to make a new release. + +1. Create a ``release-VERSION`` branch from ``upstream/main``. +2. Update ``CHANGELOG.rst``. +3. Push the branch to ``upstream``. +4. Once all tests pass, start the ``deploy`` workflow manually or via: + + ``` + gh workflow run deploy.yml --repo pytest-dev/pytest-mock --ref release-VERSION -f version=VERSION + ``` + +5. Merge the PR. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4839ac7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,17 @@ +[build-system] +requires = [ + "setuptools", + "setuptools-scm>=8", +] +build-backend = "setuptools.build_meta" + +[tool.pytest.ini_options] +testpaths = ["tests"] +addopts = "-r a" +asyncio_mode = "auto" +filterwarnings = [ + "ignore:.*usage of Session..*:DeprecationWarning" +] + +[tool.ruff] +line-length = 120 \ No newline at end of file diff --git a/random_order/bucket_types.py b/random_order/bucket_types.py index bc93034..b5cd150 100644 --- a/random_order/bucket_types.py +++ b/random_order/bucket_types.py @@ -11,7 +11,6 @@ def bucket_type_key(bucket_type): """ def decorator(f): - @functools.wraps(f) def wrapped(item, session): key = f(item) @@ -28,24 +27,24 @@ def wrapped(item, session): return decorator -@bucket_type_key('global') +@bucket_type_key("global") def get_global_key(item): return None -@bucket_type_key('package') +@bucket_type_key("package") def get_package_key(item): if not hasattr(item, "module"): return os.path.split(item.location[0])[0] return item.module.__package__ -@bucket_type_key('module') +@bucket_type_key("module") def get_module_key(item): return item.location[0] -@bucket_type_key('class') +@bucket_type_key("class") def get_class_key(item): if not hasattr(item, "cls"): return item.location[0] @@ -55,19 +54,19 @@ def get_class_key(item): return item.module.__name__ -@bucket_type_key('parent') +@bucket_type_key("parent") def get_parent_key(item): return item.parent -@bucket_type_key('grandparent') +@bucket_type_key("grandparent") def get_grandparent_key(item): return item.parent.parent -@bucket_type_key('none') +@bucket_type_key("none") def get_none_key(item): - raise RuntimeError('When shuffling is disabled (bucket_type=none), item key should not be calculated') + raise RuntimeError("When shuffling is disabled (bucket_type=none), item key should not be calculated") bucket_types = bucket_type_keys.keys() diff --git a/random_order/cache.py b/random_order/cache.py index 44e1ee5..8fa6827 100644 --- a/random_order/cache.py +++ b/random_order/cache.py @@ -5,24 +5,24 @@ """ -FAILED_FIRST_LAST_FAILED_BUCKET_KEY = '' +FAILED_FIRST_LAST_FAILED_BUCKET_KEY = "" def process_failed_first_last_failed(session, config, items): - if not hasattr(config, 'cache'): + if not hasattr(config, "cache"): return - if not config.getoption('failedfirst'): + if not config.getoption("failedfirst"): return - last_failed_raw = config.cache.get('cache/lastfailed', None) + last_failed_raw = config.cache.get("cache/lastfailed", None) if not last_failed_raw: return # Get the names of last failed tests last_failed = [] for key in last_failed_raw.keys(): - parts = key.split('::') + parts = key.split("::") if len(parts) == 3: last_failed.append(tuple(parts)) elif len(parts) == 2: diff --git a/random_order/config.py b/random_order/config.py index cf7b1d2..514619a 100644 --- a/random_order/config.py +++ b/random_order/config.py @@ -1,5 +1,4 @@ class Config: - @classmethod def default_value(cls, value): return "default:" + str(value) @@ -10,25 +9,22 @@ def __init__(self, config): @property def bucket_type(self): if not self.is_enabled: - return 'none' + return "none" else: - return self._remove_default_prefix(self._config.getoption('random_order_bucket')) + return self._remove_default_prefix(self._config.getoption("random_order_bucket")) @property def is_enabled(self): - return ( - self._config.getoption('random_order_enabled') or - any( - not self._config.getoption(name).startswith('default:') - for name in ('random_order_bucket', 'random_order_seed') - ) + return self._config.getoption("random_order_enabled") or any( + not self._config.getoption(name).startswith("default:") + for name in ("random_order_bucket", "random_order_seed") ) @property def seed(self): - return self._remove_default_prefix(self._config.getoption('random_order_seed')) + return self._remove_default_prefix(self._config.getoption("random_order_seed")) def _remove_default_prefix(self, value): - if value.startswith('default:'): - return value[len('default:'):] + if value.startswith("default:"): + return value[len("default:") :] return value diff --git a/random_order/plugin.py b/random_order/plugin.py index 9e2390f..195e188 100644 --- a/random_order/plugin.py +++ b/random_order/plugin.py @@ -13,45 +13,44 @@ def pytest_addoption(parser): - group = parser.getgroup('pytest-random-order options') + group = parser.getgroup("pytest-random-order options") group.addoption( - '--random-order', - action='store_true', - dest='random_order_enabled', - help='Randomise test order (by default, it is disabled) with default configuration.', + "--random-order", + action="store_true", + dest="random_order_enabled", + help="Randomise test order (by default, it is disabled) with default configuration.", ) group.addoption( - '--random-order-bucket', - action='store', - dest='random_order_bucket', - default=Config.default_value('module'), + "--random-order-bucket", + action="store", + dest="random_order_bucket", + default=Config.default_value("module"), choices=bucket_types, - help='Randomise test order within specified test buckets.', + help="Randomise test order within specified test buckets.", ) group.addoption( - '--random-order-seed', - action='store', - dest='random_order_seed', + "--random-order-seed", + action="store", + dest="random_order_seed", default=Config.default_value(str(random.randint(1, 1000000))), - help='Randomise test order using a specific seed.', + help="Randomise test order using a specific seed.", ) def pytest_configure(config): config.addinivalue_line( - 'markers', - 'random_order(disabled=True): disable reordering of tests within a module or class' + "markers", "random_order(disabled=True): disable reordering of tests within a module or class" ) - if config.pluginmanager.hasplugin('xdist'): + if config.pluginmanager.hasplugin("xdist"): config.pluginmanager.register(XdistHooks()) - if hasattr(config, 'workerinput'): + if hasattr(config, "workerinput"): # pytest-xdist: use seed generated on main. - seed = config.workerinput['random_order_seed'] - if hasattr(config, 'cache'): + seed = config.workerinput["random_order_seed"] + if hasattr(config, "cache"): assert config.cache is not None - config.cache.set('random_order_seed', seed) + config.cache.set("random_order_seed", seed) config.option.random_order_seed = seed @@ -59,10 +58,9 @@ def pytest_report_header(config): plugin = Config(config) if not plugin.is_enabled: return "Test order randomisation NOT enabled. Enable with --random-order or --random-order-bucket=" - return ( - 'Using --random-order-bucket={plugin.bucket_type}\n' - 'Using --random-order-seed={plugin.seed}\n' - ).format(plugin=plugin) + return ("Using --random-order-bucket={plugin.bucket_type}\n" "Using --random-order-seed={plugin.seed}\n").format( + plugin=plugin + ) def pytest_collection_modifyitems(session, config, items): @@ -78,7 +76,7 @@ def pytest_collection_modifyitems(session, config, items): try: seed = plugin.seed bucket_type = plugin.bucket_type - if bucket_type != 'none': + if bucket_type != "none": _shuffle_items( items, bucket_key=bucket_type_keys[bucket_type], @@ -90,8 +88,8 @@ def pytest_collection_modifyitems(session, config, items): except Exception as e: # See the finally block -- we only fail if we have lost user's tests. _, _, exc_tb = sys.exc_info() - failure = 'pytest-random-order plugin has failed with {0!r}:\n{1}'.format( - e, ''.join(traceback.format_tb(exc_tb, 10)) + failure = "pytest-random-order plugin has failed with {0!r}:\n{1}".format( + e, "".join(traceback.format_tb(exc_tb, 10)) ) if not hasattr(pytest, "PytestWarning"): config.warn(0, failure, None) @@ -102,5 +100,5 @@ def pytest_collection_modifyitems(session, config, items): # Fail only if we have lost user's tests if item_ids != _get_set_of_item_ids(items): if not failure: - failure = 'pytest-random-order plugin has failed miserably' + failure = "pytest-random-order plugin has failed miserably" raise RuntimeError(failure) diff --git a/random_order/shuffler.py b/random_order/shuffler.py index 1211a6e..9ee3299 100644 --- a/random_order/shuffler.py +++ b/random_order/shuffler.py @@ -16,7 +16,7 @@ to preserve a distinct disabled sub-bucket within a larger bucket and not mix it up with another disabled sub-bucket of the same larger bucket. """ -ItemKey = namedtuple('ItemKey', field_names=('bucket', 'disabled', 'x')) +ItemKey = namedtuple("ItemKey", field_names=("bucket", "disabled", "x")) ItemKey.__new__.__defaults__ = (None, None) @@ -101,12 +101,12 @@ def _get_set_of_item_ids(items): def _disable(item, session): - if hasattr(item, 'get_closest_marker'): - marker = item.get_closest_marker('random_order') + if hasattr(item, "get_closest_marker"): + marker = item.get_closest_marker("random_order") else: - marker = item.get_marker('random_order') + marker = item.get_marker("random_order") if marker: - is_disabled = marker.kwargs.get('disabled', False) + is_disabled = marker.kwargs.get("disabled", False) if is_disabled: # A test item can only be disabled in its parent context -- where it is part of some order. # We use parent name as the key so that all children of the same parent get the same disabled key. diff --git a/random_order/xdist.py b/random_order/xdist.py index d79e978..84f5662 100644 --- a/random_order/xdist.py +++ b/random_order/xdist.py @@ -2,7 +2,6 @@ class XdistHooks: - def pytest_configure_node(self, node: pytest.Item) -> None: - seed = node.config.getoption('random_order_seed') - node.workerinput['random_order_seed'] = seed + seed = node.config.getoption("random_order_seed") + node.workerinput["random_order_seed"] = seed diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 119c20d..0000000 --- a/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -coverage -py -pytest -pytest-xdist -sphinx diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 0ea7b8d..0000000 --- a/setup.cfg +++ /dev/null @@ -1,15 +0,0 @@ -[flake8] -max-line-length = 120 -exclude = - .tox - build - -[isort] -known_first_party = - random_order -line_length = 120 -multi_line_output = 5 -not_skip = __init__.py -skip = - .tox - build diff --git a/setup.py b/setup.py index 53245bf..5200544 100644 --- a/setup.py +++ b/setup.py @@ -9,46 +9,45 @@ def read(fname): file_path = os.path.join(os.path.dirname(__file__), fname) - return codecs.open(file_path, encoding='utf-8').read() + return codecs.open(file_path, encoding="utf-8").read() setup( - name='pytest-random-order', - version='1.1.1', - author='Jazeps Basko', - author_email='jazeps.basko@gmail.com', - maintainer='Jazeps Basko', - maintainer_email='jazeps.basko@gmail.com', - license='MIT', - url='https://github.com/jbasko/pytest-random-order', - description='Randomise the order in which pytest tests are run with some control over the randomness', - long_description=read('README.rst'), + name="pytest-random-order", + author="Jazeps Basko", + author_email="jazeps.basko@gmail.com", + maintainer="Jazeps Basko", + maintainer_email="jazeps.basko@gmail.com", + license="MIT", + url="https://github.com/jbasko/pytest-random-order", + description="Randomise the order in which pytest tests are run with some control over the randomness", + long_description=read("README.rst"), packages=[ - 'random_order', + "random_order", ], include_package_data=True, python_requires=">=3.9", install_requires=[ - 'pytest', + "pytest", ], classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Framework :: Pytest', - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Testing', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - 'Programming Language :: Python :: 3.13', - 'License :: OSI Approved :: MIT License', + "Development Status :: 5 - Production/Stable", + "Framework :: Pytest", + "Intended Audience :: Developers", + "Topic :: Software Development :: Testing", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "License :: OSI Approved :: MIT License", ], - keywords='pytest random test order shuffle', + keywords="pytest random test order shuffle", entry_points={ - 'pytest11': [ - 'random_order = random_order.plugin', # >=1.0.0 + "pytest11": [ + "random_order = random_order.plugin", # >=1.0.0 ], }, ) diff --git a/tests/conftest.py b/tests/conftest.py index 8f02add..c183eee 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,10 +2,10 @@ import pytest -pytest_plugins = 'pytester' +pytest_plugins = "pytester" -Call = collections.namedtuple('Call', field_names=('package', 'module', 'cls', 'name')) +Call = collections.namedtuple("Call", field_names=("package", "module", "cls", "name")) def _get_test_calls(result): @@ -14,13 +14,15 @@ def _get_test_calls(result): """ calls = [] - for c in result.reprec.getcalls('pytest_runtest_call'): - calls.append(Call( - package=c.item.module.__package__, - module=c.item.module.__name__, - cls=(c.item.module.__name__, c.item.cls.__name__) if c.item.cls else None, - name=c.item.name, - )) + for c in result.reprec.getcalls("pytest_runtest_call"): + calls.append( + Call( + package=c.item.module.__package__, + module=c.item.module.__name__, + cls=(c.item.module.__name__, c.item.cls.__name__) if c.item.cls else None, + name=c.item.name, + ) + ) return tuple(calls) @@ -36,13 +38,13 @@ def get_test_calls(): def twenty_tests(): code = [] for i in range(20): - code.append('def test_a{0}(): assert True\n'.format(str(i).zfill(2))) - return ''.join(code) + code.append("def test_a{0}(): assert True\n".format(str(i).zfill(2))) + return "".join(code) @pytest.fixture def twenty_cls_tests(): code = [] for i in range(20): - code.append('\tdef test_b{0}(self): self.assertTrue\n'.format(str(i).zfill(2))) - return ''.join(code) + code.append("\tdef test_b{0}(self): self.assertTrue\n".format(str(i).zfill(2))) + return "".join(code) diff --git a/tests/test_actual_test_runs.py b/tests/test_actual_test_runs.py index 593f52d..f2d29a8 100644 --- a/tests/test_actual_test_runs.py +++ b/tests/test_actual_test_runs.py @@ -22,50 +22,61 @@ def tmp_tree_of_tests(testdir): If module name doesn't start with "test_", it isn't picked up by runpytest. """ - sup = testdir.mkpydir('shallow_tests') + sup = testdir.mkpydir("shallow_tests") - sup.join('test_a.py').write(py.code.Source(""" + sup.join("test_a.py").write( + py.code.Source(""" def test_a1(): assert False def test_a2(): assert True def test_a3(): assert True - """)) + """) + ) - sup.join('test_ax.py').write(py.code.Source(""" + sup.join("test_ax.py").write( + py.code.Source(""" def test_ax1(): assert True def test_ax2(): assert True def test_ax3(): assert True - """)) + """) + ) - sub = testdir.mkpydir('shallow_tests/deep_tests') + sub = testdir.mkpydir("shallow_tests/deep_tests") - sub.join('test_b.py').write(py.code.Source(""" + sub.join("test_b.py").write( + py.code.Source(""" def test_b1(): assert True def test_b2(): assert False def test_b3(): assert True - """)) + """) + ) - sub.join('test_c.py').write(py.code.Source(""" + sub.join("test_c.py").write( + py.code.Source(""" def test_c1(): assert True - """)) + """) + ) - sub.join('test_d.py').write(py.code.Source(""" + sub.join("test_d.py").write( + py.code.Source(""" def test_d1(): assert True def test_d2(): assert True - """)) + """) + ) - sub.join('test_e.py').write(py.code.Source(""" + sub.join("test_e.py").write( + py.code.Source(""" from unittest import TestCase class EeTest(TestCase): def test_ee1(self): @@ -80,12 +91,13 @@ def test_ex1(self): self.assertTrue(True) def test_ex2(self): self.assertTrue(True) - """)) + """) + ) return testdir -def check_call_sequence(seq, bucket='module'): +def check_call_sequence(seq, bucket="module"): all_values = collections.defaultdict(list) num_switches = collections.defaultdict(int) @@ -98,35 +110,35 @@ def inspect_attr(this_call, prev_call, attr_name): for i, this_call in enumerate(seq): prev_call = seq[i - 1] if i > 0 else None - inspect_attr(this_call, prev_call, 'package') - inspect_attr(this_call, prev_call, 'module') - inspect_attr(this_call, prev_call, 'cls') + inspect_attr(this_call, prev_call, "package") + inspect_attr(this_call, prev_call, "module") + inspect_attr(this_call, prev_call, "cls") - num_packages = len(set(all_values['package'])) - num_package_switches = num_switches['package'] - num_modules = len(set(all_values['module'])) - num_module_switches = num_switches['module'] - num_classes = len(set(all_values['class'])) - num_class_switches = num_switches['class'] + num_packages = len(set(all_values["package"])) + num_package_switches = num_switches["package"] + num_modules = len(set(all_values["module"])) + num_module_switches = num_switches["module"] + num_classes = len(set(all_values["class"])) + num_class_switches = num_switches["class"] # These are just sanity tests, the actual shuffling is tested in test_shuffle, # assertions here are very relaxed. - if bucket == 'global': + if bucket == "global": if num_module_switches <= num_modules: - pytest.fail('Too few module switches for global shuffling') + pytest.fail("Too few module switches for global shuffling") if num_package_switches <= num_packages: - pytest.fail('Too few package switches for global shuffling') + pytest.fail("Too few package switches for global shuffling") - elif bucket == 'package': + elif bucket == "package": assert num_package_switches == num_packages if num_module_switches <= num_modules: - pytest.fail('Too few module switches for package-limited shuffling') + pytest.fail("Too few module switches for package-limited shuffling") - elif bucket == 'module': + elif bucket == "module": assert num_module_switches == num_modules - elif bucket == 'class': + elif bucket == "class": # Each class can contribute to 1 or 2 switches. assert num_class_switches <= num_classes * 2 @@ -136,20 +148,23 @@ def inspect_attr(this_call, prev_call, attr_name): assert num_modules <= num_module_switches <= num_modules + 1 -@pytest.mark.parametrize('bucket,min_sequences,max_sequences', [ - ('class', 2, 5), - ('module', 2, 5), - ('package', 2, 5), - ('global', 2, 5), - ('none', 1, 1), - ('parent', 1, 5), - ('grandparent', 1, 5), -]) +@pytest.mark.parametrize( + "bucket,min_sequences,max_sequences", + [ + ("class", 2, 5), + ("module", 2, 5), + ("package", 2, 5), + ("global", 2, 5), + ("none", 1, 1), + ("parent", 1, 5), + ("grandparent", 1, 5), + ], +) def test_it_works_with_actual_tests(tmp_tree_of_tests, get_test_calls, bucket, min_sequences, max_sequences): sequences = set() for x in range(5): - result = tmp_tree_of_tests.runpytest('--random-order-bucket={0}'.format(bucket), '--verbose') + result = tmp_tree_of_tests.runpytest("--random-order-bucket={0}".format(bucket), "--verbose") result.assert_outcomes(passed=14, failed=3) seq = get_test_calls(result) check_call_sequence(seq, bucket=bucket) @@ -162,68 +177,71 @@ def test_it_works_with_actual_tests(tmp_tree_of_tests, get_test_calls, bucket, m def test_random_order_seed_is_respected(testdir, twenty_tests, get_test_calls): testdir.makepyfile(twenty_tests) call_sequences = { - '1': None, - '2': None, - '3': None, + "1": None, + "2": None, + "3": None, } for seed in call_sequences.keys(): - result = testdir.runpytest('--random-order-seed={0}'.format(seed)) + result = testdir.runpytest("--random-order-seed={0}".format(seed)) - result.stdout.fnmatch_lines([ - '*Using --random-order-seed={0}*'.format(seed), - ]) + result.stdout.fnmatch_lines( + [ + "*Using --random-order-seed={0}*".format(seed), + ] + ) result.assert_outcomes(passed=20) call_sequences[seed] = get_test_calls(result) for seed in call_sequences.keys(): - result = testdir.runpytest('--random-order-seed={0}'.format(seed)) + result = testdir.runpytest("--random-order-seed={0}".format(seed)) result.assert_outcomes(passed=20) assert call_sequences[seed] == get_test_calls(result) - assert call_sequences['1'] != call_sequences['2'] != call_sequences['3'] + assert call_sequences["1"] != call_sequences["2"] != call_sequences["3"] def test_generated_seed_is_reported_and_run_can_be_reproduced(testdir, twenty_tests, get_test_calls): testdir.makepyfile(twenty_tests) - result = testdir.runpytest('-v', '--random-order') + result = testdir.runpytest("-v", "--random-order") result.assert_outcomes(passed=20) - result.stdout.fnmatch_lines([ - '*Using --random-order-seed=*' - ]) + result.stdout.fnmatch_lines(["*Using --random-order-seed=*"]) calls = get_test_calls(result) # find the seed in output seed = None for line in result.outlines: - g = re.match('^Using --random-order-seed=(.+)$', line) + g = re.match("^Using --random-order-seed=(.+)$", line) if g: seed = g.group(1) break assert seed - result2 = testdir.runpytest('-v', '--random-order-seed={0}'.format(seed)) + result2 = testdir.runpytest("-v", "--random-order-seed={0}".format(seed)) result2.assert_outcomes(passed=20) calls2 = get_test_calls(result2) assert calls == calls2 -@pytest.mark.parametrize('bucket', [ - 'global', - 'package', - 'module', - 'class', - 'parent', - 'grandparent', - 'none', -]) +@pytest.mark.parametrize( + "bucket", + [ + "global", + "package", + "module", + "class", + "parent", + "grandparent", + "none", + ], +) def test_failed_first(tmp_tree_of_tests, get_test_calls, bucket): - result1 = tmp_tree_of_tests.runpytest('--random-order-bucket={0}'.format(bucket), '--verbose') + result1 = tmp_tree_of_tests.runpytest("--random-order-bucket={0}".format(bucket), "--verbose") result1.assert_outcomes(passed=14, failed=3) - result2 = tmp_tree_of_tests.runpytest('--random-order-bucket={0}'.format(bucket), '--failed-first', '--verbose') + result2 = tmp_tree_of_tests.runpytest("--random-order-bucket={0}".format(bucket), "--failed-first", "--verbose") result2.assert_outcomes(passed=14, failed=3) calls2 = get_test_calls(result2) first_three_tests = set(c.name for c in calls2[:3]) - assert set(['test_a1', 'test_b2', 'test_ee2']) == first_three_tests + assert set(["test_a1", "test_b2", "test_ee2"]) == first_three_tests diff --git a/tests/test_cli.py b/tests/test_cli.py index 15ee8e0..bbadac3 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,18 +1,22 @@ def test_help_message(testdir): result = testdir.runpytest( - '--help', + "--help", + ) + result.stdout.fnmatch_lines( + [ + "pytest-random-order options:", + "*--random-order-bucket={global,package,module,class,parent,grandparent,none}*", + "*--random-order-seed=*", + ] ) - result.stdout.fnmatch_lines([ - 'pytest-random-order options:', - '*--random-order-bucket={global,package,module,class,parent,grandparent,none}*', - '*--random-order-seed=*', - ]) def test_markers_message(testdir): result = testdir.runpytest( - '--markers', + "--markers", + ) + result.stdout.fnmatch_lines( + [ + "*@pytest.mark.random_order(disabled=True): disable reordering*", + ] ) - result.stdout.fnmatch_lines([ - '*@pytest.mark.random_order(disabled=True): disable reordering*', - ]) diff --git a/tests/test_doctests.py b/tests/test_doctests.py index cd159f1..3aed080 100644 --- a/tests/test_doctests.py +++ b/tests/test_doctests.py @@ -12,10 +12,11 @@ def tmp_tree_of_tests(testdir): """ - utils_package = testdir.mkpydir('utils') - utils_package.join('__init__.py').write('') + utils_package = testdir.mkpydir("utils") + utils_package.join("__init__.py").write("") - utils_package.join('foo.py').write(py.code.Source(''' + utils_package.join("foo.py").write( + py.code.Source(''' def add(a, b): """ >>> add(1, 1) @@ -33,24 +34,31 @@ def subtract(a, b): -2 """ return a - b - ''')) + ''') + ) return testdir -@pytest.mark.parametrize('bucket', [ - 'global', - 'package', - 'module', - 'class', - 'parent', - 'grandparent', - 'none', -]) +@pytest.mark.parametrize( + "bucket", + [ + "global", + "package", + "module", + "class", + "parent", + "grandparent", + "none", + ], +) def test_doctests(tmp_tree_of_tests, get_test_calls, bucket): result1 = tmp_tree_of_tests.runpytest( - '--doctest-modules', '--random-order-bucket={0}'.format(bucket), '--verbose', '-s', + "--doctest-modules", + "--random-order-bucket={0}".format(bucket), + "--verbose", + "-s", ) result1.assert_outcomes(passed=2, failed=0) - assert 'PytestWarning' not in result1.stdout.str() - assert 'PytestWarning' not in result1.stderr.str() + assert "PytestWarning" not in result1.stdout.str() + assert "PytestWarning" not in result1.stderr.str() diff --git a/tests/test_markers.py b/tests/test_markers.py index 03ceeee..a301c37 100644 --- a/tests/test_markers.py +++ b/tests/test_markers.py @@ -1,15 +1,13 @@ import pytest -@pytest.mark.parametrize('disabled', [True, False]) +@pytest.mark.parametrize("disabled", [True, False]) def test_marker_disables_random_order_in_module(testdir, twenty_tests, get_test_calls, disabled): testdir.makepyfile( - 'import pytest\n' + - ('pytestmark = pytest.mark.random_order(disabled={0})\n'.format(disabled)) + - twenty_tests + "import pytest\n" + ("pytestmark = pytest.mark.random_order(disabled={0})\n".format(disabled)) + twenty_tests ) - result = testdir.runpytest('--random-order', '-v') + result = testdir.runpytest("--random-order", "-v") result.assert_outcomes(passed=20) names = [c.name for c in get_test_calls(result)] sorted_names = sorted(list(names)) @@ -20,17 +18,18 @@ def test_marker_disables_random_order_in_module(testdir, twenty_tests, get_test_ assert names != sorted_names -@pytest.mark.parametrize('disabled', [True, False]) +@pytest.mark.parametrize("disabled", [True, False]) def test_marker_disables_random_order_in_class(testdir, twenty_cls_tests, get_test_calls, disabled): testdir.makepyfile( - 'import pytest\n\n' + - 'from unittest import TestCase\n\n' + - 'class MyTest(TestCase):\n' + - '\tpytestmark = pytest.mark.random_order(disabled={0})\n'.format(disabled) + - twenty_cls_tests + '\n' + "import pytest\n\n" + + "from unittest import TestCase\n\n" + + "class MyTest(TestCase):\n" + + "\tpytestmark = pytest.mark.random_order(disabled={0})\n".format(disabled) + + twenty_cls_tests + + "\n" ) - result = testdir.runpytest('--random-order', '-v') + result = testdir.runpytest("--random-order", "-v") result.assert_outcomes(passed=20) names = [c.name for c in get_test_calls(result)] sorted_names = sorted(list(names)) diff --git a/tests/test_plugin_failure.py b/tests/test_plugin_failure.py index f1fb922..3ca588d 100644 --- a/tests/test_plugin_failure.py +++ b/tests/test_plugin_failure.py @@ -3,13 +3,13 @@ def acceptably_failing_shuffle_items(items, **kwargs): # Does not mess up items collection - raise ValueError('shuffling failed') + raise ValueError("shuffling failed") def critically_failing_shuffle_items(items, **kwargs): # Messes up items collection, an item is effectively lost items[1] = items[0] - raise ValueError('shuffling failed') + raise ValueError("shuffling failed") def critically_not_failing_shuffle_items(items, **kwargs): @@ -30,9 +30,9 @@ def test_a2(): def test_faulty_shuffle_that_preserves_items_does_not_fail_test_run(monkeypatch, simple_testdir): - monkeypatch.setattr('random_order.plugin._shuffle_items', acceptably_failing_shuffle_items) + monkeypatch.setattr("random_order.plugin._shuffle_items", acceptably_failing_shuffle_items) - result = simple_testdir.runpytest('--random-order') + result = simple_testdir.runpytest("--random-order") result.assert_outcomes(passed=2) result.stdout.fnmatch_lines(""" *pytest-random-order plugin has failed with ValueError* @@ -40,8 +40,8 @@ def test_faulty_shuffle_that_preserves_items_does_not_fail_test_run(monkeypatch, def test_faulty_shuffle_that_loses_items_fails_test_run(monkeypatch, simple_testdir): - monkeypatch.setattr('random_order.plugin._shuffle_items', critically_failing_shuffle_items) - result = simple_testdir.runpytest('--random-order') + monkeypatch.setattr("random_order.plugin._shuffle_items", critically_failing_shuffle_items) + result = simple_testdir.runpytest("--random-order") result.assert_outcomes(passed=0, failed=0, skipped=0) result.stdout.fnmatch_lines(""" *INTERNALERROR> RuntimeError: pytest-random-order plugin has failed with ValueError* @@ -49,8 +49,8 @@ def test_faulty_shuffle_that_loses_items_fails_test_run(monkeypatch, simple_test def test_seemingly_ok_shuffle_that_loses_items_fails_test_run(monkeypatch, simple_testdir): - monkeypatch.setattr('random_order.plugin._shuffle_items', critically_not_failing_shuffle_items) - result = simple_testdir.runpytest('--random-order') + monkeypatch.setattr("random_order.plugin._shuffle_items", critically_not_failing_shuffle_items) + result = simple_testdir.runpytest("--random-order") result.assert_outcomes(passed=0, failed=0, skipped=0) result.stdout.fnmatch_lines(""" *INTERNALERROR> RuntimeError: pytest-random-order plugin has failed miserably* diff --git a/tests/test_shuffle.py b/tests/test_shuffle.py index 70d2415..13373f7 100644 --- a/tests/test_shuffle.py +++ b/tests/test_shuffle.py @@ -23,11 +23,14 @@ def disable_if_gt_1000(item, session): return False -@pytest.mark.parametrize('key', [ - None, - lambda item, session: None, - lambda item, session: item % 2, -]) +@pytest.mark.parametrize( + "key", + [ + None, + lambda item, session: None, + lambda item, session: item % 2, + ], +) def test_shuffles_empty_list_in_place(key): items = [] items_id = id(items) @@ -36,11 +39,14 @@ def test_shuffles_empty_list_in_place(key): assert id(items) == items_id -@pytest.mark.parametrize('key', [ - None, - lambda item, session: None, - lambda item, session: item % 2, -]) +@pytest.mark.parametrize( + "key", + [ + None, + lambda item, session: None, + lambda item, session: item % 2, + ], +) def test_shuffles_one_item_list_in_place(key): items = [42] items_id = id(items) @@ -75,14 +81,22 @@ def test_two_bucket_reshuffle(): def test_eight_bucket_reshuffle(): # This is a cross-check to test shuffling of buckets. items = [ - 1, 1, - 2, 2, - 3, 3, - 4, 4, - 5, 5, - 6, 6, - 7, 7, - 8, 8, + 1, + 1, + 2, + 2, + 3, + 3, + 4, + 4, + 5, + 5, + 6, + 6, + 7, + 7, + 8, + 8, ] items_copy = list(items) @@ -98,8 +112,26 @@ def test_eight_bucket_reshuffle(): def test_shuffle_respects_single_disabled_group_in_each_of_two_buckets(): items = [ - 11, 13, 9995, 9997, 19, 21, 23, 25, 27, 29, # bucket 1 -- odd numbers - 12, 14, 9996, 9998, 20, 22, 24, 26, 28, 30, # bucket 2 -- even numbers + 11, + 13, + 9995, + 9997, + 19, + 21, + 23, + 25, + 27, + 29, # bucket 1 -- odd numbers + 12, + 14, + 9996, + 9998, + 20, + 22, + 24, + 26, + 28, + 30, # bucket 2 -- even numbers ] items_copy = list(items) @@ -117,15 +149,24 @@ def test_shuffle_respects_two_distinct_disabled_groups_in_one_bucket(): # This is simulating two disabled modules within same package. # The two modules shouldn't be mixed up in one bucket. items = [ - 11, 13, 8885, 8887, 8889, 21, 23, 9995, 9997, 9999, + 11, + 13, + 8885, + 8887, + 8889, + 21, + 23, + 9995, + 9997, + 9999, ] items_copy = list(items) for i in range(5): _shuffle_items(items, bucket_key=modulus_2_key, disable=disable_if_gt_1000) if items != items_copy: - assert items[items.index(8885):items.index(8885) + 3] == [8885, 8887, 8889] - assert items[items.index(9995):items.index(9995) + 3] == [9995, 9997, 9999] + assert items[items.index(8885) : items.index(8885) + 3] == [8885, 8887, 8889] + assert items[items.index(9995) : items.index(9995) + 3] == [9995, 9997, 9999] return assert False diff --git a/tests/test_xdist.py b/tests/test_xdist.py index a88a923..6bcda1a 100644 --- a/tests/test_xdist.py +++ b/tests/test_xdist.py @@ -1,6 +1,5 @@ - def test_xdist_not_broken(testdir, twenty_tests): testdir.makepyfile(twenty_tests) - result = testdir.runpytest('--random-order', '-n', '5') + result = testdir.runpytest("--random-order", "-n", "5") result.assert_outcomes(passed=20) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..e8e39c1 --- /dev/null +++ b/tox.ini @@ -0,0 +1,10 @@ +[tox] +minversion = 4.0 +envlist = py{39,310,311,312,313} + +[testenv] +deps = + py + pytest-xdist +commands = + pytest tests --color=yes From de7dc794bd70b9154ccfd5f78594a7a082b16209 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 22 Jun 2025 10:53:48 -0300 Subject: [PATCH 08/12] Fix versions --- .github/workflows/deploy.yml | 21 +++++++++++++++------ .github/workflows/test.yml | 13 ++++++++----- .pre-commit-config.yaml | 1 - 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e009732..7b6dfe4 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -12,14 +12,21 @@ jobs: package: runs-on: ubuntu-latest + # Required by attest-build-provenance-github. + permissions: + id-token: write + attestations: write env: SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.event.inputs.version }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v1.5 + uses: hynek/build-and-inspect-python-package@v2.13.0 + with: + attest-build-provenance-github: 'true' + deploy: needs: package @@ -30,16 +37,18 @@ jobs: contents: write # For tag and release notes. steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Download Package - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: Packages path: dist - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@v1.8.5 + uses: pypa/gh-action-pypi-publish@v1.12.4 + with: + attestations: true - name: Push tag run: | @@ -49,7 +58,7 @@ jobs: git push origin v${{ github.event.inputs.version }} - name: GitHub Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: body_path: scripts/latest-release-notes.md files: dist/* diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 926a590..ff92c24 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,10 +17,13 @@ jobs: package: runs-on: ubuntu-latest + permissions: + id-token: write + attestations: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v1.5 + uses: hynek/build-and-inspect-python-package@v2.13.0 test: @@ -35,16 +38,16 @@ jobs: os: [ubuntu-latest, windows-latest] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Download Package - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: Packages path: dist - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4206469..1619312 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,3 @@ -exclude: '^($|.*\.bin)' repos: - repo: local hooks: From b879d5038384627604687d2df69cacd7976d3f94 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 22 Jun 2025 10:55:31 -0300 Subject: [PATCH 09/12] Fix long_description_content_type --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 5200544..9c78ad3 100644 --- a/setup.py +++ b/setup.py @@ -22,6 +22,7 @@ def read(fname): url="https://github.com/jbasko/pytest-random-order", description="Randomise the order in which pytest tests are run with some control over the randomness", long_description=read("README.rst"), + long_description_content_type="text/x-rst", packages=[ "random_order", ], From b352b8982872a88d0e50f06fa3ec5af3673b3578 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 22 Jun 2025 11:01:46 -0300 Subject: [PATCH 10/12] Remove 3.8 --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ff92c24..5ef500c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,7 +34,7 @@ jobs: strategy: fail-fast: false matrix: - python: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + python: ["3.9", "3.10", "3.11", "3.12", "3.13"] os: [ubuntu-latest, windows-latest] steps: From 5fc20ec75a522ae704d299b145965b67ae1ada0d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 22 Jun 2025 11:05:26 -0300 Subject: [PATCH 11/12] Fix pytest config --- MANIFEST.in | 6 ------ pyproject.toml | 4 ---- pytest.ini | 3 --- 3 files changed, 13 deletions(-) delete mode 100644 MANIFEST.in delete mode 100644 pytest.ini diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 6895eff..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,6 +0,0 @@ -include LICENSE -include README.rst -graft tests - -recursive-exclude * __pycache__ -recursive-exclude * *.py[co] diff --git a/pyproject.toml b/pyproject.toml index 4839ac7..52b3191 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,10 +8,6 @@ build-backend = "setuptools.build_meta" [tool.pytest.ini_options] testpaths = ["tests"] addopts = "-r a" -asyncio_mode = "auto" -filterwarnings = [ - "ignore:.*usage of Session..*:DeprecationWarning" -] [tool.ruff] line-length = 120 \ No newline at end of file diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index c79545f..0000000 --- a/pytest.ini +++ /dev/null @@ -1,3 +0,0 @@ -[pytest] -filterwarnings = - ignore:.*usage of Session..*:DeprecationWarning From 758c411e33962866fa880fd355365d2aa69a22de Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 22 Jun 2025 11:06:20 -0300 Subject: [PATCH 12/12] Fix classified --- setup.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 9c78ad3..3825a61 100644 --- a/setup.py +++ b/setup.py @@ -38,17 +38,12 @@ def read(fname): "Topic :: Software Development :: Testing", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", "License :: OSI Approved :: MIT License", ], keywords="pytest random test order shuffle", entry_points={ "pytest11": [ - "random_order = random_order.plugin", # >=1.0.0 + "random_order = random_order.plugin", ], }, )