Skip to content

Conversation

@PaulYuuu
Copy link
Contributor

@PaulYuuu PaulYuuu commented Jun 18, 2024

Call python setup.py directly is deprecated, to modernize this
project, let's introduce pyproject.toml, it supports more features. For
compatibility with the past, keep setup.py in present.

Signed-off-by: Yihuang Yu yihyu@redhat.com

Summary by CodeRabbit

  • New Features

    • Added Makefile targets to enable developing all or specific optional plugins.
    • Introduced a new remote-host spawner with slot reservation for remote task execution.
  • Bug Fixes

    • Improved Robot plugin test discovery to support newer test case APIs.
  • Documentation

    • Updated contributor and plugin guides to use pyproject.toml and pip workflows; added plugin READMEs (Ansible, others).
  • Chores

    • Migrated CI/workflows and developer tooling from setup.py to pip/module-based commands; added pyproject.toml manifests across core and optional plugins.
  • Tests

    • Simplified unit tests to use direct queue-based invocation for software manager paths.

@PaulYuuu PaulYuuu force-pushed the pyproject branch 3 times, most recently from c7b2dc8 to 09420d5 Compare June 19, 2024 01:49
@clebergnu
Copy link
Contributor

Hi @PaulYuuu , we've dropped Python 3.8 so this may be a simpler task now. Let me know if you want to evolve this.

@PaulYuuu
Copy link
Contributor Author

Hi @PaulYuuu , we've dropped Python 3.8 so this may be a simpler task now. Let me know if you want to evolve this.

Hello @clebergnu, thanks for the heads-up. Avocado has quite a few subprojects, so I’d like to confirm the exact scope of this PR before making updates.

From my view, I hope we can split to much tasks, e.g. a PR for subprojects pyproject.toml, a PR for avocado pyproject, and one or more for some usage update?

@coderabbitai
Copy link

coderabbitai bot commented Oct 11, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

Project-wide migration from setup.py to PEP 517/621 packaging and pip installs: add a top-level pyproject.toml, convert many plugins to pyproject.toml, simplify setup.py, update Makefiles, refactor CI/workflows to use python -m pip and python -m selftests.check, and move remote spawner logic into a new spawner module.

Changes

Cohort / File(s) Summary
CI / GitHub Actions
.github/actions/version/action.yml, .github/workflows/ci.yml, .github/workflows/prerelease.yml, .github/workflows/setup.yml, .github/workflows/vmimage.yml, .github/workflows/weekly.yml
Replace setup.py installs/tests with python -m pip/editable installs and python -m selftests.check; standardize python version flags; add explicit python3-pip/venv/ca-certificates package installs; other step adjustments.
Top-level packaging & build
pyproject.toml, setup.py, Makefile, Makefile.gh, python-avocado.spec
Add comprehensive pyproject.toml; reduce setup.py to a minimal shim; update Makefiles to use pip, add develop-plugins/develop-plugin targets, guard manpage build; adjust wheel build loop; update spec macros and %setup names and selftests invocation.
Docs & examples (guides & READMEs)
docs/source/guides/.../*.rst, examples/plugins/README.rst, examples/plugins/tests/README.rst, optional_plugins/README.rst, spell.ignore
Update contributor/writer/plugin docs to use pyproject.toml/pip install -e .; formatting tweaks; add pyproject and toml to spell ignore.
Example plugins packaging
examples/plugins/**/pyproject.toml, examples/plugins/**/setup.py, examples/plugins/*/README.rst
Add pyproject.toml manifests for example plugins with entry points; keep minimal legacy setup.py stubs/comments for backward compatibility; update example install/uninstall instructions.
Optional plugins packaging
optional_plugins/*/pyproject.toml, optional_plugins/*/setup.py, optional_plugins/ansible/README.rst
Add/introduce pyproject.toml for many optional plugins (ansible, golang, html, mail, result_upload, resultsdb, robot, spawner_remote, varianter_*, etc.); simplify setup.py files (remove long_description/metadata, add python_requires=">=3.9", align entry_points); add Ansible README.
Remote spawner refactor
optional_plugins/spawner_remote/avocado_spawner_remote/__init__.py, optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py
Remove prior spawner classes/exports from __init__.py; add spawner.py implementing RemoteSpawnerException, RemoteSpawnerInit, with_slot_reservation, and a full RemoteSpawner lifecycle (async remote exec, slot reservation, setup hook, timeouts, liveness, output dir).
Robot plugin tweak
optional_plugins/robot/avocado_robot/robot.py
Update find_tests to use test_case.name when present and fall back to prior string-based usage.
Unit test adjustment
selftests/unit/runner_package.py
Replace multiprocessing/status-generator test pattern with direct _run_software_manager calls and SimpleQueue-based assertions and mocks.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.54% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly describes the primary change of migrating configuration from setup.py to pyproject.toml and includes an RFC prefix to indicate discussion intent without unnecessary detail or noise.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Oct 11, 2025

Codecov Report

❌ Patch coverage is 38.51852% with 83 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.48%. Comparing base (b6ac559) to head (52fc5b8).

Files with missing lines Patch % Lines
...s/spawner_remote/avocado_spawner_remote/spawner.py 38.05% 83 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5962      +/-   ##
==========================================
+ Coverage   73.46%   73.48%   +0.01%     
==========================================
  Files         206      206              
  Lines       22487    22488       +1     
==========================================
+ Hits        16521    16526       +5     
+ Misses       5966     5962       -4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@PaulYuuu PaulYuuu marked this pull request as ready for review October 11, 2025 12:11
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (6)
.github/workflows/push_ci.yml (1)

95-98: Drop --user from the editable install.

The preceding pip install -r requirements-dev.txt already installs dependencies into the toolcache site-packages, so adding --user here puts Avocado’s editable install under ~/.local/lib while its deps stay in the global site. That split setup makes reproducibility harder (and raises PATH warnings on the runner) without buying anything inside CI. Please install into the same environment as the dependencies instead:

-      - name: Avocado build
-        run: pip install -e . --user
+      - name: Avocado build
+        run: pip install -e .
.github/workflows/setup.yml (1)

33-43: LGTM with optional consolidation suggestion.

The migration from setup.py install --user to pip install . --user is correct. The individual plugin installations are explicit and functional.

Consider consolidating the plugin installations for brevity:

-      - name: Installing plugins
-        run: |
-         pip install ./optional_plugins/golang --user
-         pip install ./optional_plugins/html --user
-         pip install ./optional_plugins/result_upload --user
-         pip install ./optional_plugins/resultsdb --user
-         pip install ./optional_plugins/robot --user
-         pip install ./optional_plugins/varianter_cit --user
-         pip install ./optional_plugins/varianter_pict --user
-         pip install ./optional_plugins/varianter_yaml_to_mux --user
+      - name: Installing plugins
+        run: |
+         for plugin in golang html result_upload resultsdb robot varianter_cit varianter_pict varianter_yaml_to_mux; do
+           pip install "./optional_plugins/$plugin" --user
+         done
optional_plugins/ansible/pyproject.toml (1)

14-21: Document the rationale for Python-version-specific dependency constraints.

The dependencies include specific version pinning for older Python versions:

  • cffi==1.17.1 for Python < 3.10
  • cryptography<46.0.0 for Python < 3.10

While these constraints suggest known compatibility issues, they should be documented. Consider:

  • Adding comments in pyproject.toml explaining why these constraints exist
  • Documenting in CHANGELOG or migration notes
  • Linking to relevant upstream issues if these are workarounds

Add inline comments to explain the version constraints:

 dependencies = [
     "avocado-framework",
+    # cffi 1.17.1 is the last version compatible with Python 3.9
     "cffi==1.17.1; python_version<'3.10'",
     "cffi; python_version>='3.10'",
+    # cryptography 46.0.0+ drops Python 3.9 support
     "cryptography<46.0.0; python_version<'3.10'",
     "pycparser",
     "ansible-core",
 ]
.github/actions/wheel/action.yml (2)

11-12: Consider using a user-local directory instead of /tmp.

Installing to /tmp/avocado_install works but could have issues in some CI environments. Consider using a user-local directory for better isolation:

-       python3 -m pip install dist/*.whl -t /tmp/avocado_install
-       export PYTHONPATH=/tmp/avocado_install:$PYTHONPATH
+       python3 -m pip install dist/*.whl -t ~/.local/avocado_wheel_test
+       export PYTHONPATH=~/.local/avocado_wheel_test:$PYTHONPATH

13-15: Simplify the avocado invocation.

The Python one-liner python3 -c 'from avocado.core.main import main; import sys; sys.exit(main())' is verbose and could be simplified. Since the wheel is installed, you should be able to invoke avocado directly.

-       python3 -c 'from avocado.core.main import main; import sys; sys.exit(main())' run /bin/true
-       cd /tmp
-       python3 -c 'from avocado.core.main import main; import sys; sys.exit(main())' run /bin/true
+       python3 -m avocado run /bin/true
+       # Test from a different directory to ensure imports work
+       (cd /tmp && python3 -m avocado run /bin/true)

Note: If the wheel doesn't include console scripts, this approach verifies the module entry point works correctly.

examples/plugins/tests/magic/pyproject.toml (1)

1-25: LGTM! Consider adding package discovery configuration.

The pyproject.toml structure is correct with proper entry points defined. The empty [tool.setuptools.packages.find] section will use default discovery, which should work for a simple plugin structure.

If you want to be explicit about package discovery, consider adding:

 [tool.setuptools.packages.find]
+include = ["avocado_magic*"]

This ensures only the intended package is included and makes the configuration more explicit.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e37e1d7 and 98c078b.

📒 Files selected for processing (58)
  • .github/actions/version/action.yml (1 hunks)
  • .github/actions/wheel/action.yml (1 hunks)
  • .github/workflows/ci.yml (10 hunks)
  • .github/workflows/prerelease.yml (2 hunks)
  • .github/workflows/push_ci.yml (1 hunks)
  • .github/workflows/setup.yml (6 hunks)
  • .github/workflows/vmimage.yml (1 hunks)
  • .github/workflows/weekly.yml (2 hunks)
  • Makefile (4 hunks)
  • Makefile.gh (2 hunks)
  • docs/source/guides/contributor/chapters/environment.rst (1 hunks)
  • docs/source/guides/contributor/chapters/plugins.rst (4 hunks)
  • docs/source/guides/writer/chapters/subclassing.rst (2 hunks)
  • examples/plugins/README.rst (1 hunks)
  • examples/plugins/cli-cmd/hello/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_option/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_option/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/setup.py (0 hunks)
  • examples/plugins/job-pre-post/sleep/pyproject.toml (1 hunks)
  • examples/plugins/job-pre-post/sleep/setup.py (0 hunks)
  • examples/plugins/test-pre-post/hello/pyproject.toml (1 hunks)
  • examples/plugins/test-pre-post/hello/setup.py (0 hunks)
  • examples/plugins/tests/README.rst (1 hunks)
  • examples/plugins/tests/magic/pyproject.toml (1 hunks)
  • examples/plugins/tests/magic/setup.py (0 hunks)
  • examples/plugins/tests/rogue/pyproject.toml (1 hunks)
  • examples/plugins/tests/rogue/setup.py (0 hunks)
  • optional_plugins/README.rst (1 hunks)
  • optional_plugins/ansible/pyproject.toml (1 hunks)
  • optional_plugins/ansible/setup.py (1 hunks)
  • optional_plugins/golang/pyproject.toml (1 hunks)
  • optional_plugins/golang/setup.py (1 hunks)
  • optional_plugins/html/pyproject.toml (1 hunks)
  • optional_plugins/html/setup.py (1 hunks)
  • optional_plugins/mail/pyproject.toml (1 hunks)
  • optional_plugins/mail/setup.py (1 hunks)
  • optional_plugins/result_upload/pyproject.toml (1 hunks)
  • optional_plugins/result_upload/setup.py (1 hunks)
  • optional_plugins/resultsdb/pyproject.toml (1 hunks)
  • optional_plugins/resultsdb/setup.py (1 hunks)
  • optional_plugins/robot/pyproject.toml (1 hunks)
  • optional_plugins/robot/setup.py (1 hunks)
  • optional_plugins/spawner_remote/pyproject.toml (1 hunks)
  • optional_plugins/spawner_remote/setup.py (1 hunks)
  • optional_plugins/varianter_cit/pyproject.toml (1 hunks)
  • optional_plugins/varianter_cit/setup.py (1 hunks)
  • optional_plugins/varianter_pict/pyproject.toml (1 hunks)
  • optional_plugins/varianter_pict/setup.py (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/setup.py (1 hunks)
  • pyproject.toml (1 hunks)
  • python-avocado.spec (5 hunks)
  • selftests/pre_release/tests/check-copr-rpm-version.sh (1 hunks)
  • setup.py (2 hunks)
💤 Files with no reviewable changes (8)
  • examples/plugins/tests/magic/setup.py
  • examples/plugins/cli-cmd/hello/setup.py
  • examples/plugins/cli-cmd/hello_priority/setup.py
  • examples/plugins/tests/rogue/setup.py
  • examples/plugins/test-pre-post/hello/setup.py
  • examples/plugins/cli-cmd/hello_parser/setup.py
  • examples/plugins/job-pre-post/sleep/setup.py
  • examples/plugins/cli-cmd/hello_option/setup.py
🧰 Additional context used
🪛 GitHub Actions: GH Actions
Makefile.gh

[error] 1-1: Command 'make -f Makefile.gh build-wheel check-wheel' failed. Wheel checks detected errors.

Makefile

[error] 1-1: Command 'make -f Makefile.gh build-wheel check-wheel' failed. Wheel checks detected errors.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (360)
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
🔇 Additional comments (62)
.github/workflows/prerelease.yml (2)

28-28: LGTM! Modern packaging workflow adopted.

The migration from python3 setup.py develop --user to pip install -e . --user aligns with PEP 660 and modern Python packaging best practices, as direct setup.py invocation is deprecated.

Also applies to: 49-49


51-51: LGTM! Test invocation moved to dedicated script.

Moving test invocation from setup.py test to selftests/check.py is correct, as setup.py test is deprecated and using a dedicated test runner provides better control and clarity.

optional_plugins/varianter_yaml_to_mux/pyproject.toml (1)

1-49: LGTM! Plugin packaging configuration is well-structured.

The pyproject.toml configuration follows modern Python packaging standards with proper build system declaration, dynamic version/readme handling, and correct entry point definitions. The structure is consistent with other plugins in the repository.

examples/plugins/README.rst (1)

11-24: LGTM! Documentation updated for modern packaging workflow.

The documentation correctly reflects the migration to pip-based editable installs and includes helpful uninstall examples. The instructions are clear and align with current Python packaging best practices.

optional_plugins/resultsdb/setup.py (1)

2-6: LGTM! Setup.py simplified for transition period.

The simplification to a bare setup() call is correct, as packaging configuration is now in pyproject.toml. The comment clearly explains that the file is retained for egg building compatibility during the transition period.

optional_plugins/varianter_yaml_to_mux/setup.py (1)

2-6: LGTM! Setup.py simplified for transition period.

The simplification to a bare setup() call is correct, as packaging configuration has been moved to the accompanying pyproject.toml. The comment clearly explains the file is retained for backward compatibility during the transition period.

optional_plugins/ansible/setup.py (1)

2-6: LGTM! Setup.py simplified for transition period.

The simplification to a bare setup() call is correct, with packaging configuration now in pyproject.toml. The comment appropriately explains that the file is kept for egg building compatibility during the transition.

Makefile.gh (1)

66-68: Verify all plugins have pyproject.toml before removing conditionals.

The removal of conditional checks for setup.py existence means the build will unconditionally attempt to build packages in all plugin directories. This assumes every plugin has a valid pyproject.toml. The pipeline failure indicates wheel checks detected errors, which could be caused by missing or misconfigured pyproject.toml files in some plugins.

Run the following script to verify all plugins have proper pyproject.toml configuration:

Also applies to: 79-81

optional_plugins/varianter_cit/setup.py (1)

1-6: LGTM! Proper backward compatibility pattern.

The bare setup() call correctly delegates all configuration to pyproject.toml, maintaining backward compatibility for tools that still invoke python setup.py commands during the transition period.

optional_plugins/varianter_cit/pyproject.toml (3)

31-35: LGTM! Entry points correctly configured.

The plugin entry points are properly structured for both CLI and varianter functionality:

  • CLI registration under avocado.plugins.cli
  • Varianter registration under avocado.plugins.varianter

7-7: Verify dynamic content source files exist.

The configuration declares version and readme as dynamic, sourced from files at lines 44-45.

Ensure that VERSION and README.rst files exist in the optional_plugins/varianter_cit/ directory:


13-13: Verify Python version requirement consistency.

The Python requirement is set to >=3.9. According to the PR comments, Python 3.8 has been dropped from the project.

Verify this is consistent with the project's overall Python version requirements:

optional_plugins/README.rst (2)

10-24: LGTM! Documentation correctly reflects modern pip-based workflow.

The updated instructions properly document:

  • Using pip install -e for development installation
  • Using pip uninstall with the full package name for removal
  • Providing concrete examples for common plugins

26-34: LGTM! Updated make targets align with new workflow.

The documentation correctly introduces the new make targets for plugin development:

  • make develop-plugins for all plugins
  • make develop-plugin PLUGIN=html for specific plugins

Verify these make targets exist in the Makefile:

optional_plugins/spawner_remote/setup.py (1)

1-6: LGTM! Consistent backward compatibility pattern.

This follows the same backward compatibility approach as other plugins in the PR, properly delegating configuration to pyproject.toml.

optional_plugins/varianter_pict/setup.py (1)

1-6: LGTM! Proper backward compatibility maintained.

Consistent with the migration strategy across all optional plugins, this setup.py correctly delegates to pyproject.toml while maintaining backward compatibility.

examples/plugins/cli-cmd/hello_priority/pyproject.toml (1)

1-14: LGTM! Standard pyproject.toml structure for a CLI plugin.

The configuration follows the modern pyproject.toml format correctly:

  • Build system is properly configured with setuptools and wheel
  • Project metadata is clear and concise
  • Entry point registration syntax is correct
  • Module packaging configuration matches the entry point

However, ensure that the hello_priority.py module file exists in this directory and contains a HelloWorld class, as referenced in the entry point.

examples/plugins/cli-cmd/hello_parser/pyproject.toml (1)

1-14: LGTM! Consistent structure with other CLI plugin configurations.

The configuration properly mirrors the structure used in hello_priority/pyproject.toml, maintaining consistency across example plugins.

Verify that the hello_parser.py module exists and contains the HelloWorld class:

examples/plugins/tests/rogue/pyproject.toml (1)

10-11: Verify script entry point module structure.

The script entry point references avocado_rogue.runner:main, but line 20 declares this as a py-modules (single module file) rather than a package.

Verify whether avocado_rogue is a single-file module or a package:

If it's a package (directory with __init__.py), the configuration should use [tool.setuptools.packages.find] instead of py-modules. If it's a single file named avocado_rogue.py, then the entry point syntax avocado_rogue.runner:main is incorrect—it should be avocado_rogue:main.

examples/plugins/test-pre-post/hello/pyproject.toml (3)

1-3: LGTM!

Standard and correct build system configuration using setuptools and wheel.


16-17: LGTM!

Correct configuration for packaging a single Python module.


10-14: Verify that HelloWorld class supports both pre and post phases.

Both entry points map to the same class hello:HelloWorld. Ensure that this class is designed to handle both pre-test and post-test execution contexts appropriately.

Run the following script to examine the HelloWorld class implementation:

optional_plugins/golang/pyproject.toml (3)

28-38: LGTM!

Entry points are correctly configured for script installation and plugin registration.


40-48: LGTM!

Setuptools configuration correctly enables package data inclusion and uses appropriate package discovery pattern with dynamic field configuration.


1-26: LGTM!

Well-structured project metadata with appropriate use of dynamic fields for version and readme. The Python version requirement (>=3.9) aligns with the project's stated drop of Python 3.8 support.

Verify that the VERSION and README.rst files exist:

.github/workflows/setup.yml (4)

73-83: LGTM!

System-wide installation correctly uses pip install . without the --user flag.


115-125: LGTM!

Development installation correctly uses editable mode (pip install -e .), and the test command properly switches to direct invocation of selftests/check.py.


183-191: LGTM!

Virtualenv installation correctly omits the --user flag and properly installs the framework and plugins.


216-219: LGTM!

Development virtualenv correctly uses editable install and runs the test suite with appropriate skip flags.

optional_plugins/mail/pyproject.toml (1)

1-44: LGTM!

The pyproject.toml configuration is well-structured and follows the same pattern as other optional plugins in this migration. Entry points for initialization and job pre/post hooks are appropriately configured for a mail notification plugin.

optional_plugins/mail/setup.py (1)

1-6: LGTM! Excellent transitional approach.

Simplifying setup.py to a bare setup() call while moving configuration to pyproject.toml is the recommended migration pattern. The explanatory comment clearly communicates the transition state.

Makefile (6)

9-10: LGTM!

Clear documentation for the new plugin development targets.


34-61: LGTM!

The expanded clean target is comprehensive and includes proper cleanup for optional plugins and example plugins. Error handling with || true ensures robustness, and user feedback via echo statements improves the experience.


64-67: LGTM!

Proper migration to pip-based installation and uninstallation commands.


112-119: LGTM!

Excellent error handling for the man page generation with fallback to rst2man.py if rst2man is not available, and a clear error message if neither tool is found.


83-108: LGTM with observation.

The new development targets provide good flexibility for plugin development workflows. Note that develop and develop-core targets are currently identical (lines 83 and 85-86), which may be intentional for compatibility or future differentiation.

Verify if develop and develop-core are intentionally identical or if there are plans to differentiate them:


1-129: Pipeline failure: Wheel building errors detected.

The GitHub Actions pipeline reports a failure with wheel building via make -f Makefile.gh build-wheel check-wheel. This likely relates to the pyproject.toml migration and needs investigation to ensure the new packaging configuration produces valid wheels.

Investigate the wheel building failure:

optional_plugins/resultsdb/pyproject.toml (2)

33-40: LGTM!

Entry points for CLI, result events, and result handling are correctly configured for the ResultsDB plugin.


42-50: LGTM!

Setuptools configuration follows the standard pattern with appropriate dynamic field handling and package discovery.

optional_plugins/result_upload/pyproject.toml (1)

1-45: LGTM!

The pyproject.toml configuration is well-structured and consistent with the migration pattern used across other optional plugins. All metadata, dependencies, entry points, and dynamic fields are correctly configured.

optional_plugins/result_upload/setup.py (1)

1-6: LGTM! Clean migration pattern.

The transition to a bare setup() call with explanatory comments is a clean approach for maintaining backward compatibility during the pyproject.toml migration.

.github/workflows/weekly.yml (2)

39-39: LGTM! Correct migration to pip-based editable install.

The change from python3 setup.py develop --user to pip install -e . --user aligns with modern Python packaging best practices and the deprecation of direct setup.py invocation.


83-83: LGTM! Consistent with the migration pattern.

This change matches the update on line 39, ensuring consistent installation across all workflow jobs.

optional_plugins/html/setup.py (1)

1-6: LGTM! Consistent migration pattern.

This follows the same clean migration pattern as other plugins, maintaining backward compatibility during the transition to pyproject.toml.

optional_plugins/golang/setup.py (1)

1-6: LGTM! Consistent migration pattern.

This follows the same clean migration pattern used across all plugins, maintaining backward compatibility with a bare setup() call while configuration moves to pyproject.toml.

python-avocado.spec (2)

8-18: Verify the naming change is applied consistently.

The macro change from avocado-%{version}.tar.gz to avocado_framework-%{version}.tar.gz is a significant naming update. Ensure this naming convention is consistent across:

  • Source tarball generation scripts
  • Release automation
  • Download URLs referenced in documentation
  • CI/CD pipelines

Run the following script to verify consistent naming:


243-272: Verify the exclusion patterns match the actual installed packages.

The added exclusions for contrib*, scripts*, and various plugin packages ensure clean package separation. However, confirm that:

  • All excluded patterns actually exist in the installation
  • No legitimate core packages are accidentally excluded
  • The conditional exclusions for RHEL/Fedora align with the availability of those plugins

Run the following script after a test build to verify exclusions:

optional_plugins/ansible/pyproject.toml (1)

48-49: Verify the VERSION file exists and is tracked.

The dynamic version reads from a VERSION file. Ensure:

  • The file exists at optional_plugins/ansible/VERSION
  • It's tracked in git
  • It's included in source distributions
optional_plugins/robot/setup.py (1)

2-6: LGTM!

The migration to a minimal setup() call is the correct approach for transitioning to pyproject.toml-based packaging. The comments clearly explain the backward compatibility purpose during the transition period.

.github/workflows/ci.yml (11)

28-32: LGTM!

The migration from python3 setup.py develop --user to pip install -e . --user and from python3 setup.py test to python3 selftests/check.py correctly modernizes the CI workflow to use pip-based installation and direct test invocation.


95-99: LGTM!

Consistent migration to pip-based installation and direct test script invocation.


135-135: LGTM!

Consistent with the pip-based installation approach used elsewhere in the workflow.


171-171: LGTM!

Consistent with the pip-based installation approach used elsewhere in the workflow.


359-383: LGTM!

The job renames from egg_task_fedora_* to wheel_task_fedora_* and the dependency updates from python3-setuptools to python3-pip correctly reflect the migration to wheel-based packaging. The use of ./.github/actions/wheel action aligns with the new packaging approach.


398-409: LGTM!

The job rename from egg_task_ubi_9 to wheel_task_ubi_9 and the dependency update to python3-pip are consistent with the wheel-based packaging migration.


411-422: LGTM!

Consistent wheel task migration for Debian 12.


437-461: LGTM!

The wheel task migrations for Ubuntu 24.04 and 22.04 are consistent with the pattern used in other distribution-specific jobs.


487-497: LGTM!

The migration to pip-based installation for both the main framework and the example plugin is correct and consistent.


509-519: Verify the cleanup and reinstallation sequence.

Line 515 removes build artifacts (rm -rf build/ dist/ *.egg-info) between uninstall and reinstall operations. Ensure that:

  1. The cleanup doesn't interfere with pip's internal state or cause issues on subsequent installs.
  2. All necessary artifacts are properly regenerated on the subsequent pip install -e . at line 516.

472-476: Verify the egg path generation and usage.

Line 474 generates the egg path dynamically using ls dist/avocado_framework-*.egg. Ensure this glob pattern correctly matches the generated egg filename format, especially if the version format or naming convention changes.

Run the following script to verify the egg naming pattern:

optional_plugins/html/pyproject.toml (1)

1-48: Verify the dynamic file references and dependency versions.

The pyproject.toml configuration looks well-structured, but please verify:

  1. Dynamic file references (lines 47-48): Ensure that VERSION and README.rst files exist in the optional_plugins/html/ directory, as setuptools will look for them relative to the pyproject.toml location.
  2. jinja2 dependency (line 16): Consider pinning or constraining the jinja2 version to avoid potential compatibility issues with future major releases (e.g., jinja2>=2.0,<4.0).

Run the following script to verify the file existence:

optional_plugins/robot/pyproject.toml (1)

1-49: Verify dynamic file references, package structure, and dependency constraints.

The pyproject.toml configuration is well-structured, but please verify:

  1. Dynamic file references (lines 48-49): Ensure that VERSION and README.rst files exist in the optional_plugins/robot/ directory.
  2. robotframework version constraint (line 16): The constraint robotframework>=4.1, <7.0 excludes robotframework 7.0+. Verify that this constraint is intentional and that there are known compatibility issues with robotframework 7.0+, or consider relaxing it to allow future minor versions (e.g., <8.0).
  3. Package finding pattern (line 45): The pattern avocado_robot* will include all packages starting with avocado_robot. Verify that this matches the actual package structure and doesn't inadvertently include unintended packages.

Run the following scripts to verify:

Comment on lines +14 to +16
dependencies = [
"setuptools",
]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Restore the project’s runtime dependencies

[project].dependencies now contains only "setuptools", but the core package still imports third-party modules (for example, avocado.plugins.sysinfo imports psutil, avocado.utils.asset imports requests, and the varianter plugins rely on jsonschema). These were previously declared in install_requires; without them, pip install avocado-framework succeeds yet the package immediately raises ImportError when those modules are loaded. Please port the full dependency list from the former setup.py into this section (group purely optional items under extras if needed) so end users receive the required packages on installation.

🤖 Prompt for AI Agents
In pyproject.toml around lines 14 to 16, the project runtime dependencies were
reduced to just "setuptools", causing ImportError at runtime; restore the full
list that used to be in setup.py by populating [project].dependencies with all
required packages (e.g., psutil, requests, jsonschema and any other entries from
the former install_requires) as PEP-621 string entries so pip installs them;
move purely optional/extra features into [project.optional-dependencies] groups
(extras) and ensure build-system still declares setuptools only if needed.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (2)
pyproject.toml (2)

5-29: Restore required runtime dependencies before release.

[project].dependencies is empty, so a fresh pip install avocado-framework pulls no third-party modules. The codebase still imports non-stdlib packages (e.g. psutil in avocado.plugins.sysinfo, requests in avocado.utils.asset, jsonschema in varianter plugins). This regresses immediately with ImportError. Please port the full install_requires list from the previous setup.py (splitting optional bits into extras as needed) so users receive the necessary dependencies.


14-29: Remove the unsupported Python 3.14 classifier.

PyPI rejects uploads containing Programming Language :: Python :: 3.14, because that trove classifier has not been published yet. Drop it for now and re-add only once PyPI exposes the official classifier.

🧹 Nitpick comments (5)
optional_plugins/varianter_yaml_to_mux/pyproject.toml (1)

1-49: Pin PyYAML to a stable release
Replace PyYAML>=4.2b2 with PyYAML>=5.4.1 (latest stable patch: 6.0.3).

examples/plugins/tests/rogue/pyproject.toml (1)

7-7: Consider dynamic version management.

The version is hardcoded to "1.0". For consistency with other plugins in the repository that use dynamic version loading from a VERSION file, consider updating this configuration.

Example pattern from other plugins:

[project]
name = "avocado-rogue"
dynamic = ["version"]
description = "Avocado \"rogue\" test type"

[tool.setuptools.dynamic]
version = {file = "VERSION"}
examples/plugins/tests/magic/pyproject.toml (1)

7-7: Consider dynamic version management.

The version is hardcoded to "1.0". For consistency with other plugins in the repository that use dynamic version loading from a VERSION file, consider updating this configuration.

Example pattern from other plugins:

[project]
name = "magic"
dynamic = ["version"]
description = "Avocado \"magic\" test type"

[tool.setuptools.dynamic]
version = {file = "VERSION"}
examples/plugins/test-pre-post/hello/pyproject.toml (1)

7-7: Consider dynamic version management.

The version is hardcoded to "1.0". For consistency with other plugins in the repository that use dynamic version loading from a VERSION file, consider updating this configuration.

Example pattern from other plugins:

[project]
name = "avocado-hello-world-pre-post-test"
dynamic = ["version"]
description = "Avocado Hello World pre and post test plugin"

[tool.setuptools.dynamic]
version = {file = "VERSION"}
optional_plugins/robot/pyproject.toml (1)

14-17: Remove redundant environment marker.

The environment marker python_version >= '3.9' on Line 16 is redundant because Line 13 already specifies requires-python = ">=3.9". When the package is installed, the Python version will always satisfy this condition.

Apply this diff to simplify the dependency:

 dependencies = [
     "avocado-framework",
-    "robotframework>=4.1, <7.0; python_version >= '3.9'",
+    "robotframework>=4.1, <7.0",
 ]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 98c078b and 0c89c21.

📒 Files selected for processing (63)
  • .github/actions/version/action.yml (1 hunks)
  • .github/actions/wheel/action.yml (1 hunks)
  • .github/workflows/ci.yml (9 hunks)
  • .github/workflows/prerelease.yml (2 hunks)
  • .github/workflows/push_ci.yml (1 hunks)
  • .github/workflows/release.yml (1 hunks)
  • .github/workflows/setup.yml (6 hunks)
  • .github/workflows/vmimage.yml (1 hunks)
  • .github/workflows/weekly.yml (2 hunks)
  • Makefile (4 hunks)
  • Makefile.gh (2 hunks)
  • docs/source/guides/contributor/chapters/environment.rst (1 hunks)
  • docs/source/guides/contributor/chapters/plugins.rst (4 hunks)
  • docs/source/guides/writer/chapters/subclassing.rst (2 hunks)
  • examples/plugins/README.rst (1 hunks)
  • examples/plugins/cli-cmd/hello/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_option/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_option/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/setup.py (0 hunks)
  • examples/plugins/job-pre-post/sleep/pyproject.toml (1 hunks)
  • examples/plugins/job-pre-post/sleep/setup.py (0 hunks)
  • examples/plugins/test-pre-post/hello/pyproject.toml (1 hunks)
  • examples/plugins/test-pre-post/hello/setup.py (0 hunks)
  • examples/plugins/tests/README.rst (1 hunks)
  • examples/plugins/tests/magic/pyproject.toml (1 hunks)
  • examples/plugins/tests/magic/setup.py (0 hunks)
  • examples/plugins/tests/rogue/pyproject.toml (1 hunks)
  • examples/plugins/tests/rogue/setup.py (0 hunks)
  • optional_plugins/README.rst (1 hunks)
  • optional_plugins/ansible/pyproject.toml (1 hunks)
  • optional_plugins/ansible/setup.py (1 hunks)
  • optional_plugins/golang/pyproject.toml (1 hunks)
  • optional_plugins/golang/setup.py (1 hunks)
  • optional_plugins/html/pyproject.toml (1 hunks)
  • optional_plugins/html/setup.py (1 hunks)
  • optional_plugins/mail/pyproject.toml (1 hunks)
  • optional_plugins/mail/setup.py (1 hunks)
  • optional_plugins/result_upload/pyproject.toml (1 hunks)
  • optional_plugins/result_upload/setup.py (1 hunks)
  • optional_plugins/resultsdb/pyproject.toml (1 hunks)
  • optional_plugins/resultsdb/setup.py (1 hunks)
  • optional_plugins/robot/pyproject.toml (1 hunks)
  • optional_plugins/robot/setup.py (1 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/__init__.py (0 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1 hunks)
  • optional_plugins/spawner_remote/pyproject.toml (1 hunks)
  • optional_plugins/spawner_remote/setup.py (1 hunks)
  • optional_plugins/varianter_cit/pyproject.toml (1 hunks)
  • optional_plugins/varianter_cit/setup.py (1 hunks)
  • optional_plugins/varianter_pict/README.rst (1 hunks)
  • optional_plugins/varianter_pict/pyproject.toml (1 hunks)
  • optional_plugins/varianter_pict/setup.py (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/README.rst (3 hunks)
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/setup.py (1 hunks)
  • pyproject.toml (1 hunks)
  • python-avocado.spec (5 hunks)
  • selftests/pre_release/tests/check-copr-rpm-version.sh (1 hunks)
  • setup.py (2 hunks)
💤 Files with no reviewable changes (9)
  • optional_plugins/spawner_remote/avocado_spawner_remote/init.py
  • examples/plugins/job-pre-post/sleep/setup.py
  • examples/plugins/tests/magic/setup.py
  • examples/plugins/cli-cmd/hello_parser/setup.py
  • examples/plugins/cli-cmd/hello_priority/setup.py
  • examples/plugins/tests/rogue/setup.py
  • examples/plugins/cli-cmd/hello/setup.py
  • examples/plugins/cli-cmd/hello_option/setup.py
  • examples/plugins/test-pre-post/hello/setup.py
✅ Files skipped from review due to trivial changes (2)
  • optional_plugins/varianter_pict/README.rst
  • optional_plugins/varianter_yaml_to_mux/README.rst
🚧 Files skipped from review as they are similar to previous changes (22)
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml
  • .github/workflows/vmimage.yml
  • selftests/pre_release/tests/check-copr-rpm-version.sh
  • optional_plugins/varianter_pict/setup.py
  • examples/plugins/README.rst
  • optional_plugins/ansible/pyproject.toml
  • examples/plugins/tests/README.rst
  • .github/actions/version/action.yml
  • optional_plugins/varianter_pict/pyproject.toml
  • python-avocado.spec
  • .github/actions/wheel/action.yml
  • optional_plugins/golang/pyproject.toml
  • docs/source/guides/writer/chapters/subclassing.rst
  • examples/plugins/job-pre-post/sleep/pyproject.toml
  • Makefile.gh
  • examples/plugins/cli-cmd/hello_option/pyproject.toml
  • optional_plugins/varianter_cit/pyproject.toml
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml
  • optional_plugins/result_upload/setup.py
  • .github/workflows/prerelease.yml
  • examples/plugins/cli-cmd/hello/pyproject.toml
  • .github/workflows/push_ci.yml
🧰 Additional context used
🧬 Code graph analysis (1)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (5)
avocado/core/plugin_interfaces.py (1)
  • Spawner (321-441)
avocado/core/spawners/common.py (3)
  • SpawnerMixin (35-47)
  • SpawnMethod (6-18)
  • task_output_dir (46-47)
avocado/core/settings.py (3)
  • section (137-138)
  • register_option (642-799)
  • key (141-142)
avocado/core/nrunner/runnable.py (1)
  • pick_runner_module_from_entry_point_kind (630-643)
avocado/core/nrunner/task.py (1)
  • setup_output_dir (171-174)
🪛 checkmake (0.2.2)
Makefile

[warning] 129-129: Missing required phony target "all"

(minphony)


[warning] 129-129: Missing required phony target "test"

(minphony)

🪛 Ruff (0.13.3)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py

73-73: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


74-74: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


126-129: Avoid specifying long messages outside the exception class

(TRY003)


197-197: Probable insecure usage of temporary file or directory: "/tmp/.avocado_task_output_dir"

(S108)


213-213: Consider moving this statement to an else block

(TRY300)


215-215: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


219-219: Unused static method argument: runtime_task

(ARG004)


224-224: Unused static method argument: runtime_task

(ARG004)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (43)
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: Linux with Python 3.11
  • GitHub Check: Linux with Python 3.12
  • GitHub Check: Linux with Python 3.13
  • GitHub Check: Linux with Python 3.9
  • GitHub Check: Linux with Python 3.10
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: Fedora selftests
  • GitHub Check: macOS with Python 3.11
🔇 Additional comments (31)
.github/workflows/release.yml (1)

134-136: LGTM!

Using python3 -m pip instead of pip directly is best practice, as it ensures the correct Python interpreter's pip is invoked. The conditional setuptools version constraint for Python >3.11 is retained appropriately.

docs/source/guides/contributor/chapters/environment.rst (1)

55-62: LGTM! Documentation accurately reflects modern packaging practices.

The update from python setup.py develop to pip install -e . aligns with the deprecation of direct setup.py invocations. The terminology updates ("editable install" and "egg info directory") are accurate and reflect current Python packaging conventions.

optional_plugins/robot/setup.py (1)

1-6: LGTM! Clean migration to pyproject.toml.

The minimal setup.py with a clear transitional comment is the recommended pattern for pyproject.toml migration while maintaining backward compatibility for egg builds. The comment clearly indicates that pyproject.toml is now the primary configuration source.

optional_plugins/varianter_yaml_to_mux/setup.py (1)

1-6: LGTM! Consistent migration pattern.

This follows the same clean migration pattern as other optional plugins, maintaining backward compatibility while delegating to pyproject.toml.

optional_plugins/README.rst (1)

8-34: LGTM! Clear and consistent instructions.

The updated installation and uninstallation instructions properly reflect the pip-based workflow. The examples for both individual plugins (html, ansible) and the Makefile targets are helpful and accurate.

optional_plugins/ansible/setup.py (1)

1-6: LGTM! Consistent migration pattern.

This follows the same clean migration pattern as other optional plugins.

docs/source/guides/contributor/chapters/plugins.rst (4)

97-101: LGTM! Documentation updated to reflect pyproject.toml workflow.

The changes correctly guide developers to use pyproject.toml for plugin registration instead of setup.py, and to use pip install -e . for development installation. This aligns with modern Python packaging standards.


111-114: LGTM! Entry points examples properly updated to TOML format.

The Job Pre/Post plugin entry points example correctly demonstrates the TOML syntax for registering plugins in pyproject.toml.


170-173: LGTM! Settings plugin entry points example updated correctly.

The settings plugin registration example properly shows the TOML syntax for extending configuration paths.


325-327: LGTM! Test type plugin examples consistently updated.

The magic test plugin example references are updated to use pyproject.toml and pip-based installation, maintaining consistency with the rest of the documentation.

optional_plugins/varianter_cit/setup.py (1)

2-6: LGTM! Transitional approach is appropriate.

The bare setup() call with the explanatory comment correctly delegates packaging configuration to pyproject.toml while maintaining backward compatibility.

examples/plugins/tests/rogue/pyproject.toml (1)

1-20: LGTM! Plugin packaging configuration is correct.

The pyproject.toml configuration properly defines the build system, project metadata, script entry points, and plugin entry points for the rogue test type.

optional_plugins/golang/setup.py (1)

2-6: LGTM! Transitional approach is appropriate.

The bare setup() call with the explanatory comment correctly delegates packaging configuration to pyproject.toml while maintaining backward compatibility.

examples/plugins/tests/magic/pyproject.toml (1)

1-25: LGTM! Plugin packaging configuration is correct.

The pyproject.toml configuration properly defines the build system, project metadata, and multiple plugin entry points for the magic test type (init, resolver, discoverer, and runnable.runner).

examples/plugins/test-pre-post/hello/pyproject.toml (1)

1-17: LGTM! Plugin packaging configuration is correct.

The pyproject.toml configuration properly defines the build system, project metadata, and test pre/post hook entry points.

optional_plugins/mail/pyproject.toml (1)

1-44: LGTM! Comprehensive plugin packaging configuration.

The pyproject.toml configuration is well-structured with:

  • Proper dynamic version and readme loading from VERSION and README.rst files
  • Comprehensive project metadata including license, authors, and classifiers
  • Correct Python version requirement (>=3.9)
  • Appropriate dependency on avocado-framework
  • Properly defined plugin entry points for initialization and job pre/post hooks
optional_plugins/mail/setup.py (1)

2-6: LGTM! Transitional approach is appropriate.

The bare setup() call with the explanatory comment correctly delegates packaging configuration to pyproject.toml while maintaining backward compatibility.

Makefile (6)

9-10: LGTM! Clear documentation for new plugin development targets.

The help text clearly explains the new develop-plugins and develop-plugin targets, making it easy for developers to understand how to install plugins.


33-61: LGTM! Comprehensive cleanup implementation.

The enhanced clean target now properly handles:

  • Build artifacts and caches
  • Optional plugins cleanup
  • Example plugins cleanup
  • User-friendly progress messages

The implementation correctly uses shell loops and error suppression to handle missing directories gracefully.


64-67: LGTM! Proper migration to pip-based workflows.

The install and uninstall targets correctly use $(PYTHON) -m pip instead of calling setup.py directly, aligning with modern Python packaging practices.


82-107: LGTM! Well-designed plugin development targets.

The new development targets provide:

  • develop: Core framework installation
  • develop-core: Explicit core-only installation
  • develop-plugins: Batch installation of all optional plugins
  • develop-plugin: Selective installation with PLUGIN parameter

All correctly use pip editable installs (-e) and include proper error handling for missing parameters.


112-119: LGTM! Robust man page generation with fallback.

The conditional logic properly checks for both rst2man and rst2man.py commands, providing good cross-platform compatibility with clear error messaging if neither is available.


129-129: Ignore the static analysis warning about missing "all" target.

The checkmake warning is a false positive—the "all" target is defined at line 1 of the Makefile. The warning about a missing "test" target may be a recommendation rather than a requirement, and its absence doesn't indicate an issue with the current changes.

optional_plugins/resultsdb/pyproject.toml (1)

1-49: LGTM! Configuration is clean and well-structured.

The pyproject.toml configuration is correct and follows the standard pattern for Avocado plugins. The previous concern about the impossible urllib3 constraint has been resolved—the dependency list is now clean and minimal.

optional_plugins/resultsdb/setup.py (1)

1-6: LGTM! Proper transition approach.

The minimal setup.py with a bare setup() call and clear documentation is the correct approach for maintaining backward compatibility during the migration to pyproject.toml.

optional_plugins/result_upload/pyproject.toml (1)

1-45: LGTM! Clean plugin configuration.

The configuration follows the standard pattern and correctly defines the result upload plugin with appropriate entry points and dependencies.

.github/workflows/weekly.yml (2)

37-47: LGTM! Properly modernized to pip-based workflows.

The changes correctly migrate from deprecated setup.py develop to modern pip install -e . and invoke selftests as a module. This aligns with current Python packaging best practices.


81-89: LGTM! Consistent migration in the second job.

The changes are consistent with the first job and properly modernize the workflow to use pip-based installation and module invocation.

optional_plugins/html/setup.py (1)

1-6: LGTM! Consistent transition pattern.

This follows the same correct approach as other plugins in maintaining backward compatibility during the migration to pyproject.toml.

setup.py (1)

16-38: LGTM! Well-documented migration strategy.

The minimal setup.py is well-structured with clear documentation explaining its purpose. Providing only name and version for egg builds while deferring all other configuration to pyproject.toml is the correct approach for this migration.

optional_plugins/spawner_remote/pyproject.toml (1)

1-46: LGTM! Entry point paths are now correct.

The configuration is properly structured. The previous issue with missing .spawner module component in entry point paths has been resolved—both entry points now correctly reference avocado_spawner_remote.spawner module.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (4)
optional_plugins/golang/setup.py (1)

1-1: Fix the shebang path.

The shebang should use /usr/bin/env instead of /bin/env for better portability across systems.

Apply this diff:

-#!/bin/env python3
+#!/usr/bin/env python3
optional_plugins/varianter_cit/setup.py (1)

1-1: Fix the shebang path.

The shebang should use /usr/bin/env instead of /bin/env for better portability across systems.

Apply this diff:

-#!/bin/env python3
+#!/usr/bin/env python3
.github/workflows/ci.yml (1)

169-169: Fix broken Windows setuptools fallback.

python -m python3 -m pip … tries to import a module named python3 and crashes (ModuleNotFoundError), so setuptools is never installed on Windows ≥3.12. Keep the existing probe but call pip via python -m pip instead.

-        run: python -c 'import setuptools' || python -m python3 -m pip install "setuptools<80"
+        run: python -c 'import setuptools' || python -m pip install "setuptools<80"
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1)

195-201: [Duplicate] Use per-task remote output directories to avoid data corruption.

This issue was previously identified: every task uses the same hardcoded path /tmp/.avocado_task_output_dir, causing concurrent tasks to overwrite each other's results.

Apply the previously suggested fix:

-        output_lxc_path = "/tmp/.avocado_task_output_dir"
+        safe_id = runtime_task.task.identifier.replace("/", "_")
+        output_lxc_path = os.path.join("/tmp", f".avocado-task-{safe_id}")
🧹 Nitpick comments (4)
optional_plugins/robot/pyproject.toml (1)

14-16: Consider minor style improvement in version specifier.

The dependency specification is valid. However, the space after the comma in "robotframework>=4.1, <7.0; python_version >= '3.9'" is unconventional. Consider writing it as "robotframework>=4.1,<7.0; python_version >= '3.9'" for consistency with common Python packaging style, though both forms are technically correct per PEP 440.

Makefile (1)

94-101: Optionally add failure handling for plugin installs.

If any plugin install fails the loop continues silently and the target exits 0. Consider adding set -e or testing the exit status inside the loop so CI/devs notice broken plugins immediately.

optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (2)

73-74: Annotate mutable class attributes with ClassVar.

The slots_cache dictionary is intentionally shared across all instances but should be annotated with typing.ClassVar to make this intent explicit and satisfy type checkers.

Apply this diff:

+from typing import ClassVar
+
 class RemoteSpawner(Spawner, SpawnerMixin):
 
     description = "Remote (host) based spawner"
     METHODS = [SpawnMethod.STANDALONE_EXECUTABLE]
-    slots_cache = {}
+    slots_cache: ClassVar[dict] = {}

208-216: Fix f-string and use logging.exception for better error context.

Line 215 is missing the f prefix, and using logging.exception instead of logging.error within an exception handler provides automatic traceback information.

Apply this diff:

     async def terminate_task(self, runtime_task):
         session = runtime_task.spawner_handle
         session.sendcontrol("c")
         try:
             session.read_up_to_prompt()
             return True
         except exceptions.ExpectTimeoutError:
-            LOG.error("Failed to terminate task on {session.host}")
+            LOG.exception(f"Failed to terminate task on {session.host}")
             return False
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c89c21 and af71902.

📒 Files selected for processing (65)
  • .github/actions/version/action.yml (1 hunks)
  • .github/actions/wheel/action.yml (1 hunks)
  • .github/workflows/ci.yml (9 hunks)
  • .github/workflows/prerelease.yml (2 hunks)
  • .github/workflows/push_ci.yml (1 hunks)
  • .github/workflows/release.yml (1 hunks)
  • .github/workflows/setup.yml (6 hunks)
  • .github/workflows/vmimage.yml (1 hunks)
  • .github/workflows/weekly.yml (2 hunks)
  • Makefile (4 hunks)
  • Makefile.gh (2 hunks)
  • docs/source/guides/contributor/chapters/environment.rst (1 hunks)
  • docs/source/guides/contributor/chapters/plugins.rst (4 hunks)
  • docs/source/guides/writer/chapters/subclassing.rst (2 hunks)
  • examples/plugins/README.rst (1 hunks)
  • examples/plugins/cli-cmd/hello/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_option/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_option/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/setup.py (0 hunks)
  • examples/plugins/job-pre-post/sleep/pyproject.toml (1 hunks)
  • examples/plugins/job-pre-post/sleep/setup.py (0 hunks)
  • examples/plugins/test-pre-post/hello/pyproject.toml (1 hunks)
  • examples/plugins/test-pre-post/hello/setup.py (0 hunks)
  • examples/plugins/tests/README.rst (1 hunks)
  • examples/plugins/tests/magic/pyproject.toml (1 hunks)
  • examples/plugins/tests/magic/setup.py (0 hunks)
  • examples/plugins/tests/rogue/pyproject.toml (1 hunks)
  • examples/plugins/tests/rogue/setup.py (0 hunks)
  • optional_plugins/README.rst (1 hunks)
  • optional_plugins/ansible/README.rst (1 hunks)
  • optional_plugins/ansible/pyproject.toml (1 hunks)
  • optional_plugins/ansible/setup.py (1 hunks)
  • optional_plugins/golang/pyproject.toml (1 hunks)
  • optional_plugins/golang/setup.py (1 hunks)
  • optional_plugins/html/pyproject.toml (1 hunks)
  • optional_plugins/html/setup.py (1 hunks)
  • optional_plugins/mail/pyproject.toml (1 hunks)
  • optional_plugins/mail/setup.py (1 hunks)
  • optional_plugins/result_upload/pyproject.toml (1 hunks)
  • optional_plugins/result_upload/setup.py (1 hunks)
  • optional_plugins/resultsdb/pyproject.toml (1 hunks)
  • optional_plugins/resultsdb/setup.py (1 hunks)
  • optional_plugins/robot/pyproject.toml (1 hunks)
  • optional_plugins/robot/setup.py (1 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/__init__.py (0 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1 hunks)
  • optional_plugins/spawner_remote/pyproject.toml (1 hunks)
  • optional_plugins/spawner_remote/setup.py (1 hunks)
  • optional_plugins/varianter_cit/pyproject.toml (1 hunks)
  • optional_plugins/varianter_cit/setup.py (1 hunks)
  • optional_plugins/varianter_pict/README.rst (1 hunks)
  • optional_plugins/varianter_pict/pyproject.toml (1 hunks)
  • optional_plugins/varianter_pict/setup.py (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/README.rst (4 hunks)
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/setup.py (1 hunks)
  • pyproject.toml (1 hunks)
  • python-avocado.spec (5 hunks)
  • selftests/pre_release/tests/check-copr-rpm-version.sh (1 hunks)
  • setup.py (2 hunks)
  • spell.ignore (1 hunks)
💤 Files with no reviewable changes (9)
  • examples/plugins/job-pre-post/sleep/setup.py
  • examples/plugins/cli-cmd/hello_parser/setup.py
  • examples/plugins/cli-cmd/hello_option/setup.py
  • examples/plugins/test-pre-post/hello/setup.py
  • examples/plugins/cli-cmd/hello_priority/setup.py
  • examples/plugins/cli-cmd/hello/setup.py
  • examples/plugins/tests/magic/setup.py
  • optional_plugins/spawner_remote/avocado_spawner_remote/init.py
  • examples/plugins/tests/rogue/setup.py
✅ Files skipped from review due to trivial changes (3)
  • optional_plugins/varianter_pict/README.rst
  • optional_plugins/varianter_yaml_to_mux/README.rst
  • optional_plugins/ansible/README.rst
🚧 Files skipped from review as they are similar to previous changes (31)
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml
  • .github/workflows/weekly.yml
  • selftests/pre_release/tests/check-copr-rpm-version.sh
  • optional_plugins/spawner_remote/pyproject.toml
  • optional_plugins/varianter_yaml_to_mux/setup.py
  • docs/source/guides/contributor/chapters/plugins.rst
  • examples/plugins/cli-cmd/hello_option/pyproject.toml
  • examples/plugins/job-pre-post/sleep/pyproject.toml
  • Makefile.gh
  • optional_plugins/resultsdb/pyproject.toml
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml
  • optional_plugins/varianter_pict/pyproject.toml
  • examples/plugins/tests/README.rst
  • optional_plugins/ansible/pyproject.toml
  • .github/actions/wheel/action.yml
  • examples/plugins/test-pre-post/hello/pyproject.toml
  • examples/plugins/tests/magic/pyproject.toml
  • optional_plugins/spawner_remote/setup.py
  • examples/plugins/README.rst
  • .github/actions/version/action.yml
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml
  • optional_plugins/mail/pyproject.toml
  • pyproject.toml
  • .github/workflows/push_ci.yml
  • examples/plugins/cli-cmd/hello/pyproject.toml
  • examples/plugins/tests/rogue/pyproject.toml
  • optional_plugins/golang/pyproject.toml
  • python-avocado.spec
  • optional_plugins/mail/setup.py
  • docs/source/guides/contributor/chapters/environment.rst
  • .github/workflows/release.yml
🧰 Additional context used
🧬 Code graph analysis (1)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (5)
avocado/core/plugin_interfaces.py (2)
  • Init (23-28)
  • Spawner (321-441)
avocado/core/spawners/common.py (3)
  • SpawnerMixin (35-47)
  • SpawnMethod (6-18)
  • task_output_dir (46-47)
avocado/core/settings.py (3)
  • section (137-138)
  • register_option (642-799)
  • key (141-142)
avocado/core/nrunner/runnable.py (1)
  • pick_runner_module_from_entry_point_kind (630-643)
avocado/core/nrunner/task.py (1)
  • setup_output_dir (171-174)
🪛 checkmake (0.2.2)
Makefile

[warning] 129-129: Missing required phony target "all"

(minphony)


[warning] 129-129: Missing required phony target "test"

(minphony)

🪛 Ruff (0.13.3)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py

73-73: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


74-74: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


126-129: Avoid specifying long messages outside the exception class

(TRY003)


197-197: Probable insecure usage of temporary file or directory: "/tmp/.avocado_task_output_dir"

(S108)


213-213: Consider moving this statement to an else block

(TRY300)


215-215: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


219-219: Unused static method argument: runtime_task

(ARG004)


224-224: Unused static method argument: runtime_task

(ARG004)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (44)
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: Linux with Python 3.10
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: Linux with Python 3.12
  • GitHub Check: Linux with Python 3.11
  • GitHub Check: Linux with Python 3.13
  • GitHub Check: Linux with Python 3.9
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: Fedora selftests
  • GitHub Check: Wheel task ubuntu:22.04
  • GitHub Check: macOS with Python 3.11
🔇 Additional comments (27)
spell.ignore (1)

806-807: LGTM! Appropriate additions for the pyproject.toml migration.

Adding "pyproject" and "toml" to the spell-checker ignore list is necessary and appropriate given the PR's objective to migrate packaging configuration to pyproject.toml files across the codebase. These technical terms will now appear frequently in documentation, code comments, and configuration files.

.github/workflows/vmimage.yml (1)

40-42: LGTM! Modern packaging commands adopted.

The migration from setup.py develop to python3 -m pip install -e . and explicit python3 -m pip invocations align with modern Python packaging best practices and the pyproject.toml-based configuration introduced in this PR.

.github/workflows/prerelease.yml (2)

25-28: LGTM! Workflow migrated to pip-based development.

The replacement of setup.py develop with python3 -m pip install -e . --user and explicit python3 -m pip invocations align with modern Python packaging practices and the pyproject.toml migration.


48-51: LGTM! Test invocation modernized.

The migration from setup.py test to python3 -m selftests.check and pip-based installation aligns with the repository's shift to pyproject.toml-based packaging and modern test infrastructure.

optional_plugins/golang/setup.py (1)

2-6: LGTM! Transitional setup.py delegates to pyproject.toml.

The bare setup() call correctly delegates packaging configuration to pyproject.toml, and the transitional comment clearly explains the purpose of retaining this file for egg-building compatibility.

setup.py (1)

16-39: LGTM! Well-designed transitional setup.py.

This minimal setup.py provides excellent backward compatibility:

  • Clear docstring explaining the transitional nature
  • Reads VERSION for egg builds that don't fully support pyproject.toml
  • Minimal implementation (name + version only) delegates other configuration to pyproject.toml
  • Maintains compatibility with legacy tools while embracing modern packaging standards

The approach balances backward compatibility with the shift to PEP 517-based builds.

optional_plugins/ansible/setup.py (1)

2-6: LGTM! Transitional setup.py delegates to pyproject.toml.

The bare setup() call correctly delegates packaging configuration to pyproject.toml, and the transitional comment clearly explains the purpose of retaining this file for egg-building compatibility.

optional_plugins/varianter_cit/setup.py (1)

2-6: LGTM! Transitional setup.py delegates to pyproject.toml.

The bare setup() call correctly delegates packaging configuration to pyproject.toml, and the transitional comment clearly explains the purpose of retaining this file for egg-building compatibility.

optional_plugins/varianter_cit/pyproject.toml (1)

1-45: Approve pyproject.toml configuration

  • Build system, project metadata, entry points, dynamic version/readme loading, and package discovery correctly defined
  • VERSION and README.rst files confirmed present in optional_plugins/varianter_cit/ for dynamic loading
optional_plugins/resultsdb/setup.py (1)

2-6: pyproject.toml exists and is properly configured: includes required [build-system], [project], and [project.entry-points] sections.

optional_plugins/varianter_pict/setup.py (1)

1-6: LGTM! Clean migration pattern.

The transitional setup.py correctly delegates all configuration to pyproject.toml while maintaining backward compatibility. The explanatory comment is helpful.

docs/source/guides/writer/chapters/subclassing.rst (2)

20-47: LGTM! Clear and accurate documentation.

The updated example correctly demonstrates pyproject.toml-based packaging with proper structure including build-system, project metadata, dynamic versioning, and setuptools configuration.


88-101: LGTM! Modern installation instructions.

The updated installation example correctly uses pip install -e . (editable install) instead of the deprecated setup.py develop approach, aligning with modern Python packaging practices.

optional_plugins/html/setup.py (1)

1-6: LGTM! Consistent migration pattern.

The transitional setup.py follows the same clean pattern as other plugins in this migration.

optional_plugins/result_upload/setup.py (1)

1-6: LGTM! Consistent migration pattern.

The transitional setup.py follows the established pattern for this migration.

optional_plugins/robot/setup.py (1)

1-6: LGTM! Consistent migration pattern.

The transitional setup.py follows the established pattern, completing a consistent migration approach across all reviewed plugins.

optional_plugins/result_upload/pyproject.toml (1)

1-45: Approve plugin metadata and entry points

VERSION and README.rst exist; build-system configuration, dependencies, and entry points are correctly defined.

optional_plugins/robot/pyproject.toml (1)

1-49: VERSION and README.rst present
Confirmed both files exist under optional_plugins/robot/, satisfying the dynamic entries. No further action required.

optional_plugins/html/pyproject.toml (1)

1-48: VERSION and README.rst exist as expected.

optional_plugins/README.rst (1)

10-34: Docs look good.

Thanks for updating the plugin dev workflow to the new pip-based commands—everything lines up with the Makefile targets.

.github/workflows/setup.yml (1)

33-44: All good.

Editable/user installs and per-plugin pip installs align with the new packaging workflow.

optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (6)

1-14: LGTM!

The imports are appropriate for remote spawner functionality. The use of aexpect for remote session management aligns with the library's intended purpose for test automation.


17-18: LGTM!

The exception class is properly defined for spawner-specific errors.


21-46: LGTM!

The configuration options are well-defined with appropriate defaults. The timeout values (1 hour for setup, 4 hours for tests) are reasonable for remote execution scenarios.


49-67: LGTM!

The decorator properly manages slot reservation lifecycle using a context manager, ensuring slots are released even if errors occur.


79-90: LGTM!

The async wrapper properly handles aexpect exceptions and provides consistent error reporting.


218-233: LGTM!

The requirement cache methods are correctly stubbed as no-ops, which is appropriate for a remote spawner that doesn't implement caching. The unused argument warnings from static analysis are false positives, as these parameters are required by the Spawner interface.

Comment on lines +106 to +114
if len(RemoteSpawner.slots_cache) == 0:
# TODO: consider whether to provide persistence across runs via external storage
for session_slot in self.config.get("spawner.remote.slots"):
if not session_slot:
continue
with open(session_slot, "r", encoding="utf-8") as f:
session_data = json.load(f)
session = remote.remote_login(**session_data)
RemoteSpawner.slots_cache[session] = False
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Add error handling for slot initialization.

Reading JSON configuration files and creating remote sessions can fail in multiple ways (malformed JSON, missing files, connection failures), but no error handling is present. A single bad slot configuration will crash the spawner initialization.

Apply this diff to add robust error handling:

         if len(RemoteSpawner.slots_cache) == 0:
             # TODO: consider whether to provide persistence across runs via external storage
             for session_slot in self.config.get("spawner.remote.slots"):
                 if not session_slot:
                     continue
-                with open(session_slot, "r", encoding="utf-8") as f:
-                    session_data = json.load(f)
-                session = remote.remote_login(**session_data)
-                RemoteSpawner.slots_cache[session] = False
+                try:
+                    with open(session_slot, "r", encoding="utf-8") as f:
+                        session_data = json.load(f)
+                    session = remote.remote_login(**session_data)
+                    RemoteSpawner.slots_cache[session] = False
+                except (FileNotFoundError, json.JSONDecodeError, KeyError) as e:
+                    LOG.warning(f"Failed to initialize slot from {session_slot}: {e}")
+                except Exception as e:
+                    LOG.error(f"Unexpected error initializing slot from {session_slot}: {e}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if len(RemoteSpawner.slots_cache) == 0:
# TODO: consider whether to provide persistence across runs via external storage
for session_slot in self.config.get("spawner.remote.slots"):
if not session_slot:
continue
with open(session_slot, "r", encoding="utf-8") as f:
session_data = json.load(f)
session = remote.remote_login(**session_data)
RemoteSpawner.slots_cache[session] = False
if len(RemoteSpawner.slots_cache) == 0:
# TODO: consider whether to provide persistence across runs via external storage
for session_slot in self.config.get("spawner.remote.slots"):
if not session_slot:
continue
try:
with open(session_slot, "r", encoding="utf-8") as f:
session_data = json.load(f)
session = remote.remote_login(**session_data)
RemoteSpawner.slots_cache[session] = False
except (FileNotFoundError, json.JSONDecodeError, KeyError) as e:
LOG.warning(f"Failed to initialize slot from {session_slot}: {e}")
except Exception as e:
LOG.error(f"Unexpected error initializing slot from {session_slot}: {e}")

)
return False

cmd = shlex.join(entry_point_args) + " > /dev/null"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Output redirection loses all test results.

Redirecting output to /dev/null discards all test output, including results, logs, stdout, and stderr. This defeats the purpose of running tests remotely and makes debugging impossible.

Remove the output redirection or redirect to a file that can be retrieved:

-        cmd = shlex.join(entry_point_args) + " > /dev/null"
+        cmd = shlex.join(entry_point_args)

Or if you need to capture output remotely:

-        cmd = shlex.join(entry_point_args) + " > /dev/null"
+        remote_log = f"/tmp/avocado-task-{task.identifier.replace('/', '_')}.log"
+        cmd = shlex.join(entry_point_args) + f" > {shlex.quote(remote_log)} 2>&1"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cmd = shlex.join(entry_point_args) + " > /dev/null"
cmd = shlex.join(entry_point_args)
🤖 Prompt for AI Agents
In optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py around line
182 the command string currently appends " > /dev/null" which discards all
stdout/stderr (losing test results and logs); remove the output redirection or
replace it with safe capture: either omit " > /dev/null" so output is preserved,
or redirect to a file (e.g., a temp file path) that is collected and returned,
or pipe stdout/stderr into a capture mechanism (logging or subprocess capture)
so remote test output can be retrieved for debugging.

@PaulYuuu PaulYuuu force-pushed the pyproject branch 4 times, most recently from 2795168 to 56fcda5 Compare October 11, 2025 15:13
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (6)
optional_plugins/ansible/setup.py (1)

1-1: Fix the shebang path.

The shebang still uses /bin/env instead of /usr/bin/env, which can cause portability issues across different systems. Although this was previously flagged as addressed, the issue remains in the current code.

Apply this diff:

-#!/bin/env python3
+#!/usr/bin/env python3
optional_plugins/varianter_cit/setup.py (1)

1-1: The shebang is still incorrect and likely unnecessary.

The shebang continues to use /bin/env instead of the more portable /usr/bin/env. However, since this file is now a minimal shim for pyproject.toml-based packaging and is not intended to be executed directly (it's imported by build tools like pip and setuptools), the shebang may no longer be necessary and could be removed entirely.

If the shebang must remain for backward compatibility, apply this diff to fix it:

-#!/bin/env python3
+#!/usr/bin/env python3

Otherwise, consider removing the shebang entirely:

-#!/bin/env python3
-# This file is kept for egg building compatibility during the transition period.
+# This file is kept for egg building compatibility during the transition period.
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (4)

106-115: Add error handling around slot initialization.

Malformed JSON or connection failures will crash the spawner.

         if len(RemoteSpawner.slots_cache) == 0:
             # TODO: consider whether to provide persistence across runs via external storage
             for session_slot in self.config.get("spawner.remote.slots"):
                 if not session_slot:
                     continue
-                with open(session_slot, "r", encoding="utf-8") as f:
-                    session_data = json.load(f)
-                session = remote.remote_login(**session_data)
-                RemoteSpawner.slots_cache[session] = False
+                try:
+                    with open(session_slot, "r", encoding="utf-8") as f:
+                        session_data = json.load(f)
+                    session = remote.remote_login(**session_data)
+                    RemoteSpawner.slots_cache[session] = False
+                except (FileNotFoundError, json.JSONDecodeError, KeyError) as e:
+                    LOG.warning(f"Failed to initialize slot from {session_slot}: {e}")
+                except Exception as e:
+                    LOG.error(f"Unexpected error initializing slot from {session_slot}: {e}")

136-146: Shell-escape task identifier in pgrep.

Avoid breakage/injection with special chars.

         session = runtime_task.spawner_handle
-        status, _ = session.cmd_status_output(
-            f"pgrep -r R,S -f {runtime_task.task.identifier}"
-        )
+        safe_identifier = shlex.quote(runtime_task.task.identifier)
+        status, _ = session.cmd_status_output(
+            f"pgrep -r R,S -f {safe_identifier}"
+        )

182-185: Do not discard task output.

Dropping stdout/stderr prevents debugging and may hide failures.

-        cmd = shlex.join(entry_point_args) + " > /dev/null"
+        cmd = shlex.join(entry_point_args)

If you prefer remote capture:

+        remote_log = f"/tmp/avocado-task-{runtime_task.task.identifier.replace('/', '_')}.log"
+        cmd = shlex.join(entry_point_args) + f" > {shlex.quote(remote_log)} 2>&1"

202-206: Add a timeout to the wait loop.

Prevents hanging indefinitely if liveness never flips.

-    async def wait_task(self, runtime_task):
-        while True:
-            if not RemoteSpawner.is_task_alive(runtime_task):
-                return
-            await asyncio.sleep(0.1)
+    async def wait_task(self, runtime_task):
+        timeout = self.config.get("spawner.remote.test_timeout")
+        start_time = asyncio.get_event_loop().time()
+        while True:
+            if not RemoteSpawner.is_task_alive(runtime_task):
+                return
+            if asyncio.get_event_loop().time() - start_time > timeout:
+                LOG.error(f"Task {runtime_task.task.identifier} exceeded wait timeout of {timeout}s")
+                return
+            await asyncio.sleep(0.1)
🧹 Nitpick comments (17)
selftests/pre_release/tests/check-copr-rpm-version.sh (2)

14-16: Source VERSION from origin/master for consistency, and hard‑fail if missing.

Other metadata (date/commit) come from $ORIGIN/master; reading VERSION from the working tree can drift. Also, if VERSION is missing/empty the NVR becomes malformed and fails later without a clear message. Recommend:

  • Try origin/master:VERSION first, fallback to local.
  • Exit early with a clear error if still empty.
-VERSION=$(cat VERSION 2>/dev/null)
+VERSION=$(git show "$ORIGIN/master:VERSION" 2>/dev/null || cat VERSION 2>/dev/null)
+if [ -z "$VERSION" ]; then
+    echo "ERROR: VERSION not found (checked $ORIGIN/master and working tree)" >&2
+    exit 1
+fi

23-26: Guard container runtime detection.

If neither podman nor docker is installed, $PODMAN is empty and the script errors cryptically. Add a check with a clear message.

-PODMAN=$(which podman 2>/dev/null || which docker)
+PODMAN=$(command -v podman 2>/dev/null || command -v docker 2>/dev/null)
+if [ -z "$PODMAN" ]; then
+    echo "ERROR: Neither podman nor docker found in PATH" >&2
+    exit 1
+fi
examples/plugins/tests/rogue/pyproject.toml (1)

5-9: Add requires-python to match the project’s supported versions.

Given 3.8 was dropped, declare the minimum supported version (likely >=3.9).

 [project]
 name = "avocado-rogue"
 version = "1.0"
 description = "Avocado \"rogue\" test type"
+requires-python = ">=3.9"
optional_plugins/varianter_yaml_to_mux/pyproject.toml (1)

29-30: Consider using HTTPS for the homepage URL.

The homepage URL uses http:// instead of https://. GitHub Pages typically supports HTTPS, which provides better security.

Apply this diff if the site supports HTTPS:

 [project.urls]
-Homepage = "http://avocado-framework.github.io/"
+Homepage = "https://avocado-framework.github.io/"
optional_plugins/golang/pyproject.toml (6)

1-3: Setuptools minimum version: ensure features are supported

[project.scripts] and [tool.setuptools.dynamic] should be supported by the declared minimum. Suggest bumping to a safer floor (e.g., 61.3+) to avoid edge cases.

If you keep 61.0, please confirm it works across your CI matrix.


12-12: License metadata: prefer SPDX or file reference

Using license.text = "GPLv2+" is non-standard. Prefer SPDX identifier or the license file.

Apply one of:

-license = {text = "GPLv2+"}
+license = {text = "GPL-2.0-or-later"}

or (recommended if a license file exists in this plugin dir):

-license = {text = "GPLv2+"}
+license = {file = "LICENSE"}

Please confirm the actual license file name/path (LICENSE/COPYING) in this subproject.


15-16: Add a lower bound on avocado-framework

Pin a minimal compatible core version to prevent incompatible installs.

Example:

-dependencies = [
-    "avocado-framework",
-]
+dependencies = [
+    "avocado-framework>=X.Y",
+]

What is the minimal Avocado version required by this plugin? Let's set it explicitly.


17-26: Optional: be explicit about supported Python versions

Add concrete Python classifiers for better PyPI metadata.

Example additions:

  • Programming Language :: Python :: 3.9
  • Programming Language :: Python :: 3.10
  • Programming Language :: Python :: 3.11
  • Programming Language :: Python :: 3.12

28-29: Use HTTPS for homepage

Switch to HTTPS.

-Homepage = "http://avocado-framework.github.io/"
+Homepage = "https://avocado-framework.github.io/"

7-8: Prefer native PEP 621 readme over setuptools dynamic

You can keep dynamic version, but set readme via [project.readme] and drop the dynamic readme stanza.

 [project]
 name = "avocado-framework-plugin-golang"
-dynamic = ["version", "readme"]
+dynamic = ["version"]
 description = "Avocado Plugin for Execution of Golang tests"
@@
-[tool.setuptools.dynamic]
-version = {file = "VERSION"}
-readme = {file = "README.rst", content-type = "text/x-rst"}
+[tool.setuptools.dynamic]
+version = {file = "VERSION"}

And add under [project] (near description/authors):

+readme = {file = "README.rst", content-type = "text/x-rst"}

Also applies to: 46-48

.github/actions/wheel/action.yml (1)

11-13: Consider using a virtual environment or --prefix for more robust isolation.

The combination of pip install -t /tmp/avocado_install with manual PYTHONPATH manipulation can be fragile. If the target directory structure conflicts with system packages or has path precedence issues, imports may fail unexpectedly.

Consider using a virtual environment for cleaner isolation:

-       python3 -m pip install dist/*.whl -t /tmp/avocado_install
-       export PYTHONPATH=/tmp/avocado_install:$PYTHONPATH
-       python3 -c 'from avocado.core.main import main; import sys; sys.exit(main())' run /bin/true
+       python3 -m venv /tmp/avocado_venv
+       /tmp/avocado_venv/bin/pip install dist/*.whl
+       /tmp/avocado_venv/bin/avocado run /bin/true

Or use --user install if system-wide isolation is not required:

-       python3 -m pip install dist/*.whl -t /tmp/avocado_install
-       export PYTHONPATH=/tmp/avocado_install:$PYTHONPATH
+       python3 -m pip install --user dist/*.whl
Makefile.gh (1)

65-82: Consider using pushd/popd for clearer directory navigation.

The pattern cd $$PLUGIN; ... cd -; works but pushd/popd is more explicit and maintains a directory stack for complex navigation scenarios.

Apply this pattern:

 	for PLUGIN in $(AVOCADO_OPTIONAL_PLUGINS); do\
-		cd $$PLUGIN;\
+		pushd $$PLUGIN;\
 		$(PYTHON) -m build -o ../../PYPI_UPLOAD;\
-		cd -;\
+		popd;\
 	done
examples/plugins/cli-cmd/hello_parser/pyproject.toml (1)

1-14: LGTM!

The pyproject.toml configuration is correct for this example plugin. The minimal metadata (no license, authors, or classifiers) is appropriate for an example, and the entry point mapping is properly structured.

Optional: Consider dynamic versioning for consistency.

While the hardcoded version 1.0 is fine for examples, using dynamic versioning (like other plugins in this PR) would maintain consistency across the project.

If you prefer consistency with other plugins:

 [project]
 name = "avocado-hello-world-parser"
-version = "1.0"
+dynamic = ["version"]
 description = "Avocado Hello World CLI command with config parser"
+
+[tool.setuptools.dynamic]
+version = {file = "VERSION"}

Then create a VERSION file with the version number.

optional_plugins/spawner_remote/pyproject.toml (2)

14-17: Consider bumping aexpect minimum for reliability fixes.

aexpect 1.8.0 includes notable bug fixes (remote_login logging loss, cleanup, output handling). Safe to require it if CI allows.

Based on learnings

 dependencies = [
-    "avocado-framework",
-    "aexpect>=1.6.2",
+    "avocado-framework",
+    "aexpect>=1.8.0",
 ]

29-31: Prefer HTTPS for project URL.

Minor polish; also consider declaring license files for wheels/sdists.

 [project.urls]
-Homepage = "http://avocado-framework.github.io/"
+Homepage = "https://avocado-framework.github.io/"

Additionally (optional) in the setuptools tool config:

 [tool.setuptools]
 include-package-data = true
+license-files = ["LICENSE", "COPYING*"]
optional_plugins/spawner_remote/setup.py (1)

1-1: Use the conventional env path in shebang.

Portable shebang is /usr/bin/env.

-#!/bin/env python3
+#!/usr/bin/env python3
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1)

72-75: Annotate mutable class attributes with ClassVar.

Silences linters and documents intent.

-    METHODS = [SpawnMethod.STANDALONE_EXECUTABLE]
-    slots_cache = {}
+    METHODS: "ClassVar" = [SpawnMethod.STANDALONE_EXECUTABLE]
+    slots_cache: "ClassVar[dict]" = {}

Add at the top of the file:

from typing import ClassVar
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af71902 and 56fcda5.

📒 Files selected for processing (65)
  • .github/actions/version/action.yml (1 hunks)
  • .github/actions/wheel/action.yml (1 hunks)
  • .github/workflows/ci.yml (16 hunks)
  • .github/workflows/prerelease.yml (2 hunks)
  • .github/workflows/push_ci.yml (1 hunks)
  • .github/workflows/release.yml (1 hunks)
  • .github/workflows/setup.yml (6 hunks)
  • .github/workflows/vmimage.yml (1 hunks)
  • .github/workflows/weekly.yml (2 hunks)
  • Makefile (4 hunks)
  • Makefile.gh (2 hunks)
  • docs/source/guides/contributor/chapters/environment.rst (1 hunks)
  • docs/source/guides/contributor/chapters/plugins.rst (4 hunks)
  • docs/source/guides/writer/chapters/subclassing.rst (2 hunks)
  • examples/plugins/README.rst (1 hunks)
  • examples/plugins/cli-cmd/hello/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_option/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_option/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/setup.py (0 hunks)
  • examples/plugins/job-pre-post/sleep/pyproject.toml (1 hunks)
  • examples/plugins/job-pre-post/sleep/setup.py (0 hunks)
  • examples/plugins/test-pre-post/hello/pyproject.toml (1 hunks)
  • examples/plugins/test-pre-post/hello/setup.py (0 hunks)
  • examples/plugins/tests/README.rst (1 hunks)
  • examples/plugins/tests/magic/pyproject.toml (1 hunks)
  • examples/plugins/tests/magic/setup.py (0 hunks)
  • examples/plugins/tests/rogue/pyproject.toml (1 hunks)
  • examples/plugins/tests/rogue/setup.py (0 hunks)
  • optional_plugins/README.rst (1 hunks)
  • optional_plugins/ansible/README.rst (1 hunks)
  • optional_plugins/ansible/pyproject.toml (1 hunks)
  • optional_plugins/ansible/setup.py (1 hunks)
  • optional_plugins/golang/pyproject.toml (1 hunks)
  • optional_plugins/golang/setup.py (1 hunks)
  • optional_plugins/html/pyproject.toml (1 hunks)
  • optional_plugins/html/setup.py (1 hunks)
  • optional_plugins/mail/pyproject.toml (1 hunks)
  • optional_plugins/mail/setup.py (1 hunks)
  • optional_plugins/result_upload/pyproject.toml (1 hunks)
  • optional_plugins/result_upload/setup.py (1 hunks)
  • optional_plugins/resultsdb/pyproject.toml (1 hunks)
  • optional_plugins/resultsdb/setup.py (1 hunks)
  • optional_plugins/robot/pyproject.toml (1 hunks)
  • optional_plugins/robot/setup.py (1 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/__init__.py (0 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1 hunks)
  • optional_plugins/spawner_remote/pyproject.toml (1 hunks)
  • optional_plugins/spawner_remote/setup.py (1 hunks)
  • optional_plugins/varianter_cit/pyproject.toml (1 hunks)
  • optional_plugins/varianter_cit/setup.py (1 hunks)
  • optional_plugins/varianter_pict/README.rst (1 hunks)
  • optional_plugins/varianter_pict/pyproject.toml (1 hunks)
  • optional_plugins/varianter_pict/setup.py (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/README.rst (4 hunks)
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/setup.py (1 hunks)
  • pyproject.toml (1 hunks)
  • python-avocado.spec (5 hunks)
  • selftests/pre_release/tests/check-copr-rpm-version.sh (1 hunks)
  • setup.py (2 hunks)
  • spell.ignore (1 hunks)
💤 Files with no reviewable changes (9)
  • optional_plugins/spawner_remote/avocado_spawner_remote/init.py
  • examples/plugins/tests/rogue/setup.py
  • examples/plugins/cli-cmd/hello_option/setup.py
  • examples/plugins/tests/magic/setup.py
  • examples/plugins/cli-cmd/hello_priority/setup.py
  • examples/plugins/test-pre-post/hello/setup.py
  • examples/plugins/cli-cmd/hello_parser/setup.py
  • examples/plugins/cli-cmd/hello/setup.py
  • examples/plugins/job-pre-post/sleep/setup.py
✅ Files skipped from review due to trivial changes (2)
  • optional_plugins/varianter_yaml_to_mux/README.rst
  • optional_plugins/varianter_pict/README.rst
🚧 Files skipped from review as they are similar to previous changes (26)
  • examples/plugins/job-pre-post/sleep/pyproject.toml
  • pyproject.toml
  • docs/source/guides/contributor/chapters/environment.rst
  • examples/plugins/cli-cmd/hello_option/pyproject.toml
  • spell.ignore
  • optional_plugins/result_upload/setup.py
  • optional_plugins/README.rst
  • .github/workflows/release.yml
  • examples/plugins/cli-cmd/hello/pyproject.toml
  • examples/plugins/tests/magic/pyproject.toml
  • docs/source/guides/contributor/chapters/plugins.rst
  • optional_plugins/varianter_cit/pyproject.toml
  • optional_plugins/ansible/pyproject.toml
  • optional_plugins/robot/setup.py
  • .github/workflows/push_ci.yml
  • examples/plugins/test-pre-post/hello/pyproject.toml
  • optional_plugins/ansible/README.rst
  • optional_plugins/golang/setup.py
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml
  • optional_plugins/mail/pyproject.toml
  • setup.py
  • optional_plugins/result_upload/pyproject.toml
  • docs/source/guides/writer/chapters/subclassing.rst
  • .github/workflows/prerelease.yml
  • .github/workflows/vmimage.yml
  • optional_plugins/robot/pyproject.toml
🧰 Additional context used
🧬 Code graph analysis (1)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (5)
avocado/core/plugin_interfaces.py (1)
  • Spawner (321-441)
avocado/core/spawners/common.py (3)
  • SpawnerMixin (35-47)
  • SpawnMethod (6-18)
  • task_output_dir (46-47)
avocado/core/settings.py (3)
  • section (137-138)
  • register_option (642-799)
  • key (141-142)
avocado/core/nrunner/runnable.py (1)
  • pick_runner_module_from_entry_point_kind (630-643)
avocado/core/nrunner/task.py (1)
  • setup_output_dir (171-174)
🪛 checkmake (0.2.2)
Makefile

[warning] 129-129: Missing required phony target "all"

(minphony)


[warning] 129-129: Missing required phony target "test"

(minphony)

🪛 Ruff (0.13.3)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py

73-73: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


74-74: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


126-129: Avoid specifying long messages outside the exception class

(TRY003)


197-197: Probable insecure usage of temporary file or directory: "/tmp/.avocado_task_output_dir"

(S108)


213-213: Consider moving this statement to an else block

(TRY300)


215-215: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


219-219: Unused static method argument: runtime_task

(ARG004)


224-224: Unused static method argument: runtime_task

(ARG004)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (37)
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: Podman Egg task
  • GitHub Check: Wheel task debian:12.4
  • GitHub Check: Egg task debian:11.0
  • GitHub Check: Fedora selftests
  • GitHub Check: Wheel task fedora:40
  • GitHub Check: Analyze (python)
  • GitHub Check: Wheel task ubuntu:22.04
  • GitHub Check: Version task debian:11.0
  • GitHub Check: Version task ubuntu:24.04
  • GitHub Check: Version task ubuntu:22.04
  • GitHub Check: Version task debian:12.4
  • GitHub Check: Windows with Python 3.11
  • GitHub Check: Windows with Python 3.13
  • GitHub Check: Build Package (wheel/tarball) for Python 3.9
  • GitHub Check: Build Package (wheel/tarball) for Python 3.11
  • GitHub Check: macOS with Python 3.11
🔇 Additional comments (36)
optional_plugins/ansible/setup.py (1)

2-6: pyproject.toml contains all necessary metadata
Name, dynamic version & readme, dependencies, scripts, and entry points are correctly specified.

examples/plugins/tests/rogue/pyproject.toml (1)

13-17: Entry-point group names ‘avocado.plugins.resolver’ and ‘avocado.plugins.runnable.runner’ are correct.

optional_plugins/varianter_pict/pyproject.toml (5)

1-3: LGTM! Build system configuration follows modern standards.

The build system configuration correctly uses setuptools>=61.0, which has full pyproject.toml support, and specifies the standard build backend.


31-35: Entry points look correct.

The plugin correctly registers both CLI and varianter interfaces. The use of the same name varianter_pict in different namespaces is appropriate.


37-41: Package discovery configuration is appropriate.

The setuptools configuration correctly enables package data inclusion and uses a glob pattern for package discovery.


43-45: Dynamic metadata files exist: optional_plugins/varianter_pict/VERSION and README.rst are present.


14-16: Leave avocado-framework unconstrained
The lack of a minimum version matches all other optional plugins’ pyproject.toml entries. No change required.

optional_plugins/varianter_pict/setup.py (1)

1-6: LGTM! Minimal setup.py shim is correctly implemented.

The file correctly delegates all packaging configuration to pyproject.toml while maintaining backward compatibility. The comment clearly explains the transition period.

optional_plugins/varianter_cit/setup.py (1)

2-6: LGTM! Clean migration to pyproject.toml.

The transition approach is correct:

  • The explanatory comments clearly document the purpose and transitional nature of this minimal setup.py
  • The empty setup() call is the standard pattern when migrating to pyproject.toml, allowing setuptools to automatically read configuration from pyproject.toml
  • Keeping this file maintains backward compatibility with older build tools that expect setup.py
optional_plugins/varianter_yaml_to_mux/setup.py (1)

1-6: LGTM! Standard pyproject.toml migration pattern.

This setup.py shim correctly maintains backward compatibility during the transition to pyproject.toml. The bare setup() call reads all configuration from pyproject.toml, and the comment clearly documents the transition.

optional_plugins/varianter_yaml_to_mux/pyproject.toml (5)

1-3: LGTM! Build system correctly configured.

The build system configuration is correct: setuptools>=61.0 enables dynamic metadata support, and the build backend is properly specified.


5-27: LGTM! Project metadata is well-defined.

The project metadata is correctly structured. The requires-python = ">=3.9" aligns with the project dropping Python 3.8 support, and the dependency specifications are appropriate.


41-45: LGTM! Setuptools configuration is appropriate.

The setuptools configuration correctly enables package data inclusion and uses a proper wildcard pattern for package discovery.


47-49: Dynamic file references are valid
VERSION and README.rst exist in optional_plugins/varianter_yaml_to_mux as expected; dynamic configuration is correct.


32-39: Entry points verified

All referenced classes (YamlToMuxInit, YamlToMuxCLI, YamlToMux) are present in varianter_yaml_to_mux.py; entry-point configuration is correct.

optional_plugins/golang/pyproject.toml (3)

40-45: Package discovery looks good

include-package-data and packages.find include = ["avocado_golang*"] align with this plugin. No issues.

If the plugin needs non-package files (e.g., templates), add [tool.setuptools.package-data] accordingly.


31-38: Entry point group is correct ‘avocado.plugins.runnable.runner’ matches Avocado’s loader code and documentation.


46-47: Include VERSION and README.rst in optional_plugins/golang sdist
There’s no MANIFEST.in in that directory, so add one alongside pyproject.toml:

include VERSION README.rst

Then rebuild the sdist and verify both files are present.

examples/plugins/tests/README.rst (1)

15-16: LGTM!

The update to use pip install -e . instead of the deprecated python setup.py develop --user aligns with modern Python packaging best practices and the PR's migration objectives.

examples/plugins/README.rst (1)

10-24: LGTM!

The updated instructions provide clear, modern pip-based workflows for plugin development. The explicit install and uninstall examples with concrete paths make the documentation more helpful for developers.

optional_plugins/mail/setup.py (1)

2-6: LGTM!

The simplified setup.py correctly delegates all packaging configuration to pyproject.toml, maintaining backward compatibility during the transition period as noted in the comment.

python-avocado.spec (2)

243-263: LGTM!

The conditional exclusions for avocado_result_html, avocado_resultsdb, and framework plugin packages are correctly structured with appropriate RHEL/Fedora version gating.


8-8: Verify custom tarball naming on GitHub and CI workflows.
The spec uses avocado_framework-%{version}.tar.gz (underscore). GitHub’s default is avocado-framework-<version>.tar.gz (hyphen), so ensure your release pipeline (CI jobs, PyPI sdist, GitHub release assets) uploads the custom avocado_framework-<version>.tar.gz artifact.

optional_plugins/resultsdb/pyproject.toml (1)

1-49: LGTM!

The pyproject.toml configuration for the resultsdb plugin is well-structured and complete. The dynamic version/readme loading, dependency specifications, entry points for CLI/result_events/result handlers, and package discovery are all correctly configured.

Makefile.gh (1)

65-69: No missing packaging configurations detected. Verified that every optional plugin directory contains a setup.py or pyproject.toml.

optional_plugins/resultsdb/setup.py (1)

2-6: LGTM! Clean transition shim.

The minimal setup() call with clear comments about the transition period is the correct approach for maintaining backward compatibility while migrating to pyproject.toml-based configuration.

.github/actions/version/action.yml (1)

9-9: LGTM! Correctly migrated to pip-based editable install.

The change from python3 setup.py develop --user to python3 -m pip install -e . --user aligns with the modern Python packaging workflow and the PR's migration goals.

optional_plugins/html/setup.py (1)

2-6: LGTM! Clean transition shim.

The minimal setup() call with clear comments about the transition period maintains backward compatibility while delegating configuration to pyproject.toml.

.github/workflows/weekly.yml (1)

37-47: LGTM! Consistent migration to modern Python workflows.

The changes correctly replace setup.py-based commands with pip module invocations and Python module execution for tests, aligning with the PR's modernization goals.

.github/workflows/setup.yml (1)

32-50: LGTM! Comprehensive installation testing.

The updated user installation workflow correctly uses python3 -m pip install for both the core framework and optional plugins, ensuring compatibility with modern Python packaging standards.

.github/workflows/ci.yml (2)

28-32: LGTM! CI workflow correctly updated.

The static checks job properly uses python3 -m pip install -e . --user for editable installation and python3 -m selftests.check for running tests, aligning with the pyproject.toml migration.


361-386: LGTM! Modernized packaging terminology.

The renaming of "egg" tasks to "wheel" tasks (lines 361-386) correctly reflects the shift to modern Python wheel-based packaging, which is the preferred distribution format.

Makefile (1)

94-108: All plugin directories align with PLUGIN values. Directory listing confirms convention; no changes required.

optional_plugins/html/pyproject.toml (1)

1-48: Entry points correct The classes HTML, HTMLInit, and HTMLResult are defined in the avocado_result_html module, matching the entry-point definitions.

optional_plugins/spawner_remote/pyproject.toml (1)

32-36: Entry points look correct now.

The module paths include .spawner and match the new implementation.

optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1)

151-156: No issues with pick_runner_module_from_entry_point_kind access. It's defined as a staticmethod on Runnable and callable via the instance.

Comment on lines +208 to +216
async def terminate_task(self, runtime_task):
session = runtime_task.spawner_handle
session.sendcontrol("c")
try:
session.read_up_to_prompt()
return True
except exceptions.ExpectTimeoutError:
LOG.error("Failed to terminate task on {session.host}")
return False
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix logging message and include traceback on termination failure.

Current message lacks f-string; use logging.exception for context.

     async def terminate_task(self, runtime_task):
         session = runtime_task.spawner_handle
         session.sendcontrol("c")
         try:
             session.read_up_to_prompt()
-            return True
-        except exceptions.ExpectTimeoutError:
-            LOG.error("Failed to terminate task on {session.host}")
-            return False
+        except exceptions.ExpectTimeoutError:
+            LOG.exception(f"Failed to terminate task on {session.host}:{session.port}")
+            return False
+        else:
+            return True
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async def terminate_task(self, runtime_task):
session = runtime_task.spawner_handle
session.sendcontrol("c")
try:
session.read_up_to_prompt()
return True
except exceptions.ExpectTimeoutError:
LOG.error("Failed to terminate task on {session.host}")
return False
async def terminate_task(self, runtime_task):
session = runtime_task.spawner_handle
session.sendcontrol("c")
try:
session.read_up_to_prompt()
except exceptions.ExpectTimeoutError:
LOG.exception(f"Failed to terminate task on {session.host}:{session.port}")
return False
else:
return True
🧰 Tools
🪛 Ruff (0.13.3)

213-213: Consider moving this statement to an else block

(TRY300)


215-215: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

🤖 Prompt for AI Agents
In optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py around
lines 208 to 216, the failure log uses a plain string and omits traceback;
change the LOG.error call to log the actual host and include the exception
traceback by using LOG.exception(f"Failed to terminate task on {session.host}")
(or LOG.error(..., exc_info=True)) so the host is interpolated and the
stacktrace is recorded.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (9)
optional_plugins/mail/setup.py (1)

1-1: Fix the shebang for better portability.

The shebang uses /bin/env which is not portable across all systems. Use /usr/bin/env instead.

Apply this diff:

-#!/bin/env python3
+#!/usr/bin/env python3
optional_plugins/ansible/setup.py (1)

1-1: Fix the shebang for better portability.

The shebang uses /bin/env which is not portable across all systems. Use /usr/bin/env instead.

Apply this diff:

-#!/bin/env python3
+#!/usr/bin/env python3
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (7)

38-46: Timeout options should specify key_type=int.

Without key_type=int, the settings will be stored as strings, which can break arithmetic operations or comparisons in downstream code that expects integers.

Apply this diff to fix the type declarations:

 help_msg = "Test timeout enforced for remote host setup hook"
 settings.register_option(
-    section=section, key="setup_timeout", help_msg=help_msg, default=3600
+    section=section, key="setup_timeout", help_msg=help_msg, default=3600, key_type=int
 )
 
 help_msg = "Test timeout enforced for sessions (just for this spawner)"
 settings.register_option(
-    section=section, key="test_timeout", help_msg=help_msg, default=14400
+    section=section, key="test_timeout", help_msg=help_msg, default=14400, key_type=int
 )

106-114: Add error handling for slot initialization.

Reading JSON configuration files and creating remote sessions can fail in multiple ways (malformed JSON, missing files, connection failures). Without error handling, a single bad slot configuration will crash the spawner initialization.

Apply this diff to add robust error handling:

         if len(RemoteSpawner.slots_cache) == 0:
             # TODO: consider whether to provide persistence across runs via external storage
             for session_slot in self.config.get("spawner.remote.slots"):
                 if not session_slot:
                     continue
-                with open(session_slot, "r", encoding="utf-8") as f:
-                    session_data = json.load(f)
-                session = remote.remote_login(**session_data)
-                RemoteSpawner.slots_cache[session] = False
+                try:
+                    with open(session_slot, "r", encoding="utf-8") as f:
+                        session_data = json.load(f)
+                    session = remote.remote_login(**session_data)
+                    RemoteSpawner.slots_cache[session] = False
+                except (FileNotFoundError, json.JSONDecodeError, KeyError) as e:
+                    LOG.warning(f"Failed to initialize slot from {session_slot}: {e}")
+                except Exception as e:
+                    LOG.error(f"Unexpected error initializing slot from {session_slot}: {e}")

143-145: Escape task identifier in shell command.

The task identifier is interpolated directly into the shell command without escaping. If the identifier contains special characters (spaces, quotes, shell metacharacters), the command will break or could lead to command injection.

Apply this diff to properly escape the identifier:

     @staticmethod
     def is_task_alive(runtime_task):
         if runtime_task.spawner_handle is None:
             return False
         # NOTE: since this is called at the end of each test, it is reasonable
         # to reuse the same session with a new command
         session = runtime_task.spawner_handle
+        safe_identifier = shlex.quote(runtime_task.task.identifier)
         status, _ = session.cmd_status_output(
-            f"pgrep -r R,S -f {runtime_task.task.identifier}"
+            f"pgrep -r R,S -f {safe_identifier}"
         )
         return status == 0

182-182: Critical: Output redirection loses all test results.

Redirecting output to /dev/null discards all test output, including results, logs, stdout, and stderr. This defeats the purpose of running tests remotely and makes debugging impossible.

Remove the output redirection or redirect to a file that can be retrieved:

-        cmd = shlex.join(entry_point_args) + " > /dev/null"
+        cmd = shlex.join(entry_point_args)

Or if you need to capture output remotely:

-        cmd = shlex.join(entry_point_args) + " > /dev/null"
+        remote_log = f"/tmp/avocado-task-{task.identifier.replace('/', '_')}.log"
+        cmd = shlex.join(entry_point_args) + f" > {shlex.quote(remote_log)} 2>&1"

195-200: Avoid fixed /tmp path; use a sanitized, unique remote directory.

The hardcoded /tmp/.avocado_task_output_dir path can lead to collisions when multiple tasks run concurrently, and presents TOCTOU (time-of-check-time-of-use) risks.

Apply this diff to use a unique, sanitized directory per task:

     def create_task_output_dir(self, runtime_task):
         output_dir_path = self.task_output_dir(runtime_task)
-        output_lxc_path = "/tmp/.avocado_task_output_dir"
+        safe_id = "".join(c if c.isalnum() or c in "._-" else "_" for c in runtime_task.task.identifier)
+        output_lxc_path = f"/tmp/avocado-task-{safe_id}"
 
         os.makedirs(output_dir_path, exist_ok=True)
         runtime_task.task.setup_output_dir(output_lxc_path)

202-206: Add timeout to prevent infinite waiting.

The wait loop has no timeout, so if a task hangs or is_task_alive has a bug, the spawner will wait indefinitely and block the job runner.

Apply this diff to add a configurable timeout:

     async def wait_task(self, runtime_task):
+        timeout = self.config.get("spawner.remote.test_timeout")
+        start_time = asyncio.get_event_loop().time()
         while True:
             if not RemoteSpawner.is_task_alive(runtime_task):
                 return
+            if asyncio.get_event_loop().time() - start_time > timeout:
+                LOG.error(f"Task {runtime_task.task.identifier} exceeded wait timeout of {timeout}s")
+                return
             await asyncio.sleep(0.1)

208-216: Fix logging message and include traceback on termination failure.

The error message on line 215 is missing f-string interpolation and should use logging.exception to capture the full traceback.

Apply this diff to fix the logging:

     async def terminate_task(self, runtime_task):
         session = runtime_task.spawner_handle
         session.sendcontrol("c")
         try:
             session.read_up_to_prompt()
-            return True
-        except exceptions.ExpectTimeoutError:
-            LOG.error("Failed to terminate task on {session.host}")
-            return False
+        except exceptions.ExpectTimeoutError:
+            LOG.exception(f"Failed to terminate task on {session.host}:{session.port}")
+            return False
+        else:
+            return True
🧹 Nitpick comments (3)
.github/workflows/ci.yml (2)

89-89: Remove redundant version flag.

The -V flag already displays the Python version, so adding --version is redundant.

Apply this diff:

-        run: python3 -V --version
+        run: python3 -V

131-131: Remove redundant version flag.

The -V flag already displays the Python version, so adding --version is redundant.

Apply this diff:

-        run: python3 -V --version
+        run: python3 -V
optional_plugins/robot/pyproject.toml (1)

16-16: Remove redundant python_version condition.

The python_version >= '3.9' condition on line 16 is redundant because line 13 already specifies requires-python = ">=3.9", which applies to all dependencies.

Apply this diff to remove the redundant condition:

 dependencies = [
     "avocado-framework",
-    "robotframework>=4.1, <7.0; python_version >= '3.9'",
+    "robotframework>=4.1, <7.0",
 ]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 56fcda5 and 2f1afd1.

📒 Files selected for processing (66)
  • .github/actions/version/action.yml (1 hunks)
  • .github/actions/wheel/action.yml (1 hunks)
  • .github/workflows/ci.yml (16 hunks)
  • .github/workflows/prerelease.yml (2 hunks)
  • .github/workflows/push_ci.yml (1 hunks)
  • .github/workflows/release.yml (1 hunks)
  • .github/workflows/setup.yml (6 hunks)
  • .github/workflows/vmimage.yml (1 hunks)
  • .github/workflows/weekly.yml (2 hunks)
  • Makefile (4 hunks)
  • Makefile.gh (2 hunks)
  • docs/source/guides/contributor/chapters/environment.rst (1 hunks)
  • docs/source/guides/contributor/chapters/plugins.rst (4 hunks)
  • docs/source/guides/writer/chapters/subclassing.rst (2 hunks)
  • examples/plugins/README.rst (1 hunks)
  • examples/plugins/cli-cmd/hello/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_option/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_option/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/setup.py (0 hunks)
  • examples/plugins/job-pre-post/sleep/pyproject.toml (1 hunks)
  • examples/plugins/job-pre-post/sleep/setup.py (0 hunks)
  • examples/plugins/test-pre-post/hello/pyproject.toml (1 hunks)
  • examples/plugins/test-pre-post/hello/setup.py (0 hunks)
  • examples/plugins/tests/README.rst (1 hunks)
  • examples/plugins/tests/magic/pyproject.toml (1 hunks)
  • examples/plugins/tests/magic/setup.py (0 hunks)
  • examples/plugins/tests/rogue/pyproject.toml (1 hunks)
  • examples/plugins/tests/rogue/setup.py (0 hunks)
  • optional_plugins/README.rst (1 hunks)
  • optional_plugins/ansible/README.rst (1 hunks)
  • optional_plugins/ansible/pyproject.toml (1 hunks)
  • optional_plugins/ansible/setup.py (1 hunks)
  • optional_plugins/golang/pyproject.toml (1 hunks)
  • optional_plugins/golang/setup.py (1 hunks)
  • optional_plugins/html/pyproject.toml (1 hunks)
  • optional_plugins/html/setup.py (1 hunks)
  • optional_plugins/mail/pyproject.toml (1 hunks)
  • optional_plugins/mail/setup.py (1 hunks)
  • optional_plugins/result_upload/pyproject.toml (1 hunks)
  • optional_plugins/result_upload/setup.py (1 hunks)
  • optional_plugins/resultsdb/pyproject.toml (1 hunks)
  • optional_plugins/resultsdb/setup.py (1 hunks)
  • optional_plugins/robot/pyproject.toml (1 hunks)
  • optional_plugins/robot/setup.py (1 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/__init__.py (0 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1 hunks)
  • optional_plugins/spawner_remote/pyproject.toml (1 hunks)
  • optional_plugins/spawner_remote/setup.py (1 hunks)
  • optional_plugins/varianter_cit/pyproject.toml (1 hunks)
  • optional_plugins/varianter_cit/setup.py (1 hunks)
  • optional_plugins/varianter_pict/README.rst (1 hunks)
  • optional_plugins/varianter_pict/pyproject.toml (1 hunks)
  • optional_plugins/varianter_pict/setup.py (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/README.rst (4 hunks)
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/setup.py (1 hunks)
  • pyproject.toml (1 hunks)
  • python-avocado.spec (6 hunks)
  • selftests/pre_release/tests/check-copr-rpm-version.sh (1 hunks)
  • selftests/run_coverage (1 hunks)
  • setup.py (2 hunks)
  • spell.ignore (1 hunks)
💤 Files with no reviewable changes (9)
  • examples/plugins/cli-cmd/hello_option/setup.py
  • examples/plugins/tests/magic/setup.py
  • examples/plugins/cli-cmd/hello_priority/setup.py
  • examples/plugins/cli-cmd/hello_parser/setup.py
  • examples/plugins/test-pre-post/hello/setup.py
  • examples/plugins/tests/rogue/setup.py
  • optional_plugins/spawner_remote/avocado_spawner_remote/init.py
  • examples/plugins/job-pre-post/sleep/setup.py
  • examples/plugins/cli-cmd/hello/setup.py
🚧 Files skipped from review as they are similar to previous changes (32)
  • optional_plugins/resultsdb/setup.py
  • .github/actions/version/action.yml
  • examples/plugins/tests/rogue/pyproject.toml
  • optional_plugins/ansible/README.rst
  • .github/workflows/weekly.yml
  • docs/source/guides/contributor/chapters/plugins.rst
  • examples/plugins/cli-cmd/hello_option/pyproject.toml
  • spell.ignore
  • examples/plugins/cli-cmd/hello/pyproject.toml
  • examples/plugins/job-pre-post/sleep/pyproject.toml
  • selftests/pre_release/tests/check-copr-rpm-version.sh
  • optional_plugins/ansible/pyproject.toml
  • optional_plugins/html/setup.py
  • optional_plugins/varianter_cit/pyproject.toml
  • examples/plugins/tests/README.rst
  • examples/plugins/tests/magic/pyproject.toml
  • optional_plugins/html/pyproject.toml
  • optional_plugins/golang/setup.py
  • optional_plugins/spawner_remote/setup.py
  • examples/plugins/test-pre-post/hello/pyproject.toml
  • .github/workflows/vmimage.yml
  • .github/workflows/release.yml
  • optional_plugins/varianter_pict/setup.py
  • .github/workflows/push_ci.yml
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml
  • pyproject.toml
  • optional_plugins/golang/pyproject.toml
  • .github/actions/wheel/action.yml
  • optional_plugins/varianter_pict/README.rst
  • docs/source/guides/contributor/chapters/environment.rst
  • .github/workflows/prerelease.yml
  • optional_plugins/README.rst
🧰 Additional context used
🧬 Code graph analysis (1)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (5)
avocado/core/plugin_interfaces.py (2)
  • Init (23-28)
  • Spawner (321-441)
avocado/core/spawners/common.py (3)
  • SpawnerMixin (35-47)
  • SpawnMethod (6-18)
  • task_output_dir (46-47)
avocado/core/settings.py (3)
  • section (137-138)
  • register_option (642-799)
  • key (141-142)
avocado/core/nrunner/runnable.py (1)
  • pick_runner_module_from_entry_point_kind (630-643)
avocado/core/nrunner/task.py (1)
  • setup_output_dir (171-174)
🪛 checkmake (0.2.2)
Makefile

[warning] 129-129: Missing required phony target "all"

(minphony)


[warning] 129-129: Missing required phony target "test"

(minphony)

🪛 Ruff (0.13.3)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py

73-73: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


74-74: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


126-129: Avoid specifying long messages outside the exception class

(TRY003)


197-197: Probable insecure usage of temporary file or directory: "/tmp/.avocado_task_output_dir"

(S108)


213-213: Consider moving this statement to an else block

(TRY300)


215-215: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


219-219: Unused static method argument: runtime_task

(ARG004)


224-224: Unused static method argument: runtime_task

(ARG004)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (43)
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: Linux with Python 3.13
  • GitHub Check: Linux with Python 3.12
  • GitHub Check: Linux with Python 3.11
  • GitHub Check: Linux with Python 3.9
  • GitHub Check: Linux with Python 3.10
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: macOS with Python 3.11
  • GitHub Check: Code Coverage (3.11)
🔇 Additional comments (18)
optional_plugins/varianter_yaml_to_mux/setup.py (1)

2-6: LGTM! Clear transitional approach.

The minimal setup.py with a transitional comment clearly documents the migration path to pyproject.toml-based packaging while maintaining backward compatibility.

selftests/run_coverage (1)

30-30: LGTM! Aligns with module-based execution pattern.

Changing from direct script execution to python -m selftests.check is consistent with Python best practices and aligns with the broader repository migration to module-based test execution.

optional_plugins/varianter_yaml_to_mux/README.rst (1)

6-6: LGTM! Documentation formatting improved.

Converting Sphinx-specific roles to inline code literals simplifies the documentation and makes it more portable while maintaining clarity.

Also applies to: 11-11, 14-14, 632-632, 639-639, 649-649, 786-786, 790-790, 798-800

Makefile.gh (2)

66-68: LGTM! Simplified plugin build iteration.

Removing the conditional setup.py existence check aligns with the pyproject.toml migration, where all plugins now have standardized packaging configurations. The unconditional directory entry assumes all plugin directories have proper build configurations, which is reasonable for this transition.


79-81: LGTM! Consistent with wheel build changes.

The egg build target follows the same pattern as the wheel build, removing conditional checks and standardizing the plugin iteration logic.

docs/source/guides/writer/chapters/subclassing.rst (2)

20-47: LGTM! Documentation modernized for pyproject.toml.

The documentation accurately reflects modern Python packaging practices, showing the complete pyproject.toml structure with dynamic versioning, build system configuration, and proper metadata declaration.


88-101: LGTM! Updated installation instructions.

The installation guidance correctly shifts from python setup.py develop to pip install -e ., which is the modern, recommended approach for editable installs. The example output clearly demonstrates the expected behavior.

optional_plugins/result_upload/setup.py (1)

2-6: LGTM! Consistent transitional pattern.

The minimal setup.py follows the same transitional pattern as other plugins, maintaining backward compatibility while documenting the migration to pyproject.toml.

optional_plugins/mail/setup.py (1)

2-6: LGTM! Consistent transitional pattern.

The minimal setup.py maintains backward compatibility while properly delegating configuration to pyproject.toml.

optional_plugins/ansible/setup.py (1)

2-6: LGTM! Consistent transitional pattern.

The minimal setup.py follows the established pattern for the pyproject.toml migration, maintaining compatibility during the transition period.

Makefile (3)

64-67: LGTM: Pip-based install/uninstall modernizes the workflow.

The migration from setup.py install/develop to pip install aligns with the broader pyproject.toml transition and follows current Python packaging best practices.


76-80: LGTM: Module-based invocation is correct.

Using python -m avocado and python -m selftests.check ensures the correct module resolution and is consistent with the pip-based editable install approach.


94-108: LGTM: Plugin development targets enhance developer experience.

The new develop-plugins and develop-plugin targets provide convenient workflows for installing optional plugins in editable mode, which aligns well with the pyproject.toml-based plugin packaging.

examples/plugins/README.rst (1)

10-24: LGTM: Documentation accurately reflects the pip-based workflow.

The updated examples clearly demonstrate the modern pip install -e . and pip uninstall commands, which align with the pyproject.toml migration across the repository.

optional_plugins/robot/setup.py (1)

2-6: LGTM: Minimal setup.py maintains backward compatibility.

The transitional comment and minimal setup() call appropriately maintain backward compatibility while delegating configuration to pyproject.toml.

optional_plugins/result_upload/pyproject.toml (1)

1-45: LGTM: Well-structured pyproject.toml for result_upload plugin.

The packaging configuration is complete and follows the same pattern as other optional plugins. Entry points are correctly defined for CLI and result plugin integration.

optional_plugins/spawner_remote/pyproject.toml (2)

32-36: LGTM: Entry points correctly reference the spawner module.

The entry points now correctly point to avocado_spawner_remote.spawner:RemoteSpawnerInit and avocado_spawner_remote.spawner:RemoteSpawner, which aligns with the new spawner.py implementation.


16-16: No change needed for aexpect dependency. aexpect>=1.6.2 already includes version 1.8.0, which introduces only bug fixes and improvements without breaking changes.

requires-python = ">=3.9"
dependencies = [
"avocado-framework",
"robotframework>=4.1, <7.0; python_version >= '3.9'",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Verify the robotframework version constraint.

The constraint <7.0 may be outdated. Since my knowledge is from March 2025 and robotframework releases frequently, please verify whether robotframework 7.x is available and compatible.


🌐 Web query:

What is the latest stable version of robotframework as of October 2025, and is version 7.0 or higher available?

💡 Result:

As of Oct 13, 2025 the latest stable Robot Framework release is 7.3.2. [1][2]
Yes — versions 7.0 and higher are available (7.0 was released Jan 11, 2024 and the 7.x series continues through 7.3.2). [2][1]

Sources:
[1] PyPI project page for robotframework (showing v7.3.2).
[2] Robot Framework GitHub releases (showing v7.3 and related 7.x releases).


Update Robot Framework version constraint: in optional_plugins/robot/pyproject.toml, change the dependency from

"robotframework>=4.1, <7.0; python_version >= '3.9'"

to

"robotframework>=4.1, <8.0; python_version >= '3.9'"

to support the latest 7.x releases (current latest 7.3.2).

🤖 Prompt for AI Agents
In optional_plugins/robot/pyproject.toml around line 16, update the Robot
Framework version upper bound from <7.0 to <8.0 so the dependency line reads
that Robot Framework versions 4.1 and above up to (but not including) 8.0 are
allowed; edit the quoted dependency string accordingly and save the file.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (7)
examples/plugins/cli-cmd/hello_priority/pyproject.toml (1)

10-11: Duplicate entry point name causes collision.

Both examples/plugins/cli-cmd/hello/pyproject.toml and this file define the entry point key hello under [project.entry-points."avocado.plugins.cli.cmd"]. This collision will prevent both plugins from being installed simultaneously. Rename this entry point to hello_priority to match the module name and avoid conflicts.

optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (6)

38-46: Fix timeout options to use integer type.

The timeout options are registered without key_type=int, which means they will be parsed as strings. This can cause issues when the values are used in arithmetic operations or comparisons.

Apply this diff:

 help_msg = "Test timeout enforced for remote host setup hook"
 settings.register_option(
-    section=section, key="setup_timeout", help_msg=help_msg, default=3600
+    section=section, key="setup_timeout", help_msg=help_msg, default=3600, key_type=int
 )

 help_msg = "Test timeout enforced for sessions (just for this spawner)"
 settings.register_option(
-    section=section, key="test_timeout", help_msg=help_msg, default=14400
+    section=section, key="test_timeout", help_msg=help_msg, default=14400, key_type=int
 )

182-182: Critical: Output redirection loses all test results.

Redirecting output to /dev/null discards all test output, including results, logs, stdout, and stderr. This makes debugging impossible and defeats the purpose of running tests remotely.

Remove the output redirection:

-        cmd = shlex.join(entry_point_args) + " > /dev/null"
+        cmd = shlex.join(entry_point_args)

Or if you need to capture output remotely for later retrieval:

-        cmd = shlex.join(entry_point_args) + " > /dev/null"
+        safe_id = "".join(c if c.isalnum() or c in "._-" else "_" for c in task.identifier)
+        remote_log = f"/tmp/avocado-task-{safe_id}.log"
+        cmd = shlex.join(entry_point_args) + f" > {shlex.quote(remote_log)} 2>&1"

202-206: Add timeout to prevent infinite waiting.

The wait loop has no timeout, so if a task hangs or is_task_alive has a bug, the spawner will wait indefinitely and block the job runner.

Apply this diff to add a configurable timeout:

     async def wait_task(self, runtime_task):
+        timeout = self.config.get("spawner.remote.test_timeout")
+        start_time = asyncio.get_event_loop().time()
         while True:
             if not RemoteSpawner.is_task_alive(runtime_task):
                 return
+            if asyncio.get_event_loop().time() - start_time > timeout:
+                LOG.error(f"Task {runtime_task.task.identifier} exceeded wait timeout of {timeout}s")
+                return
             await asyncio.sleep(0.1)

106-114: Add error handling for slot initialization.

Reading JSON configuration files and creating remote sessions can fail in multiple ways (malformed JSON, missing files, connection failures). Without error handling, a single bad slot configuration will crash the spawner initialization.

Apply this diff:

         if len(RemoteSpawner.slots_cache) == 0:
             # TODO: consider whether to provide persistence across runs via external storage
             for session_slot in self.config.get("spawner.remote.slots"):
                 if not session_slot:
                     continue
-                with open(session_slot, "r", encoding="utf-8") as f:
-                    session_data = json.load(f)
-                session = remote.remote_login(**session_data)
-                RemoteSpawner.slots_cache[session] = False
+                try:
+                    with open(session_slot, "r", encoding="utf-8") as f:
+                        session_data = json.load(f)
+                    session = remote.remote_login(**session_data)
+                    RemoteSpawner.slots_cache[session] = False
+                except (FileNotFoundError, json.JSONDecodeError, KeyError) as e:
+                    LOG.warning(f"Failed to initialize slot from {session_slot}: {e}")
+                except Exception as e:
+                    LOG.error(f"Unexpected error initializing slot from {session_slot}: {e}")

195-200: Avoid fixed /tmp path to reduce collision and security risks.

Using a fixed path /tmp/.avocado_task_output_dir can lead to collisions between concurrent tasks and TOCTOU (time-of-check-time-of-use) security vulnerabilities.

Apply this diff to use a unique, sanitized path per task:

     def create_task_output_dir(self, runtime_task):
         output_dir_path = self.task_output_dir(runtime_task)
-        output_lxc_path = "/tmp/.avocado_task_output_dir"
+        safe_id = "".join(c if c.isalnum() or c in "._-" else "_" for c in runtime_task.task.identifier)
+        output_lxc_path = f"/tmp/avocado-task-{safe_id}"

         os.makedirs(output_dir_path, exist_ok=True)
         runtime_task.task.setup_output_dir(output_lxc_path)

208-216: Fix logging and improve exception handling.

The error message is missing an f-string prefix and should use logging.exception to include the traceback. Also, the return statement should be moved to an else block per Python conventions.

Apply this diff:

     async def terminate_task(self, runtime_task):
         session = runtime_task.spawner_handle
         session.sendcontrol("c")
         try:
             session.read_up_to_prompt()
-            return True
         except exceptions.ExpectTimeoutError:
-            LOG.error("Failed to terminate task on {session.host}")
+            LOG.exception(f"Failed to terminate task on {session.host}:{session.port}")
             return False
+        else:
+            return True
🧹 Nitpick comments (9)
examples/plugins/tests/magic/pyproject.toml (1)

25-25: Add explicit package discovery constraints.

The [tool.setuptools.packages.find] section is empty, which means setuptools will discover all packages in the directory. This may pick up unwanted packages or fail to find the intended package.

Apply this diff to explicitly include the avocado_magic package:

 [tool.setuptools.packages.find]
+include = ["avocado_magic*"]

Alternatively, if avocado_magic is in a non-standard location:

 [tool.setuptools.packages.find]
+where = ["."]
+include = ["avocado_magic*"]
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1)

73-74: Add ClassVar annotations for mutable class attributes.

The static analysis tool correctly identifies that METHODS and slots_cache are mutable class attributes that should be annotated with typing.ClassVar to clarify their scope and prevent potential issues with subclasses.

Apply this diff:

+from typing import ClassVar
+
 class RemoteSpawner(Spawner, SpawnerMixin):

     description = "Remote (host) based spawner"
-    METHODS = [SpawnMethod.STANDALONE_EXECUTABLE]
-    slots_cache = {}
+    METHODS: ClassVar[list] = [SpawnMethod.STANDALONE_EXECUTABLE]
+    slots_cache: ClassVar[dict] = {}
.github/actions/wheel/action.yml (2)

14-16: Consider using the avocado command directly.

The wheel installation should provide the avocado console script via entry points. Using the Python one-liner to import and call main() directly bypasses the installed entry point, which may not fully test the wheel installation.

Consider updating to use the installed command:

-       python3 -c 'from avocado.core.main import main; import sys; sys.exit(main())' run /bin/true
+       avocado run /bin/true
        cd /tmp
-       python3 -c 'from avocado.core.main import main; import sys; sys.exit(main())' run /bin/true
+       avocado run /bin/true

However, if the avocado command is not available in PATH after the wheel installation with -t, the current approach may be necessary. Please verify the intent.


9-9: Remove unnecessary PIP_BREAK_SYSTEM_PACKAGES override

  • .github/actions/wheel/action.yml:9: drop export PIP_BREAK_SYSTEM_PACKAGES=1pip install --target already isolates installs without touching system site-packages
  • Keep the Python one-liner—pip install --target doesn’t generate console_scripts wrappers, so invoking main() directly is correct.
Makefile (1)

83-86: Clarify the distinction between develop and develop-core.

Lines 83 and 85-86 define both develop and develop-core targets with identical implementations. This duplication suggests either:

  1. These targets are meant to be aliases (in which case, document this)
  2. They will diverge in the future (in which case, document the intended distinction)

If they're meant to be aliases, consider making this explicit:

develop:
	$(PYTHON) -m pip install -e . $(PYTHON_DEVELOP_ARGS)

develop-core: develop
	@# Alias for develop target

Otherwise, document in the help text (lines 6-7) what distinguishes develop from develop-core.

.github/actions/version/action.yml (1)

9-9: Remove unnecessary PIP_BREAK_SYSTEM_PACKAGES
Since pip install -e . --user installs into the user site and cannot overwrite system packages, this override is redundant. Remove it:

-        export PIP_BREAK_SYSTEM_PACKAGES=1
         python3 -m pip install -e . --user
setup.py (1)

24-26: Add CI validation for entry_points synchronization

  • Egg builds are still in use (Makefile targets, fetch-eggs scripts, plugin spawners, eggenv), so the duplicated entry_points in setup.py is required.
  • Introduce a CI check that programmatically compares entry_points in setup.py with those in pyproject.toml to prevent drift.
.github/workflows/ci.yml (2)

536-536: Inconsistent installation method.

Line 536 uses make develop while all other jobs in this workflow use python3 -m pip install -e . --user for consistency with the pip-based migration. Using different installation methods across jobs may lead to subtle behavioral differences.

Apply this diff to align with the rest of the workflow:

-         make develop
+         python3 -m pip install -e . --user

517-517: Replace *.egg-info with *.dist-info in cleanup
The rm -rf build/ dist/ *.egg-info glob matches nothing under pip/wheel packaging—add or replace with *.dist-info to remove wheel metadata directories.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f1afd1 and c18d6b3.

📒 Files selected for processing (67)
  • .github/actions/version/action.yml (1 hunks)
  • .github/actions/wheel/action.yml (1 hunks)
  • .github/workflows/ci.yml (16 hunks)
  • .github/workflows/prerelease.yml (2 hunks)
  • .github/workflows/push_ci.yml (1 hunks)
  • .github/workflows/release.yml (1 hunks)
  • .github/workflows/setup.yml (6 hunks)
  • .github/workflows/vmimage.yml (1 hunks)
  • .github/workflows/weekly.yml (2 hunks)
  • Makefile (4 hunks)
  • Makefile.gh (2 hunks)
  • contrib/scripts/avocado-safeloader-find-python-unittest (1 hunks)
  • docs/source/guides/contributor/chapters/environment.rst (1 hunks)
  • docs/source/guides/contributor/chapters/plugins.rst (4 hunks)
  • docs/source/guides/writer/chapters/subclassing.rst (2 hunks)
  • examples/plugins/README.rst (1 hunks)
  • examples/plugins/cli-cmd/hello/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_option/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_option/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/setup.py (0 hunks)
  • examples/plugins/job-pre-post/sleep/pyproject.toml (1 hunks)
  • examples/plugins/job-pre-post/sleep/setup.py (0 hunks)
  • examples/plugins/test-pre-post/hello/pyproject.toml (1 hunks)
  • examples/plugins/test-pre-post/hello/setup.py (0 hunks)
  • examples/plugins/tests/README.rst (1 hunks)
  • examples/plugins/tests/magic/pyproject.toml (1 hunks)
  • examples/plugins/tests/magic/setup.py (0 hunks)
  • examples/plugins/tests/rogue/pyproject.toml (1 hunks)
  • examples/plugins/tests/rogue/setup.py (0 hunks)
  • optional_plugins/README.rst (1 hunks)
  • optional_plugins/ansible/README.rst (1 hunks)
  • optional_plugins/ansible/pyproject.toml (1 hunks)
  • optional_plugins/ansible/setup.py (1 hunks)
  • optional_plugins/golang/pyproject.toml (1 hunks)
  • optional_plugins/golang/setup.py (1 hunks)
  • optional_plugins/html/pyproject.toml (1 hunks)
  • optional_plugins/html/setup.py (1 hunks)
  • optional_plugins/mail/pyproject.toml (1 hunks)
  • optional_plugins/mail/setup.py (1 hunks)
  • optional_plugins/result_upload/pyproject.toml (1 hunks)
  • optional_plugins/result_upload/setup.py (1 hunks)
  • optional_plugins/resultsdb/pyproject.toml (1 hunks)
  • optional_plugins/resultsdb/setup.py (1 hunks)
  • optional_plugins/robot/pyproject.toml (1 hunks)
  • optional_plugins/robot/setup.py (1 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/__init__.py (0 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1 hunks)
  • optional_plugins/spawner_remote/pyproject.toml (1 hunks)
  • optional_plugins/spawner_remote/setup.py (1 hunks)
  • optional_plugins/varianter_cit/pyproject.toml (1 hunks)
  • optional_plugins/varianter_cit/setup.py (1 hunks)
  • optional_plugins/varianter_pict/README.rst (1 hunks)
  • optional_plugins/varianter_pict/pyproject.toml (1 hunks)
  • optional_plugins/varianter_pict/setup.py (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/README.rst (4 hunks)
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/setup.py (1 hunks)
  • pyproject.toml (1 hunks)
  • python-avocado.spec (6 hunks)
  • selftests/pre_release/tests/check-copr-rpm-version.sh (1 hunks)
  • selftests/run_coverage (1 hunks)
  • setup.py (2 hunks)
  • spell.ignore (1 hunks)
💤 Files with no reviewable changes (9)
  • examples/plugins/job-pre-post/sleep/setup.py
  • examples/plugins/cli-cmd/hello_parser/setup.py
  • optional_plugins/spawner_remote/avocado_spawner_remote/init.py
  • examples/plugins/test-pre-post/hello/setup.py
  • examples/plugins/cli-cmd/hello_priority/setup.py
  • examples/plugins/tests/magic/setup.py
  • examples/plugins/cli-cmd/hello_option/setup.py
  • examples/plugins/tests/rogue/setup.py
  • examples/plugins/cli-cmd/hello/setup.py
✅ Files skipped from review due to trivial changes (2)
  • optional_plugins/varianter_yaml_to_mux/README.rst
  • optional_plugins/varianter_pict/README.rst
🚧 Files skipped from review as they are similar to previous changes (21)
  • Makefile.gh
  • optional_plugins/varianter_cit/pyproject.toml
  • docs/source/guides/writer/chapters/subclassing.rst
  • pyproject.toml
  • optional_plugins/varianter_yaml_to_mux/setup.py
  • .github/workflows/release.yml
  • examples/plugins/test-pre-post/hello/pyproject.toml
  • .github/workflows/vmimage.yml
  • examples/plugins/tests/README.rst
  • optional_plugins/golang/pyproject.toml
  • examples/plugins/job-pre-post/sleep/pyproject.toml
  • optional_plugins/robot/setup.py
  • optional_plugins/varianter_pict/pyproject.toml
  • optional_plugins/ansible/README.rst
  • .github/workflows/weekly.yml
  • spell.ignore
  • optional_plugins/html/pyproject.toml
  • .github/workflows/prerelease.yml
  • optional_plugins/result_upload/setup.py
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml
  • docs/source/guides/contributor/chapters/environment.rst
🧰 Additional context used
🧬 Code graph analysis (1)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (5)
avocado/core/plugin_interfaces.py (2)
  • Init (23-28)
  • Spawner (321-441)
avocado/core/spawners/common.py (3)
  • SpawnerMixin (35-47)
  • SpawnMethod (6-18)
  • task_output_dir (46-47)
avocado/core/settings.py (3)
  • section (137-138)
  • register_option (642-799)
  • key (141-142)
avocado/core/nrunner/runnable.py (1)
  • pick_runner_module_from_entry_point_kind (630-643)
avocado/core/nrunner/task.py (1)
  • setup_output_dir (171-174)
🪛 checkmake (0.2.2)
Makefile

[warning] 129-129: Missing required phony target "all"

(minphony)


[warning] 129-129: Missing required phony target "test"

(minphony)

🪛 Ruff (0.13.3)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py

73-73: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


74-74: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


126-129: Avoid specifying long messages outside the exception class

(TRY003)


197-197: Probable insecure usage of temporary file or directory: "/tmp/.avocado_task_output_dir"

(S108)


213-213: Consider moving this statement to an else block

(TRY300)


215-215: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


219-219: Unused static method argument: runtime_task

(ARG004)


224-224: Unused static method argument: runtime_task

(ARG004)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (26)
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: Linux with Python 3.12
  • GitHub Check: Linux with Python 3.13
  • GitHub Check: Linux with Python 3.11
  • GitHub Check: Linux with Python 3.9
  • GitHub Check: Linux with Python 3.10
  • GitHub Check: Fedora selftests
  • GitHub Check: macOS with Python 3.11
  • GitHub Check: Code Coverage (3.11)
🔇 Additional comments (41)
selftests/pre_release/tests/check-copr-rpm-version.sh (1)

14-14: LGTM! Version reading now aligns with pyproject.toml migration.

The change from python setup.py --version to reading directly from the VERSION file is correct and consistent with the broader migration to pyproject.toml-based packaging.

optional_plugins/varianter_pict/setup.py (1)

1-6: LGTM! Standard migration pattern to pyproject.toml.

The minimal setup() call with the transition comment is the correct approach for maintaining backward compatibility while migrating to pyproject.toml-based packaging.

optional_plugins/resultsdb/setup.py (1)

1-6: LGTM! Standard migration pattern.

The minimal setup() implementation with transition comment follows the expected pattern for pyproject.toml migration.

examples/plugins/README.rst (1)

8-24: LGTM! Documentation accurately reflects pip-based workflow.

The updated instructions correctly document the migration from python setup.py develop to pip install -e . and include clear uninstall examples. The changes are well-structured and easy to follow.

examples/plugins/cli-cmd/hello/pyproject.toml (1)

10-11: Entry point configuration is correct.

The entry point name hello appropriately matches the module name. Note that examples/plugins/cli-cmd/hello_priority/pyproject.toml incorrectly uses the same entry point name, which will cause a collision.

optional_plugins/html/setup.py (1)

1-6: LGTM! Standard migration pattern.

The minimal setup() implementation follows the correct pattern for transitioning to pyproject.toml-based packaging.

optional_plugins/spawner_remote/pyproject.toml (2)

14-16: Verify aexpect minimum version requirement.

The dependency specifies aexpect>=1.6.2. While this is reasonable (1.8.0 is the latest version per learnings), ensure that version 1.6.2 actually provides all the features required by the remote spawner implementation.

Based on learnings


32-36: LGTM! Entry points are correctly configured.

The entry points now correctly reference avocado_spawner_remote.spawner:RemoteSpawnerInit and avocado_spawner_remote.spawner:RemoteSpawner, which matches the module structure.

optional_plugins/golang/setup.py (1)

2-6: LGTM! Clean transitional setup.py shim.

The transitional comments clearly document the purpose, and the bare setup() call appropriately delegates packaging configuration to pyproject.toml. This approach maintains backward compatibility during the migration period.

optional_plugins/mail/setup.py (1)

2-6: LGTM! Consistent with the migration pattern.

The file correctly implements the transitional setup.py shim pattern, maintaining backward compatibility while delegating to pyproject.toml.

optional_plugins/spawner_remote/setup.py (1)

2-6: LGTM! Proper transitional shim.

The setup.py correctly serves as a backward-compatible shim while delegating packaging to pyproject.toml. The approach aligns with the repository-wide migration strategy.

docs/source/guides/contributor/chapters/plugins.rst (4)

97-101: LGTM! Documentation correctly updated for pyproject.toml.

The documentation properly reflects the migration from setup.py to pyproject.toml and correctly recommends using pip install -e . for editable installs. This aligns with modern Python packaging best practices.


111-114: LGTM! Entry point syntax correctly documented.

The TOML entry point syntax is accurate and follows the standard [project.entry-points."namespace"] format. The example properly demonstrates how to register Avocado plugins in pyproject.toml.


170-173: LGTM! Settings plugin registration correctly documented.

The example accurately shows how to register settings plugins using TOML entry points, maintaining consistency with the overall migration approach.


325-327: LGTM! Test type plugin documentation properly updated.

The documentation correctly references the pyproject.toml file and uses the recommended pip install -e . command for development installs.

optional_plugins/varianter_cit/setup.py (1)

2-6: LGTM! Consistent transitional shim.

The file follows the established pattern for the pyproject.toml migration, serving as a backward-compatible shim while delegating configuration to pyproject.toml.

examples/plugins/tests/magic/pyproject.toml (1)

10-23: Package structure and entry points verified. All specified modules and classes (MagicInit, MagicResolver, MagicDiscoverer in resolver.py; MagicRunner and main() in runner.py) are present in examples/plugins/tests/magic/avocado_magic.

examples/plugins/cli-cmd/hello_option/pyproject.toml (1)

1-14: Configuration valid: examples/plugins/cli-cmd/hello_option/hello_option.py exists and defines HelloWorld as expected.

optional_plugins/mail/pyproject.toml (1)

1-44: LGTM! Clean pyproject.toml configuration for the mail plugin.

The packaging configuration is well-structured and consistent with the other optional plugins in this PR. The dynamic version/readme setup, entry points, and dependencies are properly defined.

python-avocado.spec (3)

8-18: LGTM! Tarball naming updated to match new packaging structure.

The tarball name changes from avocado- to avocado_framework- align with the modernized pyproject.toml-based packaging. This is consistent across both release and snapshot builds.


102-104: LGTM! Setup paths updated to match new tarball naming.

The %setup directives correctly reference the new avocado_framework- prefix for both release and commit-based builds.


220-220: LGTM! Selftests invocation modernized.

Updated from selftests/check.py to python3 -m selftests.check, which is more consistent with modern Python module execution practices.

examples/plugins/cli-cmd/hello_parser/pyproject.toml (1)

1-14: LGTM! Simple and appropriate configuration for an example plugin.

The pyproject.toml is well-suited for the example CLI plugin. The use of py-modules is appropriate for a single-module plugin, and the hardcoded version is acceptable for examples.

optional_plugins/ansible/pyproject.toml (1)

1-50: LGTM! Well-structured pyproject.toml with appropriate version constraints.

The packaging configuration properly handles Python version-specific dependencies for cffi and cryptography. The entry points for the ansible module runner and plugin are correctly defined.

optional_plugins/ansible/setup.py (1)

1-6: LGTM! Proper transitional setup.py for backward compatibility.

The minimal setup.py with a bare setup() call is the correct approach during the migration to pyproject.toml. The comment clearly documents that this is for egg-building compatibility.

optional_plugins/resultsdb/pyproject.toml (1)

1-49: LGTM! Clean configuration with past issues resolved.

The pyproject.toml is well-structured, and the impossible urllib3 constraint issue from previous review has been properly addressed. The entry points for CLI, result events, and result plugins are correctly defined.

optional_plugins/result_upload/pyproject.toml (1)

1-45: LGTM! Straightforward configuration for result upload plugin.

The pyproject.toml follows the established pattern for optional plugins with proper dynamic metadata setup and correctly defined entry points for CLI and result plugins.

.github/workflows/push_ci.yml (1)

95-97: LGTM!

The migration from setup.py develop to pip install -e . follows modern Python packaging best practices and aligns with PEP 517/518 standards.

selftests/run_coverage (1)

30-30: LGTM!

The migration from direct script invocation to module invocation (-m selftests.check) is consistent with modern Python packaging practices and aligns with the broader migration in this PR.

optional_plugins/README.rst (1)

10-34: LGTM!

The documentation accurately reflects the migration to pip-based workflows and provides clear examples for both installation and removal of plugins. The updated Makefile target references are helpful.

.github/workflows/setup.yml (2)

33-125: LGTM!

The workflow comprehensively covers different installation scenarios (user, system, develop) and consistently uses pip-based installation commands. The migration from setup.py to pip install is implemented correctly across all jobs.


149-219: LGTM!

The remaining installation scenarios (system-wide develop, virtualenv, develop-virtualenv) are correctly updated to use pip-based workflows, maintaining consistency with the earlier jobs.

Makefile (5)

34-61: LGTM! Enhanced clean target with clear messaging.

The expanded clean target now handles build artifacts, caches, and plugin directories comprehensively with helpful progress messages.


64-67: LGTM! Migration to pip-based install/uninstall.

The install and uninstall targets correctly use pip instead of setup.py, aligning with modern Python packaging practices.


80-80: LGTM! Module-based test invocation.

The check target now uses python -m selftests.check, which is consistent with the broader migration in this PR.


94-107: LGTM! New plugin development targets.

The develop-plugins and develop-plugin targets provide convenient workflows for installing optional plugins in editable mode, with clear error messages and progress indicators.


112-119: LGTM! Improved man page generation with error handling.

The man target now properly checks for the availability of rst2man or rst2man.py and provides a clear error message if neither is found.

.github/workflows/ci.yml (4)

28-28: LGTM on pip-based installation migration.

The transition from setup.py develop to python3 -m pip install -e . --user aligns well with modern Python packaging practices and the pyproject.toml migration objectives. The changes are consistent across most jobs (with the exception noted in Line 536).

Also applies to: 95-95, 135-135, 171-171, 512-518


32-32: LGTM on test execution migration.

The switch from setup.py test invocations to python3 -m selftests.check is correct and follows modern Python testing practices.

Also applies to: 99-99, 143-143


265-265: LGTM on python3-pip installations.

Adding explicit python3-pip installation steps across all distribution containers ensures pip is available for the new pip-based workflows. This is a necessary prerequisite for the migration.

Also applies to: 278-278, 291-291, 304-304, 317-317, 330-330, 343-343, 356-356, 369-369, 382-382, 408-408, 421-421, 447-447, 460-460


361-463: Wheel action implementation verified. The .github/actions/wheel/action.yml exists and correctly builds, installs, and tests the wheel package.

python-version: ${{ matrix.python-version }}
- name: Display Python version
run: python -V --version
run: python3 -V --version
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix invalid flag combination.

The command python3 -V --version combines two version flags incorrectly. Python's -V flag is shorthand for --version, and combining them is redundant and may cause parsing issues depending on the Python version.

Apply this diff:

-        run: python3 -V --version
+        run: python3 --version
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
run: python3 -V --version
run: python3 --version
🤖 Prompt for AI Agents
In .github/workflows/ci.yml around line 89, the step uses the invalid redundant
flag combination `python3 -V --version`; replace it with a single version flag
(for example `python3 --version`) so the workflow calls Python with a single,
valid version flag.

python-version: ${{ matrix.python-version }}
- name: Display Python version
run: python -V --version
run: python3 -V --version
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix invalid flag combination.

Same issue as Line 89: python3 -V --version combines two version flags incorrectly.

Apply this diff:

-        run: python3 -V --version
+        run: python3 --version
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
run: python3 -V --version
run: python3 --version
🤖 Prompt for AI Agents
.github/workflows/ci.yml around line 131: the workflow runs `python3 -V
--version`, which wrongly combines two version flags; replace the command with a
single valid flag such as `python3 --version` (or `python3 -V`) to print the
Python version, and ensure the similar issue on line 89 is fixed consistently.

Comment on lines +10 to +20
[project.scripts]
avocado-runner-rogue = "avocado_rogue.runner:main"

[project.entry-points."avocado.plugins.resolver"]
rogue = "avocado_rogue.resolver:RogueResolver"

[project.entry-points."avocado.plugins.runnable.runner"]
rogue = "avocado_rogue.runner:RogueRunner"

[tool.setuptools]
py-modules = ["avocado_rogue"]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify package structure matches the configuration.

The configuration has potential ambiguity:

  • py-modules = ["avocado_rogue"] declares a single-file module
  • Entry points target submodules like avocado_rogue.resolver:RogueResolver

This works only if avocado_rogue is a package directory (with __init__.py), not a single file. Although past reviews marked this as addressed, please confirm the actual structure.

Run the following script to verify the package structure:


🏁 Script executed:

#!/bin/bash
# Description: Verify avocado_rogue package structure

cd examples/plugins/tests/rogue || exit 1

if [ -d "avocado_rogue" ]; then
    echo "✓ avocado_rogue is a package directory"
    
    # Check for __init__.py
    if [ -f "avocado_rogue/__init__.py" ]; then
        echo "✓ __init__.py exists"
    else
        echo "✗ __init__.py missing"
    fi
    
    # Check for resolver.py and runner.py
    if [ -f "avocado_rogue/resolver.py" ]; then
        echo "✓ resolver.py exists"
    else
        echo "✗ resolver.py missing"
    fi
    
    if [ -f "avocado_rogue/runner.py" ]; then
        echo "✓ runner.py exists"
    else
        echo "✗ runner.py missing"
    fi
    
    # Verify classes exist
    rg -n "class RogueResolver" avocado_rogue/resolver.py
    rg -n "class RogueRunner" avocado_rogue/runner.py
    rg -n "def main" avocado_rogue/runner.py
    
elif [ -f "avocado_rogue.py" ]; then
    echo "✗ avocado_rogue.py is a single file"
    echo "Configuration declares py-modules but entry points target submodules"
    echo "This mismatch will cause import errors"
else
    echo "✗ avocado_rogue not found"
fi

Length of output: 182


Replace py-modules with packages for avocado_rogue
Under [tool.setuptools] in examples/plugins/tests/rogue/pyproject.toml, change

py-modules = ["avocado_rogue"]

to

packages = ["avocado_rogue"]
🤖 Prompt for AI Agents
In examples/plugins/tests/rogue/pyproject.toml around lines 10 to 20, the
[tool.setuptools] section incorrectly uses py-modules = ["avocado_rogue"] which
should be packages = ["avocado_rogue"]; update that key to packages so
setuptools treats avocado_rogue as a package directory rather than a
single-module file.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (20)
optional_plugins/varianter_yaml_to_mux/setup.py (1)

1-1: Verify the shebang fix was applied.

Past review comments indicate the shebang issue (#!/bin/env#!/usr/bin/env) was addressed in previous commits, but the code still shows the incorrect shebang. Please confirm whether this fix was actually applied or if there has been a regression.

Additionally, the file has a shebang but is not executable (Ruff EXE001). Either make the file executable with chmod +x or remove the shebang if the file is not intended to be executed directly.

optional_plugins/golang/setup.py (1)

1-1: Verify the shebang fix was applied.

Past review comments indicate the shebang issue was addressed in previous commits, but the code still shows #!/bin/env python3 instead of #!/usr/bin/env python3. Please verify whether this fix was actually applied or if there has been a regression.

Additionally, the file has a shebang but is not executable. Either make the file executable or remove the shebang if direct execution is not intended.

optional_plugins/resultsdb/setup.py (1)

1-1: Verify the shebang fix was applied.

Past review comments indicate the shebang issue was addressed, but the code still shows #!/bin/env python3 instead of #!/usr/bin/env python3. Please confirm whether this fix was applied or if there has been a regression.

Additionally, the file has a shebang but is not executable. Either make the file executable or remove the shebang.

optional_plugins/html/setup.py (1)

1-1: Verify the shebang fix was applied.

Past review comments indicate the shebang issue was addressed, but the code still shows #!/bin/env python3. Please verify the fix was applied or if there has been a regression.

Additionally, the file has a shebang but is not executable. Either make the file executable or remove the shebang.

optional_plugins/mail/setup.py (1)

1-1: Restore portable shebang

/bin/env is non-portable and breaks on systems where env lives under /usr/bin. Please switch back to /usr/bin/env python3 so the interpreter is resolved via PATH.

-#!/bin/env python3
+#!/usr/bin/env python3
optional_plugins/ansible/setup.py (1)

1-1: Use /usr/bin/env for portability

Keeping /bin/env will fail on most POSIX environments that only ship /usr/bin/env. Please update the shebang accordingly.

-#!/bin/env python3
+#!/usr/bin/env python3
optional_plugins/result_upload/setup.py (1)

1-1: Fix the shebang path

env typically resides in /usr/bin; using /bin/env breaks compatibility. Please switch to /usr/bin/env python3.

-#!/bin/env python3
+#!/usr/bin/env python3
python-avocado.spec (1)

243-250: Verify the conditional block structure is correct.

The nested conditional blocks at lines 246-250 and 259-263 use %if ! 0%{?rhel} followed immediately by another %if ! 0%{?fedora} > 35. Ensure that this nesting is intentional and that the logic correctly excludes packages only on non-RHEL systems where Fedora > 35.

Additionally, the past review flagged that the avocado_result_html* exclusion path on Line 245 is incorrect and should point to %{python3_sitelib}/optional_plugins/html/avocado_result_html* instead of %{python3_sitelib}/avocado_result_html*.

Run the following script to verify the actual installation path of avocado_result_html:

#!/bin/bash
# Description: Verify the installation path of avocado_result_html package

# Search for avocado_result_html references in optional_plugins structure
fd -t d 'avocado_result_html' optional_plugins/

Also applies to: 259-263

examples/plugins/cli-cmd/hello_priority/pyproject.toml (1)

10-11: Avoid duplicate CLI entry-point names.

Both examples/plugins/cli-cmd/hello/pyproject.toml and this file define the entry point hello under [project.entry-points."avocado.plugins.cli.cmd"]. This creates a naming collision that could cause issues when both plugins are installed. Consider renaming this entry point to hello_priority to match the module name and avoid conflicts.

Apply this diff to fix the collision:

 [project.entry-points."avocado.plugins.cli.cmd"]
-hello = "hello_priority:HelloWorld"
+hello_priority = "hello_priority:HelloWorld"
setup.py (1)

39-176: Entry_points duplication is documented and acknowledged.

The 130+ line entry_points configuration duplicates pyproject.toml as noted in your comments (lines 24-25, 38). This duplication creates maintenance burden but is necessary for egg build compatibility during the transition period.

.github/workflows/ci.yml (3)

89-89: Redundant version flags remain.

The command combines both -V and --version flags, which is redundant. This issue was previously flagged but still appears in the code.


131-131: Redundant version flags remain.

Same issue as Line 89: the command combines both -V and --version flags redundantly. This was previously flagged.


167-167: Redundant version flags in Windows workflow.

The Windows workflow also has the redundant -V --version flags. For consistency, this should be corrected along with Lines 89 and 131.

optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (7)

38-46: Timeout options need explicit integer type.

The setup_timeout and test_timeout options register with integer defaults but lack key_type=int, which may result in string values being returned by the config system, breaking timeout arithmetic.


106-114: Missing error handling for slot initialization.

Reading JSON configuration files and creating remote sessions can fail (malformed JSON, missing files, connection failures), but no error handling is present. A single bad slot configuration will crash the spawner.


136-146: Shell injection risk in task identifier.

The task identifier is interpolated directly into the shell command without escaping. Special characters in the identifier (spaces, quotes, shell metacharacters) will break the command or create security risks. Past review indicated this was addressed, but the code still lacks escaping.


182-182: Critical: Output redirection discards all test results.

Redirecting to /dev/null loses all test output, results, logs, stdout, and stderr, defeating the purpose of remote execution and making debugging impossible.


195-201: Use unique, sanitized remote directory path.

The fixed /tmp/.avocado_task_output_dir path can cause collisions and TOCTOU security issues. Use a unique per-task directory with a sanitized identifier.


202-206: Missing timeout in wait loop.

The wait_task loop has no timeout, risking indefinite blocking if is_task_alive has a bug or the task hangs.


208-216: Fix logging message and use exception logging.

Line 215 lacks an f-string prefix, so {session.host} won't interpolate. Additionally, use logging.exception instead of logging.error to capture the traceback for debugging.

🧹 Nitpick comments (1)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1)

70-78: Consider adding ClassVar annotations for mutable class attributes.

The METHODS and slots_cache class attributes are mutable and should be annotated with typing.ClassVar for better type checking support.

+from typing import ClassVar
+
 class RemoteSpawner(Spawner, SpawnerMixin):
 
     description = "Remote (host) based spawner"
-    METHODS = [SpawnMethod.STANDALONE_EXECUTABLE]
-    slots_cache = {}
+    METHODS: ClassVar = [SpawnMethod.STANDALONE_EXECUTABLE]
+    slots_cache: ClassVar = {}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c18d6b3 and 3f6a187.

📒 Files selected for processing (67)
  • .github/actions/version/action.yml (1 hunks)
  • .github/actions/wheel/action.yml (1 hunks)
  • .github/workflows/ci.yml (17 hunks)
  • .github/workflows/prerelease.yml (2 hunks)
  • .github/workflows/push_ci.yml (1 hunks)
  • .github/workflows/release.yml (1 hunks)
  • .github/workflows/setup.yml (6 hunks)
  • .github/workflows/vmimage.yml (1 hunks)
  • .github/workflows/weekly.yml (2 hunks)
  • Makefile (4 hunks)
  • Makefile.gh (2 hunks)
  • contrib/scripts/avocado-safeloader-find-python-unittest (1 hunks)
  • docs/source/guides/contributor/chapters/environment.rst (1 hunks)
  • docs/source/guides/contributor/chapters/plugins.rst (4 hunks)
  • docs/source/guides/writer/chapters/subclassing.rst (2 hunks)
  • examples/plugins/README.rst (1 hunks)
  • examples/plugins/cli-cmd/hello/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_option/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_option/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/setup.py (0 hunks)
  • examples/plugins/job-pre-post/sleep/pyproject.toml (1 hunks)
  • examples/plugins/job-pre-post/sleep/setup.py (0 hunks)
  • examples/plugins/test-pre-post/hello/pyproject.toml (1 hunks)
  • examples/plugins/test-pre-post/hello/setup.py (0 hunks)
  • examples/plugins/tests/README.rst (1 hunks)
  • examples/plugins/tests/magic/pyproject.toml (1 hunks)
  • examples/plugins/tests/magic/setup.py (0 hunks)
  • examples/plugins/tests/rogue/pyproject.toml (1 hunks)
  • examples/plugins/tests/rogue/setup.py (0 hunks)
  • optional_plugins/README.rst (1 hunks)
  • optional_plugins/ansible/README.rst (1 hunks)
  • optional_plugins/ansible/pyproject.toml (1 hunks)
  • optional_plugins/ansible/setup.py (1 hunks)
  • optional_plugins/golang/pyproject.toml (1 hunks)
  • optional_plugins/golang/setup.py (1 hunks)
  • optional_plugins/html/pyproject.toml (1 hunks)
  • optional_plugins/html/setup.py (1 hunks)
  • optional_plugins/mail/pyproject.toml (1 hunks)
  • optional_plugins/mail/setup.py (1 hunks)
  • optional_plugins/result_upload/pyproject.toml (1 hunks)
  • optional_plugins/result_upload/setup.py (1 hunks)
  • optional_plugins/resultsdb/pyproject.toml (1 hunks)
  • optional_plugins/resultsdb/setup.py (1 hunks)
  • optional_plugins/robot/pyproject.toml (1 hunks)
  • optional_plugins/robot/setup.py (1 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/__init__.py (0 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1 hunks)
  • optional_plugins/spawner_remote/pyproject.toml (1 hunks)
  • optional_plugins/spawner_remote/setup.py (1 hunks)
  • optional_plugins/varianter_cit/pyproject.toml (1 hunks)
  • optional_plugins/varianter_cit/setup.py (1 hunks)
  • optional_plugins/varianter_pict/README.rst (1 hunks)
  • optional_plugins/varianter_pict/pyproject.toml (1 hunks)
  • optional_plugins/varianter_pict/setup.py (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/README.rst (4 hunks)
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/setup.py (1 hunks)
  • pyproject.toml (1 hunks)
  • python-avocado.spec (6 hunks)
  • selftests/pre_release/tests/check-copr-rpm-version.sh (1 hunks)
  • selftests/run_coverage (1 hunks)
  • setup.py (2 hunks)
  • spell.ignore (1 hunks)
💤 Files with no reviewable changes (9)
  • examples/plugins/cli-cmd/hello_option/setup.py
  • examples/plugins/cli-cmd/hello_priority/setup.py
  • examples/plugins/cli-cmd/hello/setup.py
  • examples/plugins/cli-cmd/hello_parser/setup.py
  • examples/plugins/test-pre-post/hello/setup.py
  • optional_plugins/spawner_remote/avocado_spawner_remote/init.py
  • examples/plugins/tests/rogue/setup.py
  • examples/plugins/job-pre-post/sleep/setup.py
  • examples/plugins/tests/magic/setup.py
✅ Files skipped from review due to trivial changes (2)
  • optional_plugins/varianter_pict/README.rst
  • optional_plugins/varianter_yaml_to_mux/README.rst
🚧 Files skipped from review as they are similar to previous changes (27)
  • optional_plugins/varianter_cit/pyproject.toml
  • .github/workflows/release.yml
  • contrib/scripts/avocado-safeloader-find-python-unittest
  • optional_plugins/mail/pyproject.toml
  • .github/workflows/vmimage.yml
  • examples/plugins/cli-cmd/hello/pyproject.toml
  • selftests/run_coverage
  • optional_plugins/varianter_pict/setup.py
  • optional_plugins/ansible/README.rst
  • docs/source/guides/writer/chapters/subclassing.rst
  • optional_plugins/README.rst
  • optional_plugins/golang/pyproject.toml
  • examples/plugins/test-pre-post/hello/pyproject.toml
  • optional_plugins/robot/pyproject.toml
  • optional_plugins/varianter_pict/pyproject.toml
  • spell.ignore
  • selftests/pre_release/tests/check-copr-rpm-version.sh
  • optional_plugins/varianter_cit/setup.py
  • optional_plugins/result_upload/pyproject.toml
  • optional_plugins/ansible/pyproject.toml
  • examples/plugins/job-pre-post/sleep/pyproject.toml
  • examples/plugins/README.rst
  • .github/workflows/push_ci.yml
  • examples/plugins/tests/rogue/pyproject.toml
  • optional_plugins/html/pyproject.toml
  • .github/actions/version/action.yml
  • pyproject.toml
🧰 Additional context used
🧬 Code graph analysis (1)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (5)
avocado/core/plugin_interfaces.py (1)
  • Spawner (321-441)
avocado/core/spawners/common.py (3)
  • SpawnerMixin (35-47)
  • SpawnMethod (6-18)
  • task_output_dir (46-47)
avocado/core/settings.py (3)
  • section (137-138)
  • register_option (642-799)
  • key (141-142)
avocado/core/nrunner/runnable.py (1)
  • pick_runner_module_from_entry_point_kind (630-643)
avocado/core/nrunner/task.py (1)
  • setup_output_dir (171-174)
🪛 checkmake (0.2.2)
Makefile

[warning] 129-129: Missing required phony target "all"

(minphony)


[warning] 129-129: Missing required phony target "test"

(minphony)

🪛 Ruff (0.13.3)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py

73-73: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


74-74: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


126-129: Avoid specifying long messages outside the exception class

(TRY003)


197-197: Probable insecure usage of temporary file or directory: "/tmp/.avocado_task_output_dir"

(S108)


213-213: Consider moving this statement to an else block

(TRY300)


215-215: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


219-219: Unused static method argument: runtime_task

(ARG004)


224-224: Unused static method argument: runtime_task

(ARG004)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (41)
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: Linux with Python 3.13
  • GitHub Check: Linux with Python 3.9
  • GitHub Check: Linux with Python 3.10
  • GitHub Check: Linux with Python 3.12
  • GitHub Check: Linux with Python 3.11
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: Fedora selftests
  • GitHub Check: macOS with Python 3.11
  • GitHub Check: Code Coverage (3.11)
🔇 Additional comments (33)
examples/plugins/tests/README.rst (1)

15-16: LGTM!

The update from python setup.py develop --user to pip install -e . correctly reflects modern Python packaging best practices and aligns with the migration to pyproject.toml-based configuration.

Makefile.gh (2)

79-82: Ensure all plugin directories support egg building.

Similar to the wheel build target, the egg build target now runs unconditionally. If a plugin directory doesn't have a valid setup.py that supports bdist_egg, the build will fail.

Consider validating that all plugin directories support the required build commands, or add error handling to skip plugins that cannot build eggs.


66-69: All optional plugin directories have valid packaging configuration Verified that each directory under optional_plugins includes either setup.py or pyproject.toml; no further validation needed.

.github/actions/wheel/action.yml (1)

12-16: Verify wheel installation and PATH handling.

The workflow installs the wheel to /tmp/avocado_install and modifies PYTHONPATH, but this approach has some concerns:

  1. Using -t (target directory) instead of proper installation may miss script/entrypoint creation
  2. No cleanup of /tmp/avocado_install between runs could cause test pollution
  3. The Python one-liner approach is unconventional compared to calling avocado directly

Consider testing the installed avocado command directly (if entry points are properly installed) rather than using Python one-liners:

       python3 -m pip install dist/*.whl
       avocado run /bin/true
       cd /tmp
       avocado run /bin/true

This would verify that entry points are correctly installed and accessible.

python-avocado.spec (3)

8-8: LGTM!

The tarball naming change from avocado-%{version}.tar.gz to avocado_framework-%{version}.tar.gz aligns with the broader pyproject.toml-based packaging migration across the PR.

Also applies to: 18-18


102-104: LGTM!

The setup directory name changes from avocado-%{version} to avocado_framework-%{version} correctly match the updated tarball naming conventions.


220-220: LGTM!

The selftests invocation has been correctly updated to use python3 -m selftests.check, consistent with the module-based invocation pattern adopted throughout the PR.

optional_plugins/resultsdb/pyproject.toml (1)

1-48: LGTM!

The pyproject.toml configuration for the resultsdb plugin follows the standard pattern established across other plugins in this PR. The previously flagged urllib3 constraint issue has been addressed (the impossible urllib3<2.3.0; python_version < '3.9' constraint has been removed), and the current dependencies are appropriate for the Python 3.9+ requirement.

.github/workflows/prerelease.yml (2)

25-26: LGTM!

The migration from direct pip invocations to python3 -m pip and from setup.py develop to python3 -m pip install -e . --user aligns with modern Python packaging best practices and is consistent with the broader migration across the repository.

Also applies to: 28-28


48-49: LGTM!

The updates to use module-based invocations (python3 -m pip and python3 -m selftests.check) are consistent with the workflow standardization across the PR and follow Python best practices.

Also applies to: 51-51

docs/source/guides/contributor/chapters/environment.rst (1)

55-62: LGTM!

The documentation correctly updates the development installation instructions to use pip install -e . instead of the deprecated python3 setup.py develop. The revised description accurately explains the editable install mechanism, including the creation of the egg info directory and the need to re-run the command only when adding new entry points.

optional_plugins/spawner_remote/pyproject.toml (1)

1-45: LGTM!

The pyproject.toml configuration for the spawner_remote plugin follows the established pattern. The previously flagged issue with missing .spawner module component in the entry point paths has been addressed—the current entry points correctly reference avocado_spawner_remote.spawner:RemoteSpawnerInit and avocado_spawner_remote.spawner:RemoteSpawner.

.github/workflows/weekly.yml (2)

37-39: LGTM!

The workflow correctly adopts module-based invocations for pip and selftests, consistent with the standardization across all CI workflows in this PR.

Also applies to: 47-47


81-83: LGTM!

The updates maintain consistency with the module-based invocation pattern established across the repository's CI workflows.

Also applies to: 89-89

docs/source/guides/contributor/chapters/plugins.rst (4)

97-101: LGTM!

The plugin registration documentation correctly updates to reflect pyproject.toml-based packaging and the use of pip install -e . for editable installs.


111-114: LGTM!

The entry point example has been correctly updated to the pyproject.toml TOML format, accurately reflecting the new packaging standard.


170-173: LGTM!

The settings plugin registration example correctly demonstrates the pyproject.toml entry point format.


325-327: LGTM!

The test type plugin activation instructions correctly reference pyproject.toml and use pip install -e . for installation.

optional_plugins/spawner_remote/setup.py (1)

1-6: LGTM! Clean migration to pyproject.toml.

The minimal setup.py shim correctly maintains backward compatibility for egg builds while moving configuration to pyproject.toml. The transitional comment clearly explains the purpose.

examples/plugins/cli-cmd/hello_option/pyproject.toml (1)

1-14: LGTM! Well-structured pyproject.toml configuration.

The build system, project metadata, and entry-point configuration follow standard conventions and are properly aligned with Avocado's plugin architecture.

Makefile (4)

9-10: LGTM! Clear documentation for new plugin development targets.

The help text for the new develop-plugins and develop-plugin targets is clear and informative.


33-61: LGTM! Comprehensive cleanup improvements.

The enhanced clean target with explicit loops for plugins and examples, along with improved error handling, makes the build process more robust and maintainable.


63-108: LGTM! Successful migration to pip-based workflows.

The migration from setup.py to python -m pip commands is correct and aligns with modern Python packaging practices. The new plugin development targets provide a convenient workflow for installing optional plugins.


109-119: LGTM! Robust manpage generation with proper fallbacks.

The updated manpage generation logic with fallback between rst2man and rst2man.py, along with clear error handling, ensures better compatibility across different environments.

.github/workflows/setup.yml (2)

33-125: LGTM! Consistent migration across installation workflows.

The migration from setup.py to python3 -m pip commands is consistently applied across user, system-wide, and developer installation workflows. The changes also correctly update test execution to use make man and python3 -m selftests.check.


149-219: LGTM! Complete workflow migration.

The remaining workflow jobs (system-wide developer and virtualenv installations) correctly adopt the pip-based installation pattern, maintaining consistency across all CI workflows.

setup.py (1)

16-36: LGTM! Clear documentation of backward compatibility approach.

The header comments clearly explain the purpose of this minimal setup.py file and acknowledge the entry_points duplication for egg build support. The version reading logic is straightforward.

.github/workflows/ci.yml (3)

28-32: LGTM! Static checks workflow updated correctly.

The migration to python3 -m pip and python3 -m selftests.check is correct and consistent with the overall packaging modernization.


169-169: LGTM! Setuptools installation fixed correctly.

The setuptools fallback command has been corrected to use python -m pip install instead of the erroneous python -m python3 -m pip pattern.


265-536: LGTM! Comprehensive CI workflow modernization.

The CI workflow updates consistently adopt pip-based installation patterns across all platform-specific jobs (Fedora, UBI, Debian, Ubuntu). The explicit installation of python3-pip ensures the required tooling is available.

examples/plugins/cli-cmd/hello_parser/pyproject.toml (1)

1-14: LGTM! Standard plugin packaging configuration.

The pyproject.toml follows the same well-structured pattern as other plugin examples, with proper build system, metadata, and entry-point configuration.

optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (2)

79-90: LGTM! Robust async command execution.

The run_remote_cmd_async method properly handles shell timeouts and process termination errors, providing clear error messages.


218-233: LGTM! Stub methods for future implementation.

These stub methods provide the required interface for the Spawner base class. The unused runtime_task arguments are intentional placeholders for future implementation.

- name: Test running avocado from wheels
shell: sh
run: |
export PIP_BREAK_SYSTEM_PACKAGES=1
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid using PIP_BREAK_SYSTEM_PACKAGES in CI workflows.

Setting PIP_BREAK_SYSTEM_PACKAGES=1 bypasses pip's externally-managed-environment protection, which can lead to system package conflicts and unstable CI environments. This flag is intended only for containerized environments where breaking system packages is acceptable.

Consider these alternatives:

  1. Use a virtual environment for isolation
  2. Use --user flag with pip install
  3. If running in a container, document why this flag is necessary

Apply this diff to use a virtual environment instead:

-       export PIP_BREAK_SYSTEM_PACKAGES=1
-       python3 -m pip install build wheel setuptools
+       python3 -m venv /tmp/avocado_venv
+       . /tmp/avocado_venv/bin/activate
+       pip install build wheel setuptools

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In .github/actions/wheel/action.yml around line 9 the workflow exports
PIP_BREAK_SYSTEM_PACKAGES=1 which bypasses pip's protection; remove that export
and instead create and activate a virtual environment in the action before
running pip (python -m venv <env> then source <env>/bin/activate or use the
setup-python action to create venv-like isolation), install packages into that
venv (or use pip install --user as an alternative), and add a comment explaining
why a venv is used; if this action truly runs in a container and you must keep
the flag, document the rationale rather than exporting it unconditionally.

@PaulYuuu
Copy link
Contributor Author

The change will also include some enhance but also introduce some regression that make CI no green, I will do some debug until all CI green again, then split to multiple commits.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (10)
setup.py (1)

167-296: Acknowledged: Entry points duplication is a known trade-off.

The 130-line entry_points dictionary duplicates configuration from pyproject.toml for egg build compatibility, as noted in lines 24-25. This is a known maintenance burden.

Consider adding a CI validation step that compares entry_points between setup.py and pyproject.toml to catch drift.

pyproject.toml (2)

14-16: Critical: Restore runtime dependencies.

The [project].dependencies list contains only "setuptools", but the core package imports third-party modules (e.g., avocado.plugins.sysinfo imports psutil, avocado.utils.asset imports requests, varianter plugins rely on jsonschema). Without these dependencies, pip install avocado-framework will succeed but fail at runtime with ImportError.

Please restore the full dependency list from the former setup.py into this section.


30-30: Remove unsupported Python 3.14 classifier.

The classifier "Programming Language :: Python :: 3.14" is not yet published by PyPI. Including it will cause upload rejections with "Invalid classifier" errors. Remove this line and re-add only after PyPI officially supports it.

optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (7)

38-46: Register timeouts as integers (prevents type bugs).

Without key_type=int, values become strings, breaking arithmetic/timeouts downstream. See avocado.core.settings.register_option.

Apply:

         help_msg = "Test timeout enforced for remote host setup hook"
         settings.register_option(
-            section=section, key="setup_timeout", help_msg=help_msg, default=3600
+            section=section, key="setup_timeout", help_msg=help_msg, default=3600, key_type=int
         )
 ...
         help_msg = "Test timeout enforced for sessions (just for this spawner)"
         settings.register_option(
-            section=section, key="test_timeout", help_msg=help_msg, default=14400
+            section=section, key="test_timeout", help_msg=help_msg, default=14400, key_type=int
         )

Based on learnings (relevant_code_snippets: avocado/core/settings.py).


106-115: Add error handling when initializing slots (robustness).

Malformed/missing files or login failures crash initialization.

Apply:

-                with open(session_slot, "r", encoding="utf-8") as f:
-                    session_data = json.load(f)
-                session = remote.remote_login(**session_data)
-                RemoteSpawner.slots_cache[session] = False
+                try:
+                    with open(session_slot, "r", encoding="utf-8") as f:
+                        session_data = json.load(f)
+                    session = remote.remote_login(**session_data)
+                except (FileNotFoundError, json.JSONDecodeError, KeyError) as e:
+                    LOG.warning(f"Failed to initialize slot from {session_slot}: {e}")
+                    continue
+                except Exception as e:
+                    LOG.error(f"Unexpected error initializing slot from {session_slot}: {e}")
+                    continue
+                else:
+                    RemoteSpawner.slots_cache[session] = False

143-146: Escape identifier in shell command to avoid breakage/injection.

Quote the pattern passed to pgrep.

-        status, _ = session.cmd_status_output(
-            f"pgrep -r R,S -f {runtime_task.task.identifier}"
-        )
+        safe_identifier = shlex.quote(runtime_task.task.identifier)
+        status, _ = session.cmd_status_output(f"pgrep -r R,S -f {safe_identifier}")

182-185: Do not discard remote output.

Redirecting to /dev/null loses test logs/results; breaks debugging.

-        cmd = shlex.join(entry_point_args) + " > /dev/null"
+        cmd = shlex.join(entry_point_args)

Optionally, redirect to a file and collect it later.


195-201: Avoid fixed /tmp path; use a unique, sanitized per-task dir.

Prevents collisions and improves isolation.

-        output_lxc_path = "/tmp/.avocado_task_output_dir"
+        safe_id = "".join(c if c.isalnum() or c in "._-" else "_" for c in runtime_task.task.identifier)
+        output_lxc_path = f"/tmp/avocado-task-{safe_id}"

If needed, create it before running the task:

# In spawn_task, before executing the command:
await RemoteSpawner.run_remote_cmd_async(session, f"mkdir -p {shlex.quote(runtime_task.task.runnable.output_dir)}", 30)

202-206: Add a wait timeout to avoid infinite loops.

Prevents hangs when tasks never terminate or liveness checks fail.

-    async def wait_task(self, runtime_task):
-        while True:
-            if not RemoteSpawner.is_task_alive(runtime_task):
-                return
-            await asyncio.sleep(0.1)
+    async def wait_task(self, runtime_task):
+        timeout = self.config.get("spawner.remote.test_timeout")
+        start = asyncio.get_running_loop().time()
+        while True:
+            if not RemoteSpawner.is_task_alive(runtime_task):
+                return
+            if asyncio.get_running_loop().time() - start > timeout:
+                LOG.error(f"Task {runtime_task.task.identifier} exceeded wait timeout of {timeout}s")
+                return
+            await asyncio.sleep(0.1)

208-216: Fix logging and include traceback on termination failure.

Current message lacks f-string and loses context.

-        try:
-            session.read_up_to_prompt()
-            return True
-        except exceptions.ExpectTimeoutError:
-            LOG.error("Failed to terminate task on {session.host}")
-            return False
+        try:
+            session.read_up_to_prompt()
+        except exceptions.ExpectTimeoutError:
+            LOG.exception(f"Failed to terminate task on {session.host}:{session.port}")
+            return False
+        else:
+            return True
🧹 Nitpick comments (13)
optional_plugins/robot/avocado_robot/robot.py (1)

41-42: Consider more explicit type checking.

The hasattr() check is functional but may mask errors if test_case.name doesn't exist or isn't a string. Consider using isinstance() or getattr() with a default value for more robust handling.

Apply this diff for a more explicit approach:

-        # Handle both string (old API) and TestCase object (new API)
-        test_name = test_case.name if hasattr(test_case, 'name') else test_case
+        # Handle both string (old API) and TestCase object (new API)
+        test_name = getattr(test_case, 'name', test_case)

Alternatively, if the type is known, use isinstance() checks:

# Handle both string (old API) and TestCase object (new API)
if isinstance(test_case, str):
    test_name = test_case
else:
    test_name = test_case.name
.github/workflows/ci.yml (4)

27-30: Migrate from setup.py develop to pip editable install.

For consistency with the rest of the PR and modern packaging, switch to pip editable:

-      - name: Installing Avocado in develop mode
-        run: python3 setup.py develop --user
+      - name: Installing Avocado in develop mode
+        run: python3 -m pip install --user -e .

94-98: Same here: use pip editable install.

Replace setup.py develop with pip -e:

-      - name: Installing Avocado in develop mode
-        run: python3 setup.py develop --user
+      - name: Installing Avocado in develop mode
+        run: python3 -m pip install --user -e .

132-136: macOS: avoid setup.py, prefer pip editable.

Also align interpreter usage to python3:

-      - name: Install avocado
-        run: |
-          python3 -m pip install -r requirements-dev.txt
-          python setup.py develop --user
+      - name: Install avocado
+        run: |
+          python3 -m pip install -r requirements-dev.txt
+          python3 -m pip install --user -e .

96-99: Avoid PATH issues by invoking avocado as a module.

User installs may not add ~/.local/bin to PATH. Use module invocation:

-      - name: Avocado version
-        run: avocado --version
+      - name: Avocado version
+        run: python3 -m avocado --version
Makefile (3)

124-124: Add missing PHONY targets (all, test).

Satisfy checkmake and clarify intent:

-.PHONY: pip install clean uninstall requirements-dev smokecheck check develop develop-external develop-plugins develop-plugin variables man
+.PHONY: all test pip install clean uninstall requirements-dev smokecheck check develop develop-external develop-plugins develop-plugin variables man
+
+# Optional: alias test to check to satisfy tools expecting a 'test' target
+test: check

Static analysis flagged these (minphony). Based on hints.


82-84: Consider using pip editable for develop.

Align with pyproject/pip flow:

-develop:
-	$(PYTHON) setup.py develop $(PYTHON_DEVELOP_ARGS)
+develop:
+	$(PYTHON) -m pip install -e . $(PYTHON_DEVELOP_ARGS)

108-114: Use automatic variables in manpage rule.

More idiomatic and future-proof:

-%.1: %.rst
-	@if command -v rst2man >/dev/null 2>&1; then \
-		rst2man man/avocado.rst man/avocado.1; \
+%.1: %.rst
+	@if command -v rst2man >/dev/null 2>&1; then \
+		rst2man $< $@; \
.github/workflows/setup.yml (3)

112-114: Invoke pip via Python for reliability.

Avoid pip3/which-pip issues:

-      - name: Install dependencies
-        run: pip3 install -r requirements-dev.txt
+      - name: Install dependencies
+        run: python3 -m pip install -r requirements-dev.txt

148-151: Same here: use python -m pip.

-      - name: Install dependencies
-        run: pip3 install -r requirements-dev.txt
+      - name: Install dependencies
+        run: python3 -m pip install -r requirements-dev.txt

176-179: Install python3-venv instead of virtualenv when using venv.

You're using python3 -m venv, so install python3-venv:

-         sudo apt-get install -y  python3-virtualenv
+         sudo apt-get install -y  python3-venv
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (2)

72-75: Annotate mutable class attributes as ClassVar to avoid shared-state lint issues.

Silences Ruff RUF012 and clarifies intent.

+from typing import ClassVar
 ...
-    METHODS = [SpawnMethod.STANDALONE_EXECUTABLE]
-    slots_cache = {}
+    METHODS: ClassVar[list] = [SpawnMethod.STANDALONE_EXECUTABLE]
+    slots_cache: ClassVar[dict] = {}

81-85: Use asyncio.get_running_loop() in async context.

get_event_loop() is deprecated in 3.10+ when called in coroutines.

-        loop = asyncio.get_event_loop()
+        loop = asyncio.get_running_loop()
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f6a187 and dce17a9.

📒 Files selected for processing (63)
  • .github/actions/version/action.yml (1 hunks)
  • .github/workflows/ci.yml (13 hunks)
  • .github/workflows/prerelease.yml (1 hunks)
  • .github/workflows/push_ci.yml (1 hunks)
  • .github/workflows/setup.yml (6 hunks)
  • .github/workflows/vmimage.yml (1 hunks)
  • .github/workflows/weekly.yml (2 hunks)
  • Makefile (5 hunks)
  • Makefile.gh (1 hunks)
  • docs/source/guides/contributor/chapters/environment.rst (1 hunks)
  • docs/source/guides/contributor/chapters/plugins.rst (4 hunks)
  • docs/source/guides/writer/chapters/subclassing.rst (2 hunks)
  • examples/plugins/README.rst (1 hunks)
  • examples/plugins/cli-cmd/hello/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_option/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_option/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/setup.py (0 hunks)
  • examples/plugins/job-pre-post/sleep/pyproject.toml (1 hunks)
  • examples/plugins/job-pre-post/sleep/setup.py (0 hunks)
  • examples/plugins/test-pre-post/hello/pyproject.toml (1 hunks)
  • examples/plugins/test-pre-post/hello/setup.py (0 hunks)
  • examples/plugins/tests/README.rst (1 hunks)
  • examples/plugins/tests/magic/pyproject.toml (1 hunks)
  • examples/plugins/tests/magic/setup.py (0 hunks)
  • examples/plugins/tests/rogue/pyproject.toml (1 hunks)
  • examples/plugins/tests/rogue/setup.py (0 hunks)
  • optional_plugins/README.rst (1 hunks)
  • optional_plugins/ansible/README.rst (1 hunks)
  • optional_plugins/ansible/pyproject.toml (1 hunks)
  • optional_plugins/ansible/setup.py (1 hunks)
  • optional_plugins/golang/pyproject.toml (1 hunks)
  • optional_plugins/golang/setup.py (1 hunks)
  • optional_plugins/html/pyproject.toml (1 hunks)
  • optional_plugins/html/setup.py (1 hunks)
  • optional_plugins/mail/pyproject.toml (1 hunks)
  • optional_plugins/mail/setup.py (1 hunks)
  • optional_plugins/result_upload/pyproject.toml (1 hunks)
  • optional_plugins/result_upload/setup.py (1 hunks)
  • optional_plugins/resultsdb/pyproject.toml (1 hunks)
  • optional_plugins/resultsdb/setup.py (1 hunks)
  • optional_plugins/robot/avocado_robot/robot.py (1 hunks)
  • optional_plugins/robot/pyproject.toml (1 hunks)
  • optional_plugins/robot/setup.py (1 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/__init__.py (0 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1 hunks)
  • optional_plugins/spawner_remote/pyproject.toml (1 hunks)
  • optional_plugins/spawner_remote/setup.py (1 hunks)
  • optional_plugins/varianter_cit/pyproject.toml (1 hunks)
  • optional_plugins/varianter_cit/setup.py (1 hunks)
  • optional_plugins/varianter_pict/README.rst (1 hunks)
  • optional_plugins/varianter_pict/pyproject.toml (1 hunks)
  • optional_plugins/varianter_pict/setup.py (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/README.rst (4 hunks)
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/setup.py (1 hunks)
  • pyproject.toml (1 hunks)
  • python-avocado.spec (6 hunks)
  • selftests/run_coverage (1 hunks)
  • setup.py (3 hunks)
💤 Files with no reviewable changes (9)
  • examples/plugins/job-pre-post/sleep/setup.py
  • examples/plugins/cli-cmd/hello_priority/setup.py
  • examples/plugins/cli-cmd/hello/setup.py
  • examples/plugins/cli-cmd/hello_parser/setup.py
  • examples/plugins/test-pre-post/hello/setup.py
  • examples/plugins/tests/magic/setup.py
  • examples/plugins/cli-cmd/hello_option/setup.py
  • optional_plugins/spawner_remote/avocado_spawner_remote/init.py
  • examples/plugins/tests/rogue/setup.py
✅ Files skipped from review due to trivial changes (2)
  • optional_plugins/varianter_pict/README.rst
  • optional_plugins/ansible/README.rst
🚧 Files skipped from review as they are similar to previous changes (22)
  • examples/plugins/tests/rogue/pyproject.toml
  • examples/plugins/cli-cmd/hello_option/pyproject.toml
  • selftests/run_coverage
  • examples/plugins/tests/magic/pyproject.toml
  • optional_plugins/robot/pyproject.toml
  • optional_plugins/varianter_pict/pyproject.toml
  • optional_plugins/mail/setup.py
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml
  • optional_plugins/varianter_yaml_to_mux/README.rst
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml
  • docs/source/guides/contributor/chapters/environment.rst
  • optional_plugins/resultsdb/setup.py
  • examples/plugins/test-pre-post/hello/pyproject.toml
  • examples/plugins/cli-cmd/hello/pyproject.toml
  • .github/workflows/vmimage.yml
  • optional_plugins/resultsdb/pyproject.toml
  • Makefile.gh
  • examples/plugins/tests/README.rst
  • .github/workflows/push_ci.yml
  • optional_plugins/spawner_remote/pyproject.toml
  • optional_plugins/html/pyproject.toml
  • optional_plugins/README.rst
🧰 Additional context used
🧬 Code graph analysis (2)
optional_plugins/robot/avocado_robot/robot.py (1)
avocado/core/test.py (1)
  • name (367-373)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (5)
avocado/core/plugin_interfaces.py (2)
  • Init (23-28)
  • Spawner (321-441)
avocado/core/spawners/common.py (3)
  • SpawnerMixin (35-47)
  • SpawnMethod (6-18)
  • task_output_dir (46-47)
avocado/core/settings.py (3)
  • section (137-138)
  • register_option (642-799)
  • key (141-142)
avocado/core/nrunner/runnable.py (1)
  • pick_runner_module_from_entry_point_kind (630-643)
avocado/core/nrunner/task.py (1)
  • setup_output_dir (171-174)
🪛 checkmake (0.2.2)
Makefile

[warning] 124-124: Missing required phony target "all"

(minphony)


[warning] 124-124: Missing required phony target "test"

(minphony)

🪛 Ruff (0.14.0)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py

73-73: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


74-74: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


126-129: Avoid specifying long messages outside the exception class

(TRY003)


197-197: Probable insecure usage of temporary file or directory: "/tmp/.avocado_task_output_dir"

(S108)


213-213: Consider moving this statement to an else block

(TRY300)


215-215: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


219-219: Unused static method argument: runtime_task

(ARG004)


224-224: Unused static method argument: runtime_task

(ARG004)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (41)
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: Fedora selftests
  • GitHub Check: Smokecheck on Linux with Python 3.11
  • GitHub Check: Smokecheck on Linux with Python 3.10
  • GitHub Check: macOS with Python 3.11
  • GitHub Check: Code Coverage (3.11)
🔇 Additional comments (32)
optional_plugins/robot/avocado_robot/robot.py (2)

43-45: LGTM!

The change from storing raw test_case to the normalized test_name improves consistency and ensures the dictionary always contains string test names regardless of the Robot Framework API version.


41-45: Verify the relationship to pyproject.toml migration.

This API compatibility change appears unrelated to the PR's stated objective of migrating from setup.py to pyproject.toml. Please confirm whether this change is:

  • A side effect of updating Robot Framework or other dependencies
  • An independent fix that happened to be included in this PR
  • Related to the migration in a way not immediately obvious from the PR description

If this is a separate concern, consider extracting it to a dedicated PR or documenting the connection in the PR description.

optional_plugins/varianter_pict/setup.py (1)

2-6: LGTM! Clean migration approach.

The transition strategy is well-documented. Keeping setup.py as a compatibility shim while moving primary configuration to pyproject.toml aligns with modern Python packaging standards (PEP 517/621).

optional_plugins/varianter_yaml_to_mux/setup.py (1)

2-6: Consistent migration pattern.

This follows the same clean transition approach as other plugins, maintaining backward compatibility while centralizing configuration in pyproject.toml.

examples/plugins/README.rst (1)

10-24: Clear and accurate workflow documentation.

The updated examples correctly demonstrate the modern pip-based development workflow, replacing deprecated python setup.py commands with their pip equivalents.

optional_plugins/robot/setup.py (1)

2-6: Consistent with the migration pattern.

The transition approach matches other plugins, properly delegating configuration to pyproject.toml.

optional_plugins/html/setup.py (1)

2-6: Correct transition implementation.

The migration follows the established pattern across plugins, maintaining compatibility while modernizing the packaging configuration.

docs/source/guides/writer/chapters/subclassing.rst (1)

20-101: Comprehensive and accurate modernization guide.

The updated documentation provides a complete example of modern Python packaging using pyproject.toml. The example demonstrates:

  • Proper build-system configuration with setuptools>=61.0
  • Dynamic version sourcing from VERSION file
  • Correct dependency declaration
  • Modern installation workflow with pip
optional_plugins/varianter_cit/setup.py (1)

2-6: Standard migration implementation.

Correctly implements the transition pattern with proper documentation of the backward compatibility shim.

optional_plugins/varianter_cit/pyproject.toml (1)

1-44: Configuration verified: entry points and dynamic files present
All entry points reference existing classes and the VERSION/README files are present.

examples/plugins/job-pre-post/sleep/pyproject.toml (1)

1-17: LGTM!

The pyproject.toml configuration for the sleep plugin is correctly structured with appropriate build system requirements, project metadata, and entry points for the job pre/post hooks.

optional_plugins/mail/pyproject.toml (1)

1-43: LGTM!

The pyproject.toml configuration for the mail notification plugin is correctly structured with appropriate dependencies on the core framework, proper entry points, and dynamic version/readme handling.

optional_plugins/ansible/pyproject.toml (1)

1-47: LGTM!

The pyproject.toml configuration for the Ansible plugin is well-structured with:

  • Appropriate dependencies (avocado-framework, cffi, pycparser, ansible-core)
  • Console script for the ansible module runner
  • Proper entry point for the runnable runner plugin
  • Package discovery pattern for avocado_ansible modules
optional_plugins/ansible/setup.py (1)

2-6: LGTM!

The setup.py has been correctly reduced to a minimal compatibility shim. The transitional comment clearly explains the purpose, and the bare setup() call delegates to pyproject.toml configuration, which is the modern approach.

setup.py (1)

16-26: LGTM!

The docstring clearly explains the purpose of keeping setup.py for backward compatibility and egg builds. The note about entry_points duplication is explicit and sets expectations for maintainers.

python-avocado.spec (4)

8-8: LGTM!

The tarball name change from avocado-%{version}.tar.gz to avocado_framework-%{version}.tar.gz aligns with the pyproject.toml-based packaging structure.


102-104: LGTM!

The %setup directory name changes from avocado-%{version} to avocado_framework-%{version} correctly match the updated tarball naming convention.


220-220: LGTM!

The change to module-based invocation (%{python3} -m selftests.check) is the modern Python best practice and aligns with the pyproject.toml migration.


243-245: Exclusions are correct: Verified that contrib*, scripts*, and avocado_result_html* match actual directories (contrib/, scripts/, optional_plugins/html/avocado_result_html/); no changes needed.

examples/plugins/cli-cmd/hello_parser/pyproject.toml (1)

1-14: LGTM!

The pyproject.toml configuration for the hello_parser CLI plugin is correctly structured with appropriate build system requirements, project metadata, and entry point mapping to the CLI command group.

pyproject.toml (1)

168-232: Optional plugin package names are consistent. Verified that each entry in the root pyproject.toml matches its respective plugin’s pyproject.toml.

docs/source/guides/contributor/chapters/plugins.rst (2)

97-101: Documentation correctly updated for pyproject.toml migration.

The changes properly reflect the migration from setup.py to pyproject.toml-based configuration, including the updated installation command using pip install -e . for development mode.


111-114: Entry-point format correctly updated to TOML syntax.

The entry-point example properly demonstrates the TOML-based format using [project.entry-points."avocado.plugins.job.prepost"] instead of the old setuptools dictionary format.

optional_plugins/result_upload/pyproject.toml (1)

1-44: Well-structured pyproject.toml configuration.

The pyproject.toml file is properly configured with:

  • Build system declaration using setuptools>=61.0
  • Complete project metadata including name, description, authors, and license
  • Correct Python version requirement (>=3.9)
  • Proper entry-point registration for both CLI and result plugins
  • Dynamic version and readme loading from VERSION and README.rst files
optional_plugins/result_upload/setup.py (1)

2-6: Backward compatibility approach is correct.

The bare setup() call with a clear comment explaining the transition period is the recommended approach for maintaining backward compatibility while migrating to pyproject.toml-based configuration.

optional_plugins/golang/setup.py (1)

2-6: Consistent backward compatibility approach.

The setup.py follows the same pattern as other migrated plugins, with a bare setup() call and clear transitional comment. This ensures consistent backward compatibility across the project.

.github/workflows/prerelease.yml (1)

48-49: Installation commands correctly updated.

The migration to python3 -m pip for both dependency installation and editable install aligns with the new pyproject.toml-based workflow and follows Python best practices.

.github/actions/version/action.yml (1)

9-11: Verify the necessity of --break-system-packages flag.

The --break-system-packages flag bypasses pip's protection against installing packages that conflict with system packages. While this may be necessary in containerized or specific CI environments, ensure this is intentional and required.

If this workflow runs in a controlled CI environment where system package conflicts are not a concern, the flag is acceptable. Otherwise, consider using a virtual environment or removing this flag.

.github/workflows/weekly.yml (2)

39-47: Workflow commands correctly migrated to module-based invocations.

The changes properly migrate from setup.py develop to pip install --user -e . and from script-based execution to python3 -m selftests.check, aligning with modern Python packaging practices and the pyproject.toml migration.


83-89: Consistent module-based invocations in without-plugins job.

The without-plugins job correctly uses the same module-based approach, with appropriate plugin disabling via --disable-plugin-checks for testing without optional dependencies.

optional_plugins/spawner_remote/setup.py (1)

2-6: Consistent migration pattern maintained.

The spawner_remote plugin follows the same backward compatibility pattern as other migrated plugins, ensuring consistency across the project during the transition period.

optional_plugins/golang/pyproject.toml (1)

5-25: LGTM for plugin packaging.

Metadata, entry points, and dynamic fields look correct. No issues found.

python3 -m pip install --user -e .
- name: Avocado pre-release job
run: python3 setup.py test --select=pre-release,vmimage
run: python3 -m selftets.check --select=pre-release,vmimage
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical typo in module name.

The command contains a typo: selftets.check should be selftests.check. This will cause the workflow to fail with a module not found error.

Apply this diff:

-        run: python3 -m selftets.check --select=pre-release,vmimage
+        run: python3 -m selftests.check --select=pre-release,vmimage
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
run: python3 -m selftets.check --select=pre-release,vmimage
run: python3 -m selftests.check --select=pre-release,vmimage
🤖 Prompt for AI Agents
In .github/workflows/prerelease.yml at line 51, the workflow command misspells
the Python module as "selftets.check"; update it to "selftests.check" so the run
step reads the correct module name, ensuring the workflow invokes python3 -m
selftests.check --select=pre-release,vmimage.

Comment on lines +148 to +193
@with_slot_reservation
async def spawn_task(self, runtime_task):
self.create_task_output_dir(runtime_task)
task = runtime_task.task
full_module_name = (
runtime_task.task.runnable.pick_runner_module_from_entry_point_kind(
runtime_task.task.runnable.kind
)
)
if full_module_name is None:
msg = f"Could not determine Python module name for runnable with kind {runtime_task.task.runnable.kind}"
raise RemoteSpawnerException(msg)
# using the "python" symlink will result in the container default python version
entry_point_args = ["python3", "-m", full_module_name, "task-run"]
entry_point_args.extend(task.get_command_args())

session = runtime_task.spawner_handle
LOG.info(f"Hostname: {session.host} Port: {session.port}")

setup_hook = self.config.get("spawner.remote.setup_hook")
# Customize and deploy test data to the container
if setup_hook:
setup_timeout = self.config.get("spawner.remote.setup_timeout")
status, output = await RemoteSpawner.run_remote_cmd_async(
session, setup_hook, setup_timeout
)
LOG.debug(f"Customization command exited with code {status}")
if status != 0:
LOG.error(
f"Error exit code {status} on {session.host}:{session.port} "
f"from setup hook with output:\n{output}"
)
return False

cmd = shlex.join(entry_point_args) + " > /dev/null"
timeout = self.config.get("spawner.remote.test_timeout")
status, output = await RemoteSpawner.run_remote_cmd_async(session, cmd, timeout)
LOG.debug(f"Command exited with code {status}")
if status != 0:
LOG.error(
f"Error exit code {status} on {session.host}:{session.port} "
f"with output:\n{output}"
)
return False

return True
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

spawn_task blocks until completion; breaks spawner semantics and concurrency.

run_remote_cmd_async waits for the command to finish, so spawn_task is synchronous. This defeats wait_task/is_task_alive and prevents parallelism.

Recommendation:

  • Launch the remote task in background (e.g., nohup ... >/tmp/log 2>&1 & echo $!), store the PID/handle in runtime_task.spawner_handle, and return True immediately.
  • Update is_task_alive to check the stored PID, and wait_task to poll until the process exits.
🤖 Prompt for AI Agents
In optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py around
lines 148-193, spawn_task currently waits for the remote command to finish
because run_remote_cmd_async is used for the entry-point execution; change it to
launch the remote process in the background and return immediately: build a
background-safe command (e.g., wrap the entry_point_args with nohup ...
>/tmp/<taskid>.log 2>&1 & echo $!), run that via run_remote_cmd_async, capture
the echoed PID from the command output, store that PID (and any remote
host/port/session info) on runtime_task.spawner_handle (or add a small .pid
field), and return True without waiting for process completion; keep the
setup_hook execution synchronous if needed but keep the task launch
non-blocking; then update is_task_alive to check the stored PID on the remote
host (e.g., kill -0 or ps check) and update wait_task to poll the PID until it
exits, returning exit status/output as appropriate.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (11)
optional_plugins/robot/pyproject.toml (1)

16-16: Update Robot Framework version constraint.

The dependency constraint "robotframework" has no upper bound, but a previous review suggested adding <8.0 to support the latest 7.x releases while preventing breaking changes from 8.x.

pyproject.toml (2)

14-16: Critical: Missing runtime dependencies.

The [project].dependencies section only contains "setuptools", but the core package imports third-party modules like psutil, requests, and jsonschema. Without these dependencies, users will encounter ImportError at runtime after installation.


30-30: Major: Remove unsupported Python 3.14 classifier.

PyPI does not yet support the "Programming Language :: Python :: 3.14" classifier, which will cause package upload failures.

optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (8)

38-46: Major: Timeout options need key_type=int.

Without key_type=int, the configuration values will be strings, breaking arithmetic and timeout logic downstream.


106-114: Critical: Add error handling for slot initialization.

Slot initialization can fail due to malformed JSON, missing files, or connection failures. Without error handling, a single bad slot will crash the spawner.


136-146: Major: Escape task identifier in shell command.

The task identifier is interpolated directly into the shell command without escaping, creating command injection risk and breakage with special characters.


148-193: Major: spawn_task blocks until completion, breaking spawner semantics.

Using run_remote_cmd_async to execute the task makes spawn_task synchronous, defeating the purpose of wait_task and preventing concurrency.


182-182: Critical: Output redirection loses all test results.

Redirecting output to /dev/null discards all test output, logs, and results, making debugging impossible.


195-201: Major: Use sanitized, unique remote directory path.

The fixed /tmp/.avocado_task_output_dir path creates collision and TOCTOU risks.


202-206: Major: Add timeout to prevent infinite waiting.

The wait loop has no timeout, potentially blocking the job runner indefinitely.


208-216: Minor: Fix logging and add traceback on termination failure.

The error log lacks f-string interpolation and should use logging.exception for context.

🧹 Nitpick comments (2)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1)

70-74: Annotate mutable class attributes with ClassVar.

Static analysis correctly identifies that slots_cache should be annotated with typing.ClassVar to clarify it's shared across instances.

Apply this diff:

+from typing import ClassVar
+
 class RemoteSpawner(Spawner, SpawnerMixin):
 
     description = "Remote (host) based spawner"
     METHODS = [SpawnMethod.STANDALONE_EXECUTABLE]
-    slots_cache = {}
+    slots_cache: ClassVar[dict] = {}
Makefile (1)

124-124: Static analysis note: optional PHONY targets.

The static analysis tool suggests adding "all" and "test" to the PHONY declarations. While these are common conventions in Makefiles, they're not strictly required for this project. The current PHONY declaration covers all the actual targets that need it.

Based on static analysis hints

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dce17a9 and 7a74e10.

📒 Files selected for processing (64)
  • .github/actions/version/action.yml (1 hunks)
  • .github/workflows/ci.yml (13 hunks)
  • .github/workflows/prerelease.yml (1 hunks)
  • .github/workflows/push_ci.yml (1 hunks)
  • .github/workflows/setup.yml (6 hunks)
  • .github/workflows/vmimage.yml (1 hunks)
  • .github/workflows/weekly.yml (2 hunks)
  • Makefile (5 hunks)
  • Makefile.gh (1 hunks)
  • docs/source/guides/contributor/chapters/environment.rst (1 hunks)
  • docs/source/guides/contributor/chapters/plugins.rst (4 hunks)
  • docs/source/guides/writer/chapters/subclassing.rst (2 hunks)
  • examples/plugins/README.rst (1 hunks)
  • examples/plugins/cli-cmd/hello/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_option/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_option/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/setup.py (0 hunks)
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/setup.py (0 hunks)
  • examples/plugins/job-pre-post/sleep/pyproject.toml (1 hunks)
  • examples/plugins/job-pre-post/sleep/setup.py (0 hunks)
  • examples/plugins/test-pre-post/hello/pyproject.toml (1 hunks)
  • examples/plugins/test-pre-post/hello/setup.py (0 hunks)
  • examples/plugins/tests/README.rst (1 hunks)
  • examples/plugins/tests/magic/pyproject.toml (1 hunks)
  • examples/plugins/tests/magic/setup.py (0 hunks)
  • examples/plugins/tests/rogue/pyproject.toml (1 hunks)
  • examples/plugins/tests/rogue/setup.py (0 hunks)
  • optional_plugins/README.rst (1 hunks)
  • optional_plugins/ansible/README.rst (1 hunks)
  • optional_plugins/ansible/pyproject.toml (1 hunks)
  • optional_plugins/ansible/setup.py (1 hunks)
  • optional_plugins/golang/pyproject.toml (1 hunks)
  • optional_plugins/golang/setup.py (1 hunks)
  • optional_plugins/html/pyproject.toml (1 hunks)
  • optional_plugins/html/setup.py (1 hunks)
  • optional_plugins/mail/pyproject.toml (1 hunks)
  • optional_plugins/mail/setup.py (1 hunks)
  • optional_plugins/result_upload/pyproject.toml (1 hunks)
  • optional_plugins/result_upload/setup.py (1 hunks)
  • optional_plugins/resultsdb/pyproject.toml (1 hunks)
  • optional_plugins/resultsdb/setup.py (1 hunks)
  • optional_plugins/robot/avocado_robot/robot.py (1 hunks)
  • optional_plugins/robot/pyproject.toml (1 hunks)
  • optional_plugins/robot/setup.py (1 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/__init__.py (0 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1 hunks)
  • optional_plugins/spawner_remote/pyproject.toml (1 hunks)
  • optional_plugins/spawner_remote/setup.py (1 hunks)
  • optional_plugins/varianter_cit/pyproject.toml (1 hunks)
  • optional_plugins/varianter_cit/setup.py (1 hunks)
  • optional_plugins/varianter_pict/README.rst (1 hunks)
  • optional_plugins/varianter_pict/pyproject.toml (1 hunks)
  • optional_plugins/varianter_pict/setup.py (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/README.rst (4 hunks)
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/setup.py (1 hunks)
  • pyproject.toml (1 hunks)
  • python-avocado.spec (6 hunks)
  • selftests/run_coverage (1 hunks)
  • setup.py (3 hunks)
  • spell.ignore (1 hunks)
💤 Files with no reviewable changes (9)
  • examples/plugins/test-pre-post/hello/setup.py
  • examples/plugins/job-pre-post/sleep/setup.py
  • examples/plugins/cli-cmd/hello/setup.py
  • optional_plugins/spawner_remote/avocado_spawner_remote/init.py
  • examples/plugins/tests/rogue/setup.py
  • examples/plugins/tests/magic/setup.py
  • examples/plugins/cli-cmd/hello_priority/setup.py
  • examples/plugins/cli-cmd/hello_parser/setup.py
  • examples/plugins/cli-cmd/hello_option/setup.py
✅ Files skipped from review due to trivial changes (2)
  • optional_plugins/spawner_remote/pyproject.toml
  • optional_plugins/varianter_cit/pyproject.toml
🚧 Files skipped from review as they are similar to previous changes (23)
  • examples/plugins/tests/rogue/pyproject.toml
  • examples/plugins/cli-cmd/hello/pyproject.toml
  • .github/workflows/push_ci.yml
  • .github/workflows/weekly.yml
  • optional_plugins/result_upload/pyproject.toml
  • optional_plugins/ansible/pyproject.toml
  • docs/source/guides/contributor/chapters/environment.rst
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml
  • optional_plugins/varianter_yaml_to_mux/README.rst
  • examples/plugins/tests/README.rst
  • optional_plugins/varianter_pict/README.rst
  • optional_plugins/varianter_pict/pyproject.toml
  • optional_plugins/html/setup.py
  • examples/plugins/cli-cmd/hello_option/pyproject.toml
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml
  • selftests/run_coverage
  • optional_plugins/varianter_cit/setup.py
  • optional_plugins/mail/pyproject.toml
  • .github/workflows/prerelease.yml
  • optional_plugins/resultsdb/pyproject.toml
  • examples/plugins/tests/magic/pyproject.toml
  • examples/plugins/README.rst
  • python-avocado.spec
🧰 Additional context used
🧬 Code graph analysis (2)
optional_plugins/robot/avocado_robot/robot.py (1)
avocado/core/test.py (1)
  • name (367-373)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (5)
avocado/core/plugin_interfaces.py (2)
  • Init (23-28)
  • Spawner (321-441)
avocado/core/spawners/common.py (3)
  • SpawnerMixin (35-47)
  • SpawnMethod (6-18)
  • task_output_dir (46-47)
avocado/core/settings.py (3)
  • section (137-138)
  • register_option (642-799)
  • key (141-142)
avocado/core/nrunner/runnable.py (1)
  • pick_runner_module_from_entry_point_kind (630-643)
avocado/core/nrunner/task.py (1)
  • setup_output_dir (171-174)
🪛 checkmake (0.2.2)
Makefile

[warning] 124-124: Missing required phony target "all"

(minphony)


[warning] 124-124: Missing required phony target "test"

(minphony)

🪛 Ruff (0.14.0)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py

73-73: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


74-74: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


126-129: Avoid specifying long messages outside the exception class

(TRY003)


197-197: Probable insecure usage of temporary file or directory: "/tmp/.avocado_task_output_dir"

(S108)


213-213: Consider moving this statement to an else block

(TRY300)


215-215: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


219-219: Unused static method argument: runtime_task

(ARG004)


224-224: Unused static method argument: runtime_task

(ARG004)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (28)
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: Fedora selftests
  • GitHub Check: Fedora develop install/uninstall task
  • GitHub Check: Build Package (wheel/tarball) for Python 3.11
  • GitHub Check: Windows with Python 3.13
  • GitHub Check: macOS with Python 3.11
  • GitHub Check: Code Coverage (3.11)
  • GitHub Check: Smokecheck on Linux with Python 3.11
  • GitHub Check: Smokecheck on Linux with Python 3.10
  • GitHub Check: Analyze (python)
  • GitHub Check: Static checks
🔇 Additional comments (42)
spell.ignore (1)

806-807: LGTM!

Adding "pyproject" and "toml" to the spell checker ignore list is appropriate given the introduction of pyproject.toml files throughout the repository.

optional_plugins/robot/avocado_robot/robot.py (1)

41-44: LGTM!

The API compatibility handling correctly supports both the legacy string-based test_case and the new TestCase object with a name attribute. The hasattr check is appropriate for detecting the API version at runtime.

optional_plugins/golang/setup.py (1)

2-6: LGTM!

The minimal setup.py stub with explanatory comment correctly maintains backward compatibility for egg building while delegating primary configuration to pyproject.toml. This pattern is consistent with the broader migration strategy.

optional_plugins/varianter_yaml_to_mux/setup.py (1)

2-6: LGTM!

The setup.py stub follows the established migration pattern, maintaining egg-build compatibility while pyproject.toml handles the primary configuration.

optional_plugins/resultsdb/setup.py (1)

2-6: LGTM!

Consistent with the migration pattern across other plugins - minimal setup.py for compatibility with pyproject.toml handling primary configuration.

optional_plugins/varianter_pict/setup.py (1)

2-6: LGTM!

Final setup.py stub follows the consistent migration pattern. The uniform approach across all plugins simplifies the transition to pyproject.toml-based packaging.

optional_plugins/ansible/README.rst (1)

16-18: Installation package name is correct. The name avocado-framework-plugin-ansible in README matches pyproject.toml.

Makefile.gh (1)

65-69: All optional plugins include pyproject.toml

All current optional plugins have a pyproject.toml, so the unconditional build loop in Makefile.gh is safe.

optional_plugins/mail/setup.py (1)

2-6: LGTM! Minimal setup.py for backward compatibility.

The file correctly retains a minimal setup() call for backward compatibility during the transition to pyproject.toml-based configuration. The comment clearly explains the purpose.

examples/plugins/job-pre-post/sleep/pyproject.toml (1)

1-17: LGTM! Clean plugin packaging configuration.

The pyproject.toml correctly defines the build system, project metadata, entry points for both init and job prepost plugins, and module configuration. This aligns well with the migration strategy.

pyproject.toml (1)

1-250: Comprehensive pyproject.toml structure looks good.

The overall structure is well-organized with proper build system configuration, extensive plugin entry points, optional dependencies grouping, and dynamic version/readme sourcing. Once the dependency and classifier issues are resolved, this will be a solid foundation for the migration.

examples/plugins/test-pre-post/hello/pyproject.toml (1)

1-17: LGTM! Test plugin configuration is correct.

The pyproject.toml properly configures entry points for both test pre and post hooks, referencing the HelloWorld class from the hello module.

.github/workflows/setup.yml (3)

33-43: LGTM! Installation commands properly migrated to pip.

The workflow correctly replaces setup.py commands with python3 -m pip install for both the main package and plugins. This aligns with modern Python packaging best practices.


115-126: LGTM! Developer workflow commands updated correctly.

The developer installation flow properly uses:

  • python3 -m pip install --user -e . for editable installation
  • make man for manpage generation
  • python3 -m selftests.check for running tests
  • python3 -m pip uninstall for cleanup

All changes are consistent with the pyproject.toml migration.


181-194: LGTM! Virtualenv workflow properly updated.

The virtualenv installation correctly uses python3 -m venv and pip-based commands within the activated environment.

optional_plugins/golang/pyproject.toml (1)

1-47: LGTM! Golang plugin packaging is well-structured.

The pyproject.toml correctly defines:

  • Build system and dependencies (including avocado-framework)
  • Script entry point for the golang runner
  • Plugin entry points for resolver and runnable runner
  • Dynamic version/readme sourcing
  • Package discovery configuration
optional_plugins/robot/pyproject.toml (1)

1-48: LGTM! Robot plugin packaging structure is solid.

The pyproject.toml properly defines all necessary components for the robot framework plugin, including build system, dependencies, scripts, and entry points.

optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1)

218-233: Stub implementations are acceptable for now.

The placeholder implementations for requirement cache methods (check_task_requirements, is_requirement_in_cache, save_requirement_in_cache, update_requirement_cache) are acceptable stubs. The unused argument warnings from static analysis can be safely ignored for these abstract method implementations.

optional_plugins/html/pyproject.toml (1)

1-47: LGTM!

The pyproject.toml configuration for the HTML plugin follows modern Python packaging best practices. The build system, dependencies, entry points, and dynamic metadata configuration are all properly structured.

.github/workflows/vmimage.yml (1)

42-42: LGTM!

The migration from setup.py develop --user to python -m pip install --user -e . is correct and aligns with the PR's modernization goals.

.github/actions/version/action.yml (1)

9-12: LGTM!

The migration to pip-based installation with PIP_BREAK_SYSTEM_PACKAGES for CI environments is appropriate, and the direct avocado --version invocation is correct after pip install.

docs/source/guides/contributor/chapters/plugins.rst (4)

97-101: LGTM!

The documentation correctly updates references from setup.py to pyproject.toml and provides accurate installation instructions using pip install -e ..


111-114: LGTM!

The TOML-based entry point examples are correct and demonstrate the modern plugin registration approach.


170-173: LGTM!

The settings plugin registration example properly shows the pyproject.toml-based configuration.


325-327: LGTM!

The test type plugin registration documentation correctly reflects the migration to pyproject.toml and pip-based installation.

optional_plugins/README.rst (2)

10-24: LGTM!

The installation and uninstallation instructions are correctly updated to use pip-based commands, with clear examples for specific plugins.


30-34: LGTM!

The make target updates (develop-plugins and develop-plugin) are documented correctly and align with the Makefile changes.

Makefile (6)

9-10: LGTM!

The help text for the new plugin development targets is clear and informative.


33-61: LGTM!

The expanded clean target provides comprehensive cleanup of build artifacts, cache files, and plugin directories. The iteration over optional and example plugins ensures thorough cleanup.


64-67: LGTM!

The migration to python -m pip for install and uninstall operations follows best practices for modern Python packaging.


80-80: LGTM!

The change to python -m selftests.check aligns with the project-wide migration to module-based invocations.


91-104: LGTM!

The new develop-plugins and develop-plugin targets provide a clean interface for plugin development. The PLUGIN variable check with usage guidance is a good UX touch.


109-114: LGTM!

The enhanced man page generation with explicit error handling for missing rst2man is a good improvement over silent failures.

setup.py (1)

16-26: LGTM!

The docstring clearly explains the purpose of keeping setup.py for backward compatibility and the necessity of duplicating entry_points for egg builds. This addresses the concern raised in previous reviews.

.github/workflows/ci.yml (8)

32-32: LGTM!

The migration to python3 -m selftests.check is consistent with the project-wide modernization.


89-89: LGTM!

Using python -VV (double verbose) is a valid improvement over the previous invalid combination of -V --version mentioned in past reviews.


93-93: LGTM!

The migration to python3 -m pip install for dependencies follows best practices.


99-99: LGTM!

Using python3 -m selftests.check --skip=static-checks is the correct module-based invocation.


131-131: LGTM!

The -VV flag provides more verbose version information and is a valid flag.


143-143: LGTM!

The module-based test invocation is correct and consistent.


167-171: LGTM!

The -VV flag and pip-based installation are both correct. This addresses the previous review comments about invalid flag combinations and broken setuptools fallback.


265-356: LGTM!

The Python dependency installation commands are consistently updated across all container-based version tasks, properly installing python3-pip, python3-venv, and necessary certificates.

@PaulYuuu PaulYuuu force-pushed the pyproject branch 3 times, most recently from b3a3d96 to 918a1d4 Compare October 14, 2025 02:13
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (13)
optional_plugins/robot/setup.py (1)

1-1: Inconsistency: Past review claims shebang was fixed, but code still incorrect.

The past review comment indicates this shebang issue was "✅ Addressed in commit dce17a9", but the code still shows #!/bin/env python3 instead of the portable #!/usr/bin/env python3.

Run the following script to verify if the shebang fix was actually applied:

#!/bin/bash
# Check if commit dce17a9 exists and what changes it made to this file
git log --oneline --all | grep -i dce17a9
git show dce17a9:optional_plugins/robot/setup.py | head -1
optional_plugins/varianter_cit/setup.py (1)

1-1: Inconsistency: Past review claims shebang was fixed, but code still incorrect.

The past review comment indicates this shebang issue was "✅ Addressed in commits 662fc8c to af71902", but the code still shows #!/bin/env python3 instead of #!/usr/bin/env python3.

Run the following script to verify the commit history:

#!/bin/bash
# Check if the fix commits exist and verify the current state
git log --oneline --all | grep -E '662fc8c|af71902'
git show af71902:optional_plugins/varianter_cit/setup.py | head -1
optional_plugins/mail/setup.py (1)

1-1: Inconsistency: Past review claims shebang was fixed, but code still incorrect.

The past review comment indicates this shebang issue was "✅ Addressed in commits 662fc8c to af71902", but the code still shows #!/bin/env python3 instead of #!/usr/bin/env python3.

This is the same issue flagged in other optional_plugins setup.py files. Consider verifying all affected files at once:

#!/bin/bash
# Find all setup.py files with incorrect shebang
rg -n '^#!/bin/env' --type=py --glob='**/setup.py'
optional_plugins/ansible/setup.py (1)

1-1: Inconsistency: Past review claims shebang was fixed, but code still incorrect.

The past review comment indicates this shebang issue was "✅ Addressed in commits c31acca to 1352bf5", but the code still shows #!/bin/env python3 instead of #!/usr/bin/env python3.

This appears to be a systematic issue across multiple optional_plugins. Verify the commit history and current state:

#!/bin/bash
# Check commit history for ansible plugin
git log --oneline --all | grep -E 'c31acca|1352bf5'
# Verify current state matches what's expected
git show 1352bf5:optional_plugins/ansible/setup.py | head -1
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (6)

38-46: Register timeout options as integers.

The setup_timeout and test_timeout options are registered without key_type=int, so they will be treated as strings by default. This can cause type errors when the values are used in arithmetic operations or comparisons.

Apply this diff to fix both timeout registrations:

 help_msg = "Test timeout enforced for remote host setup hook"
 settings.register_option(
-    section=section, key="setup_timeout", help_msg=help_msg, default=3600
+    section=section, key="setup_timeout", help_msg=help_msg, default=3600, key_type=int
 )

 help_msg = "Test timeout enforced for sessions (just for this spawner)"
 settings.register_option(
-    section=section, key="test_timeout", help_msg=help_msg, default=14400
+    section=section, key="test_timeout", help_msg=help_msg, default=14400, key_type=int
 )

106-114: Add error handling for slot initialization.

Loading slot configuration files can fail in multiple ways (file not found, malformed JSON, connection errors), but there's no error handling. A single bad slot configuration will crash the spawner.

Apply this diff to add robust error handling:

         for session_slot in self.config.get("spawner.remote.slots"):
             if not session_slot:
                 continue
-            with open(session_slot, "r", encoding="utf-8") as f:
-                session_data = json.load(f)
-            session = remote.remote_login(**session_data)
-            RemoteSpawner.slots_cache[session] = False
+            try:
+                with open(session_slot, "r", encoding="utf-8") as f:
+                    session_data = json.load(f)
+                session = remote.remote_login(**session_data)
+                RemoteSpawner.slots_cache[session] = False
+            except (FileNotFoundError, json.JSONDecodeError, KeyError) as e:
+                LOG.warning(f"Failed to initialize slot from {session_slot}: {e}")
+            except Exception as e:
+                LOG.error(f"Unexpected error initializing slot from {session_slot}: {e}")

182-182: Critical: Output redirection loses all test results.

Redirecting stdout to /dev/null discards all test output, including results, logs, and error messages. This makes debugging impossible and defeats the purpose of running tests remotely.

Remove the output redirection:

-        cmd = shlex.join(entry_point_args) + " > /dev/null"
+        cmd = shlex.join(entry_point_args)

195-201: Avoid hardcoded /tmp path; use unique remote directory per task.

The hardcoded path /tmp/.avocado_task_output_dir creates collision risks when multiple tasks run concurrently. It's also flagged by static analysis as a security concern.

Apply this diff to use a unique, sanitized path per task:

     def create_task_output_dir(self, runtime_task):
         output_dir_path = self.task_output_dir(runtime_task)
-        output_lxc_path = "/tmp/.avocado_task_output_dir"
+        safe_id = "".join(c if c.isalnum() or c in "._-" else "_" for c in runtime_task.task.identifier)
+        output_lxc_path = f"/tmp/avocado-task-{safe_id}"
 
         os.makedirs(output_dir_path, exist_ok=True)
         runtime_task.task.setup_output_dir(output_lxc_path)

202-206: Add timeout to prevent infinite waiting.

The wait_task loop has no timeout, so if is_task_alive never returns False (due to a bug or hung task), the spawner will wait indefinitely and block the job runner.

Apply this diff to add a configurable timeout:

     async def wait_task(self, runtime_task):
+        timeout = self.config.get("spawner.remote.test_timeout")
+        start_time = asyncio.get_event_loop().time()
         while True:
             if not RemoteSpawner.is_task_alive(runtime_task):
                 return
+            if asyncio.get_event_loop().time() - start_time > timeout:
+                LOG.error(f"Task {runtime_task.task.identifier} exceeded wait timeout of {timeout}s")
+                return
             await asyncio.sleep(0.1)

208-216: Fix logging and include exception traceback.

Line 215 is missing the f-string prefix, so the host variable won't be interpolated. Additionally, use LOG.exception instead of LOG.error to capture the full traceback.

Apply this diff:

     async def terminate_task(self, runtime_task):
         session = runtime_task.spawner_handle
         session.sendcontrol("c")
         try:
             session.read_up_to_prompt()
-            return True
         except exceptions.ExpectTimeoutError:
-            LOG.error("Failed to terminate task on {session.host}")
+            LOG.exception(f"Failed to terminate task on {session.host}:{session.port}")
             return False
+        else:
+            return True
pyproject.toml (2)

14-16: Restore the project's runtime dependencies.

The dependencies list contains only setuptools, but the Avocado framework requires additional packages at runtime (e.g., psutil for system info, requests for asset fetching, jsonschema for validation). Without these dependencies, pip install avocado-framework will succeed but the package will raise ImportError when imported.

Based on learnings, setuptools should only be in build-system.requires, not in runtime dependencies. Please restore the full dependency list from the former setup.py (which likely included packages like psutil, requests, jsonschema, etc.) and move them to [project].dependencies.

For optional features, use [project.optional-dependencies] groups, which are already defined in this file starting at Line 168.


30-30: Remove unsupported Python 3.14 classifier.

The trove classifier "Programming Language :: Python :: 3.14" is not yet published by PyPI. Including it will cause package uploads to PyPI to be rejected with "Invalid classifier" errors.

Apply this diff to remove the unsupported classifier:

     "Programming Language :: Python :: 3.11",
     "Programming Language :: Python :: 3.12",
     "Programming Language :: Python :: 3.13",
-    "Programming Language :: Python :: 3.14",
 ]

Re-add the classifier only after PyPI officially publishes it.

optional_plugins/varianter_yaml_to_mux/setup.py (1)

1-1: Fix shebang for better portability.

The shebang uses /bin/env which is not portable across all systems. Use /usr/bin/env instead.

Apply this diff:

-#!/bin/env python3
+#!/usr/bin/env python3

Note: Since this file is now just a compatibility shim during the transition period (as stated in the comment on Line 2), this is a minor issue. However, it's still worth fixing for consistency.

🧹 Nitpick comments (1)
optional_plugins/varianter_pict/setup.py (1)

1-1: Consider fixing shebang for portability.

The shebang uses /bin/env which may not be portable across all systems. Consider using /usr/bin/env instead (though this is a minor portability concern).

Apply this diff for better portability:

-#!/bin/env python3
+#!/usr/bin/env python3
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a74e10 and 918a1d4.

📒 Files selected for processing (64)
  • .github/actions/version/action.yml (1 hunks)
  • .github/workflows/ci.yml (13 hunks)
  • .github/workflows/prerelease.yml (1 hunks)
  • .github/workflows/push_ci.yml (1 hunks)
  • .github/workflows/setup.yml (6 hunks)
  • .github/workflows/vmimage.yml (1 hunks)
  • .github/workflows/weekly.yml (2 hunks)
  • Makefile (5 hunks)
  • Makefile.gh (1 hunks)
  • docs/source/guides/contributor/chapters/environment.rst (1 hunks)
  • docs/source/guides/contributor/chapters/plugins.rst (4 hunks)
  • docs/source/guides/writer/chapters/subclassing.rst (2 hunks)
  • examples/plugins/README.rst (1 hunks)
  • examples/plugins/cli-cmd/hello/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_option/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_option/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/setup.py (1 hunks)
  • examples/plugins/job-pre-post/sleep/pyproject.toml (1 hunks)
  • examples/plugins/job-pre-post/sleep/setup.py (1 hunks)
  • examples/plugins/test-pre-post/hello/pyproject.toml (1 hunks)
  • examples/plugins/test-pre-post/hello/setup.py (1 hunks)
  • examples/plugins/tests/README.rst (1 hunks)
  • examples/plugins/tests/magic/pyproject.toml (1 hunks)
  • examples/plugins/tests/magic/setup.py (1 hunks)
  • examples/plugins/tests/rogue/pyproject.toml (1 hunks)
  • examples/plugins/tests/rogue/setup.py (1 hunks)
  • optional_plugins/README.rst (1 hunks)
  • optional_plugins/ansible/README.rst (1 hunks)
  • optional_plugins/ansible/pyproject.toml (1 hunks)
  • optional_plugins/ansible/setup.py (1 hunks)
  • optional_plugins/golang/pyproject.toml (1 hunks)
  • optional_plugins/golang/setup.py (1 hunks)
  • optional_plugins/html/pyproject.toml (1 hunks)
  • optional_plugins/html/setup.py (1 hunks)
  • optional_plugins/mail/pyproject.toml (1 hunks)
  • optional_plugins/mail/setup.py (1 hunks)
  • optional_plugins/result_upload/pyproject.toml (1 hunks)
  • optional_plugins/result_upload/setup.py (1 hunks)
  • optional_plugins/resultsdb/pyproject.toml (1 hunks)
  • optional_plugins/resultsdb/setup.py (1 hunks)
  • optional_plugins/robot/avocado_robot/robot.py (1 hunks)
  • optional_plugins/robot/pyproject.toml (1 hunks)
  • optional_plugins/robot/setup.py (1 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/__init__.py (0 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1 hunks)
  • optional_plugins/spawner_remote/pyproject.toml (1 hunks)
  • optional_plugins/spawner_remote/setup.py (1 hunks)
  • optional_plugins/varianter_cit/pyproject.toml (1 hunks)
  • optional_plugins/varianter_cit/setup.py (1 hunks)
  • optional_plugins/varianter_pict/README.rst (1 hunks)
  • optional_plugins/varianter_pict/pyproject.toml (1 hunks)
  • optional_plugins/varianter_pict/setup.py (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/README.rst (4 hunks)
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/setup.py (1 hunks)
  • pyproject.toml (1 hunks)
  • python-avocado.spec (6 hunks)
  • selftests/run_coverage (1 hunks)
  • setup.py (3 hunks)
  • spell.ignore (1 hunks)
💤 Files with no reviewable changes (1)
  • optional_plugins/spawner_remote/avocado_spawner_remote/init.py
✅ Files skipped from review due to trivial changes (1)
  • optional_plugins/result_upload/pyproject.toml
🚧 Files skipped from review as they are similar to previous changes (31)
  • spell.ignore
  • optional_plugins/varianter_cit/pyproject.toml
  • examples/plugins/cli-cmd/hello/pyproject.toml
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml
  • optional_plugins/varianter_pict/README.rst
  • optional_plugins/resultsdb/setup.py
  • examples/plugins/tests/magic/pyproject.toml
  • examples/plugins/tests/rogue/pyproject.toml
  • Makefile.gh
  • .github/workflows/weekly.yml
  • optional_plugins/ansible/README.rst
  • optional_plugins/spawner_remote/pyproject.toml
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml
  • optional_plugins/html/pyproject.toml
  • optional_plugins/varianter_pict/pyproject.toml
  • examples/plugins/README.rst
  • optional_plugins/mail/pyproject.toml
  • examples/plugins/test-pre-post/hello/setup.py
  • .github/workflows/vmimage.yml
  • examples/plugins/cli-cmd/hello_option/pyproject.toml
  • .github/workflows/setup.yml
  • examples/plugins/tests/README.rst
  • .github/workflows/prerelease.yml
  • optional_plugins/robot/pyproject.toml
  • docs/source/guides/contributor/chapters/environment.rst
  • .github/workflows/push_ci.yml
  • examples/plugins/cli-cmd/hello/setup.py
  • optional_plugins/ansible/pyproject.toml
  • optional_plugins/spawner_remote/setup.py
  • .github/actions/version/action.yml
🧰 Additional context used
🧬 Code graph analysis (2)
optional_plugins/robot/avocado_robot/robot.py (1)
avocado/core/test.py (1)
  • name (367-373)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (5)
avocado/core/plugin_interfaces.py (2)
  • Init (23-28)
  • Spawner (321-441)
avocado/core/spawners/common.py (3)
  • SpawnerMixin (35-47)
  • SpawnMethod (6-18)
  • task_output_dir (46-47)
avocado/core/settings.py (3)
  • section (137-138)
  • register_option (642-799)
  • key (141-142)
avocado/core/nrunner/runnable.py (1)
  • pick_runner_module_from_entry_point_kind (630-643)
avocado/core/nrunner/task.py (1)
  • setup_output_dir (171-174)
🪛 checkmake (0.2.2)
Makefile

[warning] 124-124: Missing required phony target "all"

(minphony)


[warning] 124-124: Missing required phony target "test"

(minphony)

🪛 Ruff (0.14.0)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py

73-73: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


74-74: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


126-129: Avoid specifying long messages outside the exception class

(TRY003)


197-197: Probable insecure usage of temporary file or directory: "/tmp/.avocado_task_output_dir"

(S108)


213-213: Consider moving this statement to an else block

(TRY300)


215-215: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


219-219: Unused static method argument: runtime_task

(ARG004)


224-224: Unused static method argument: runtime_task

(ARG004)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (49)
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: Fedora selftests
  • GitHub Check: Windows with Python 3.12
  • GitHub Check: Windows with Python 3.11
  • GitHub Check: Windows with Python 3.10
  • GitHub Check: Version task fedora:41
  • GitHub Check: Version task ubuntu:24.04
  • GitHub Check: Build Package (wheel/tarball) for Python 3.12
  • GitHub Check: Build Package (wheel/tarball) for Python 3.13
  • GitHub Check: macOS with Python 3.11
  • GitHub Check: Smokecheck on Linux with Python 3.11
  • GitHub Check: Smokecheck on Linux with Python 3.10
  • GitHub Check: Static checks
  • GitHub Check: Code Coverage (3.11)
🔇 Additional comments (30)
selftests/run_coverage (1)

30-30: LGTM: Module-based test execution.

The change from direct script execution to module invocation (-m selftests.check) aligns with modern Python best practices and the PR's migration goals.

examples/plugins/tests/magic/setup.py (1)

1-6: LGTM: Clean migration to pyproject.toml.

The minimal setup.py shim correctly delegates all configuration to pyproject.toml while maintaining backward compatibility. The implementation follows best practices with a proper shebang and clear comments.

optional_plugins/golang/setup.py (1)

2-6: LGTM: Minimal setup.py shim for compatibility.

The bare setup() call correctly delegates configuration to pyproject.toml while maintaining backward compatibility.

Note: The shebang issue on line 1 was already flagged in a previous review and marked as addressed.

examples/plugins/cli-cmd/hello_option/setup.py (1)

1-6: LGTM: Proper setup.py shim for pyproject.toml migration.

The minimal setup.py implementation correctly delegates all packaging configuration to pyproject.toml while maintaining backward compatibility. Shebang and comments are appropriate.

optional_plugins/varianter_pict/setup.py (1)

2-6: LGTM: Clean delegation to pyproject.toml.

The minimal setup.py shim properly delegates all configuration to pyproject.toml while maintaining backward compatibility.

examples/plugins/cli-cmd/hello_priority/setup.py (1)

1-6: LGTM: Correct migration pattern.

The minimal setup.py implementation properly delegates to pyproject.toml with appropriate shebang and documentation comments.

optional_plugins/html/setup.py (1)

2-6: LGTM: Minimal setup.py for pyproject.toml migration.

The bare setup() call correctly delegates all packaging configuration to pyproject.toml while maintaining backward compatibility during the transition period.

Note: The shebang issue on line 1 was already flagged in a previous review and marked as addressed.

optional_plugins/robot/avocado_robot/robot.py (1)

41-44: Compatibility fallback correct: the hasattr(test_case, "name") check cleanly handles legacy string names and new TestCase objects; no further conditional attribute access is needed.

examples/plugins/cli-cmd/hello_parser/setup.py (1)

1-6: LGTM! Clean transition shim.

The file correctly implements a minimal setup.py shim for backward compatibility during the pyproject.toml migration. The shebang is portable, and the comment clearly explains the transition strategy.

docs/source/guides/writer/chapters/subclassing.rst (2)

20-47: LGTM! Documentation correctly reflects modern packaging.

The pyproject.toml example demonstrates proper configuration with:

  • Standard build-system declaration
  • Dynamic version sourcing
  • Correct dependency specification

88-101: LGTM! Install output accurately reflects pip behavior.

The example output correctly shows the modern pip install workflow using pyproject.toml-based builds.

examples/plugins/tests/rogue/setup.py (1)

1-6: LGTM! Proper transition shim implementation.

The file correctly implements a minimal setup.py shim with a portable shebang and clear transition documentation.

docs/source/guides/contributor/chapters/plugins.rst (3)

97-101: LGTM! Documentation correctly updated for pyproject.toml.

The instructions now properly direct users to configure plugins in pyproject.toml instead of setup.py, and the install command correctly uses pip install -e . for development mode.


111-114: LGTM! Entry points syntax correctly updated.

The example correctly shows the TOML entry points syntax using [project.entry-points."avocado.plugins.job.prepost"] format.


170-173: LGTM! Settings plugin registration example is accurate.

The pyproject.toml entry point syntax for settings plugins is correct.

optional_plugins/result_upload/setup.py (1)

2-6: LGTM! Clean transition to pyproject.toml-based configuration.

The minimal setup() approach correctly maintains backward compatibility for egg builds while delegating primary configuration to pyproject.toml. The explanatory comments clearly document the transition strategy.

examples/plugins/job-pre-post/sleep/pyproject.toml (1)

1-17: LGTM! Standard plugin packaging configuration.

The pyproject.toml follows the established pattern for Avocado plugins with appropriate build system requirements, entry point declarations, and module packaging configuration.

examples/plugins/job-pre-post/sleep/setup.py (1)

1-6: LGTM! Proper transition approach with correct shebang.

The minimal setup() maintains egg build compatibility while the correct #!/usr/bin/env python3 shebang ensures portability.

examples/plugins/test-pre-post/hello/pyproject.toml (1)

1-17: LGTM! Consistent plugin configuration.

The pyproject.toml properly configures the hello plugin with entry points for both pre and post test hooks. The reuse of the same class for both hooks is appropriate for plugins that handle both phases.

Makefile (5)

9-10: Good addition of plugin development targets.

The new develop-plugins and develop-plugin targets provide convenient workflows for plugin development, consistent with the migration to pip-based installations.


64-67: Improved robustness with module-based pip invocations.

Using python -m pip instead of calling pip directly ensures the correct Python environment's pip is used, avoiding potential version mismatches in multi-Python environments.


99-104: Well-guarded plugin installation with clear error messaging.

The explicit check for the PLUGIN variable with a helpful usage message follows best practices for Makefile targets that require parameters.


109-114: Improved error handling for man page generation.

The explicit check for rst2man availability with a clear error message prevents silent failures during documentation builds.


80-80: Ensure selftests.check is invocable as a module

Verify that:

  • selftests/__init__.py exists so selftests.check is a package module.
  • check.py includes an if __name__ == "__main__": guard for -m execution.
  • the avocado-framework dependency is installed (e.g. added to test requirements/CI) so import selftests.check succeeds.
optional_plugins/README.rst (1)

10-34: LGTM! Clear documentation aligned with new pip-based workflow.

The updated instructions provide explicit examples for installing and removing plugins using pip, and correctly reference the new Makefile targets. The documentation now matches the implementation changes throughout the PR.

setup.py (1)

16-26: LGTM! Clear documentation of transition strategy.

The docstring effectively explains the backward compatibility purpose and explicitly notes the entry_points duplication for egg builds. This technical debt is appropriately documented.

optional_plugins/golang/pyproject.toml (1)

1-47: Approve Golang plugin pyproject.toml

Verified that VERSION and README.rst exist; configuration for dynamic version/readme loading, dependencies, entry points, and Python >=3.9 requirement is correct.

optional_plugins/resultsdb/pyproject.toml (1)

1-48: LGTM!

The pyproject.toml configuration for the resultsdb plugin is well-structured, with proper build-system declaration, project metadata, dependencies, and entry points. The impossible urllib3 constraint mentioned in previous reviews has been correctly removed.

optional_plugins/varianter_yaml_to_mux/README.rst (1)

1-818: LGTM!

The formatting changes appropriately replace Sphinx roles with inline code literals for better readability. These are purely documentation improvements with no functional impact.

python-avocado.spec (1)

243-245: No changes required for avocado_result_html exclusion
The plugin’s package directory matches the exclusion pattern %{python3_sitelib}/avocado_result_html*.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (14)
optional_plugins/golang/setup.py (1)

2-6: Same setup() regression as noted in result_upload

This file has the identical bare setup() change and will fail in the same way; please address it together with the fix in optional_plugins/result_upload/setup.py.

optional_plugins/mail/setup.py (1)

2-6: Same broken compatibility stub

The bare setup() here has the same failure mode as described for optional_plugins/result_upload/setup.py; please apply the same resolution.

optional_plugins/ansible/setup.py (1)

2-6: Same issue as in result_upload

This shim will also crash on python setup.py …; please fix alongside the earlier comment.

optional_plugins/varianter_cit/setup.py (1)

2-6: Same legacy-path regression

Repeats the bare setup() change that breaks the legacy workflow; please address alongside the fix for optional_plugins/result_upload/setup.py.

setup.py (1)

207-350: Keep entry_points in sync with pyproject.toml (avoid drift).

This duplicates pyproject.toml and is a maintenance risk. Ensure synchronization or generate these programmatically.

Run to compare groups and entries:

#!/bin/bash
# Compare entry point groups
echo "setup.py groups:"
rg -nP '^\s*"([^"]+)"\s*:\s*\[' -n setup.py | sed -n 's/.*"\([^"]*\)".*/\1/p' | sort -u
echo -e "\npyproject.toml groups:"
rg -nP '^\[project\.entry-points\."([^"]+)"\]' -n pyproject.toml | sed -n 's/.*"\([^"]*\)".*/\1/p' | sort -u

# Compare console_scripts entries (names only)
echo -e "\nconsole_scripts in setup.py:"
sed -n '/"console_scripts": \[/,/\],/p' setup.py | rg -nP '^\s*"([^"]+)\s*=' | sed -n 's/.*"\([^"]*\).*/\1/p' | sort
echo -e "\nconsole_scripts in pyproject.toml:"
sed -n '/^\[project\.scripts\]/,/^\[/p' pyproject.toml | rg -nP '^\s*([a-zA-Z0-9._-]+)\s*=' -n | sed -n 's/^\s*\([a-zA-Z0-9._-]\+\).*/\1/p' | sort
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (7)

208-216: Fix logging and include traceback on termination failure.

Use f-string and exception logging; return True in else block.

     async def terminate_task(self, runtime_task):
         session = runtime_task.spawner_handle
         session.sendcontrol("c")
         try:
             session.read_up_to_prompt()
-            return True
         except exceptions.ExpectTimeoutError:
-            LOG.error("Failed to terminate task on {session.host}")
-            return False
+            LOG.exception(f"Failed to terminate task on {session.host}:{session.port}")
+            return False
+        else:
+            return True

38-46: Register timeouts as integers (not strings).

Add key_type=int so values from config are parsed as ints.

 help_msg = "Test timeout enforced for remote host setup hook"
 settings.register_option(
-    section=section, key="setup_timeout", help_msg=help_msg, default=3600
+    section=section, key="setup_timeout", help_msg=help_msg, default=3600, key_type=int
 )
 ...
 help_msg = "Test timeout enforced for sessions (just for this spawner)"
 settings.register_option(
-    section=section, key="test_timeout", help_msg=help_msg, default=14400
+    section=section, key="test_timeout", help_msg=help_msg, default=14400, key_type=int
 )

106-115: Harden slot initialization (I/O, JSON, login errors).

Wrap file read/JSON/login with error handling to avoid crashing on a bad slot file.

-                with open(session_slot, "r", encoding="utf-8") as f:
-                    session_data = json.load(f)
-                session = remote.remote_login(**session_data)
-                RemoteSpawner.slots_cache[session] = False
+                try:
+                    with open(session_slot, "r", encoding="utf-8") as f:
+                        session_data = json.load(f)
+                    session = remote.remote_login(**session_data)
+                    RemoteSpawner.slots_cache[session] = False
+                except (FileNotFoundError, json.JSONDecodeError, KeyError) as e:
+                    LOG.warning(f"Failed to initialize slot from {session_slot}: {e}")
+                except Exception as e:
+                    LOG.error(f"Unexpected error initializing slot from {session_slot}: {e}")

136-146: Escape task identifier in shell command.

Prevent breakage/injection by quoting the identifier.

         session = runtime_task.spawner_handle
-        status, _ = session.cmd_status_output(
-            f"pgrep -r R,S -f {runtime_task.task.identifier}"
-        )
+        safe_identifier = shlex.quote(runtime_task.task.identifier)
+        status, _ = session.cmd_status_output(f"pgrep -r R,S -f {safe_identifier}")

182-193: Don’t discard output; spawn should be non-blocking.

  • Dropping all stdout/stderr to /dev/null loses results/logs.
  • run_remote_cmd_async waits for completion; spawn_task becomes synchronous.

Option A (preserve output, still blocking; minimal fix):

-        cmd = shlex.join(entry_point_args) + " > /dev/null"
+        cmd = shlex.join(entry_point_args)

Option B (proper async spawn; recommended):

-        cmd = shlex.join(entry_point_args) + " > /dev/null"
-        timeout = self.config.get("spawner.remote.test_timeout")
-        status, output = await RemoteSpawner.run_remote_cmd_async(session, cmd, timeout)
+        remote_log = f"/tmp/avocado-task-{runtime_task.task.identifier.replace('/', '_')}.log"
+        bg_cmd = shlex.join(entry_point_args) + f" > {shlex.quote(remote_log)} 2>&1 & echo $!"
+        status, output = await RemoteSpawner.run_remote_cmd_async(session, bg_cmd, 30)
+        if status == 0:
+            try:
+                runtime_task.spawner_pid = int(output.strip().splitlines()[-1])
+            except Exception:
+                LOG.error(f"Failed to parse remote PID from output: {output!r}")
+                return False
+        else:
+            LOG.error(f"Failed to spawn task on {session.host}:{session.port}:\n{output}")
+            return False
-        LOG.debug(f"Command exited with code {status}")
-        if status != 0:
-            LOG.error(
-                f"Error exit code {status} on {session.host}:{session.port} "
-                f"with output:\n{output}"
-            )
-            return False

Then update is_task_alive()/wait_task to check PID (e.g., kill -0) and stop polling when it exits.


195-201: Avoid fixed /tmp path; use a unique, safe per-task dir.

Fixed path risks collisions and leakage.

-        output_lxc_path = "/tmp/.avocado_task_output_dir"
+        safe_id = "".join(c if c.isalnum() or c in "._-" else "_" for c in runtime_task.task.identifier)
+        output_lxc_path = f"/tmp/avocado-task-{safe_id}"

Optionally ensure the remote dir exists before run.


202-206: Add a wait timeout to avoid infinite loops.

Break the poll if task never finishes.

-    async def wait_task(self, runtime_task):
-        while True:
-            if not RemoteSpawner.is_task_alive(runtime_task):
-                return
-            await asyncio.sleep(0.1)
+    async def wait_task(self, runtime_task):
+        timeout = self.config.get("spawner.remote.test_timeout")
+        start = asyncio.get_event_loop().time()
+        while True:
+            if not RemoteSpawner.is_task_alive(runtime_task):
+                return
+            if asyncio.get_event_loop().time() - start > timeout:
+                LOG.error(f"Task {runtime_task.task.identifier} exceeded wait timeout of {timeout}s")
+                return
+            await asyncio.sleep(0.1)
pyproject.toml (2)

14-16: Restore required runtime dependencies
Only setuptools is declared, but the core package still imports third-party modules such as psutil (see avocado/plugins/sysinfo.py), requests (see avocado/utils/asset.py) and jsonschema (see avocado/plugins/variants.py). Without listing them here, pip install avocado-framework succeeds but the first use of those modules raises ImportError. Please port the full install_requires list from the former setup.py (group optional pieces under extras if needed) so users receive the necessary dependencies on install.


29-30: Remove unsupported Python 3.14 classifier
PyPI rejects uploads that reference classifiers it does not recognize, and Programming Language :: Python :: 3.14 is not published yet. Drop it to keep releases uploadable.

🧹 Nitpick comments (4)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (2)

73-75: Annotate mutable class attributes with ClassVar.

Silences tooling and clarifies intent.

-from avocado.core.spawners.common import SpawnerMixin, SpawnMethod
+from avocado.core.spawners.common import SpawnerMixin, SpawnMethod
+from typing import ClassVar, Dict, Any
@@
-    METHODS = [SpawnMethod.STANDALONE_EXECUTABLE]
-    slots_cache = {}
+    METHODS: ClassVar[list] = [SpawnMethod.STANDALONE_EXECUTABLE]
+    slots_cache: ClassVar[Dict[Any, bool]] = {}

92-135: Guard slot reservation against races.

Concurrent async tasks can pick the same free slot. Consider protecting slots_cache with an asyncio.Lock or reserving atomically.

Would you like a patch that adds an asyncio.Lock and wraps selection inside it?

examples/plugins/tests/magic/pyproject.toml (1)

5-13: Make package discovery explicit and declare Python version.

  • Constrain discovery to avocado_magic* and declare minimum Python version (3.9+).

Apply:

 [project]
 name = "magic"
 version = "1.0"
 description = "Avocado \"magic\" test type"
+requires-python = ">=3.9"
@@
-[tool.setuptools.packages.find]
+[tool.setuptools.packages.find]
+include = ["avocado_magic*"]
optional_plugins/varianter_pict/pyproject.toml (1)

5-17: Add explicit Python version classifiers.

optional_plugins/varianter_pict/VERSION and README.rst are present.
Under classifiers, replace the existing Python entry with:

 classifiers = [
   "Development Status :: 5 - Production/Stable",
   "Intended Audience :: Developers",
   "Natural Language :: English",
   "Operating System :: POSIX",
   "Topic :: Software Development :: Quality Assurance",
   "Topic :: Software Development :: Testing",
-  "Programming Language :: Python :: 3",
+  "Programming Language :: Python :: 3",
+  "Programming Language :: Python :: 3.9",
+  "Programming Language :: Python :: 3.10",
+  "Programming Language :: Python :: 3.11",
+  "Programming Language :: Python :: 3.12",
 ]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 918a1d4 and c9b2b23.

📒 Files selected for processing (64)
  • .github/actions/version/action.yml (1 hunks)
  • .github/workflows/ci.yml (13 hunks)
  • .github/workflows/prerelease.yml (1 hunks)
  • .github/workflows/push_ci.yml (1 hunks)
  • .github/workflows/setup.yml (6 hunks)
  • .github/workflows/vmimage.yml (1 hunks)
  • .github/workflows/weekly.yml (2 hunks)
  • Makefile (5 hunks)
  • Makefile.gh (1 hunks)
  • docs/source/guides/contributor/chapters/environment.rst (1 hunks)
  • docs/source/guides/contributor/chapters/plugins.rst (4 hunks)
  • docs/source/guides/writer/chapters/subclassing.rst (2 hunks)
  • examples/plugins/README.rst (1 hunks)
  • examples/plugins/cli-cmd/hello/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_option/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_option/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/setup.py (1 hunks)
  • examples/plugins/job-pre-post/sleep/pyproject.toml (1 hunks)
  • examples/plugins/job-pre-post/sleep/setup.py (1 hunks)
  • examples/plugins/test-pre-post/hello/pyproject.toml (1 hunks)
  • examples/plugins/test-pre-post/hello/setup.py (1 hunks)
  • examples/plugins/tests/README.rst (1 hunks)
  • examples/plugins/tests/magic/pyproject.toml (1 hunks)
  • examples/plugins/tests/magic/setup.py (1 hunks)
  • examples/plugins/tests/rogue/pyproject.toml (1 hunks)
  • examples/plugins/tests/rogue/setup.py (1 hunks)
  • optional_plugins/README.rst (1 hunks)
  • optional_plugins/ansible/README.rst (1 hunks)
  • optional_plugins/ansible/pyproject.toml (1 hunks)
  • optional_plugins/ansible/setup.py (1 hunks)
  • optional_plugins/golang/pyproject.toml (1 hunks)
  • optional_plugins/golang/setup.py (1 hunks)
  • optional_plugins/html/pyproject.toml (1 hunks)
  • optional_plugins/html/setup.py (1 hunks)
  • optional_plugins/mail/pyproject.toml (1 hunks)
  • optional_plugins/mail/setup.py (1 hunks)
  • optional_plugins/result_upload/pyproject.toml (1 hunks)
  • optional_plugins/result_upload/setup.py (1 hunks)
  • optional_plugins/resultsdb/pyproject.toml (1 hunks)
  • optional_plugins/resultsdb/setup.py (1 hunks)
  • optional_plugins/robot/avocado_robot/robot.py (1 hunks)
  • optional_plugins/robot/pyproject.toml (1 hunks)
  • optional_plugins/robot/setup.py (1 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/__init__.py (0 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1 hunks)
  • optional_plugins/spawner_remote/pyproject.toml (1 hunks)
  • optional_plugins/spawner_remote/setup.py (1 hunks)
  • optional_plugins/varianter_cit/pyproject.toml (1 hunks)
  • optional_plugins/varianter_cit/setup.py (1 hunks)
  • optional_plugins/varianter_pict/README.rst (1 hunks)
  • optional_plugins/varianter_pict/pyproject.toml (1 hunks)
  • optional_plugins/varianter_pict/setup.py (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/README.rst (4 hunks)
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/setup.py (1 hunks)
  • pyproject.toml (1 hunks)
  • python-avocado.spec (6 hunks)
  • selftests/run_coverage (1 hunks)
  • setup.py (3 hunks)
  • spell.ignore (1 hunks)
💤 Files with no reviewable changes (1)
  • optional_plugins/spawner_remote/avocado_spawner_remote/init.py
✅ Files skipped from review due to trivial changes (3)
  • spell.ignore
  • optional_plugins/varianter_pict/README.rst
  • optional_plugins/varianter_yaml_to_mux/README.rst
🚧 Files skipped from review as they are similar to previous changes (25)
  • optional_plugins/varianter_cit/pyproject.toml
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml
  • .github/workflows/weekly.yml
  • optional_plugins/ansible/pyproject.toml
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml
  • examples/plugins/cli-cmd/hello_parser/setup.py
  • examples/plugins/job-pre-post/sleep/setup.py
  • optional_plugins/golang/pyproject.toml
  • selftests/run_coverage
  • optional_plugins/spawner_remote/setup.py
  • examples/plugins/job-pre-post/sleep/pyproject.toml
  • docs/source/guides/contributor/chapters/environment.rst
  • docs/source/guides/contributor/chapters/plugins.rst
  • examples/plugins/tests/README.rst
  • examples/plugins/tests/rogue/pyproject.toml
  • examples/plugins/README.rst
  • .github/workflows/push_ci.yml
  • optional_plugins/README.rst
  • examples/plugins/cli-cmd/hello_option/setup.py
  • optional_plugins/result_upload/pyproject.toml
  • .github/workflows/ci.yml
  • .github/workflows/prerelease.yml
  • optional_plugins/ansible/README.rst
  • optional_plugins/varianter_yaml_to_mux/setup.py
  • optional_plugins/mail/pyproject.toml
🧰 Additional context used
🧬 Code graph analysis (2)
optional_plugins/robot/avocado_robot/robot.py (1)
avocado/core/test.py (1)
  • name (367-373)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (5)
avocado/core/plugin_interfaces.py (2)
  • Init (23-28)
  • Spawner (321-441)
avocado/core/spawners/common.py (3)
  • SpawnerMixin (35-47)
  • SpawnMethod (6-18)
  • task_output_dir (46-47)
avocado/core/settings.py (3)
  • section (137-138)
  • register_option (642-799)
  • key (141-142)
avocado/core/nrunner/runnable.py (1)
  • pick_runner_module_from_entry_point_kind (630-643)
avocado/core/nrunner/task.py (1)
  • setup_output_dir (171-174)
🪛 checkmake (0.2.2)
Makefile

[warning] 124-124: Missing required phony target "all"

(minphony)


[warning] 124-124: Missing required phony target "test"

(minphony)

🪛 Ruff (0.14.0)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py

73-73: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


74-74: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


126-129: Avoid specifying long messages outside the exception class

(TRY003)


197-197: Probable insecure usage of temporary file or directory: "/tmp/.avocado_task_output_dir"

(S108)


213-213: Consider moving this statement to an else block

(TRY300)


215-215: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


219-219: Unused static method argument: runtime_task

(ARG004)


224-224: Unused static method argument: runtime_task

(ARG004)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (35)
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: Fedora selftests
  • GitHub Check: Version task ubuntu:22.04
  • GitHub Check: Smokecheck on Linux with Python 3.11
  • GitHub Check: macOS with Python 3.11
  • GitHub Check: Windows with Python 3.10
  • GitHub Check: Smokecheck on Linux with Python 3.10
  • GitHub Check: Static checks
  • GitHub Check: Code Coverage (3.11)
🔇 Additional comments (22)
.github/actions/version/action.yml (2)

9-9: Consider documenting the PIP_BREAK_SYSTEM_PACKAGES override.

Setting PIP_BREAK_SYSTEM_PACKAGES=1 bypasses PEP 668's externally-managed-environment protections. While this may be necessary in containerized CI environments, it can mask dependency conflicts or system-package interference.

Consider adding a comment explaining why this override is required, or verify whether the CI container is sufficiently isolated to make this safe.


10-11: LGTM!

The migration from setup.py develop to pip install -e . follows modern Python packaging best practices.

.github/workflows/vmimage.yml (1)

42-42: LGTM!

The migration to pip install -e . modernizes the workflow and aligns with the project-wide transition away from direct setup.py invocations.

examples/plugins/cli-cmd/hello/setup.py (1)

1-6: LGTM!

The minimal shim approach with a clear compatibility comment is an appropriate transition strategy during the migration to pyproject.toml-based packaging.

optional_plugins/varianter_pict/setup.py (1)

1-6: LGTM!

Consistent with the project-wide migration strategy: minimal setup.py shim for backward compatibility with configuration sourced from pyproject.toml.

optional_plugins/spawner_remote/pyproject.toml (1)

1-45: LGTM! Entry points corrected per previous review.

The pyproject.toml configuration is complete, and the entry point paths now correctly reference the .spawner module as resolved in the previous review. The dependency on aexpect>=1.6.2 is appropriate given that the latest version is 1.8.0.

Based on learnings about aexpect, version 1.6.2 is a reasonable minimum given the library's stable API and the availability of newer versions (up to 1.8.0).

examples/plugins/tests/rogue/setup.py (1)

1-6: LGTM!

Consistent minimal shim pattern for backward compatibility during the migration to pyproject.toml-based packaging.

optional_plugins/varianter_yaml_to_mux/pyproject.toml (1)

1-48: Approve pyproject.toml configuration Verified that the required VERSION and README.rst files exist.

Makefile.gh (1)

65-69: Unconditional build safe: all plugins have pyproject.toml
Verified that each directory in AVOCADO_OPTIONAL_PLUGINS contains a pyproject.toml, so removing the setup.py check is safe.

examples/plugins/tests/magic/setup.py (1)

1-6: LGTM: minimal back-compat shim.

Delegates config to pyproject.toml as intended.

examples/plugins/cli-cmd/hello_priority/setup.py (1)

1-6: LGTM: minimal back-compat shim.

setup() defers to pyproject.toml as intended.

python-avocado.spec (3)

8-9: LGTM: tarball macro and %setup -n updates.

Names now match avocado_framework-*. No further issues spotted.

Also applies to: 100-105


220-220: LGTM: switch to module-based selftests invocation.

%{python3} -m selftests.check aligns with upstream changes.


243-245: Exclusion paths are correct. The patterns %exclude %{python3_sitelib}/contrib* and %exclude %{python3_sitelib}/scripts* only apply to top-level packages and will not exclude avocado/contrib or avocado/scripts. No changes needed.

optional_plugins/robot/avocado_robot/robot.py (1)

41-44: LGTM! Backward-compatible API handling.

The conditional logic correctly handles both the old string-based API and the new TestCase object API by checking for the presence of the name attribute. This maintains backward compatibility while supporting newer versions of the Robot Framework API.

optional_plugins/html/setup.py (1)

1-6: LGTM! Standard pyproject.toml migration.

The file correctly transitions to a minimal setup.py shim, with all packaging configuration now handled by pyproject.toml. This aligns with modern Python packaging practices (PEP 517/518/621).

examples/plugins/cli-cmd/hello/pyproject.toml (1)

1-14: LGTM! Well-structured example plugin packaging.

The pyproject.toml configuration is clean and follows modern packaging standards:

  • Proper build-system declaration with setuptools>=61.0
  • Clear project metadata for the example plugin
  • Correct entry point registration for the CLI command
  • Appropriate py-modules declaration for single-module packaging
optional_plugins/resultsdb/setup.py (1)

1-6: LGTM! Standard pyproject.toml migration.

The file correctly transitions to a minimal setup.py shim following modern Python packaging practices. All packaging configuration is now handled by pyproject.toml, consistent with the project-wide migration.

examples/plugins/test-pre-post/hello/setup.py (1)

1-6: LGTM! Clean pyproject.toml migration.

The file correctly migrates to a minimal setup.py shim with proper portable shebang (/usr/bin/env). The transition to pyproject.toml-based configuration is well-executed and consistent with the project-wide modernization.

docs/source/guides/writer/chapters/subclassing.rst (1)

20-101: LGTM! Comprehensive documentation update.

The documentation successfully modernizes the subclassing example:

  • Clear migration from setup.py to pyproject.toml
  • Correct PEP 621-compliant project structure
  • Proper build-system configuration with setuptools>=61.0
  • Well-structured project metadata with dynamic version loading
  • Updated installation commands and realistic output examples
  • Maintains clear pedagogical flow for developers creating Avocado sub-frameworks
optional_plugins/robot/setup.py (1)

1-6: LGTM! Consistent pyproject.toml migration.

The file correctly transitions to a minimal setup.py shim, matching the pattern applied across other optional plugins. All packaging configuration is now centralized in pyproject.toml, aligning with modern Python packaging standards.

examples/plugins/cli-cmd/hello_option/pyproject.toml (1)

1-14: LGTM! Well-structured example plugin packaging.

The pyproject.toml configuration is clean and follows modern packaging standards:

  • Proper build-system declaration with setuptools>=61.0
  • Clear project metadata for the CLI option example
  • Correct entry point registration for the hello_option command
  • Appropriate py-modules declaration for single-module packaging

The configuration is consistent with the companion hello plugin example, which is good for educational purposes.

Comment on lines +238 to +243
[tool.setuptools.packages.find]
exclude = ["selftests*", "docs*", "examples*", "contrib*", "scripts*", "optional_plugins*"]

[tool.setuptools.dynamic]
version = {file = "VERSION"}
readme = {file = "README.rst", content-type = "text/x-rst"}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Do not exclude packages required by console scripts
[project.scripts] registers avocado-external-runner = "scripts.external_runner:main", but [tool.setuptools.packages.find] excludes scripts*, so that module never ships in the wheel/sdist and the console script fails at runtime. Remove scripts* from the exclusion list (or package the module another way) to keep the entry point importable.

🤖 Prompt for AI Agents
In pyproject.toml around lines 238 to 243, the packages.find exclude list
currently contains "scripts*", which prevents the scripts.external_runner module
(required by the avocato-external-runner console script entry point) from being
packaged; remove "scripts*" from the exclude array so the scripts package is
included in sdist/wheel, or alternatively move/register the external_runner
module inside a packaged module and update the entry point to that import path.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
python-avocado.spec (1)

243-250: Exclusion patterns need verification.

The additions of contrib* and scripts* exclusions (lines 243-244) and the conditional exclusion blocks (lines 246-250) were flagged in a previous review. The concern about avocado_result_html* path correctness remains unresolved.

Verify that these exclusion patterns match the actual installed package structure after the pyproject.toml migration:

#!/bin/bash
# Verify exclusion patterns exist in the expected locations

# Check for contrib and scripts in installed layout
fd -t d '^(contrib|scripts)$' --max-depth 3

# Check actual location of avocado_result_html after installation
# (may be in optional_plugins/html/ subdirectory)
fd -t d 'avocado_result_html' --max-depth 4
optional_plugins/robot/pyproject.toml (1)

14-16: Verify robotframework version constraint.

Line 16 declares "robotframework" as a dependency without version constraints. A previous review flagged that the constraint <7.0 was too restrictive given that robotframework 7.3.2 is available. However, the current code shows no version constraint at all.

Confirm whether an upper version bound is needed for robotframework compatibility:

Is Robot Framework 7.x compatible with Python 3.9+, and should the avocado-framework-plugin-robot declare a version constraint?
🧹 Nitpick comments (8)
selftests/utils.py (1)

27-32: Approve the migration; consider narrowing exception handling.

The migration from pkg_resources.require() to distribution() is correct and aligns with modern Python packaging standards. However, the exception handling is overly broad.

Consider catching the specific exception instead of all exceptions:

     try:
         distribution(module_name)
         return True
-    except Exception:
-        # Catch all exceptions (PackageNotFoundError, etc.)
+    except PackageNotFoundError:
         return False

If you need to handle the exception without importing it explicitly, you can catch it as:

+from importlib.metadata import distribution, PackageNotFoundError

This makes the intent clearer and follows the principle of catching specific exceptions rather than using blind exception handling, as flagged by the static analysis tool.

Based on learnings

examples/plugins/cli-cmd/hello/setup.py (1)

6-10: Consider removing hardcoded metadata to eliminate duplication.

The metadata (name, version, description) is duplicated between this file and pyproject.toml, creating a maintenance burden and risk of inconsistencies. The cleanest transition pattern is to use a bare setup() call (as seen in examples/plugins/tests/rogue/setup.py), allowing pyproject.toml to be the single source of truth for all packaging metadata.

Apply this diff to follow the cleanest pattern:

-setup(
-    name='avocado-hello-world-option',
-    version='1.0',
-    description='Avocado Hello World CLI command with config option',
-)
+setup()
examples/plugins/cli-cmd/hello_parser/setup.py (1)

6-10: Consider removing hardcoded metadata to eliminate duplication.

The metadata is duplicated between this file and pyproject.toml. Following the pattern in examples/plugins/tests/rogue/setup.py with a bare setup() call would eliminate this duplication and make pyproject.toml the single source of truth.

Apply this diff:

-setup(
-    name='avocado-hello-world-parser',
-    version='1.0',
-    description='Avocado Hello World CLI command with config parser',
-)
+setup()
optional_plugins/varianter_pict/setup.py (1)

11-15: Consider removing hardcoded metadata to eliminate duplication.

While the version is correctly read from the VERSION file, the name and description remain hardcoded, creating duplication with pyproject.toml. For consistency with the cleanest transition pattern (as in examples/plugins/tests/rogue/setup.py), consider using a bare setup() call.

Apply this diff to eliminate duplication:

-setup(
-    name='avocado-framework-plugin-varianter-pict',
-    version=VERSION,
-    description='Varianter with combinatorial capabilities by PICT',
-)
+setup()

This change would also allow you to remove the VERSION file reading logic (lines 7-9) as pyproject.toml can handle dynamic version sourcing.

optional_plugins/ansible/setup.py (1)

11-15: Consider removing hardcoded metadata to eliminate duplication.

The name and description are duplicated between this file and pyproject.toml. Following the pattern in examples/plugins/tests/rogue/setup.py with a bare setup() call would eliminate this duplication and simplify the transition.

Apply this diff:

-setup(
-    name='avocado-framework-plugin-ansible',
-    version=VERSION,
-    description='Adds to Avocado the ability to use ansible modules as dependencies for tests',
-)
+setup()

This would also allow removing the VERSION file reading logic (lines 4-9).

optional_plugins/html/setup.py (1)

11-15: Consider removing hardcoded metadata to eliminate duplication.

The name and description are duplicated between this file and pyproject.toml. Using a bare setup() call (as demonstrated in examples/plugins/tests/rogue/setup.py) would eliminate this duplication and align with the cleanest transition pattern.

Apply this diff:

-setup(
-    name='avocado-framework-plugin-result-html',
-    version=VERSION,
-    description='Avocado HTML Report for Jobs',
-)
+setup()

This would also allow removing the VERSION file reading logic (lines 4-9).

optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (2)

73-74: Annotate mutable class attributes with ClassVar.

The METHODS and slots_cache class attributes are mutable and should be annotated with typing.ClassVar for clarity and to satisfy static analysis tools.

Apply this diff:

+from typing import ClassVar
+
 class RemoteSpawner(Spawner, SpawnerMixin):
 
     description = "Remote (host) based spawner"
-    METHODS = [SpawnMethod.STANDALONE_EXECUTABLE]
-    slots_cache = {}
+    METHODS: ClassVar[list] = [SpawnMethod.STANDALONE_EXECUTABLE]
+    slots_cache: ClassVar[dict] = {}

219-233: Stub methods have unused parameters; consider documenting.

The stub implementations for requirement management methods (check_task_requirements, is_requirement_in_cache, save_requirement_in_cache, update_requirement_cache) have unused runtime_task and result parameters. If these are intentionally not implemented, add docstrings explaining that these methods are stubs.

Example for one method:

     @staticmethod
     async def check_task_requirements(runtime_task):
-        """Check the runtime task requirements needed to be able to run"""
+        """Check the runtime task requirements needed to be able to run.
+        
+        This is a stub implementation. Remote spawner does not currently
+        validate requirements.
+        """
         return True
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c9b2b23 and aa51f15.

📒 Files selected for processing (65)
  • .github/actions/version/action.yml (1 hunks)
  • .github/workflows/ci.yml (13 hunks)
  • .github/workflows/prerelease.yml (1 hunks)
  • .github/workflows/push_ci.yml (1 hunks)
  • .github/workflows/setup.yml (6 hunks)
  • .github/workflows/vmimage.yml (1 hunks)
  • .github/workflows/weekly.yml (2 hunks)
  • Makefile (5 hunks)
  • Makefile.gh (1 hunks)
  • docs/source/guides/contributor/chapters/environment.rst (1 hunks)
  • docs/source/guides/contributor/chapters/plugins.rst (4 hunks)
  • docs/source/guides/writer/chapters/subclassing.rst (2 hunks)
  • examples/plugins/README.rst (1 hunks)
  • examples/plugins/cli-cmd/hello/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_option/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_option/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/setup.py (1 hunks)
  • examples/plugins/job-pre-post/sleep/pyproject.toml (1 hunks)
  • examples/plugins/job-pre-post/sleep/setup.py (1 hunks)
  • examples/plugins/test-pre-post/hello/pyproject.toml (1 hunks)
  • examples/plugins/test-pre-post/hello/setup.py (1 hunks)
  • examples/plugins/tests/README.rst (1 hunks)
  • examples/plugins/tests/magic/pyproject.toml (1 hunks)
  • examples/plugins/tests/magic/setup.py (1 hunks)
  • examples/plugins/tests/rogue/pyproject.toml (1 hunks)
  • examples/plugins/tests/rogue/setup.py (1 hunks)
  • optional_plugins/README.rst (1 hunks)
  • optional_plugins/ansible/README.rst (1 hunks)
  • optional_plugins/ansible/pyproject.toml (1 hunks)
  • optional_plugins/ansible/setup.py (1 hunks)
  • optional_plugins/golang/pyproject.toml (1 hunks)
  • optional_plugins/golang/setup.py (1 hunks)
  • optional_plugins/html/pyproject.toml (1 hunks)
  • optional_plugins/html/setup.py (1 hunks)
  • optional_plugins/mail/pyproject.toml (1 hunks)
  • optional_plugins/mail/setup.py (1 hunks)
  • optional_plugins/result_upload/pyproject.toml (1 hunks)
  • optional_plugins/result_upload/setup.py (1 hunks)
  • optional_plugins/resultsdb/pyproject.toml (1 hunks)
  • optional_plugins/resultsdb/setup.py (1 hunks)
  • optional_plugins/robot/avocado_robot/robot.py (1 hunks)
  • optional_plugins/robot/pyproject.toml (1 hunks)
  • optional_plugins/robot/setup.py (1 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/__init__.py (0 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1 hunks)
  • optional_plugins/spawner_remote/pyproject.toml (1 hunks)
  • optional_plugins/spawner_remote/setup.py (1 hunks)
  • optional_plugins/varianter_cit/pyproject.toml (1 hunks)
  • optional_plugins/varianter_cit/setup.py (1 hunks)
  • optional_plugins/varianter_pict/README.rst (1 hunks)
  • optional_plugins/varianter_pict/pyproject.toml (1 hunks)
  • optional_plugins/varianter_pict/setup.py (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/README.rst (4 hunks)
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/setup.py (1 hunks)
  • pyproject.toml (1 hunks)
  • python-avocado.spec (6 hunks)
  • selftests/run_coverage (1 hunks)
  • selftests/utils.py (2 hunks)
  • setup.py (3 hunks)
  • spell.ignore (1 hunks)
💤 Files with no reviewable changes (1)
  • optional_plugins/spawner_remote/avocado_spawner_remote/init.py
✅ Files skipped from review due to trivial changes (1)
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml
🚧 Files skipped from review as they are similar to previous changes (22)
  • optional_plugins/ansible/README.rst
  • .github/workflows/weekly.yml
  • optional_plugins/varianter_pict/pyproject.toml
  • optional_plugins/mail/pyproject.toml
  • examples/plugins/README.rst
  • .github/workflows/prerelease.yml
  • optional_plugins/varianter_pict/README.rst
  • examples/plugins/tests/rogue/pyproject.toml
  • examples/plugins/test-pre-post/hello/pyproject.toml
  • optional_plugins/varianter_cit/pyproject.toml
  • optional_plugins/golang/pyproject.toml
  • examples/plugins/tests/README.rst
  • docs/source/guides/contributor/chapters/environment.rst
  • examples/plugins/job-pre-post/sleep/setup.py
  • examples/plugins/cli-cmd/hello/pyproject.toml
  • optional_plugins/result_upload/pyproject.toml
  • .github/workflows/vmimage.yml
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml
  • examples/plugins/tests/magic/pyproject.toml
  • docs/source/guides/writer/chapters/subclassing.rst
  • pyproject.toml
  • .github/workflows/push_ci.yml
🧰 Additional context used
🧬 Code graph analysis (2)
optional_plugins/robot/avocado_robot/robot.py (1)
avocado/core/test.py (1)
  • name (367-373)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (5)
avocado/core/plugin_interfaces.py (2)
  • Init (23-28)
  • Spawner (321-441)
avocado/core/spawners/common.py (3)
  • SpawnerMixin (35-47)
  • SpawnMethod (6-18)
  • task_output_dir (46-47)
avocado/core/settings.py (3)
  • section (137-138)
  • register_option (642-799)
  • key (141-142)
avocado/core/nrunner/runnable.py (1)
  • pick_runner_module_from_entry_point_kind (630-643)
avocado/core/nrunner/task.py (1)
  • setup_output_dir (171-174)
🪛 checkmake (0.2.2)
Makefile

[warning] 124-124: Missing required phony target "all"

(minphony)


[warning] 124-124: Missing required phony target "test"

(minphony)

🪛 Ruff (0.14.0)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py

73-73: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


74-74: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


126-129: Avoid specifying long messages outside the exception class

(TRY003)


197-197: Probable insecure usage of temporary file or directory: "/tmp/.avocado_task_output_dir"

(S108)


213-213: Consider moving this statement to an else block

(TRY300)


215-215: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


219-219: Unused static method argument: runtime_task

(ARG004)


224-224: Unused static method argument: runtime_task

(ARG004)

selftests/utils.py

29-29: Consider moving this statement to an else block

(TRY300)


30-30: Do not catch blind exception: Exception

(BLE001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (42)
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: Fedora selftests
  • GitHub Check: Smokecheck on Linux with Python 3.10
  • GitHub Check: Smokecheck on Linux with Python 3.11
  • GitHub Check: macOS with Python 3.11
  • GitHub Check: Static checks
  • GitHub Check: Code Coverage (3.11)
🔇 Additional comments (44)
spell.ignore (1)

806-807: LGTM! Necessary additions for pyproject.toml migration.

These ignore entries are appropriate and necessary given the project-wide migration to pyproject.toml configuration files. They will prevent false positives during spell checks.

selftests/utils.py (1)

6-6: LGTM! Good modernization.

Migrating from pkg_resources to importlib.metadata is the correct approach for modern Python projects, especially when moving away from setup.py-centric tooling.

Based on learnings

selftests/run_coverage (1)

30-30: LGTM: Module-based test invocation.

The change from direct script execution to module-based invocation (-m selftests.check) aligns with Python best practices and the broader migration pattern across the repository. Coverage tracking will function correctly with this approach.

examples/plugins/test-pre-post/hello/setup.py (1)

1-6: LGTM: Standard pyproject.toml migration pattern.

The minimal setup.py shim preserves backward compatibility for legacy tools while moving configuration to pyproject.toml. The explanatory comment clearly documents the transition purpose.

examples/plugins/tests/magic/setup.py (1)

1-6: LGTM: Consistent migration pattern.

Matches the established pattern for migrating to pyproject.toml-based configuration while maintaining backward compatibility.

examples/plugins/cli-cmd/hello_priority/setup.py (1)

1-6: LGTM: Consistent migration pattern.

Follows the established pattern for pyproject.toml migration with backward-compatible shim.

optional_plugins/robot/avocado_robot/robot.py (1)

41-44: LGTM: Proper API compatibility handling.

The code correctly handles both the old Robot Framework API (where test_case is a string) and the new API (where test_case is a TestCase object). The hasattr check with fallback ensures backward compatibility while supporting newer versions.

.github/actions/version/action.yml (1)

9-11: Verify necessity of PIP_BREAK_SYSTEM_PACKAGES.

This flag bypasses pip’s protection against modifying system-managed installations and isn’t used elsewhere. Confirm the CI runner is fully isolated and that this override is strictly required; if not, remove it or configure pip to install in user space without this flag.

optional_plugins/spawner_remote/pyproject.toml (1)

1-45: LGTM! Configuration follows modern packaging standards.

The pyproject.toml configuration is well-structured with appropriate build system requirements, project metadata, and entry points. The dependency on aexpect>=1.6.2 is reasonable given that the latest version is 1.8.0, and the dynamic version/readme sourcing pattern aligns with the repository's standardized approach.

examples/plugins/tests/rogue/setup.py (1)

1-6: LGTM! This is the cleanest transition pattern.

This file demonstrates the recommended approach for the setup.py transition: a bare setup() call that delegates all configuration to pyproject.toml, eliminating duplication and making pyproject.toml the single source of truth.

docs/source/guides/contributor/chapters/plugins.rst (4)

97-101: LGTM! Documentation accurately reflects the migration.

The documentation has been properly updated to reference pyproject.toml instead of setup.py for plugin registration, and the installation command correctly uses pip install -e . for development mode installation.


111-114: LGTM! Entry-point syntax updated to TOML format.

The entry-point declaration examples now correctly use the TOML syntax with [project.entry-points."<namespace>"] format, aligning with the modern pyproject.toml-based packaging approach.


170-173: LGTM! Settings plugin registration example updated.

The settings plugin registration example now uses TOML format with proper [project.entry-points."avocado.plugins.settings"] syntax, consistent with the migration to pyproject.toml-based configuration.


325-327: LGTM! Test type plugin example updated.

The test type plugin activation example has been correctly updated to reference pyproject.toml and use pip install -e . for installation, maintaining consistency with the documentation updates throughout the file.

examples/plugins/cli-cmd/hello_option/pyproject.toml (1)

1-14: LGTM! Example plugin packaging configuration is correct.

The pyproject.toml follows the correct pattern for an Avocado CLI plugin example:

  • Build system properly configured with setuptools>=61.0
  • Project metadata is minimal but complete for an example plugin
  • Entry point correctly registered under avocado.plugins.cli.cmd
  • Single-module packaging is appropriate for this simple example
optional_plugins/varianter_cit/setup.py (1)

2-15: LGTM! Minimal setup.py shim follows correct transition pattern.

The reduced setup.py correctly serves as a compatibility shim during the migration:

  • Clear transition comment indicates pyproject.toml is primary
  • VERSION file reading uses proper encoding
  • Minimal setup() with only essential fields (name, version, description)
  • Full metadata now lives in pyproject.toml per the PR's migration strategy

This pattern aligns with the project-wide shift to pyproject.toml-based packaging while maintaining backward compatibility.

examples/plugins/job-pre-post/sleep/pyproject.toml (1)

1-17: LGTM! Job pre/post plugin packaging is correctly configured.

The pyproject.toml properly defines the sleep plugin:

  • Standard build system with setuptools>=61.0
  • Both required entry points registered: avocado.plugins.init for SleepInit and avocado.plugins.job.prepost for Sleep
  • Single-module packaging appropriate for this example plugin
  • Follows consistent pattern with other example plugin configurations in the PR
optional_plugins/mail/setup.py (1)

2-15: LGTM! Mail plugin setup.py correctly minimized for transition.

The setup.py follows the correct migration pattern:

  • Transition comment clearly indicates pyproject.toml is the primary configuration
  • VERSION file reading with proper encoding
  • Minimal setup() call with essential fields only
  • Full packaging metadata (dependencies, entry points for notification plugins) now defined in pyproject.toml

Consistent with the project-wide pyproject.toml migration strategy.

python-avocado.spec (3)

8-18: LGTM! Tarball naming updated for pyproject.toml migration.

The tarball name changes from avocado-%{version} to avocado_framework-%{version} (lines 8, 18) correctly align with the standardized package naming convention established by the pyproject.toml migration.


102-104: LGTM! %setup invocations match updated tarball naming.

The %setup -q -n directives correctly updated to use avocado_framework-%{version} and avocado_framework-%{commit}, matching the new tarball naming convention.


220-220: LGTM! Module-based test invocation is correct.

The change from %{python3} selftests/check.py to %{python3} -m selftests.check follows best practices for modern Python packaging and aligns with the pyproject.toml migration strategy.

optional_plugins/result_upload/setup.py (1)

2-15: Setup.py shim is correctly configured.

The minimal setup.py provides the necessary fields (name, version, description) to avoid ValueError: Missing project name. The transition comment clearly indicates that pyproject.toml is the primary configuration source.

The past review comment about a missing project name appears to be based on an earlier version of this file. The current code correctly includes all essential fields in the setup() call.

optional_plugins/ansible/pyproject.toml (1)

1-47: LGTM! Ansible plugin packaging is comprehensive and correct.

The pyproject.toml provides complete packaging configuration:

  • Standard build system with setuptools>=61.0
  • Dynamic version/readme loading from VERSION and README.rst files
  • All necessary dependencies declared (avocado-framework, cffi, pycparser, ansible-core)
  • Console script and plugin entry point correctly registered
  • Comprehensive metadata including classifiers, license, and Python version requirement (>=3.9)
  • Package discovery properly configured for avocado_ansible* modules

Follows the consistent pattern established across optional plugins in this PR.

optional_plugins/robot/pyproject.toml (1)

1-48: LGTM! Robot plugin packaging is well-configured.

The pyproject.toml provides comprehensive packaging configuration:

  • Standard build system with setuptools>=61.0
  • Dynamic version/readme loading from VERSION and README.rst
  • Required dependencies declared (avocado-framework, robotframework)
  • Console script for the robot runner
  • Both required entry points registered: runner and resolver plugins
  • Package discovery properly configured for avocado_robot* modules
  • Comprehensive metadata with appropriate classifiers

Follows the consistent pattern across optional plugins in this PR.

optional_plugins/robot/setup.py (1)

2-15: LGTM! Transitional setup.py is correctly minimal.

The file correctly serves as a compatibility shim during the migration period, with primary packaging configuration delegated to pyproject.toml. The version reading is straightforward and appropriate.

optional_plugins/html/pyproject.toml (1)

1-48: LGTM! PyProject configuration is well-structured.

The pyproject.toml follows best practices with:

  • Modern build system (setuptools ≥61.0)
  • Dynamic version/readme sourcing
  • Proper entry points for all plugin interfaces
  • Appropriate Python version requirement (≥3.9)
optional_plugins/varianter_yaml_to_mux/README.rst (1)

6-806: LGTM! Documentation formatting improvements.

The changes consistently apply inline code formatting using backticks for module/class references, improving readability and adherence to reStructuredText best practices.

optional_plugins/varianter_yaml_to_mux/pyproject.toml (1)

1-48: LGTM! PyProject configuration is correct.

The configuration properly:

  • Defines build system and metadata
  • Specifies PyYAML dependency with minimum version
  • Maps entry points for init, cli, and varianter interfaces
  • Uses dynamic version/readme sourcing
  • Configures package discovery appropriately
optional_plugins/resultsdb/setup.py (1)

2-15: LGTM! Transitional setup.py is appropriately minimal.

The file serves as a backward-compatibility shim during migration, correctly delegating packaging configuration to pyproject.toml while maintaining simple version reading.

optional_plugins/varianter_yaml_to_mux/setup.py (1)

2-15: LGTM! Transitional setup.py follows the correct pattern.

The file appropriately serves as a compatibility shim, with primary configuration moved to pyproject.toml. The implementation is consistent with other optional plugins in this PR.

optional_plugins/README.rst (1)

10-34: LGTM! Documentation correctly reflects the new workflow.

The updated instructions accurately document the pip-based development workflow and the new Makefile targets, providing clear examples for installing, uninstalling, and enabling plugins. The changes align well with the project-wide migration to pyproject.toml.

optional_plugins/golang/setup.py (1)

2-15: LGTM! Clean minimal setup for backward compatibility.

The minimal setup.py approach is correct for the transition period. The file properly:

  • Documents its transitional purpose
  • Reads VERSION with correct utf-8 encoding
  • Provides only essential metadata for egg builds

This aligns well with the pyproject.toml-based configuration in optional_plugins/golang/pyproject.toml.

.github/workflows/setup.yml (2)

33-50: LGTM! Consistent pip-based installation workflow.

The user installation workflow correctly migrates from setup.py to pip-based commands:

  • Editable install with python3 -m pip install --user -e .
  • Plugin installations with explicit paths
  • Proper use of --user flag for user-level installs

This aligns with the broader migration pattern seen across all workflow jobs.


121-125: LGTM! Test execution migrated to module-based invocation.

The changes correctly:

  • Use make man for manpage generation
  • Execute tests via python3 -m selftests.check
  • Uninstall via python3 -m pip uninstall

This modern approach is consistent with the pyproject.toml migration.

optional_plugins/spawner_remote/setup.py (1)

2-15: LGTM! Consistent minimal setup pattern.

This file follows the same clean minimal setup pattern as other optional plugins, correctly providing backward compatibility for egg builds while delegating primary configuration to pyproject.toml.

Makefile (3)

91-104: LGTM! Useful plugin development targets.

The new develop-plugins and develop-plugin targets provide convenient workflows for plugin development:

  • develop-plugins installs all optional plugins in one command
  • develop-plugin with PLUGIN parameter allows selective installation
  • Both use pip-based editable installs consistent with the migration

The error guard for missing PLUGIN variable is good practice.


34-61: LGTM! Comprehensive cleanup.

The enhanced clean target is thorough and well-structured:

  • Clears build artifacts, docs, caches
  • Iterates through optional_plugins and example plugins
  • Includes verbose progress messages
  • Handles errors gracefully with || true

This provides a much better cleanup experience for developers.


124-124: Note: Static analysis false positives.

The checkmake warnings about missing "all" and "test" in PHONY are false positives:

  • The "all" target at line 1 is informational (displays help) and doesn't need PHONY
  • The "test" target is not used; "check" is the standard test target for this project

No action needed.

setup.py (2)

16-26: LGTM! Clear documentation of transitional approach.

The module docstring effectively communicates:

  • Purpose of retaining setup.py (backward compatibility)
  • Migration to pyproject.toml
  • Egg build support rationale
  • Acknowledgment of entry_points duplication

This transparency helps maintainers understand the transitional state.


207-350: Track removal of egg‐build support and verify external_runner entry point

  • Keep the explicit entry_points in setup.py for now; plan a follow-up task to remove egg-build support and this file once the migration is stable.
  • Instead of a direct import in the source tree (which fails without an installed distribution), install in editable mode (pip install -e .) and confirm that the avocado-external-runner console script successfully loads scripts.external_runner:main.
.github/workflows/ci.yml (3)

32-32: LGTM! Test execution migrated correctly.

Static checks now use python3 -m selftests.check --select=static-checks, which is consistent with the module-based execution pattern adopted throughout the PR.


89-99: LGTM! Correct Python version display and test execution.

The workflow properly:

  • Uses python -VV for verbose version output (correct flag usage)
  • Installs dependencies via python3 -m pip
  • Executes tests via python3 -m selftests.check --skip=static-checks

This resolves the past review concerns about flag usage.


167-171: LGTM! Windows workflow uses correct pip invocation.

The Windows workflow correctly uses python -m pip install --user -e ., which resolves the past review issue about the erroneous python -m python3 -m pip invocation.

optional_plugins/resultsdb/pyproject.toml (1)

1-48: LGTM! Clean pyproject.toml configuration.

The resultsdb plugin packaging configuration is well-structured:

  • Modern build system with setuptools>=61.0
  • Dynamic version/readme sourcing from VERSION/README.rst
  • Clean dependency declarations (the impossible urllib3 constraint from past reviews has been correctly removed)
  • Proper entry points for all plugin interfaces (cli, result_events, result)
  • Appropriate Python version requirement (>=3.9)

This configuration follows the standard pattern established across other optional plugins in this migration.

Comment on lines +66 to +68
cd $$PLUGIN;\
$(PYTHON) -m build -o ../../PYPI_UPLOAD;\
cd -;\
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify build behavior for incomplete plugin migrations.

The removal of the setup.py existence check means the build will be attempted for all plugin directories unconditionally. This could fail if any plugins haven't been migrated to pyproject.toml yet.

Additionally, note the inconsistency: the build-egg target (lines 78-83) still has the conditional check for setup.py, while build-wheel does not.


🏁 Script executed:

#!/bin/bash
# Verify all optional plugins have pyproject.toml files
echo "Checking for pyproject.toml in all optional plugins:"
for plugin in optional_plugins/*/; do
  if [ ! -f "${plugin}pyproject.toml" ]; then
    echo "Missing: ${plugin}pyproject.toml"
  fi
done

# Check if any plugins still have substantial setup.py files (not just shims)
echo -e "\nChecking for non-shim setup.py files:"
fd setup.py optional_plugins/ --exec sh -c 'lines=$(wc -l < "$1"); if [ $lines -gt 10 ]; then echo "$1 has $lines lines"; fi' sh {}

Length of output: 641


Add a guard for pyproject.toml in the build-wheel target. Unconditionally running python -m build in optional_plugins/* will fail since none include pyproject.toml; mirror the conditional logic in build-egg.

🤖 Prompt for AI Agents
In Makefile.gh around lines 66 to 68, the build-wheel step runs `python -m
build` unconditionally which fails for optional_plugins that lack
pyproject.toml; add the same file-existence guard used in the build-egg target
so the wheel build only runs when pyproject.toml exists (wrap the cd/$(PYTHON)
-m build/ cd - sequence in a conditional like `if [ -f pyproject.toml ]; then
... fi` or the Makefile-equivalent test).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
python-avocado.spec (1)

8-18: Revert the tarball/dir rename to keep RPM build functional.

GitHub still publishes archives as avocado-<tag>.tar.gz unpacking to avocado-<tag>/. Updating %{gittar} and %setup -n to avocado_framework-… makes Source0 404 and breaks %prep. Restore the previous avocado-%{version} / avocado-%{commit} naming so the spec can fetch and unpack the sources again.

Also applies to: 102-106

♻️ Duplicate comments (14)
examples/plugins/cli-cmd/hello_option/setup.py (1)

6-10: Metadata duplication between setup.py and pyproject.toml.

As previously noted, this file retains metadata in the setup() call while the comment states that configuration is in pyproject.toml. This creates duplication risk and maintenance burden.

Convert to a minimal shim consistent with other plugins:

 setup(
-    name="avocado-hello-world-option",
-    version="1.0",
-    description="Avocado Hello World CLI command with config option",
+    # All configuration is in pyproject.toml
 )
optional_plugins/varianter_yaml_to_mux/setup.py (1)

1-1: Fix the shebang path (duplicate concern).

The shebang still uses /bin/env instead of /usr/bin/env. While past review comments indicate this was addressed in earlier commits, the issue persists in the current state of the file.

Apply this diff:

-#!/bin/env python3
+#!/usr/bin/env python3
optional_plugins/varianter_cit/setup.py (1)

1-1: Fix the shebang path (duplicate concern).

The shebang still uses /bin/env instead of /usr/bin/env. While past review comments indicate this was addressed in earlier commits (662fc8c to af71902), the issue persists in the current state of the file.

Apply this diff:

-#!/bin/env python3
+#!/usr/bin/env python3
optional_plugins/result_upload/setup.py (1)

1-1: Shebang still uses non-portable path.

The shebang #!/bin/env is not portable across all systems. While this was previously flagged and marked as addressed, the code still shows the incorrect path. Use #!/usr/bin/env instead.

Apply this diff:

-#!/bin/env python3
+#!/usr/bin/env python3
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (8)

38-46: Parse timeout options as integers.

settings.register_option() returns strings from config files unless you declare key_type=int. Without it, overrides arrive as str and bleed into timeout arithmetic/run calls. Add key_type=int to both timeout registrations.


106-115: Harden slot loading with error handling.

Opening arbitrary JSON files and logging in without guards will crash the spawner on a single bad slot (missing file, malformed JSON, login failure). Wrap the load/login in try/except and log/skip failures so one bad slot doesn’t bring the whole spawner down.


143-145: Escape the task identifier before shelling out.

Task identifiers can carry spaces or shell metacharacters. Injecting them unquoted into pgrep risks breakage or command injection on the remote host. Quote the identifier (e.g. safe_id = shlex.quote(...)) before composing the command.


182-185: Preserve remote test output instead of dropping it.

Appending > /dev/null discards all stdout/stderr from the remote run, eliminating diagnostics and results. Remove the redirection or redirect into a retrievable log file so failures remain debuggable.


182-193: Don’t block spawn_task until the remote run finishes.

run_remote_cmd_async() waits for completion, so spawn_task() only returns after the task exits. That defeats the spawner contract (wait_task can never observe a running process and concurrency collapses). Launch the command in background, capture its PID/handle, store it on runtime_task, and let wait_task/is_task_alive poll it.


195-201: Use a unique, sanitized remote output directory.

Hard-coding /tmp/.avocado_task_output_dir means every task shares the same path, inviting collisions and TOCTOU races. Derive a per-task directory (sanitize the identifier, ensure creation on the remote) so runs stay isolated.


202-206: Add a timeout to the wait loop.

If is_task_alive() never flips to False (hang, bug, lost session), wait_task() loops forever. Honor the configured test_timeout (or another cap) to avoid jobs stalling indefinitely.


208-216: Fix termination logging and include traceback.

The current LOG.error("...{session.host}") logs the literal braces and omits the exception context. Use LOG.exception(f"Failed to terminate task on {session.host}:{session.port}") (or LOG.error(..., exc_info=True)) so the message interpolates correctly and captures the stack trace.

optional_plugins/robot/setup.py (1)

1-1: The shebang portability issue remains unresolved.

Despite being marked as addressed in commit dce17a9, the shebang still uses the non-portable #!/bin/env python3 path. This should be #!/usr/bin/env python3 for proper PATH-based interpreter resolution across different systems.

Apply this diff:

-#!/bin/env python3
+#!/usr/bin/env python3
optional_plugins/mail/setup.py (1)

1-1: The shebang portability issue remains unresolved.

Despite past review comments indicating this was addressed (commits 662fc8c to af71902), the shebang still uses the non-portable #!/bin/env python3 path. Use #!/usr/bin/env python3 for proper cross-platform compatibility.

Apply this diff:

-#!/bin/env python3
+#!/usr/bin/env python3
🧹 Nitpick comments (6)
.github/actions/version/action.yml (2)

10-10: Consider removing the pip upgrade step.

Upgrading pip on every workflow run adds overhead and potential instability. Modern Python installations typically include a recent pip version that's sufficient for editable installs.

If pip upgrade is necessary for specific features, add a comment explaining why. Otherwise, consider removing this line to improve workflow performance.


9-9: Limit and document PIP_BREAK_SYSTEM_PACKAGES usage or isolate with venv

  • Scope the override to the pip command only, e.g.:
    PIP_BREAK_SYSTEM_PACKAGES=1 pip install --upgrade pip
  • Add a comment in action.yml explaining why PEP 668 protections are disabled
  • Alternatively, create a python -m venv env (as in other workflows) to avoid modifying the system Python
optional_plugins/spawner_remote/pyproject.toml (1)

14-17: Consider updating the minimum aexpect version requirement.

The current requirement aexpect>=1.6.2 is quite permissive. According to available information, aexpect 1.8.0 was released in July 2025 with potential API and behavior improvements.

Consider whether:

  1. The plugin truly supports aexpect 1.6.2, or if newer features are used that require a higher minimum
  2. It would be beneficial to require a more recent version for security and stability improvements

Verify the minimum version by reviewing the spawner implementation for any aexpect features used:

#!/bin/bash
# Description: Check aexpect usage in the spawner implementation

# Find aexpect usage patterns in the spawner module
rg -n "aexpect\." optional_plugins/spawner_remote/avocado_spawner_remote/ -A 2 -B 1
optional_plugins/varianter_yaml_to_mux/pyproject.toml (1)

16-16: Update PyYAML dependency to a stable release.
Change PyYAML>=4.2b2 to PyYAML>=6.0.3 (latest stable) or at minimum PyYAML>=5.4 to include critical CVE fixes.

examples/plugins/tests/magic/pyproject.toml (1)

6-6: Generic package name may cause conflicts.

The package name "magic" is very generic and could conflict with other packages in the ecosystem. Consider a more specific name like "avocado-test-magic" or "avocado-plugin-magic" to avoid namespace collisions.

Apply this diff:

-name = "magic"
+name = "avocado-test-magic"
optional_plugins/ansible/pyproject.toml (1)

14-18: Consider removing redundant pycparser dependency.

The pycparser package is already a dependency of cffi, so explicitly listing it may be redundant. However, explicit dependencies can improve clarity and prevent issues if cffi's dependencies change.

If you prefer explicit dependencies for stability, the current setup is fine. Otherwise, consider this optional simplification:

 dependencies = [
     "avocado-framework",
     "cffi",
-    "pycparser",
     "ansible-core",
 ]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa51f15 and 0e10168.

📒 Files selected for processing (65)
  • .github/actions/version/action.yml (1 hunks)
  • .github/workflows/ci.yml (13 hunks)
  • .github/workflows/prerelease.yml (1 hunks)
  • .github/workflows/push_ci.yml (1 hunks)
  • .github/workflows/setup.yml (6 hunks)
  • .github/workflows/vmimage.yml (1 hunks)
  • .github/workflows/weekly.yml (2 hunks)
  • Makefile (5 hunks)
  • Makefile.gh (1 hunks)
  • docs/source/guides/contributor/chapters/environment.rst (1 hunks)
  • docs/source/guides/contributor/chapters/plugins.rst (4 hunks)
  • docs/source/guides/writer/chapters/subclassing.rst (2 hunks)
  • examples/plugins/README.rst (1 hunks)
  • examples/plugins/cli-cmd/hello/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_option/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_option/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/setup.py (1 hunks)
  • examples/plugins/job-pre-post/sleep/pyproject.toml (1 hunks)
  • examples/plugins/job-pre-post/sleep/setup.py (1 hunks)
  • examples/plugins/test-pre-post/hello/pyproject.toml (1 hunks)
  • examples/plugins/test-pre-post/hello/setup.py (1 hunks)
  • examples/plugins/tests/README.rst (1 hunks)
  • examples/plugins/tests/magic/pyproject.toml (1 hunks)
  • examples/plugins/tests/magic/setup.py (1 hunks)
  • examples/plugins/tests/rogue/pyproject.toml (1 hunks)
  • examples/plugins/tests/rogue/setup.py (1 hunks)
  • optional_plugins/README.rst (1 hunks)
  • optional_plugins/ansible/README.rst (1 hunks)
  • optional_plugins/ansible/pyproject.toml (1 hunks)
  • optional_plugins/ansible/setup.py (1 hunks)
  • optional_plugins/golang/pyproject.toml (1 hunks)
  • optional_plugins/golang/setup.py (1 hunks)
  • optional_plugins/html/pyproject.toml (1 hunks)
  • optional_plugins/html/setup.py (1 hunks)
  • optional_plugins/mail/pyproject.toml (1 hunks)
  • optional_plugins/mail/setup.py (1 hunks)
  • optional_plugins/result_upload/pyproject.toml (1 hunks)
  • optional_plugins/result_upload/setup.py (1 hunks)
  • optional_plugins/resultsdb/pyproject.toml (1 hunks)
  • optional_plugins/resultsdb/setup.py (1 hunks)
  • optional_plugins/robot/avocado_robot/robot.py (1 hunks)
  • optional_plugins/robot/pyproject.toml (1 hunks)
  • optional_plugins/robot/setup.py (1 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/__init__.py (0 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1 hunks)
  • optional_plugins/spawner_remote/pyproject.toml (1 hunks)
  • optional_plugins/spawner_remote/setup.py (1 hunks)
  • optional_plugins/varianter_cit/pyproject.toml (1 hunks)
  • optional_plugins/varianter_cit/setup.py (1 hunks)
  • optional_plugins/varianter_pict/README.rst (1 hunks)
  • optional_plugins/varianter_pict/pyproject.toml (1 hunks)
  • optional_plugins/varianter_pict/setup.py (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/README.rst (4 hunks)
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/setup.py (1 hunks)
  • pyproject.toml (1 hunks)
  • python-avocado.spec (6 hunks)
  • selftests/run_coverage (1 hunks)
  • selftests/utils.py (2 hunks)
  • setup.py (3 hunks)
  • spell.ignore (1 hunks)
💤 Files with no reviewable changes (1)
  • optional_plugins/spawner_remote/avocado_spawner_remote/init.py
✅ Files skipped from review due to trivial changes (2)
  • optional_plugins/varianter_yaml_to_mux/README.rst
  • optional_plugins/varianter_pict/pyproject.toml
🚧 Files skipped from review as they are similar to previous changes (25)
  • spell.ignore
  • Makefile.gh
  • optional_plugins/ansible/README.rst
  • examples/plugins/tests/rogue/pyproject.toml
  • optional_plugins/varianter_cit/pyproject.toml
  • examples/plugins/test-pre-post/hello/pyproject.toml
  • optional_plugins/mail/pyproject.toml
  • optional_plugins/spawner_remote/setup.py
  • examples/plugins/job-pre-post/sleep/pyproject.toml
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml
  • examples/plugins/cli-cmd/hello/pyproject.toml
  • pyproject.toml
  • examples/plugins/test-pre-post/hello/setup.py
  • examples/plugins/tests/README.rst
  • .github/workflows/prerelease.yml
  • examples/plugins/cli-cmd/hello_parser/setup.py
  • .github/workflows/push_ci.yml
  • examples/plugins/tests/magic/setup.py
  • optional_plugins/golang/setup.py
  • examples/plugins/tests/rogue/setup.py
  • docs/source/guides/writer/chapters/subclassing.rst
  • optional_plugins/varianter_pict/README.rst
  • optional_plugins/README.rst
  • .github/workflows/vmimage.yml
  • optional_plugins/html/setup.py
🧰 Additional context used
🧬 Code graph analysis (2)
optional_plugins/robot/avocado_robot/robot.py (1)
avocado/core/test.py (1)
  • name (367-373)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (4)
avocado/core/plugin_interfaces.py (2)
  • Init (23-28)
  • Spawner (321-441)
avocado/core/spawners/common.py (3)
  • SpawnerMixin (35-47)
  • SpawnMethod (6-18)
  • task_output_dir (46-47)
avocado/core/nrunner/runnable.py (1)
  • pick_runner_module_from_entry_point_kind (630-643)
avocado/core/nrunner/task.py (1)
  • setup_output_dir (171-174)
🪛 checkmake (0.2.2)
Makefile

[warning] 124-124: Missing required phony target "all"

(minphony)


[warning] 124-124: Missing required phony target "test"

(minphony)

🪛 Ruff (0.14.0)
selftests/utils.py

29-29: Consider moving this statement to an else block

(TRY300)


30-30: Do not catch blind exception: Exception

(BLE001)

optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py

73-73: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


74-74: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


126-129: Avoid specifying long messages outside the exception class

(TRY003)


197-197: Probable insecure usage of temporary file or directory: "/tmp/.avocado_task_output_dir"

(S108)


213-213: Consider moving this statement to an else block

(TRY300)


215-215: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


219-219: Unused static method argument: runtime_task

(ARG004)


224-224: Unused static method argument: runtime_task

(ARG004)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (42)
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: Fedora selftests
  • GitHub Check: Fedora develop install/uninstall task
  • GitHub Check: macOS with Python 3.11
  • GitHub Check: Smokecheck on Linux with Python 3.11
  • GitHub Check: Smokecheck on Linux with Python 3.10
  • GitHub Check: Static checks
  • GitHub Check: Code Coverage (3.11)
🔇 Additional comments (35)
docs/source/guides/contributor/chapters/environment.rst (1)

55-62: LGTM! Documentation accurately reflects modern packaging practices.

The migration from setup.py develop to pip install -e . and the terminology update from "egg link" to "editable install" align with current Python packaging standards (PEP 660). The explanation about the egg-info directory and when re-runs are needed is clear and helpful.

examples/plugins/README.rst (1)

8-24: LGTM! Clear and helpful plugin development workflow.

The updated documentation with explicit examples (including directory navigation and uninstall commands) makes the plugin development workflow much clearer for contributors. The migration from implicit setup.py commands to explicit pip-based commands aligns with modern Python packaging practices.

optional_plugins/spawner_remote/pyproject.toml (1)

1-45: LGTM! Well-structured pyproject.toml configuration.

The pyproject.toml file follows modern Python packaging standards with:

  • Proper build-system configuration
  • Comprehensive project metadata
  • Correct entry point registration (after previous fix)
  • Standard dynamic version/readme loading pattern
examples/plugins/cli-cmd/hello_priority/setup.py (1)

1-6: LGTM! Clean transition to pyproject.toml.

This setup.py file follows the recommended minimal shim pattern, delegating all configuration to pyproject.toml while maintaining backward compatibility.

docs/source/guides/contributor/chapters/plugins.rst (2)

111-114: LGTM! TOML entry-points syntax is correct.

The documentation properly demonstrates the pyproject.toml entry-points syntax for plugin registration.


97-101: Example path and content verified
The referenced examples/plugins/cli-cmd/hello/pyproject.toml exists and contains the correct entry-point configuration.

examples/plugins/cli-cmd/hello_option/pyproject.toml (1)

1-14: LGTM! Well-structured pyproject.toml configuration.

The configuration correctly uses py-modules for a single-module plugin and properly registers the entry point for the CLI command.

examples/plugins/job-pre-post/sleep/setup.py (1)

1-10: LGTM! Minimal setup.py for backward compatibility.

The minimal setup stub with clear transition comments aligns with the PR's migration strategy. The essential metadata (name, version, description) enables legacy workflows while directing users to pyproject.toml.

optional_plugins/golang/pyproject.toml (1)

1-47: LGTM! Well-structured pyproject.toml.

The golang plugin's pyproject.toml is properly configured with:

  • Appropriate build system and Python version requirements (>=3.9).
  • Dynamic version/readme sourcing.
  • Correct entry point registrations.
  • Proper package discovery configuration.

The migration from setup.py to pyproject.toml is complete and correct.

optional_plugins/result_upload/setup.py (1)

2-15: LGTM! Minimal setup.py with proper version loading.

The setup stub correctly:

  • Includes clear transition comments.
  • Loads version from the VERSION file.
  • Provides essential metadata for backward compatibility.

The previous concern about missing metadata causing crashes has been addressed with the minimal name/version/description setup.

optional_plugins/result_upload/pyproject.toml (1)

1-44: LGTM! Complete pyproject.toml configuration.

The result_upload plugin's pyproject.toml is properly structured with:

  • Standard build system requirements.
  • Appropriate metadata and dynamic field sourcing.
  • Correct entry point registrations for both CLI and result plugins.
  • Proper package discovery configuration.

The migration is complete and correct.

optional_plugins/ansible/pyproject.toml (1)

1-47: LGTM! Well-configured ansible plugin.

The ansible plugin's pyproject.toml is properly structured with:

  • Standard build system and Python version requirements.
  • Comprehensive metadata and dynamic field sourcing.
  • Correct entry point registrations.
  • Proper package discovery configuration.

The dependencies are appropriate for the ansible integration functionality.

examples/plugins/cli-cmd/hello_parser/pyproject.toml (1)

1-14: LGTM! Clean example plugin configuration.

The hello_parser plugin's pyproject.toml is appropriately minimal for an example:

  • Standard build system.
  • Clear, specific package name.
  • Correct entry point registration.
  • Proper module configuration using py-modules.

The simplified structure (no dynamic fields) is suitable for an example plugin.

optional_plugins/resultsdb/pyproject.toml (1)

1-48: LGTM! Complete resultsdb plugin configuration.

The resultsdb plugin's pyproject.toml is properly configured with:

  • Standard build system and Python version requirements.
  • Appropriate metadata with dynamic field sourcing.
  • Correct entry point registrations for CLI, result events, and result plugins.
  • Proper package discovery configuration.
  • Clean dependency list (the previous urllib3 constraint issue has been resolved).

The migration is complete and correct.

.github/workflows/weekly.yml (2)

39-47: LGTM! Workflow modernization is consistent.

The changes correctly migrate from setup.py develop --user to python3 -m pip install --user -e . and from direct script invocation to python3 -m selftests.check. These align with the PR's broader standardization of pip-based workflows.


83-89: LGTM! Plugin-disabled test invocation updated correctly.

The module-based selftests invocation with --disable-plugin-checks flag is properly migrated and maintains the same functionality as before.

optional_plugins/robot/setup.py (1)

2-15: LGTM! Minimal setup.py shim is appropriate.

The transitional shim correctly reads VERSION from the file and delegates all packaging metadata to pyproject.toml. This is the recommended pattern during the migration period.

optional_plugins/mail/setup.py (1)

2-15: LGTM! Minimal setup.py shim is appropriate.

The transitional shim correctly delegates packaging metadata to pyproject.toml while maintaining egg-build compatibility through a minimal setup() call.

.github/workflows/setup.yml (3)

33-43: LGTM! User installation workflow correctly updated.

The migration from setup.py-based installation to pip-based installation (python3 -m pip install --user -e . and direct plugin installations via pip) is correct and aligns with modern Python packaging best practices.


121-125: LGTM! Test execution modernized correctly.

The switch to make man for manpage generation and python3 -m selftests.check for test execution is consistent with the broader workflow modernization and eliminates direct setup.py invocations.


181-194: LGTM! Virtualenv workflow uses pip consistently.

The virtualenv installation steps correctly use python3 -m venv and python3 -m pip install for both Avocado and plugins, following modern packaging conventions.

Makefile (5)

9-10: LGTM! New plugin development targets documented.

The new develop-plugins and develop-plugin targets are well-documented and provide clear guidance for plugin development workflows, complementing the migration to pip-based installation.


34-61: LGTM! Enhanced clean target with better user feedback.

The improved clean target provides clear progress messages and comprehensively cleans both optional plugins and example plugins. The defensive error handling (|| true) ensures the clean process completes even if some files are missing.


64-80: LGTM! Core targets updated to use pip and module-based commands.

The migration of install, uninstall, and check targets to use python -m pip and python -m selftests.check is correct and eliminates direct setup.py invocations.


91-105: LGTM! Plugin development targets are well-implemented.

The new develop-plugins target iterates through all optional plugins, and develop-plugin provides targeted installation with clear usage guidance. The error message for missing PLUGIN variable is helpful.


109-114: LGTM! Man page generation with proper error handling.

The updated man page target includes proper error handling with command -v to check for rst2man availability before attempting generation, providing a clear error message if the tool is missing.

setup.py (3)

16-26: LGTM! Clear documentation of transitional setup.py purpose.

The docstring effectively communicates that this file is maintained for backward compatibility and egg builds during the migration period, with explicit acknowledgment of entry_points duplication. This transparent communication helps future maintainers understand the temporary nature of this arrangement.


38-41: LGTM! Simplified VERSION reading.

The streamlined VERSION reading using a context manager is clean and appropriate for the transitional setup.py shim.


207-350: LGTM with maintenance caveat.

The entry_points dictionary duplication for egg builds is acknowledged in the comments (lines 24-25). While this creates maintenance burden, it's a necessary transitional measure. The comprehensive entry_points configuration covers all plugin types and appears complete.

Consider adding a CI check to validate entry_points synchronization between setup.py and pyproject.toml to prevent future drift, as suggested in past review comments.

.github/workflows/ci.yml (5)

32-32: LGTM! Static checks updated to use module invocation.

The change to python3 -m selftests.check --select=static-checks correctly replaces the deprecated setup.py test invocation and aligns with the broader migration strategy.


89-99: LGTM! Check-linux job correctly modernized.

The workflow updates use python -VV (correct double-verbose flag), pip-based dependency installation, and module-based test execution. These changes align with modern Python packaging practices.


131-143: LGTM! macOS workflow updated consistently.

The macOS job follows the same modernization patterns as Linux jobs, ensuring cross-platform consistency in installation and test execution methods.


167-171: LGTM! Windows workflow updated correctly.

The Windows job uses python -VV and python -m pip install --user -e ., maintaining consistency with the Linux/macOS workflows. The setuptools fallback at line 169 is correctly implemented as python -m pip install.


265-356: LGTM! Version tasks consistently updated across distributions.

All distribution-specific version tasks (Fedora, UBI, Debian, Ubuntu) consistently use python3 python3-pip (or appropriate variant) and python3-venv for Python dependency installation, with ca-certificates included where needed. This standardization improves workflow reliability.

optional_plugins/robot/pyproject.toml (1)

1-48: LGTM! Well-structured pyproject.toml configuration.

The packaging configuration correctly defines:

  • Build system with setuptools and wheel
  • Project metadata with dynamic version/readme from files
  • Dependencies and Python version requirements
  • CLI scripts and plugin entry points
  • Package discovery rules

This modernizes the Robot plugin packaging and aligns with PEP 517/518 standards.

},
)
setup(
name="avocado-hello-world-option",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify the package name matches the directory.

The package name avocado-hello-world-option suggests this belongs in the hello_option directory, not the hello directory. Based on the PR changes, there should be a separate hello_option plugin with its own pyproject.toml.

Run this script to verify the directory structure and identify if this is a mismatch:


🏁 Script executed:

#!/bin/bash
# Verify the directory structure and package names for hello plugins

echo "=== Checking hello plugin directories ==="
fd -t d "hello" examples/plugins/cli-cmd/

echo ""
echo "=== Checking pyproject.toml files ==="
fd "pyproject.toml" examples/plugins/cli-cmd/ --exec cat {} \; --exec echo "---" \;

echo ""
echo "=== Checking setup.py files ==="
fd "setup.py" examples/plugins/cli-cmd/ --exec echo "File: {}" \; --exec grep -A2 "name=" {} \;

Length of output: 2490


Fix metadata in hello/setup.py: in examples/plugins/cli-cmd/hello/setup.py, change name to "avocado-hello-world", description to "Avocado Hello World CLI command", and entry-points to hello = "hello:HelloWorld" to align with its pyproject.toml.

🤖 Prompt for AI Agents
In examples/plugins/cli-cmd/hello/setup.py around line 7, update the package
metadata to match pyproject.toml: change name from "avocado-hello-world-option"
to "avocado-hello-world", update the description to "Avocado Hello World CLI
command", and adjust the entry-points mapping so the console/script entry is
hello = "hello:HelloWorld"; ensure the updated strings are used in the setup()
call.

[project.entry-points."avocado.plugins.runnable.runner"]
magic = "avocado_magic.runner:MagicRunner"

[tool.setuptools.packages.find]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Complete the package discovery configuration.

The [tool.setuptools.packages.find] section is empty. Since the entry points reference avocado_magic module, you should specify which packages to include.

Apply this diff:

 [tool.setuptools.packages.find]
+include = ["avocado_magic*"]
🤖 Prompt for AI Agents
In examples/plugins/tests/magic/pyproject.toml around line 25, the
[tool.setuptools.packages.find] section is empty; update it to explicitly
include the avocado_magic package (and any subpackages) by setting include =
["avocado_magic*"] and, if your project uses a src layout, also set where =
["src"]; this ensures setuptools discovers the referenced avocado_magic module
for packaging and entry points.

Comment on lines +1 to +17
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "avocado-framework-plugin-robot"
dynamic = ["version", "readme"]
description = "Avocado Plugin for Execution of Robot Framework tests"
authors = [
{name = "Avocado Developers", email = "avocado-devel@redhat.com"},
]
license = {text = "GPL-2.0-or-later"}
requires-python = ">=3.9"
dependencies = [
"avocado-framework",
"robotframework"
]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Consider adding version constraints for robotframework dependency.

The robotframework dependency at line 16 lacks version constraints. While this allows maximum flexibility, it can lead to compatibility issues if robotframework releases breaking changes. Past review comments mentioned updating a <7.0 constraint, but the current configuration has no constraints at all.

Consider adding appropriate version bounds:

 dependencies = [
     "avocado-framework",
-    "robotframework"
+    "robotframework>=4.1,<8.0"
 ]

Search the web to verify the current stable robotframework version and compatibility requirements:


🌐 Web query:

What is the latest stable version of Robot Framework as of October 2025, and what are the recommended version constraints for Python 3.9+ compatibility?

💡 Result:

  • Latest stable Robot Framework (as of Oct 14, 2025): 7.3.2 (final release published July 4, 2025). [1][2]

  • Python compatibility / recommended constraints:

    • Robot Framework itself requires Python 3.8 or newer (official docs). If you specifically want Python 3.9+ for better ecosystem support, pin your environment to Python >=3.9 — many common RF libraries (e.g., Browser) and tools declare Python 3.9+ support. Example constraints you can use:
      • robotframework==7.3.2
      • python_requires=">=3.9"
        References: Robot Framework user guide (requires Python 3.8+), Browser library and other ecosystem projects (recommend Python 3.9+). [3][4]

Sources

  1. Robot Framework releases (7.3 release candidates / release history). [GitHub releases]. [2]
  2. Release listing showing v7.3.2 (July 4, 2025). [gitclear / release metadata]. [1]
  3. Robot Framework User Guide — Python installation / requirements (requires Python 3.8+). [3]
  4. Example ecosystem library (Browser) — declares Python 3.9+ support. [4]

Add version constraints for robotframework dependency

Constrain robotframework to the current stable 7.x release to avoid unintentional upgrades to 8.x:

 dependencies = [
     "avocado-framework",
-    "robotframework"
+    "robotframework>=7.3.2,<8.0"
 ]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "avocado-framework-plugin-robot"
dynamic = ["version", "readme"]
description = "Avocado Plugin for Execution of Robot Framework tests"
authors = [
{name = "Avocado Developers", email = "avocado-devel@redhat.com"},
]
license = {text = "GPL-2.0-or-later"}
requires-python = ">=3.9"
dependencies = [
"avocado-framework",
"robotframework"
]
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "avocado-framework-plugin-robot"
dynamic = ["version", "readme"]
description = "Avocado Plugin for Execution of Robot Framework tests"
authors = [
{name = "Avocado Developers", email = "avocado-devel@redhat.com"},
]
license = {text = "GPL-2.0-or-later"}
requires-python = ">=3.9"
dependencies = [
"avocado-framework",
"robotframework>=7.3.2,<8.0"
]
🤖 Prompt for AI Agents
In optional_plugins/robot/pyproject.toml around lines 1 to 17, the
robotframework dependency is unpinned which allows accidental upgrades to 8.x;
update the dependencies entry to constrain robotframework to the 7.x stable
series (for example use a version specifier like >=7.0,<8.0) by replacing the
plain "robotframework" entry with the bounded version range so the package stays
on 7.x while allowing patch/minor updates.

Comment on lines 27 to 32
try:
pkg_resources.require(module_name)
distribution(module_name)
return True
except pkg_resources.DistributionNotFound:
except Exception:
# Catch all exceptions (PackageNotFoundError, etc.)
return False
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use specific exception type instead of bare Exception.

Catching Exception is too broad and may hide unexpected errors (e.g., AttributeError, TypeError) that should propagate. The importlib.metadata.distribution() function raises PackageNotFoundError when a distribution isn't found.

Apply this diff to handle the specific exception:

+from importlib.metadata import PackageNotFoundError, distribution
-from importlib.metadata import distribution

 def python_module_available(module_name):
     """
     Checks if a given Python module is available

     :param module_name: the name of the module
     :type module_name: str
     :returns: if the Python module is available in the system
     :rtype: bool
     """
     try:
         distribution(module_name)
         return True
-    except Exception:
-        # Catch all exceptions (PackageNotFoundError, etc.)
+    except PackageNotFoundError:
         return False
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
pkg_resources.require(module_name)
distribution(module_name)
return True
except pkg_resources.DistributionNotFound:
except Exception:
# Catch all exceptions (PackageNotFoundError, etc.)
return False
from importlib.metadata import PackageNotFoundError, distribution
def python_module_available(module_name):
"""
Checks if a given Python module is available
:param module_name: the name of the module
:type module_name: str
:returns: if the Python module is available in the system
:rtype: bool
"""
try:
distribution(module_name)
return True
- except Exception:
except PackageNotFoundError:
return False
🧰 Tools
🪛 Ruff (0.14.0)

29-29: Consider moving this statement to an else block

(TRY300)


30-30: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
In selftests/utils.py around lines 27 to 32, the try/except currently catches a
bare Exception; change it to catch importlib.metadata.PackageNotFoundError
specifically so only the "distribution not found" case is handled. Update
imports if needed (e.g., from importlib.metadata import PackageNotFoundError or
reference as importlib.metadata.PackageNotFoundError) and replace "except
Exception:" with "except PackageNotFoundError:" so other unexpected errors still
propagate.

Comment on lines +215 to +238
entry_points={
"console_scripts": [
"avocado = avocado.core.main:main",
"avocado-runner-noop = avocado.plugins.runners.noop:main",
"avocado-runner-dry-run = avocado.plugins.runners.dry_run:main",
"avocado-runner-exec-test = avocado.plugins.runners.exec_test:main",
"avocado-runner-python-unittest = avocado.plugins.runners.python_unittest:main",
"avocado-runner-avocado-instrumented = avocado.plugins.runners.avocado_instrumented:main",
"avocado-runner-tap = avocado.plugins.runners.tap:main",
"avocado-runner-asset = avocado.plugins.runners.asset:main",
"avocado-runner-package = avocado.plugins.runners.package:main",
"avocado-runner-pip = avocado.plugins.runners.pip:main",
"avocado-runner-vmimage = avocado.plugins.runners.vmimage:main",
"avocado-runner-podman-image = avocado.plugins.runners.podman_image:main",
"avocado-runner-sysinfo = avocado.plugins.runners.sysinfo:main",
"avocado-software-manager = avocado.utils.software_manager.main:main",
"avocado-external-runner = scripts.external_runner:main",
],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify scripts.external_runner is properly packaged.

Line 231 references scripts.external_runner:main as a console script entry point. Past review comments flagged concerns about whether the scripts module is properly packaged (scripts/ needs __init__.py or explicit inclusion in packages).

Run this script to verify the scripts module is properly packaged:


🏁 Script executed:

#!/bin/bash
# Description: Verify scripts module packaging configuration

# Check if scripts/__init__.py exists
if [ -f "scripts/__init__.py" ]; then
  echo "✓ scripts/__init__.py exists"
else
  echo "✗ scripts/__init__.py is missing"
fi

# Check if scripts is included in find_packages or explicitly listed
rg -A5 'packages\s*=.*find_packages' setup.py | rg -q 'scripts' && echo "✓ scripts included in packages" || echo "✗ scripts not found in packages config"

# Check if external_runner.py exists
if [ -f "scripts/external_runner.py" ]; then
  echo "✓ scripts/external_runner.py exists"
else
  echo "✗ scripts/external_runner.py is missing"
fi

# Check if external_runner has a main function
if [ -f "scripts/external_runner.py" ]; then
  rg -q 'def main\(' scripts/external_runner.py && echo "✓ main() function found" || echo "✗ main() function not found"
fi

Length of output: 123


Add __init__.py to the scripts/ directory
Without it, scripts.external_runner:main won’t be packaged and the console script will break.

🤖 Prompt for AI Agents
In setup.py around lines 215-232 the console entry "avocado-external-runner =
scripts.external_runner:main" references scripts as a package but the scripts/
directory lacks an __init__.py, so the module won't be packaged; add an
__init__.py (can be empty) to the scripts/ directory and ensure the package is
included in the distribution (e.g., include 'scripts' in the packages list or
use find_packages / update MANIFEST.in if you rely on package discovery), then
rebuild the package so the console script import works.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

♻️ Duplicate comments (9)
selftests/utils.py (1)

30-32: Address the previously flagged exception handling issue.

The broad except Exception: catch remains unaddressed from the previous review. As noted earlier and confirmed by static analysis, catching PackageNotFoundError specifically is necessary to avoid masking unexpected errors.

Apply the previously suggested fix:

+from importlib.metadata import PackageNotFoundError, distribution
-from importlib.metadata import distribution

 def python_module_available(module_name):
     """
     Checks if a given Python module is available

     :param module_name: the name of the module
     :type module_name: str
     :returns: if the Python module is available in the system
     :rtype: bool
     """
     try:
         distribution(module_name)
         return True
-    except Exception:
-        # Catch all exceptions (PackageNotFoundError, etc.)
+    except PackageNotFoundError:
         return False
optional_plugins/ansible/setup.py (1)

33-37: Consider removing duplicate metadata fields.

The past review comment remains valid: the description field duplicates what's in pyproject.toml. For consistency with other minimal setup.py files in this PR (e.g., examples/plugins/tests/rogue/setup.py which only passes name and version), consider removing the description parameter.

However, if the description is intentionally kept for specific backward-compatibility needs, the current implementation is acceptable.

pyproject.toml (3)

14-16: Restore the project's runtime dependencies.

[project].dependencies now contains only "setuptools", but the core package still imports third-party modules (for example, avocado.plugins.sysinfo imports psutil, avocado.utils.asset imports requests, and the varianter plugins rely on jsonschema). These were previously declared in install_requires; without them, pip install avocado-framework succeeds yet the package immediately raises ImportError when those modules are loaded. Please port the full dependency list from the former setup.py into this section (group purely optional items under extras if needed) so end users receive the required packages on installation.


30-30: Drop unsupported future Python classifier.

The trove classifier "Programming Language :: Python :: 3.14" is not published by PyPI yet. Keeping it here will cause uploads to PyPI to be rejected with "Invalid classifier". Please remove it (and re-add only after PyPI exposes the official classifier).

Apply this diff:

     "Programming Language :: Python :: 3.11",
     "Programming Language :: Python :: 3.12",
     "Programming Language :: Python :: 3.13",
-    "Programming Language :: Python :: 3.14",
 ]

238-239: Do not exclude packages required by console scripts.

[project.scripts] registers avocado-external-runner = "scripts.external_runner:main" at line 54, but [tool.setuptools.packages.find] excludes scripts* at line 239, so that module never ships in the wheel/sdist and the console script fails at runtime. Remove scripts* from the exclusion list (or package the module another way) to keep the entry point importable.

Apply this diff to remove scripts* from the exclusion list:

 [tool.setuptools.packages.find]
-exclude = ["selftests*", "docs*", "examples*", "contrib*", "scripts*", "optional_plugins*"]
+exclude = ["selftests*", "docs*", "examples*", "contrib*", "optional_plugins*"]
examples/plugins/cli-cmd/hello_priority/pyproject.toml (1)

10-11: Entry point name conflict persists.

Despite the previous review comment being marked as addressed, the entry point is still named hello, which conflicts with examples/plugins/cli-cmd/hello/pyproject.toml. If both plugins are installed, only one hello command will be registered, causing unpredictable behavior.

Rename the entry point to hello_priority to match the module name:

Apply this diff:

 [project.entry-points."avocado.plugins.cli.cmd"]
-hello = "hello_priority:HelloWorld"
+hello_priority = "hello_priority:HelloWorld"
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (3)

143-145: Escape task identifier before running pgrep.

The identifier is interpolated raw into the shell command, so spaces or shell metacharacters break the command and allow injection. Quote it with shlex.quote(...) before passing to cmd_status_output.

-        status, _ = session.cmd_status_output(
-            f"pgrep -r R,S -f {runtime_task.task.identifier}"
-        )
+        identifier = shlex.quote(runtime_task.task.identifier)
+        status, _ = session.cmd_status_output(f"pgrep -r R,S -f {identifier}")

38-46: Register timeouts as integers.

Options setup_timeout and test_timeout are registered without key_type=int, so user-provided values arrive as strings. Those strings are later passed to timeout-sensitive calls, causing runtime type errors. Register them with key_type=int.

-        settings.register_option(
-            section=section, key="setup_timeout", help_msg=help_msg, default=3600
-        )
+        settings.register_option(
+            section=section,
+            key="setup_timeout",
+            help_msg=help_msg,
+            default=3600,
+            key_type=int,
+        )
...
-        settings.register_option(
-            section=section, key="test_timeout", help_msg=help_msg, default=14400
-        )
+        settings.register_option(
+            section=section,
+            key="test_timeout",
+            help_msg=help_msg,
+            default=14400,
+            key_type=int,
+        )

108-115: Harden slot initialization failures.

If any slot file is missing or contains bad JSON, this will raise and brick the entire spawner initialization. Wrap the open/parse/login sequence in try/except so a single bad slot is logged and skipped instead of crashing the job.

-                with open(session_slot, "r", encoding="utf-8") as f:
-                    session_data = json.load(f)
-                session = remote.remote_login(**session_data)
-                RemoteSpawner.slots_cache[session] = False
+                try:
+                    with open(session_slot, "r", encoding="utf-8") as f:
+                        session_data = json.load(f)
+                    session = remote.remote_login(**session_data)
+                except (FileNotFoundError, json.JSONDecodeError, KeyError) as exc:
+                    LOG.warning("Skipping slot %s: %s", session_slot, exc)
+                    continue
+                except Exception as exc:
+                    LOG.error("Failed to initialize slot %s: %s", session_slot, exc)
+                    continue
+                RemoteSpawner.slots_cache[session] = False
🧹 Nitpick comments (11)
optional_plugins/varianter_yaml_to_mux/setup.py (1)

33-37: Consider removing duplicate description field.

Similar to optional_plugins/ansible/setup.py, the description parameter duplicates metadata in pyproject.toml. For consistency with truly minimal setup.py files (like examples/plugins/tests/rogue/setup.py), consider keeping only name and version. This is optional if backward compatibility requires the description field.

optional_plugins/varianter_pict/setup.py (1)

1-1: Consider removing the shebang line.

Since this file is now a backward-compatibility wrapper meant to be invoked as python setup.py rather than executed directly, the shebang is unnecessary. Removing it would eliminate the static analysis warning and better reflect the file's purpose.

Apply this diff to remove the shebang:

-#!/usr/bin/env python3
-# This program is free software; you can redistribute it and/or modify
+# This program is free software; you can redistribute it and/or modify
optional_plugins/resultsdb/setup.py (1)

1-1: Consider removing the shebang line.

The shebang is unnecessary for a file invoked as python setup.py. Removing it would resolve the static analysis warning and better reflect the backward-compatibility wrapper purpose.

Apply this diff:

-#!/usr/bin/env python3
-# This program is free software; you can redistribute it and/or modify
+# This program is free software; you can redistribute it and/or modify
optional_plugins/mail/setup.py (1)

1-1: Consider removing the shebang line.

Since this backward-compatibility wrapper is invoked as python setup.py rather than executed directly, the shebang is unnecessary and causes a static analysis warning.

Apply this diff:

-#!/usr/bin/env python3
-# This program is free software; you can redistribute it and/or modify
+# This program is free software; you can redistribute it and/or modify
optional_plugins/varianter_cit/setup.py (1)

1-1: Consider removing the shebang line.

The shebang is not needed for a file invoked as python setup.py. Removing it would eliminate the static analysis warning.

Apply this diff:

-#!/usr/bin/env python3
-# This program is free software; you can redistribute it and/or modify
+# This program is free software; you can redistribute it and/or modify
optional_plugins/html/setup.py (1)

1-1: Consider removing the shebang line.

For a backward-compatibility wrapper invoked as python setup.py, the shebang is unnecessary and triggers a static analysis warning.

Apply this diff:

-#!/usr/bin/env python3
-# This program is free software; you can redistribute it and/or modify
+# This program is free software; you can redistribute it and/or modify
optional_plugins/golang/setup.py (1)

1-1: Shebang without executable bit (EXE001).

setup.py isn’t intended to be executed directly. Remove the shebang or make the file executable. Prefer removal:

-#!/usr/bin/env python3
Makefile (1)

124-124: Satisfy checkmake: add PHONY and a test alias.

Declare all and test as phony and provide a test target aliasing check.

@@
-.PHONY: pip install clean uninstall requirements-dev smokecheck check develop develop-external develop-plugins develop-plugin variables man
+.PHONY: all test pip install clean uninstall requirements-dev smokecheck check develop develop-external develop-plugins develop-plugin variables man
+
+test: check

Also applies to: 78-81

setup.py (1)

207-215: Optional: avoid entry_points drift by loading from pyproject.toml.

To keep egg-only metadata aligned without duplicating, load entry points programmatically (same approach as suggested for plugins). I can draft a patch if you want to adopt it here too.

optional_plugins/result_upload/setup.py (1)

1-1: Shebang without executable bit (EXE001).

Remove shebang to silence the linter on a non-executable packaging module:

-#!/usr/bin/env python3
examples/plugins/cli-cmd/hello_parser/pyproject.toml (1)

5-8: Consider adding requires-python constraint.

Example plugins should demonstrate complete configuration. Add requires-python = ">=3.9" to match the main project's Python version requirement and provide a complete example.

Apply this diff:

 [project]
 name = "avocado-hello-world-parser"
 version = "1.0"
 description = "Avocado Hello World CLI command with config parser"
+requires-python = ">=3.9"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0e10168 and b7a537d.

📒 Files selected for processing (65)
  • .github/actions/version/action.yml (1 hunks)
  • .github/workflows/ci.yml (13 hunks)
  • .github/workflows/prerelease.yml (1 hunks)
  • .github/workflows/push_ci.yml (1 hunks)
  • .github/workflows/setup.yml (6 hunks)
  • .github/workflows/vmimage.yml (1 hunks)
  • .github/workflows/weekly.yml (2 hunks)
  • Makefile (5 hunks)
  • Makefile.gh (1 hunks)
  • docs/source/guides/contributor/chapters/environment.rst (1 hunks)
  • docs/source/guides/contributor/chapters/plugins.rst (4 hunks)
  • docs/source/guides/writer/chapters/subclassing.rst (2 hunks)
  • examples/plugins/README.rst (1 hunks)
  • examples/plugins/cli-cmd/hello/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_option/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_option/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/setup.py (1 hunks)
  • examples/plugins/job-pre-post/sleep/pyproject.toml (1 hunks)
  • examples/plugins/job-pre-post/sleep/setup.py (1 hunks)
  • examples/plugins/test-pre-post/hello/pyproject.toml (1 hunks)
  • examples/plugins/test-pre-post/hello/setup.py (1 hunks)
  • examples/plugins/tests/README.rst (1 hunks)
  • examples/plugins/tests/magic/pyproject.toml (1 hunks)
  • examples/plugins/tests/magic/setup.py (1 hunks)
  • examples/plugins/tests/rogue/pyproject.toml (1 hunks)
  • examples/plugins/tests/rogue/setup.py (1 hunks)
  • optional_plugins/README.rst (1 hunks)
  • optional_plugins/ansible/README.rst (1 hunks)
  • optional_plugins/ansible/pyproject.toml (1 hunks)
  • optional_plugins/ansible/setup.py (2 hunks)
  • optional_plugins/golang/pyproject.toml (1 hunks)
  • optional_plugins/golang/setup.py (2 hunks)
  • optional_plugins/html/pyproject.toml (1 hunks)
  • optional_plugins/html/setup.py (2 hunks)
  • optional_plugins/mail/pyproject.toml (1 hunks)
  • optional_plugins/mail/setup.py (2 hunks)
  • optional_plugins/result_upload/pyproject.toml (1 hunks)
  • optional_plugins/result_upload/setup.py (2 hunks)
  • optional_plugins/resultsdb/pyproject.toml (1 hunks)
  • optional_plugins/resultsdb/setup.py (2 hunks)
  • optional_plugins/robot/avocado_robot/robot.py (1 hunks)
  • optional_plugins/robot/pyproject.toml (1 hunks)
  • optional_plugins/robot/setup.py (2 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/__init__.py (0 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1 hunks)
  • optional_plugins/spawner_remote/pyproject.toml (1 hunks)
  • optional_plugins/spawner_remote/setup.py (2 hunks)
  • optional_plugins/varianter_cit/pyproject.toml (1 hunks)
  • optional_plugins/varianter_cit/setup.py (2 hunks)
  • optional_plugins/varianter_pict/README.rst (1 hunks)
  • optional_plugins/varianter_pict/pyproject.toml (1 hunks)
  • optional_plugins/varianter_pict/setup.py (2 hunks)
  • optional_plugins/varianter_yaml_to_mux/README.rst (4 hunks)
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/setup.py (2 hunks)
  • pyproject.toml (1 hunks)
  • python-avocado.spec (6 hunks)
  • selftests/run_coverage (1 hunks)
  • selftests/utils.py (2 hunks)
  • setup.py (3 hunks)
  • spell.ignore (1 hunks)
💤 Files with no reviewable changes (1)
  • optional_plugins/spawner_remote/avocado_spawner_remote/init.py
✅ Files skipped from review due to trivial changes (3)
  • optional_plugins/varianter_pict/README.rst
  • optional_plugins/ansible/README.rst
  • optional_plugins/varianter_yaml_to_mux/README.rst
🚧 Files skipped from review as they are similar to previous changes (25)
  • .github/workflows/vmimage.yml
  • optional_plugins/README.rst
  • examples/plugins/job-pre-post/sleep/setup.py
  • optional_plugins/varianter_pict/pyproject.toml
  • optional_plugins/resultsdb/pyproject.toml
  • spell.ignore
  • docs/source/guides/writer/chapters/subclassing.rst
  • optional_plugins/mail/pyproject.toml
  • examples/plugins/tests/magic/pyproject.toml
  • optional_plugins/robot/pyproject.toml
  • .github/workflows/prerelease.yml
  • docs/source/guides/contributor/chapters/plugins.rst
  • docs/source/guides/contributor/chapters/environment.rst
  • .github/workflows/push_ci.yml
  • optional_plugins/ansible/pyproject.toml
  • examples/plugins/cli-cmd/hello_option/setup.py
  • examples/plugins/cli-cmd/hello/pyproject.toml
  • examples/plugins/tests/magic/setup.py
  • examples/plugins/tests/rogue/pyproject.toml
  • .github/workflows/weekly.yml
  • examples/plugins/cli-cmd/hello_parser/setup.py
  • examples/plugins/cli-cmd/hello_option/pyproject.toml
  • Makefile.gh
  • examples/plugins/cli-cmd/hello/setup.py
  • examples/plugins/tests/README.rst
🧰 Additional context used
🧬 Code graph analysis (2)
optional_plugins/robot/avocado_robot/robot.py (1)
avocado/core/test.py (1)
  • name (367-373)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (5)
avocado/core/plugin_interfaces.py (2)
  • Init (23-28)
  • Spawner (321-441)
avocado/core/spawners/common.py (3)
  • SpawnerMixin (35-47)
  • SpawnMethod (6-18)
  • task_output_dir (46-47)
avocado/core/settings.py (3)
  • section (137-138)
  • register_option (642-799)
  • key (141-142)
avocado/core/nrunner/runnable.py (1)
  • pick_runner_module_from_entry_point_kind (630-643)
avocado/core/nrunner/task.py (1)
  • setup_output_dir (171-174)
🪛 checkmake (0.2.2)
Makefile

[warning] 124-124: Missing required phony target "all"

(minphony)


[warning] 124-124: Missing required phony target "test"

(minphony)

🪛 Ruff (0.14.0)
optional_plugins/ansible/setup.py

1-1: Shebang is present but file is not executable

(EXE001)

optional_plugins/varianter_yaml_to_mux/setup.py

1-1: Shebang is present but file is not executable

(EXE001)

optional_plugins/resultsdb/setup.py

1-1: Shebang is present but file is not executable

(EXE001)

optional_plugins/spawner_remote/setup.py

1-1: Shebang is present but file is not executable

(EXE001)

optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py

73-73: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


74-74: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


126-129: Avoid specifying long messages outside the exception class

(TRY003)


197-197: Probable insecure usage of temporary file or directory: "/tmp/.avocado_task_output_dir"

(S108)


213-213: Consider moving this statement to an else block

(TRY300)


215-215: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


219-219: Unused static method argument: runtime_task

(ARG004)


224-224: Unused static method argument: runtime_task

(ARG004)

optional_plugins/mail/setup.py

1-1: Shebang is present but file is not executable

(EXE001)

optional_plugins/result_upload/setup.py

1-1: Shebang is present but file is not executable

(EXE001)

selftests/utils.py

29-29: Consider moving this statement to an else block

(TRY300)


30-30: Do not catch blind exception: Exception

(BLE001)

optional_plugins/golang/setup.py

1-1: Shebang is present but file is not executable

(EXE001)

optional_plugins/html/setup.py

1-1: Shebang is present but file is not executable

(EXE001)

optional_plugins/robot/setup.py

1-1: Shebang is present but file is not executable

(EXE001)

optional_plugins/varianter_cit/setup.py

1-1: Shebang is present but file is not executable

(EXE001)

optional_plugins/varianter_pict/setup.py

1-1: Shebang is present but file is not executable

(EXE001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (41)
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: Fedora selftests
  • GitHub Check: Smokecheck on Linux with Python 3.11
  • GitHub Check: Smokecheck on Linux with Python 3.10
  • GitHub Check: macOS with Python 3.11
  • GitHub Check: Code Coverage (3.11)
🔇 Additional comments (26)
selftests/utils.py (1)

6-6: LGTM! Modern import for package checking.

The migration from pkg_resources to importlib.metadata is the correct approach for modern Python package checking.

selftests/run_coverage (1)

30-30: LGTM! Module-based invocation aligns with modern Python practices.

The change from script path to module invocation (-m selftests.check) is consistent with the project-wide migration and improves maintainability.

examples/plugins/test-pre-post/hello/setup.py (1)

16-30: LGTM! Clean migration with clear documentation.

The minimal setup.py approach with an explanatory docstring is appropriate. The name and version fields provide sufficient backward compatibility while pyproject.toml handles the full configuration.

examples/plugins/README.rst (1)

10-24: LGTM! Documentation reflects modern packaging practices.

The updated workflow using pip install -e . and pip uninstall aligns with current Python packaging standards and improves clarity with concrete examples.

optional_plugins/varianter_cit/pyproject.toml (1)

1-44: LGTM! Well-structured pyproject.toml configuration.

The packaging configuration follows modern standards with appropriate dynamic field sourcing, proper entry point declarations, and clear metadata. The setuptools>=61.0 requirement supports all declared features.

optional_plugins/varianter_yaml_to_mux/pyproject.toml (1)

1-48: LGTM! Complete pyproject.toml configuration with proper plugin integration.

The configuration properly defines all necessary entry points (init, cli, varianter) for the YAML-to-MUX plugin and includes appropriate dependencies with reasonable version constraints.

examples/plugins/tests/rogue/setup.py (1)

16-30: LGTM! Exemplary minimal setup.py implementation.

This file demonstrates the cleanest migration pattern with only name and version in the setup() call, fully delegating configuration to pyproject.toml as documented in the docstring. This serves as a good reference for other setup.py migrations in the codebase.

optional_plugins/varianter_pict/setup.py (1)

16-37: LGTM! Clean backward-compatibility wrapper.

The minimal setup() approach with basic metadata (name, version, description) provides appropriate backward compatibility while the full configuration lives in pyproject.toml. The VERSION file reading is safe with proper encoding.

optional_plugins/resultsdb/setup.py (1)

16-37: LGTM! Backward-compatibility approach is appropriate.

While a past comment suggested removing all metadata from setup(), keeping the minimal name/version/description here is standard practice for backward compatibility with legacy tools. The full rich metadata (dependencies, entry-points, etc.) properly lives in pyproject.toml.

examples/plugins/job-pre-post/sleep/pyproject.toml (1)

1-17: LGTM! Modern packaging configuration.

The pyproject.toml follows current best practices:

  • Build-system uses setuptools>=61.0 (supports pyproject.toml)
  • Entry points properly declare plugin hooks for Avocado's init and job.prepost lifecycle
  • py-modules is the correct approach for single-file module packaging
optional_plugins/mail/setup.py (1)

16-37: LGTM! Clean backward-compatibility implementation.

The minimal setup() provides appropriate backward compatibility with legacy tooling while configuration properly lives in pyproject.toml.

optional_plugins/varianter_cit/setup.py (1)

16-37: LGTM! Appropriate backward-compatibility wrapper.

The minimal metadata approach correctly balances backward compatibility with the modern pyproject.toml-based configuration.

optional_plugins/html/setup.py (1)

16-37: LGTM! Clean backward-compatibility implementation.

The minimal setup() with basic metadata provides appropriate legacy tool support while full configuration resides in pyproject.toml.

examples/plugins/test-pre-post/hello/pyproject.toml (1)

1-17: LGTM! Standard plugin configuration.

The pyproject.toml correctly configures the hello plugin:

  • Modern build-system with setuptools>=61.0
  • Proper entry-points for Avocado's test.pre and test.post hooks
  • py-modules is appropriate for the single-file hello module
optional_plugins/golang/pyproject.toml (1)

1-47: LGTM! Comprehensive and well-structured configuration.

This pyproject.toml exemplifies modern Python packaging best practices:

  • Proper build-system setup with setuptools>=61.0
  • Dynamic version/readme resolution from files (standard pattern)
  • Complete metadata: authors, license, classifiers, URLs
  • Correct dependency declaration (avocado-framework)
  • Multiple plugin integration points via entry-points (resolver, runnable.runner)
  • Script entry point for avocado-runner-golang command
  • Package discovery configured appropriately with include pattern
  • Python version constraint (>=3.9) aligns with project requirements

Based on learnings about setuptools 80.x series, this configuration avoids deprecated pkg_resources patterns and uses modern setuptools features appropriately.

Makefile (1)

79-81: Good move to module-based test runner.

Using “-m selftests.check” is the right direction for modern packaging.

python-avocado.spec (2)

220-221: LGTM on module-based selftests invocation.

This aligns with the rest of the migration.


8-19: Tarball name/dir mismatch does not break RPM builds Both avocado- and avocado_framework- named archives download successfully (HTTP 200), and %setup -n avocado_framework-%{ref} correctly renames the internal avocado-%{ref} directory.

Likely an incorrect or invalid review comment.

optional_plugins/result_upload/pyproject.toml (3)

1-4: LGTM! Standard build system configuration.

The build system configuration follows modern Python packaging best practices with setuptools≥61.0 and the standard setuptools.build_meta backend.


42-44: VERSION and README.rst exist: optional_plugins/result_upload/VERSION and README.rst are present, so the dynamic metadata configuration is valid.


14-16: Dependencies are complete. The plugin only imports modules from avocado.*, so no additional third-party dependencies are required.

optional_plugins/html/pyproject.toml (1)

43-44: Empty packages.find configuration looks intentional.

The empty [tool.setuptools.packages.find] section will use default behavior (find all packages). Combined with include-package-data = true, this should correctly package avocado_result_html and its subpackages. This appears intentional and correct.

.github/actions/version/action.yml (1)

9-9: Review the need for PIP_BREAK_SYSTEM_PACKAGES=1.

Setting PIP_BREAK_SYSTEM_PACKAGES=1 bypasses pip's protection against modifying system-managed Python installations (PEP 668). While this may be necessary in some CI containers, consider whether:

  1. The CI environment truly requires this override
  2. Using a virtual environment (python3 -m venv) would be safer
  3. This setting could mask packaging issues that users will encounter

If the CI environment is containerized and disposable, this is acceptable. Otherwise, prefer using virtual environments.

Based on learnings

optional_plugins/robot/avocado_robot/robot.py (1)

41-44: LGTM! Good defensive API compatibility handling.

The code correctly handles both the legacy string-based API and the newer TestCase object API by checking for the name attribute before accessing it. This ensures backward compatibility while supporting the newer API.

optional_plugins/spawner_remote/pyproject.toml (1)

31-35: LGTM! Entry points are correctly configured.

The entry point module paths correctly reference avocado_spawner_remote.spawner:RemoteSpawnerInit and avocado_spawner_remote.spawner:RemoteSpawner. This aligns with the package structure and was properly addressed from previous review feedback.

.github/workflows/setup.yml (1)

36-43: Fix pip --user flag order (commands fail).

pip install ./path --user is parsed as an invalid requirement (--user), so every plugin install step here will fail. Put --user before the path, e.g. python3 -m pip install --user ./optional_plugins/golang.

-          python3 -m pip install ./optional_plugins/golang --user
+          python3 -m pip install --user ./optional_plugins/golang
-          python3 -m pip install ./optional_plugins/html --user
+          python3 -m pip install --user ./optional_plugins/html
-          python3 -m pip install ./optional_plugins/result_upload --user
+          python3 -m pip install --user ./optional_plugins/result_upload
-          python3 -m pip install ./optional_plugins/resultsdb --user
+          python3 -m pip install --user ./optional_plugins/resultsdb
-          python3 -m pip install ./optional_plugins/robot --user
+          python3 -m pip install --user ./optional_plugins/robot
-          python3 -m pip install ./optional_plugins/varianter_cit --user
+          python3 -m pip install --user ./optional_plugins/varianter_cit
-          python3 -m pip install ./optional_plugins/varianter_pict --user
+          python3 -m pip install --user ./optional_plugins/varianter_pict
-          python3 -m pip install ./optional_plugins/varianter_yaml_to_mux --user
+          python3 -m pip install --user ./optional_plugins/varianter_yaml_to_mux

Likely an incorrect or invalid review comment.

Comment on lines +170 to +193
setup_timeout = self.config.get("spawner.remote.setup_timeout")
status, output = await RemoteSpawner.run_remote_cmd_async(
session, setup_hook, setup_timeout
)
LOG.debug(f"Customization command exited with code {status}")
if status != 0:
LOG.error(
f"Error exit code {status} on {session.host}:{session.port} "
f"from setup hook with output:\n{output}"
)
return False

cmd = shlex.join(entry_point_args) + " > /dev/null"
timeout = self.config.get("spawner.remote.test_timeout")
status, output = await RemoteSpawner.run_remote_cmd_async(session, cmd, timeout)
LOG.debug(f"Command exited with code {status}")
if status != 0:
LOG.error(
f"Error exit code {status} on {session.host}:{session.port} "
f"with output:\n{output}"
)
return False

return True
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

spawn_task waits for completion (no async concurrency).

run_remote_cmd_async blocks until the command ends, so spawn_task does not return until the task finishes. That defeats wait_task/is_task_alive and prevents the spawner from running tasks concurrently. Launch the remote process in the background (e.g. nohup ... & echo $!), store the PID/session, and return immediately so wait/terminate can manage it.

Comment on lines +182 to +189
cmd = shlex.join(entry_point_args) + " > /dev/null"
timeout = self.config.get("spawner.remote.test_timeout")
status, output = await RemoteSpawner.run_remote_cmd_async(session, cmd, timeout)
LOG.debug(f"Command exited with code {status}")
if status != 0:
LOG.error(
f"Error exit code {status} on {session.host}:{session.port} "
f"with output:\n{output}"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep remote stdout/stderr; don’t send to /dev/null.

Redirecting to /dev/null discards all task output, making debugging impossible. Drop the redirection or capture to a per-task log file you can retrieve later.

🤖 Prompt for AI Agents
In optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py around
lines 182 to 189, the command string appends " > /dev/null" which discards
stdout (and leaves stderr un-captured), preventing any output from being
available for diagnostics; remove that redirection and instead preserve
stdout/stderr by building the entry_point_args command without " > /dev/null" so
RemoteSpawner.run_remote_cmd_async can capture both streams (or, if disked logs
are preferred, redirect both stdout and stderr to a per-task file like
/tmp/spawner_{session.id}.log and return that path), ensuring the remote call
captures and returns output for logging on failure.

Comment on lines +195 to +201
def create_task_output_dir(self, runtime_task):
output_dir_path = self.task_output_dir(runtime_task)
output_lxc_path = "/tmp/.avocado_task_output_dir"

os.makedirs(output_dir_path, exist_ok=True)
runtime_task.task.setup_output_dir(output_lxc_path)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use unique, sanitized remote output dirs.

Every task writes to /tmp/.avocado_task_output_dir, so concurrent runs collide and races corrupt logs. Generate a per-task directory (e.g. based on the identifier) and ensure it exists remotely before use.

🧰 Tools
🪛 Ruff (0.14.0)

197-197: Probable insecure usage of temporary file or directory: "/tmp/.avocado_task_output_dir"

(S108)

🤖 Prompt for AI Agents
In optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py around
lines 195-201, the code always uses the fixed path
"/tmp/.avocado_task_output_dir" which causes collisions for concurrent tasks;
change this to a unique, sanitized per-task path (for example
"/tmp/.avocado_task_output_dir_<sanitized_task_id>") by deriving the id from
runtime_task (e.g. runtime_task.id or another stable identifier), sanitize it
(remove/replace path separators, whitespace, very long names and unsafe chars),
build the full path with os.path.join, create the directory locally with
os.makedirs(..., exist_ok=True) and then pass that unique path into
runtime_task.task.setup_output_dir(...) so the directory exists before use;
ensure length/charset limits and fallback to a UUID if the identifier cannot be
safely sanitized.

Comment on lines +202 to +206
async def wait_task(self, runtime_task):
while True:
if not RemoteSpawner.is_task_alive(runtime_task):
return
await asyncio.sleep(0.1)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add timeout when waiting for tasks.

wait_task loops forever if is_task_alive never flips (hangs or detection bug). Honor spawner.remote.test_timeout (or a dedicated wait timeout) to break out instead of blocking indefinitely.

🤖 Prompt for AI Agents
In optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py around
lines 202 to 206, wait_task currently loops forever if
RemoteSpawner.is_task_alive never becomes False; update it to honor a timeout
(use spawner.remote.test_timeout or add a dedicated wait timeout) by tracking
start time (or using asyncio.wait_for) and breaking out when elapsed exceeds the
configured timeout, returning or raising a TimeoutError (consistent with
surrounding API), and ensure any awaited sleep/cancellation is handled cleanly;
read the timeout from configuration with a sensible default and log or surface
the timeout event for callers.

Comment on lines +212 to +214
packages=find_packages(exclude=("selftests*",)),
include_package_data=True,
zip_safe=False,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

find_packages() is including optional plugin packages by accident.

With the project root scan and only excluding “selftests*”, packages under optional_plugins/* (e.g., avocado_result_upload) will be picked up in wheels, causing unintended bundling and conflicts with separate plugin wheels/RPM subpackages.

Restrict to the core “avocado” namespace:

-    packages=find_packages(exclude=("selftests*",)),
+    packages=find_packages(include=("avocado", "avocado.*")),

You can quickly list discovered packages during sdist/wheel builds locally and confirm only “avocado” packages are present.


🏁 Script executed:

#!/usr/bin/env bash
set -eo pipefail

echo "Top-level directories:"
find . -maxdepth 2 -type d | sed 's|^\./||'

echo
echo "Inspecting optional_plugins:"
if [ -d optional_plugins ]; then
  echo "optional_plugins directory found."
  echo "Subdirectories:"
  find optional_plugins -type d | sed 's|^\./||'
  echo
  echo "Python packages under optional_plugins (files named __init__.py):"
  find optional_plugins -type f -name "__init__.py" | sed 's|^\./||'
else
  echo "optional_plugins directory not present."
fi

Length of output: 2700


Restrict find_packages() to the avocado namespace
In setup.py around line 212, replace

-    packages=find_packages(exclude=("selftests*",)),
+    packages=find_packages(include=("avocado","avocado.*")),

This prevents any optional_plugins/* packages from being bundled.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
packages=find_packages(exclude=("selftests*",)),
include_package_data=True,
zip_safe=False,
packages=find_packages(include=("avocado","avocado.*")),
include_package_data=True,
zip_safe=False,
🤖 Prompt for AI Agents
In setup.py around lines 212 to 214, the current call
packages=find_packages(exclude=("selftests*",)) is too broad and pulls in
optional_plugins; replace it with a find_packages call that explicitly restricts
to the avocado namespace (for example:
packages=find_packages(include=("avocado*",), exclude=("selftests*",))) so only
avocado packages are bundled and optional_plugins are excluded.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
optional_plugins/golang/setup.py (1)

36-51: Egg/develop installs will miss package modules (packages not specified).

setup() lacks packages/include_package_data, so eggs and develop installs won’t include avocado_golang*. Add discovery and include data.

-from setuptools import setup
+from setuptools import setup, find_packages
@@
 setup(
     name="avocado-framework-plugin-golang",
     version=VERSION,
     description="Avocado Plugin for Execution of Golang tests",
+    packages=find_packages(include=("avocado_golang", "avocado_golang.*")),
+    include_package_data=True,
     entry_points={
optional_plugins/mail/setup.py (1)

40-47: Packages not included in egg/develop installs.

Add discovery for avocado_result_mail* and include data.

-from setuptools import setup
+from setuptools import setup, find_packages
@@
 setup(
     name="avocado-framework-plugin-result-mail",
     version=VERSION,
     description="Avocado Mail Notification for Jobs",
+    packages=find_packages(include=("avocado_result_mail", "avocado_result_mail.*")),
+    include_package_data=True,
     entry_points={
♻️ Duplicate comments (17)
selftests/utils.py (1)

27-32: Use specific exception type instead of bare Exception.

Catching Exception is too broad and may hide unexpected errors (e.g., AttributeError, TypeError) that should propagate. The importlib.metadata.distribution() function raises PackageNotFoundError when a distribution isn't found.

Apply this diff to handle the specific exception:

+from importlib.metadata import PackageNotFoundError, distribution
-from importlib.metadata import distribution

 def python_module_available(module_name):
     """
     Checks if a given Python module is available

     :param module_name: the name of the module
     :type module_name: str
     :returns: if the Python module is available in the system
     :rtype: bool
     """
     try:
         distribution(module_name)
         return True
-    except Exception:
-        # Catch all exceptions (PackageNotFoundError, etc.)
+    except PackageNotFoundError:
         return False
examples/plugins/tests/rogue/pyproject.toml (1)

19-20: Package discovery is misconfigured

py-modules = ["avocado_rogue"] tells setuptools to ship a single avocado_rogue.py, yet all entry points import avocado_rogue.runner/resolver. With the actual package layout (avocado_rogue/runner.py, etc.), the build omits those modules and any install/import will fail. Switch to package discovery instead of py-modules.

-[tool.setuptools]
-py-modules = ["avocado_rogue"]
+[tool.setuptools.packages.find]
+include = ["avocado_rogue"]
setup.py (4)

215-233: Verify console script target ‘scripts.external_runner:main’ is packaged.

Ensure scripts/ is a package and included, otherwise the entry point will fail.

#!/bin/bash
# Read-only verification of packaging for scripts.external_runner
set -euo pipefail

echo "Check scripts package files"
if [ -f "scripts/external_runner.py" ]; then
  echo "✓ scripts/external_runner.py exists"
else
  echo "✗ scripts/external_runner.py is missing"; exit 1
fi

if [ -f "scripts/__init__.py" ]; then
  echo "✓ scripts/__init__.py exists (packaged as a module)"
else
  echo "✗ scripts/__init__.py missing; console script import will fail"; exit 1
fi

echo "Check setup/pyproject include scripts package"
rg -n -C1 -P 'find_packages\([^)]*include[^)]*\)' setup.py || true
rg -n -C1 -P '^\[tool\.setuptools\.packages\.find\]' pyproject.toml || true

207-345: Avoid duplicating entry_points in setup.py; programmatically load from pyproject.toml.

Keeps metadata in sync and reduces drift/maintenance.

I can provide a helper to parse pyproject with tomllib/tomli and convert to setuptools’ entry_points mapping on demand.


16-26: Replace distutils clean import (breaks on Python 3.12+).

Line 31 still imports from distutils. Use setuptools’ clean.

Based on learnings

-from distutils.command.clean import clean  # pylint: disable=W0402
+from setuptools.command.clean import clean

207-214: find_packages() is too broad; it will pick up optional_plugins.

Restrict to the avocado namespace to avoid bundling optional plugins in the core wheel.

-    packages=find_packages(exclude=("selftests*",)),
+    packages=find_packages(include=("avocado", "avocado.*")),
pyproject.toml (3)

14-16: Restore runtime dependencies.

The [project].dependencies array contains only "setuptools", but the core package imports third-party modules at runtime (e.g., psutil, requests, jsonschema). Without these dependencies declared, pip install avocado-framework will succeed but the package will raise ImportError when imported.

Restore the full dependency list from the former setup.py. For example:

 dependencies = [
     "setuptools",
+    "psutil>=5.9.0",
+    "requests>=2.25.0",
+    "jsonschema>=3.2.0",
+    # ... other required packages
 ]

30-30: Remove unsupported Python 3.14 classifier.

The trove classifier "Programming Language :: Python :: 3.14" is not yet published by PyPI. Including it will cause package uploads to PyPI to be rejected with "Invalid classifier" errors.

Apply this diff:

     "Programming Language :: Python :: 3.12",
     "Programming Language :: Python :: 3.13",
-    "Programming Language :: Python :: 3.14",
 ]

54-54: Console script avocado-external-runner will fail at runtime.

Line 54 registers avocado-external-runner = "scripts.external_runner:main", but line 239 excludes scripts* from packaging. This means the scripts.external_runner module will not be included in the wheel/sdist, causing the console script to fail with ModuleNotFoundError at runtime.

Apply this diff:

 [tool.setuptools.packages.find]
-exclude = ["selftests*", "docs*", "examples*", "contrib*", "scripts*", "optional_plugins*"]
+exclude = ["selftests*", "docs*", "examples*", "contrib*", "optional_plugins*"]

Also applies to: 238-239

optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (8)

38-46: Register timeouts as integers.

Both setup_timeout and test_timeout are currently registered without key_type=int, so they arrive as strings and break arithmetic/time comparisons downstream. Please add key_type=int to both registrations.

-        settings.register_option(
-            section=section, key="setup_timeout", help_msg=help_msg, default=3600
-        )
+        settings.register_option(
+            section=section,
+            key="setup_timeout",
+            help_msg=help_msg,
+            default=3600,
+            key_type=int,
+        )
...
-        settings.register_option(
-            section=section, key="test_timeout", help_msg=help_msg, default=14400
-        )
+        settings.register_option(
+            section=section,
+            key="test_timeout",
+            help_msg=help_msg,
+            default=14400,
+            key_type=int,
+        )

106-114: Handle slot initialization failures.

Any missing/invalid slot file or failed login currently raises and crashes the spawner. Please wrap the load/login block in try/except, warn on per-slot failures, and continue so one bad slot doesn’t kill initialization.

-                with open(session_slot, "r", encoding="utf-8") as f:
-                    session_data = json.load(f)
-                session = remote.remote_login(**session_data)
-                RemoteSpawner.slots_cache[session] = False
+                try:
+                    with open(session_slot, "r", encoding="utf-8") as f:
+                        session_data = json.load(f)
+                    session = remote.remote_login(**session_data)
+                except (FileNotFoundError, json.JSONDecodeError, KeyError) as exc:
+                ...
+                else:
+                    RemoteSpawner.slots_cache[session] = False

137-145: Escape the task identifier in the remote command.

pgrep ... -f {identifier} without quoting breaks on whitespace/shell metacharacters and is a command-injection risk because task IDs are user-controlled. Please wrap the identifier with shlex.quote() (import already present).

-        status, _ = session.cmd_status_output(
-            f"pgrep -r R,S -f {runtime_task.task.identifier}"
-        )
+        safe_identifier = shlex.quote(runtime_task.task.identifier)
+        status, _ = session.cmd_status_output(f"pgrep -r R,S -f {safe_identifier}")

148-193: Don’t run the task synchronously.

run_remote_cmd_async waits for completion, so spawn_task() blocks until the task exits. That defeats wait_task()/is_task_alive() semantics and prevents concurrency. Launch the remote command in the background (e.g. nohup ... & echo $!), store the PID/handle on runtime_task, and return immediately so wait/terminate can manage it.


182-191: Keep remote stdout/stderr.

Appending > /dev/null discards all task output, leaving nothing for debugging or result collection. Drop the redirection or send it to a per-task log you can retrieve.

-        cmd = shlex.join(entry_point_args) + " > /dev/null"
+        cmd = shlex.join(entry_point_args)

195-201: Use a unique, sanitized output directory per task.

Hard-coding /tmp/.avocado_task_output_dir causes collisions and violates temp-dir safety checks (Ruff S108). Derive a per-task path (sanitize the identifier or use UUID), ensure it exists remotely, and pass that into setup_output_dir.


202-206: Add a timeout to wait_task.

wait_task() loops forever if is_task_alive never flips, so a hung remote task stalls the job indefinitely. Honor spawner.remote.test_timeout (or a dedicated wait timeout) and abort once exceeded.


208-216: Improve termination logging.

The failure log doesn’t interpolate the host and drops the traceback; Ruff also suggests moving the return to an else. Use LOG.exception(f"Failed to terminate task on {session.host}:{session.port}") and place the success return True in an else block.

         try:
             session.read_up_to_prompt()
-            return True
-        except exceptions.ExpectTimeoutError:
-            LOG.error("Failed to terminate task on {session.host}")
-            return False
+        except exceptions.ExpectTimeoutError:
+            LOG.exception(
+                f"Failed to terminate task on {session.host}:{session.port}"
+            )
+            return False
+        else:
+            return True
🧹 Nitpick comments (10)
optional_plugins/golang/pyproject.toml (2)

1-3: Consider raising the setuptools lower bound (and dropping wheel here).

Setuptools 80.x is current and includes many PEP 621 fixes; 61.0 is quite old. Recommend setuptools>=68 (or higher) and omitting wheel from [build-system] (pip brings it as needed).

Based on learnings

-[build-system]
-requires = ["setuptools>=61.0", "wheel"]
+[build-system]
+requires = ["setuptools>=68"]
 build-backend = "setuptools.build_meta"

27-29: Use HTTPS for the homepage URL.

Avoid HTTP and redirects.

 [project.urls]
-Homepage = "http://avocado-framework.github.io/"
+Homepage = "https://avocado-framework.github.io/"
optional_plugins/golang/setup.py (1)

1-1: Shebang present but file not executable.

Either make it executable or drop the shebang.

Please confirm intent. If not directly executed, remove the first line.

-#!/usr/bin/env python3
+#
optional_plugins/robot/setup.py (1)

1-1: Shebang present but file not executable.

Either chmod +x or remove the shebang.

If not intended to be run directly, remove it:

-#!/usr/bin/env python3
+#
optional_plugins/mail/setup.py (1)

1-1: Shebang present but file not executable.

Make executable or remove the shebang.

-#!/usr/bin/env python3
+#
.github/workflows/setup.yml (2)

113-113: Standardize on python -m pip.

Use the interpreter-managed pip consistently to avoid PATH/venv mismatches.

-        run: pip3 install -r requirements-dev.txt
+        run: python3 -m pip install -r requirements-dev.txt
@@
-        run: pip3 install -r requirements-dev.txt
+        run: python3 -m pip install -r requirements-dev.txt
@@
-         pip install -r requirements-dev.txt
+         python3 -m pip install -r requirements-dev.txt

Also applies to: 149-149, 215-215


176-179: Install venv support (python3-venv) or drop the package install.

You’re using python3 -m venv, which requires the python3-venv package on Ubuntu. Installing python3-virtualenv is unnecessary here.

-      - name: Install packages
-        run: |
-         sudo apt-get update
-         sudo apt-get install -y  python3-virtualenv
+      - name: Ensure venv support
+        run: |
+         sudo apt-get update
+         sudo apt-get install -y python3-venv

Repeat the same change in the Developer Virtualenv job.

Also applies to: 209-211

Makefile (3)

78-81: Add a “test” alias and mark missing phony targets.

Satisfy checkmake and provide a conventional test target aliasing check.

 check: clean uninstall develop
 	# Unless manually set, this is equivalent to AVOCADO_CHECK_LEVEL=0
 	$(PYTHON) -m selftests.check
+
+test: check
@@
-.PHONY: pip install clean uninstall requirements-dev smokecheck check develop develop-external develop-plugins develop-plugin variables man
+.PHONY: all test pip install clean uninstall requirements-dev smokecheck check develop develop-external develop-plugins develop-plugin variables man

Based on static analysis hints

Also applies to: 124-124


82-84: Prefer pip editable installs over setup.py develop.

Align with the pyproject/pip migration and reduce reliance on legacy setup.py behavior.

 develop:
-	$(PYTHON) setup.py develop $(PYTHON_DEVELOP_ARGS)
+	$(PYTHON) -m pip install -e . $(PYTHON_DEVELOP_ARGS)
@@
-	$(PYTHON) setup.py develop $(PYTHON_DEVELOP_ARGS) --external
+	$(PYTHON) -m pip install -e . $(PYTHON_DEVELOP_ARGS)
@@
 develop-plugins:
 	@echo "Installing all optional plugins in develop mode..."
 	@for plugin in $(AVOCADO_OPTIONAL_PLUGINS); do \
 		echo "Installing $$plugin..."; \
-		$(PYTHON) -m pip install -e "$$plugin" $(PYTHON_DEVELOP_ARGS); \
+		$(PYTHON) -m pip install -e "$$plugin" $(PYTHON_DEVELOP_ARGS); \
 	done
@@
 develop-plugin:
 ifndef PLUGIN
 	$(error PLUGIN is not defined. Usage: make develop-plugin PLUGIN=html)
 endif
 	@echo "Installing plugin: $(PLUGIN)..."
-	$(PYTHON) -m pip install -e "optional_plugins/$(PLUGIN)" $(PYTHON_DEVELOP_ARGS)
+	$(PYTHON) -m pip install -e "optional_plugins/$(PLUGIN)" $(PYTHON_DEVELOP_ARGS)

Also applies to: 89-89, 91-98, 99-105


108-115: Use pattern variables in the man rule.

Make the rule generic and future-proof.

-	@if command -v rst2man >/dev/null 2>&1; then \
-		rst2man man/avocado.rst man/avocado.1; \
+	@if command -v rst2man >/dev/null 2>&1; then \
+		rst2man "$<" "$@"; \
 	else \
 		echo "ERROR: rst2man not found, cannot build manpage"; \
 		exit 1; \
 	fi
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b7a537d and aa38d8c.

📒 Files selected for processing (64)
  • .github/actions/version/action.yml (1 hunks)
  • .github/workflows/ci.yml (13 hunks)
  • .github/workflows/prerelease.yml (1 hunks)
  • .github/workflows/push_ci.yml (1 hunks)
  • .github/workflows/setup.yml (6 hunks)
  • .github/workflows/vmimage.yml (1 hunks)
  • .github/workflows/weekly.yml (2 hunks)
  • Makefile (5 hunks)
  • Makefile.gh (1 hunks)
  • docs/source/guides/contributor/chapters/environment.rst (1 hunks)
  • docs/source/guides/contributor/chapters/plugins.rst (4 hunks)
  • docs/source/guides/writer/chapters/subclassing.rst (2 hunks)
  • examples/plugins/README.rst (1 hunks)
  • examples/plugins/cli-cmd/hello/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_option/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_option/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/setup.py (1 hunks)
  • examples/plugins/job-pre-post/sleep/pyproject.toml (1 hunks)
  • examples/plugins/job-pre-post/sleep/setup.py (1 hunks)
  • examples/plugins/test-pre-post/hello/pyproject.toml (1 hunks)
  • examples/plugins/test-pre-post/hello/setup.py (1 hunks)
  • examples/plugins/tests/README.rst (1 hunks)
  • examples/plugins/tests/magic/pyproject.toml (1 hunks)
  • examples/plugins/tests/magic/setup.py (1 hunks)
  • examples/plugins/tests/rogue/pyproject.toml (1 hunks)
  • examples/plugins/tests/rogue/setup.py (1 hunks)
  • optional_plugins/README.rst (1 hunks)
  • optional_plugins/ansible/README.rst (1 hunks)
  • optional_plugins/ansible/pyproject.toml (1 hunks)
  • optional_plugins/ansible/setup.py (2 hunks)
  • optional_plugins/golang/pyproject.toml (1 hunks)
  • optional_plugins/golang/setup.py (3 hunks)
  • optional_plugins/html/pyproject.toml (1 hunks)
  • optional_plugins/html/setup.py (2 hunks)
  • optional_plugins/mail/pyproject.toml (1 hunks)
  • optional_plugins/mail/setup.py (2 hunks)
  • optional_plugins/result_upload/pyproject.toml (1 hunks)
  • optional_plugins/result_upload/setup.py (2 hunks)
  • optional_plugins/resultsdb/pyproject.toml (1 hunks)
  • optional_plugins/resultsdb/setup.py (2 hunks)
  • optional_plugins/robot/avocado_robot/robot.py (1 hunks)
  • optional_plugins/robot/pyproject.toml (1 hunks)
  • optional_plugins/robot/setup.py (2 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/__init__.py (0 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1 hunks)
  • optional_plugins/spawner_remote/pyproject.toml (1 hunks)
  • optional_plugins/spawner_remote/setup.py (2 hunks)
  • optional_plugins/varianter_cit/pyproject.toml (1 hunks)
  • optional_plugins/varianter_cit/setup.py (2 hunks)
  • optional_plugins/varianter_pict/README.rst (1 hunks)
  • optional_plugins/varianter_pict/pyproject.toml (1 hunks)
  • optional_plugins/varianter_pict/setup.py (2 hunks)
  • optional_plugins/varianter_yaml_to_mux/README.rst (4 hunks)
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/setup.py (3 hunks)
  • pyproject.toml (1 hunks)
  • python-avocado.spec (6 hunks)
  • selftests/utils.py (2 hunks)
  • setup.py (3 hunks)
  • spell.ignore (1 hunks)
💤 Files with no reviewable changes (1)
  • optional_plugins/spawner_remote/avocado_spawner_remote/init.py
✅ Files skipped from review due to trivial changes (2)
  • examples/plugins/cli-cmd/hello/pyproject.toml
  • optional_plugins/varianter_pict/README.rst
🚧 Files skipped from review as they are similar to previous changes (28)
  • .github/actions/version/action.yml
  • optional_plugins/varianter_yaml_to_mux/README.rst
  • optional_plugins/varianter_cit/pyproject.toml
  • .github/workflows/prerelease.yml
  • optional_plugins/spawner_remote/pyproject.toml
  • optional_plugins/mail/pyproject.toml
  • examples/plugins/tests/README.rst
  • optional_plugins/varianter_pict/pyproject.toml
  • examples/plugins/test-pre-post/hello/setup.py
  • optional_plugins/robot/pyproject.toml
  • optional_plugins/ansible/README.rst
  • docs/source/guides/contributor/chapters/plugins.rst
  • examples/plugins/tests/magic/pyproject.toml
  • optional_plugins/ansible/pyproject.toml
  • examples/plugins/cli-cmd/hello_option/pyproject.toml
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml
  • examples/plugins/test-pre-post/hello/pyproject.toml
  • Makefile.gh
  • spell.ignore
  • optional_plugins/html/pyproject.toml
  • examples/plugins/job-pre-post/sleep/pyproject.toml
  • examples/plugins/cli-cmd/hello_option/setup.py
  • examples/plugins/tests/magic/setup.py
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml
  • .github/workflows/ci.yml
  • examples/plugins/cli-cmd/hello/setup.py
  • .github/workflows/push_ci.yml
🧰 Additional context used
🧬 Code graph analysis (2)
optional_plugins/robot/avocado_robot/robot.py (1)
avocado/core/test.py (1)
  • name (367-373)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (5)
avocado/core/plugin_interfaces.py (2)
  • Init (23-28)
  • Spawner (321-441)
avocado/core/spawners/common.py (3)
  • SpawnerMixin (35-47)
  • SpawnMethod (6-18)
  • task_output_dir (46-47)
avocado/core/settings.py (3)
  • section (137-138)
  • register_option (642-799)
  • key (141-142)
avocado/core/nrunner/runnable.py (1)
  • pick_runner_module_from_entry_point_kind (630-643)
avocado/core/nrunner/task.py (1)
  • setup_output_dir (171-174)
🪛 checkmake (0.2.2)
Makefile

[warning] 124-124: Missing required phony target "all"

(minphony)


[warning] 124-124: Missing required phony target "test"

(minphony)

🪛 Ruff (0.14.0)
optional_plugins/resultsdb/setup.py

1-1: Shebang is present but file is not executable

(EXE001)

optional_plugins/robot/setup.py

1-1: Shebang is present but file is not executable

(EXE001)

optional_plugins/html/setup.py

1-1: Shebang is present but file is not executable

(EXE001)

optional_plugins/result_upload/setup.py

1-1: Shebang is present but file is not executable

(EXE001)

optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py

73-73: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


74-74: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


126-129: Avoid specifying long messages outside the exception class

(TRY003)


197-197: Probable insecure usage of temporary file or directory: "/tmp/.avocado_task_output_dir"

(S108)


213-213: Consider moving this statement to an else block

(TRY300)


215-215: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


219-219: Unused static method argument: runtime_task

(ARG004)


224-224: Unused static method argument: runtime_task

(ARG004)

optional_plugins/varianter_yaml_to_mux/setup.py

1-1: Shebang is present but file is not executable

(EXE001)

optional_plugins/spawner_remote/setup.py

1-1: Shebang is present but file is not executable

(EXE001)

optional_plugins/varianter_pict/setup.py

1-1: Shebang is present but file is not executable

(EXE001)

optional_plugins/golang/setup.py

1-1: Shebang is present but file is not executable

(EXE001)

optional_plugins/mail/setup.py

1-1: Shebang is present but file is not executable

(EXE001)

optional_plugins/varianter_cit/setup.py

1-1: Shebang is present but file is not executable

(EXE001)

optional_plugins/ansible/setup.py

1-1: Shebang is present but file is not executable

(EXE001)

selftests/utils.py

29-29: Consider moving this statement to an else block

(TRY300)


30-30: Do not catch blind exception: Exception

(BLE001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (40)
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: Fedora selftests
  • GitHub Check: Smokecheck on Linux with Python 3.11
  • GitHub Check: Smokecheck on Linux with Python 3.10
  • GitHub Check: macOS with Python 3.11
🔇 Additional comments (26)
optional_plugins/robot/avocado_robot/robot.py (2)

44-44: LGTM! Consistent with the API compatibility change.

The usage of test_name is correct and maintains backward compatibility while supporting the new TestCase object API.


41-42: Require manual verification of Robot Framework API compatibility

  • Test with both old (string test names) and new (TestCase objects) Robot Framework versions to confirm hasattr(test_case, "name") handles both correctly and that name is always a valid string.
  • Add tests covering both scenarios and document supported Robot Framework versions (e.g., in requirements or project docs).
docs/source/guides/contributor/chapters/environment.rst (1)

55-62: LGTM! Documentation correctly updated for modern packaging.

The migration from python setup.py develop to pip install -e . is the recommended approach, and the terminology updates (editable install vs. egg link) align with current Python packaging standards.

examples/plugins/README.rst (1)

10-24: LGTM! Clear documentation for pip-based plugin workflow.

The updated instructions provide clear examples for both installation and uninstallation using pip, which is more intuitive than the previous setup.py-based approach.

examples/plugins/cli-cmd/hello_parser/setup.py (1)

1-30: LGTM! Minimal setup.py correctly implements backward compatibility.

The file now serves its intended purpose: maintaining compatibility with legacy tooling while delegating all configuration to pyproject.toml. The docstring clearly explains the rationale.

examples/plugins/job-pre-post/sleep/setup.py (1)

1-30: LGTM! Consistent backward-compatible setup.py.

This file follows the same minimal pattern as other plugins in the PR, correctly delegating all configuration to pyproject.toml while maintaining backward compatibility.

examples/plugins/tests/rogue/setup.py (1)

1-30: LGTM! Backward-compatible setup.py follows PR conventions.

Consistent with the other minimal setup.py files in this migration, providing backward compatibility while moving configuration to pyproject.toml.

optional_plugins/README.rst (1)

10-34: LGTM! Clear documentation for pip-based plugin management.

The updated instructions provide comprehensive examples for installing, uninstalling, and managing optional plugins using modern pip-based workflows and updated make targets.

examples/plugins/cli-cmd/hello_priority/setup.py (1)

1-30: LGTM! Consistent minimal setup.py pattern.

This file correctly follows the established pattern for backward-compatible setup.py files in the migration to pyproject.toml-based configuration.

optional_plugins/varianter_cit/setup.py (2)

16-26: LGTM!

The docstring clearly explains the purpose of this minimal setup.py for backward compatibility with egg distributions.


32-48: LGTM!

The minimal setup() call correctly includes entry_points for egg builds, keeping them in sync with pyproject.toml as noted in the docstring.

python-avocado.spec (4)

8-8: LGTM!

The package naming updates from avocado- to avocado_framework- correctly align with the pyproject.toml-based packaging structure.

Also applies to: 18-18


102-104: LGTM!

The setup directory names correctly match the updated gittar archive naming.


220-220: LGTM!

Using python3 -m selftests.check for test invocation is the correct approach for module-based execution, aligning with the pyproject.toml migration.


243-250: Adjust spec exclusions to match actual install layout

  • Exclude avocado_result_html under %{python3_sitelib}/optional_plugins/html/avocado_result_html* instead of directly under %{python3_sitelib}.
  • Verify whether contrib and scripts land at top-level site-packages or within a package namespace (e.g. avocado/contrib*, avocado/scripts*) and update the %exclude directives accordingly.
optional_plugins/result_upload/pyproject.toml (1)

1-44: LGTM!

The pyproject.toml configuration correctly defines the build system, project metadata, entry points, and packaging configuration following PEP-621 standards. The dynamic version/readme loading and entry points align with the broader migration strategy.

optional_plugins/result_upload/setup.py (1)

36-48: LGTM!

The minimal setup() call correctly includes entry_points for egg builds, addressing the previous review concerns about broken plugin discovery. The entry points are properly synced with pyproject.toml.

pyproject.toml (2)

1-3: LGTM!

The build system configuration, core project metadata, and project URLs are correctly defined following PEP-621 standards.

Also applies to: 5-12, 33-37


39-53: LGTM!

The console scripts and plugin entry points are comprehensively defined, covering all plugin groups (init, cli, runners, spawners, etc.). The structure aligns well with the broader migration to pyproject.toml-based packaging.

Also applies to: 56-166

docs/source/guides/writer/chapters/subclassing.rst (2)

20-47: LGTM!

The documentation correctly updates the packaging example from setup.py to pyproject.toml, following PEP-621 standards. The example demonstrates dynamic version sourcing and avocado-framework dependency declaration, which aligns with the migration strategy.


88-101: LGTM!

The installation instructions correctly update from the deprecated python setup.py develop to the modern pip install -e . approach for editable installs.

.github/workflows/vmimage.yml (1)

42-42: LGTM!

The update from python setup.py develop to python -m pip install --user -e . correctly modernizes the workflow to use pip-based editable installs, aligning with the pyproject.toml migration.

.github/workflows/weekly.yml (2)

39-39: LGTM!

The updates from python3 setup.py develop to python3 -m pip install --user -e . correctly modernize both workflow jobs to use pip-based editable installs.

Also applies to: 83-83


47-47: LGTM!

The changes from direct script invocation to module-based execution (python3 -m selftests.check) align with the pyproject.toml migration and ensure the correct module is executed.

Also applies to: 89-89

.github/workflows/setup.yml (1)

33-44: Verify user-base bin is on PATH for --user installs.

avocado goes to ~/.local/bin with --user; ensure PATH includes it before invoking avocado.

Add before “Avocado version”:

- name: Ensure user bin on PATH
  run: echo "$HOME/.local/bin" >> $GITHUB_PATH
optional_plugins/resultsdb/pyproject.toml (1)

1-48: Packaging config looks good.

Project metadata, dynamic version/readme, entry points, and package discovery are correctly set.

@@ -1,4 +1,4 @@
#!/bin/env python3
#!/usr/bin/env python3
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Shebang present but file not executable.

The static analysis tool flags that this file has a shebang but lacks executable permissions. Either add executable permissions (chmod +x) or remove the shebang if the file is not meant to be executed directly.

Apply this fix:

chmod +x optional_plugins/result_upload/setup.py
🧰 Tools
🪛 Ruff (0.14.0)

1-1: Shebang is present but file is not executable

(EXE001)

🤖 Prompt for AI Agents
In optional_plugins/result_upload/setup.py at line 1, the file contains a
shebang but is not marked executable; make the file executable by adding the
executable permission (e.g., chmod +x) so the shebang is respected, or
alternatively remove the shebang if this script is not intended to be run
directly—prefer adding the executable bit if it should be runnable.

@@ -1,4 +1,4 @@
#!/bin/env python3
#!/usr/bin/env python3
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Drop the shebang (or make the file executable).

This is not a runnable script; the shebang triggers EXE001. Prefer removing it.

As a fix:

-#!/usr/bin/env python3

Based on static analysis hints

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#!/usr/bin/env python3
🧰 Tools
🪛 Ruff (0.14.0)

1-1: Shebang is present but file is not executable

(EXE001)

🤖 Prompt for AI Agents
In optional_plugins/resultsdb/setup.py at lines 1-1, the file starts with a
shebang but is not an executable script which triggers EXE001; remove the
leading "#!/usr/bin/env python3" line (or alternatively make the file executable
and ensure it is intended to be run as a script). After removing the shebang,
run lint/static analysis to confirm EXE001 is resolved.

@@ -1,4 +1,4 @@
#!/bin/env python3
#!/usr/bin/env python3
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Drop the shebang (or make the file executable).

Not a script; remove to silence EXE001.

As a fix:

-#!/usr/bin/env python3

Based on static analysis hints

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#!/usr/bin/env python3
🧰 Tools
🪛 Ruff (0.14.0)

1-1: Shebang is present but file is not executable

(EXE001)

🤖 Prompt for AI Agents
In optional_plugins/spawner_remote/setup.py around line 1, the file contains a
shebang but is not an executable script which triggers EXE001; remove the
leading "#!/usr/bin/env python3" line to silence the warning (or alternatively
make the file executable and keep the shebang if it is intended to be run
directly).

@PaulYuuu PaulYuuu force-pushed the pyproject branch 2 times, most recently from b149318 to 1d4c3bf Compare October 14, 2025 09:25
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
optional_plugins/ansible/setup.py (1)

28-36: Align dependency markers in pyproject.toml with setup.py
Mirror the cffi python_version markers, add cryptography<46.0.0; python_version<'3.10', and include or confirm markupsafe<3.0.0 in optional_plugins/ansible/pyproject.toml.

♻️ Duplicate comments (23)
selftests/utils.py (1)

30-32: Catch specific exception type instead of bare Exception.

This issue was previously identified: catching Exception is too broad and may mask unexpected errors (e.g., AttributeError, TypeError) that should propagate. The importlib.metadata.distribution() function raises PackageNotFoundError when a distribution isn't found.

The fix was already suggested in the previous review comment—import and catch PackageNotFoundError specifically instead of the bare Exception.

examples/plugins/cli-cmd/hello/setup.py (1)

1-15: Past review comment already addressed.

The metadata in this setup.py now correctly matches the pyproject.toml configuration. The name, description, and entry points are consistent.

Makefile.gh (1)

65-69: Add guard for pyproject.toml existence before building wheels.

The unconditional python -m build invocation will fail for any plugin directory lacking a pyproject.toml file. This inconsistency with the build-egg target (lines 78-83, which checks for setup.py existence) creates a fragile build process.

Apply this diff to add the guard:

 build-wheel: pip
 	$(PYTHON) -m pip install $(PYTHON_DEVELOP_ARGS) build
 	if test ! -d PYPI_UPLOAD; then mkdir PYPI_UPLOAD; fi
 	$(PYTHON) -m build -o PYPI_UPLOAD
 	for PLUGIN in $(AVOCADO_OPTIONAL_PLUGINS); do\
-		cd $$PLUGIN;\
-		$(PYTHON) -m build -o ../../PYPI_UPLOAD;\
-		cd -;\
+		if test -f $$PLUGIN/pyproject.toml; then\
+			cd $$PLUGIN;\
+			$(PYTHON) -m build -o ../../PYPI_UPLOAD;\
+			cd -;\
+		fi;\
 	done
examples/plugins/tests/magic/pyproject.toml (1)

25-25: Complete the package discovery configuration.

The [tool.setuptools.packages.find] section is empty. Entry points reference avocado_magic module, so you must specify which packages to include for proper packaging.

Apply this diff:

 [tool.setuptools.packages.find]
+include = ["avocado_magic*"]
examples/plugins/tests/rogue/pyproject.toml (1)

19-20: Package declaration doesn't match entry point targets.

The configuration declares py-modules = ["avocado_rogue"] (single-file module) but entry points target submodules like avocado_rogue.runner:main and avocado_rogue.resolver:RogueResolver, which require a package directory structure.

If avocado_rogue is a package directory (with __init__.py, resolver.py, runner.py), apply this diff:

 [tool.setuptools]
-py-modules = ["avocado_rogue"]
+packages = ["avocado_rogue"]

If it's a single file avocado_rogue.py, the entry points must be updated to target the top-level module instead.

python-avocado.spec (1)

243-250: Verify the new package exclusions match actual install locations.

The additions of contrib*, scripts*, and avocado_result_html* to the exclusions list should align with the actual package structure. A past review flagged that avocado_result_html* may need path correction.

Run the following script to verify these directories/packages exist and their install locations:

#!/bin/bash
# Description: Verify excluded paths exist and check their installed locations

# Check contrib directory
echo "=== Checking contrib directory ==="
fd -t d '^contrib$' --max-depth 2

# Check scripts directory  
echo "=== Checking scripts directory ==="
fd -t d '^scripts$' --max-depth 2

# Check avocado_result_html locations
echo "=== Checking avocado_result_html package ==="
fd -t d 'avocado_result_html' --max-depth 4

# Check if avocado_result_html is in optional_plugins
echo "=== Checking optional_plugins structure ==="
fd -t d . optional_plugins --max-depth 2
setup.py (4)

31-31: Critical: distutils import breaks on Python 3.12+.

The import from distutils.command.clean import clean will fail on Python 3.12+ as distutils was removed from the standard library.

Apply this diff:

-from distutils.command.clean import clean  # pylint: disable=W0402
+from setuptools.command.clean import clean

Based on learnings


212-212: Critical: find_packages() includes optional plugins unintentionally.

With packages=find_packages(exclude=("selftests*",)), packages under optional_plugins/* (e.g., avocado_result_upload) will be included in the wheel, causing conflicts with separate plugin packages.

Apply this diff:

-    packages=find_packages(exclude=("selftests*",)),
+    packages=find_packages(include=("avocado", "avocado.*")),

237-237: Critical: scripts.external_runner requires scripts/init.py.

The console script entry point "avocado-external-runner = scripts.external_runner:main" references scripts as a package, but without scripts/__init__.py, the module won't be packaged and the entry point will fail at runtime.

Verify and add scripts/__init__.py:

#!/bin/bash
# Description: Check if scripts/__init__.py exists and scripts package is properly configured

echo "=== Checking scripts/__init__.py ==="
if [ -f "scripts/__init__.py" ]; then
  echo "✓ scripts/__init__.py exists"
else
  echo "✗ scripts/__init__.py is missing - will break console script"
fi

echo "=== Checking scripts/external_runner.py ==="
if [ -f "scripts/external_runner.py" ]; then
  echo "✓ scripts/external_runner.py exists"
  rg -q 'def main\(' scripts/external_runner.py && echo "✓ main() function found" || echo "✗ main() function not found"
else
  echo "✗ scripts/external_runner.py is missing"
fi

221-350: Major: Entry points duplication creates maintenance burden.

The 130-line entry_points dictionary duplicates configuration from pyproject.toml. Any plugin changes must be updated in both files, creating a significant maintenance risk.

Consider these options:

  1. Eliminate egg builds (preferred): Deprecate egg distribution support and remove setup.py entirely, relying only on wheel/sdist from pyproject.toml.

  2. Programmatic sync: Parse pyproject.toml at build time to generate entry_points.

  3. Add validation: Create a CI check comparing entry_points between both files.

Would you like me to generate a validation script?

pyproject.toml (3)

14-16: Critical: Missing runtime dependencies.

The [project].dependencies list contains only "setuptools", but the codebase imports many third-party packages (e.g., psutil, requests, jsonschema) that were previously in setup.py's install_requires. This will cause ImportError at runtime after pip install avocado-framework.

Restore the full dependency list from setup.py. At minimum, add:

  • psutil (for avocado.plugins.sysinfo)
  • requests (for avocado.utils.asset)
  • jsonschema (for schema validation)
 dependencies = [
     "setuptools",
+    "psutil>=5.9.5",
+    "requests>=2.32.3",
+    "jsonschema>=3.2.0",
 ]

Move purely optional features to [project.optional-dependencies].

Based on learnings


30-30: Major: Remove unsupported Python 3.14 classifier.

PyPI does not yet publish the "Programming Language :: Python :: 3.14" classifier. Including it will cause package upload failures.

Apply this diff:

     "Programming Language :: Python :: 3.12",
     "Programming Language :: Python :: 3.13",
-    "Programming Language :: Python :: 3.14",
 ]

Re-add it only after PyPI publishes the official classifier.


54-54: Critical: scripts.external_runner entry point will fail.

The console script avocado-external-runner = "scripts.external_runner:main" references the scripts module, but line 239 excludes "scripts*" from packaging. The entry point will fail at runtime with ModuleNotFoundError.

Apply this diff:

 [tool.setuptools.packages.find]
-exclude = ["selftests*", "docs*", "examples*", "contrib*", "scripts*", "optional_plugins*"]
+exclude = ["selftests*", "docs*", "examples*", "contrib*", "optional_plugins*"]

Alternatively, move the external_runner module into a packaged location (e.g., avocado.scripts.external_runner) and update the entry point.

.github/workflows/prerelease.yml (1)

51-51: Critical: Typo in module name.

The command contains selftets.check instead of selftests.check. This will cause the workflow to fail with a ModuleNotFoundError.

Apply this diff:

-        run: python3 -m selftets.check --select=pre-release,vmimage
+        run: python3 -m selftests.check --select=pre-release,vmimage
optional_plugins/spawner_remote/setup.py (1)

1-1: Drop the non-portable shebang (or make the file executable).

Not a script; remove to silence EXE001. Also uses /bin/env.

-#!/bin/env python3
optional_plugins/ansible/setup.py (1)

1-1: Drop the shebang (or use /usr/bin/env and make executable).

Not intended to be executed directly; remove to avoid EXE001.

-#!/bin/env python3
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (7)

143-145: Shell-escape the identifier in pgrep.

Avoid breakage/injection with special chars.

-        status, _ = session.cmd_status_output(
-            f"pgrep -r R,S -f {runtime_task.task.identifier}"
-        )
+        safe_identifier = shlex.quote(runtime_task.task.identifier)
+        status, _ = session.cmd_status_output(f"pgrep -r R,S -f {safe_identifier}")

148-193: Spawn must be non-blocking; keep output (don’t redirect to /dev/null).

run_remote_cmd_async waits for completion, so current spawn_task blocks and discards logs.

-        cmd = shlex.join(entry_point_args) + " > /dev/null"
-        timeout = self.config.get("spawner.remote.test_timeout")
-        status, output = await RemoteSpawner.run_remote_cmd_async(session, cmd, timeout)
+        # Launch in background and capture PID; keep stdout/stderr to a remote log
+        remote_log = f"/tmp/avocado-task-{task.identifier.replace('/', '_')}.log"
+        cmd = shlex.join(entry_point_args) + f" > {shlex.quote(remote_log)} 2>&1 & echo $!"
+        # Short timeout since backgrounding returns immediately
+        status, output = await RemoteSpawner.run_remote_cmd_async(session, cmd, 30)
         LOG.debug(f"Command exited with code {status}")
         if status != 0:
             LOG.error(
                 f"Error exit code {status} on {session.host}:{session.port} "
                 f"with output:\n{output}"
             )
             return False

Optionally parse and store the PID from output for advanced liveness checks.


195-201: Use a unique, sanitized remote output directory per task.

Single fixed path collides across tasks and is flagged insecure.

-        output_lxc_path = "/tmp/.avocado_task_output_dir"
+        safe_id = "".join(c if c.isalnum() or c in "._-" else "_" for c in runtime_task.task.identifier)[:64]
+        output_lxc_path = f"/tmp/.avocado_task_output_dir_{safe_id}"

202-206: Add timeout to wait_task to avoid infinite waits.

Honor spawner.remote.test_timeout (or a dedicated wait timeout).

-    async def wait_task(self, runtime_task):
-        while True:
-            if not RemoteSpawner.is_task_alive(runtime_task):
-                return
-            await asyncio.sleep(0.1)
+    async def wait_task(self, runtime_task):
+        timeout = self.config.get("spawner.remote.test_timeout")
+        start = asyncio.get_event_loop().time()
+        while True:
+            if not RemoteSpawner.is_task_alive(runtime_task):
+                return
+            if timeout and (asyncio.get_event_loop().time() - start) > timeout:
+                raise RemoteSpawnerException("Task wait timed out")
+            await asyncio.sleep(0.1)

208-216: Improve terminate_task logging and flow.

Use LOG.exception for traceback; return in else as per TRY300.

     async def terminate_task(self, runtime_task):
         session = runtime_task.spawner_handle
         session.sendcontrol("c")
         try:
             session.read_up_to_prompt()
-            return True
         except exceptions.ExpectTimeoutError:
-            LOG.error("Failed to terminate task on {session.host}")
+            LOG.exception(f"Failed to terminate task on {session.host}:{session.port}")
             return False
+        else:
+            return True

38-46: Timeout options need key_type=int to avoid string timeouts.

Without key_type=int these become strings, breaking arithmetic/timeout usage.

 help_msg = "Test timeout enforced for remote host setup hook"
 settings.register_option(
-    section=section, key="setup_timeout", help_msg=help_msg, default=3600
+    section=section, key="setup_timeout", help_msg=help_msg, default=3600, key_type=int
 )
 ...
 help_msg = "Test timeout enforced for sessions (just for this spawner)"
 settings.register_option(
-    section=section, key="test_timeout", help_msg=help_msg, default=14400
+    section=section, key="test_timeout", help_msg=help_msg, default=14400, key_type=int
 )

106-115: Harden slot initialization with error handling.

Single bad file/malformed JSON/failed login currently crashes initialization.

-            for session_slot in self.config.get("spawner.remote.slots"):
+            for session_slot in self.config.get("spawner.remote.slots"):
                 if not session_slot:
                     continue
-                with open(session_slot, "r", encoding="utf-8") as f:
-                    session_data = json.load(f)
-                session = remote.remote_login(**session_data)
-                RemoteSpawner.slots_cache[session] = False
+                try:
+                    with open(session_slot, "r", encoding="utf-8") as f:
+                        session_data = json.load(f)
+                    session = remote.remote_login(**session_data)
+                    RemoteSpawner.slots_cache[session] = False
+                except (FileNotFoundError, json.JSONDecodeError, KeyError) as e:
+                    LOG.warning(f"Failed to initialize slot from {session_slot}: {e}")
+                except Exception as e:
+                    LOG.error(f"Unexpected error initializing slot from {session_slot}: {e}")
🧹 Nitpick comments (7)
optional_plugins/robot/avocado_robot/robot.py (1)

41-44: Handle empty test case names.

TestCase.name never returns None but can be an empty string. To prevent empty test_name values downstream, consider skipping or warning on unnamed tests:

 # Handle both string (old API) and TestCase object (new API)
 test_name = test_case.name if hasattr(test_case, "name") else test_case
-if not test_name:
-    continue
+# Skip or warn on empty test names
+if not test_name:
+    continue  # or log a warning for unnamed tests
 test_suite[data.name].append(
     {"test_name": test_name, "test_source": data.source}
 )
optional_plugins/varianter_cit/setup.py (1)

22-22: Use context manager for file handling consistency.

Other setup.py files in this PR (html, robot, varianter_pict) use a context manager when reading VERSION. Apply the same pattern here for consistency and proper resource management.

Apply this diff:

-VERSION = open("VERSION", "r", encoding="utf-8").read().strip()
+with open("VERSION", "r", encoding="utf-8") as version_file:
+    VERSION = version_file.read().strip()
.github/workflows/setup.yml (2)

176-181: Use python3-venv (or drop apt entirely) when creating venvs.

You install python3-virtualenv but call python3 -m venv. Either:

  • Use the stdlib venv (recommended) and install python3-venv if needed:
    • sudo apt-get install -y python3-venv
  • Or use virtualenv: python3 -m virtualenv env

Example fix:

- sudo apt-get install -y  python3-virtualenv
+ sudo apt-get install -y  python3-venv

113-113: Standardize on python -m pip.

Minor consistency/readability improvement; prefer python3 -m pip over bare pip3.

Also applies to: 149-149, 215-215

optional_plugins/ansible/pyproject.toml (1)

31-31: Use HTTPS for the project URL.

Small polish.

-Homepage = "http://avocado-framework.github.io/"
+Homepage = "https://avocado-framework.github.io/"
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (2)

81-85: Prefer get_running_loop() in async context.

Minor modernization.

-        loop = asyncio.get_event_loop()
+        loop = asyncio.get_running_loop()

73-75: Annotate mutable class attributes as ClassVar.

Silences RUF012 and clarifies intent.

+from typing import ClassVar
@@
-    METHODS = [SpawnMethod.STANDALONE_EXECUTABLE]
-    slots_cache = {}
+    METHODS: ClassVar = [SpawnMethod.STANDALONE_EXECUTABLE]
+    slots_cache: ClassVar[dict] = {}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aa38d8c and 1d4c3bf.

📒 Files selected for processing (64)
  • .github/actions/version/action.yml (1 hunks)
  • .github/workflows/ci.yml (13 hunks)
  • .github/workflows/prerelease.yml (1 hunks)
  • .github/workflows/push_ci.yml (1 hunks)
  • .github/workflows/setup.yml (6 hunks)
  • .github/workflows/vmimage.yml (1 hunks)
  • .github/workflows/weekly.yml (2 hunks)
  • Makefile (5 hunks)
  • Makefile.gh (1 hunks)
  • docs/source/guides/contributor/chapters/environment.rst (1 hunks)
  • docs/source/guides/contributor/chapters/plugins.rst (4 hunks)
  • docs/source/guides/writer/chapters/subclassing.rst (2 hunks)
  • examples/plugins/README.rst (1 hunks)
  • examples/plugins/cli-cmd/hello/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_option/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_option/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/setup.py (1 hunks)
  • examples/plugins/job-pre-post/sleep/pyproject.toml (1 hunks)
  • examples/plugins/job-pre-post/sleep/setup.py (1 hunks)
  • examples/plugins/test-pre-post/hello/pyproject.toml (1 hunks)
  • examples/plugins/test-pre-post/hello/setup.py (1 hunks)
  • examples/plugins/tests/README.rst (1 hunks)
  • examples/plugins/tests/magic/pyproject.toml (1 hunks)
  • examples/plugins/tests/magic/setup.py (1 hunks)
  • examples/plugins/tests/rogue/pyproject.toml (1 hunks)
  • examples/plugins/tests/rogue/setup.py (1 hunks)
  • optional_plugins/README.rst (1 hunks)
  • optional_plugins/ansible/README.rst (1 hunks)
  • optional_plugins/ansible/pyproject.toml (1 hunks)
  • optional_plugins/ansible/setup.py (2 hunks)
  • optional_plugins/golang/pyproject.toml (1 hunks)
  • optional_plugins/golang/setup.py (1 hunks)
  • optional_plugins/html/pyproject.toml (1 hunks)
  • optional_plugins/html/setup.py (1 hunks)
  • optional_plugins/mail/pyproject.toml (1 hunks)
  • optional_plugins/mail/setup.py (1 hunks)
  • optional_plugins/result_upload/pyproject.toml (1 hunks)
  • optional_plugins/result_upload/setup.py (1 hunks)
  • optional_plugins/resultsdb/pyproject.toml (1 hunks)
  • optional_plugins/resultsdb/setup.py (1 hunks)
  • optional_plugins/robot/avocado_robot/robot.py (1 hunks)
  • optional_plugins/robot/pyproject.toml (1 hunks)
  • optional_plugins/robot/setup.py (1 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/__init__.py (0 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1 hunks)
  • optional_plugins/spawner_remote/pyproject.toml (1 hunks)
  • optional_plugins/spawner_remote/setup.py (1 hunks)
  • optional_plugins/varianter_cit/pyproject.toml (1 hunks)
  • optional_plugins/varianter_cit/setup.py (1 hunks)
  • optional_plugins/varianter_pict/README.rst (1 hunks)
  • optional_plugins/varianter_pict/pyproject.toml (1 hunks)
  • optional_plugins/varianter_pict/setup.py (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/README.rst (4 hunks)
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/setup.py (1 hunks)
  • pyproject.toml (1 hunks)
  • python-avocado.spec (6 hunks)
  • selftests/utils.py (2 hunks)
  • setup.py (3 hunks)
  • spell.ignore (1 hunks)
💤 Files with no reviewable changes (1)
  • optional_plugins/spawner_remote/avocado_spawner_remote/init.py
✅ Files skipped from review due to trivial changes (4)
  • docs/source/guides/contributor/chapters/environment.rst
  • optional_plugins/varianter_pict/README.rst
  • examples/plugins/cli-cmd/hello_parser/setup.py
  • examples/plugins/cli-cmd/hello_option/setup.py
🚧 Files skipped from review as they are similar to previous changes (28)
  • spell.ignore
  • examples/plugins/job-pre-post/sleep/pyproject.toml
  • examples/plugins/README.rst
  • .github/actions/version/action.yml
  • optional_plugins/ansible/README.rst
  • examples/plugins/cli-cmd/hello_option/pyproject.toml
  • optional_plugins/varianter_yaml_to_mux/README.rst
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml
  • examples/plugins/tests/README.rst
  • optional_plugins/spawner_remote/pyproject.toml
  • optional_plugins/resultsdb/pyproject.toml
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml
  • optional_plugins/varianter_yaml_to_mux/setup.py
  • optional_plugins/resultsdb/setup.py
  • optional_plugins/golang/pyproject.toml
  • optional_plugins/golang/setup.py
  • .github/workflows/vmimage.yml
  • optional_plugins/robot/pyproject.toml
  • optional_plugins/result_upload/setup.py
  • docs/source/guides/contributor/chapters/plugins.rst
  • optional_plugins/mail/pyproject.toml
  • optional_plugins/varianter_pict/pyproject.toml
  • optional_plugins/varianter_cit/pyproject.toml
  • optional_plugins/mail/setup.py
  • .github/workflows/ci.yml
  • examples/plugins/cli-cmd/hello_priority/setup.py
  • examples/plugins/tests/magic/setup.py
  • optional_plugins/html/pyproject.toml
🧰 Additional context used
🧬 Code graph analysis (2)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (5)
avocado/core/plugin_interfaces.py (2)
  • Init (23-28)
  • Spawner (321-441)
avocado/core/spawners/common.py (3)
  • SpawnerMixin (35-47)
  • SpawnMethod (6-18)
  • task_output_dir (46-47)
avocado/core/settings.py (3)
  • section (137-138)
  • register_option (642-799)
  • key (141-142)
avocado/core/nrunner/runnable.py (1)
  • pick_runner_module_from_entry_point_kind (630-643)
avocado/core/nrunner/task.py (1)
  • setup_output_dir (171-174)
optional_plugins/robot/avocado_robot/robot.py (1)
avocado/core/test.py (1)
  • name (367-373)
🪛 checkmake (0.2.2)
Makefile

[warning] 124-124: Missing required phony target "all"

(minphony)


[warning] 124-124: Missing required phony target "test"

(minphony)

🪛 Ruff (0.14.0)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py

73-73: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


74-74: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


126-129: Avoid specifying long messages outside the exception class

(TRY003)


197-197: Probable insecure usage of temporary file or directory: "/tmp/.avocado_task_output_dir"

(S108)


213-213: Consider moving this statement to an else block

(TRY300)


215-215: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


219-219: Unused static method argument: runtime_task

(ARG004)


224-224: Unused static method argument: runtime_task

(ARG004)

selftests/utils.py

29-29: Consider moving this statement to an else block

(TRY300)


30-30: Do not catch blind exception: Exception

(BLE001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (42)
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: Egg task ubuntu:24.04
  • GitHub Check: Fedora selftests
  • GitHub Check: Smokecheck on Linux with Python 3.11
  • GitHub Check: Smokecheck on Linux with Python 3.10
  • GitHub Check: macOS with Python 3.11
  • GitHub Check: Static checks
🔇 Additional comments (32)
selftests/utils.py (1)

6-6: LGTM! Modern migration from pkg_resources to importlib.metadata.

The shift from deprecated pkg_resources to the standard-library importlib.metadata aligns with current packaging best practices. Per setuptools v80.9.0 release notes, pkg_resources is scheduled for removal later in 2025, making this migration timely and necessary.

Based on learnings

Also applies to: 28-28

docs/source/guides/writer/chapters/subclassing.rst (2)

28-47: LGTM: Well-structured pyproject.toml example.

The example correctly demonstrates:

  • Modern build-system configuration with setuptools>=61.0
  • Dynamic versioning from VERSION file
  • Proper dependency declaration on avocado-framework
  • Standard package discovery configuration

The migration from setup.py to pyproject.toml is clearly documented.


88-101: LGTM: Installation instructions properly updated.

The documentation correctly updates from python setup.py develop to pip install -e ., which aligns with modern Python packaging best practices and the deprecation of direct setup.py invocation.

examples/plugins/job-pre-post/sleep/setup.py (1)

1-22: LGTM: Consistent backward-compatibility wrapper.

The minimal setup.py correctly retains the configuration for backward compatibility while the header comments appropriately indicate that primary configuration has moved to pyproject.toml. The entry points and module configuration are complete.

examples/plugins/cli-cmd/hello/pyproject.toml (1)

1-14: LGTM: Complete pyproject.toml configuration for CLI plugin.

The configuration properly defines:

  • Modern build-system with setuptools and wheel
  • Clear project metadata matching the setup.py
  • Correctly namespaced entry point under avocado.plugins.cli.cmd
  • Appropriate py-modules declaration

This aligns with the project's migration to pyproject.toml-based packaging.

optional_plugins/varianter_yaml_to_mux/pyproject.toml (1)

1-48: LGTM: Comprehensive and well-structured plugin configuration.

This pyproject.toml exemplifies best practices for the migration:

  • Dynamic versioning and readme from external files (DRY principle)
  • Properly categorized entry points (init, cli, varianter)
  • Clear dependency declarations including avocado-framework
  • Appropriate Python version constraint (>=3.9)
  • Complete metadata with license and classifiers

The configuration is thorough and production-ready.

examples/plugins/tests/rogue/setup.py (1)

1-24: LGTM: Proper backward-compatibility wrapper with essential configuration.

The setup.py correctly:

  • Adds header comments indicating configuration moved to pyproject.toml
  • Removes long_description (now handled by pyproject.toml)
  • Retains essential configuration: name, version, description, py_modules, and entry_points
  • Maintains all necessary entry points (resolver, runner, console script)

This follows the established migration pattern across the repository.

optional_plugins/README.rst (1)

10-34: LGTM: Documentation properly updated for pip-based workflow.

The documentation changes correctly:

  • Replace python setup.py develop with pip install -e commands
  • Add explicit uninstall instructions using pip uninstall
  • Provide concrete examples for html and ansible plugins
  • Update make target names to more descriptive develop-plugins and develop-plugin

These changes align with the deprecation of direct setup.py invocation and modern Python packaging best practices.

optional_plugins/html/setup.py (1)

16-18: Clear documentation of backward compatibility approach.

The comments effectively explain why setup.py is retained and how to maintain consistency with pyproject.toml.

examples/plugins/test-pre-post/hello/pyproject.toml (1)

1-17: LGTM!

The packaging configuration is correct and consistent - the single-file module declaration (py-modules) aligns with the entry point targets.

examples/plugins/test-pre-post/hello/setup.py (1)

1-2: Clear backward compatibility documentation.

The comments effectively communicate the purpose of this minimal setup.py file.

optional_plugins/robot/setup.py (1)

16-18: LGTM!

The backward compatibility approach is well-documented and consistent with other plugins in this PR.

optional_plugins/varianter_pict/setup.py (1)

16-18: LGTM!

The backward compatibility documentation is clear and aligns with the migration strategy.

optional_plugins/varianter_cit/setup.py (1)

16-18: LGTM!

The backward compatibility documentation is clear and consistent with other plugins.

python-avocado.spec (3)

8-18: LGTM: Tarball naming aligns with pyproject.toml package structure.

The renaming from avocado-*.tar.gz to avocado_framework-*.tar.gz is consistent with the package name declared in pyproject.toml and follows Python package naming conventions.


102-104: LGTM: Setup extraction paths updated correctly.

The %setup -q -n directives now reference avocado_framework-* instead of avocado-*, matching the updated tarball structure.


220-220: LGTM: Selftests invocation modernized.

The change from selftests/check.py to python3 -m selftests.check aligns with Python best practices for module execution and is consistent with the broader PR migration strategy.

optional_plugins/result_upload/pyproject.toml (1)

1-44: LGTM: Well-structured plugin configuration.

The pyproject.toml configuration follows PEP 621 standards and properly declares:

  • Build system requirements with setuptools>=61.0
  • Package metadata with appropriate classifiers
  • Entry points for both CLI and result plugins
  • Dynamic version/readme loading
  • Package discovery limited to avocado_result_upload*

The configuration is consistent with other optional plugins in the repository.

Makefile (6)

9-10: LGTM: New plugin development targets improve workflow.

The addition of develop-plugins and develop-plugin targets provides convenient shortcuts for plugin development, aligning with the new pyproject.toml-based plugin structure.


33-61: LGTM: Enhanced clean target is thorough.

The expanded clean target now explicitly handles:

  • Build artifacts and cache files
  • Optional plugins cleanup
  • Example plugins cleanup

The explicit cleanup with error handling (|| true) ensures robustness.


64-67: LGTM: Modernized install/uninstall to use pip.

The migration from setup.py install to python -m pip install and from setup.py develop --uninstall to pip uninstall aligns with modern Python packaging best practices.


80-80: LGTM: Selftests invocation modernized.

The change to python -m selftests.check is consistent with the repository-wide migration to module-based test execution.


91-104: LGTM: Plugin installation targets well-implemented.

The new develop-plugins and develop-plugin targets:

  • Use pip for editable installs
  • Provide clear error messages when PLUGIN is not defined
  • Support the new optional plugins structure

These targets will streamline plugin development workflows.


108-114: LGTM: Manpage generation guarded appropriately.

The addition of rst2man availability checking prevents cryptic failures when the tool is missing. The explicit error message guides users to install the required dependency.

pyproject.toml (2)

168-232: LGTM: Optional dependencies well-organized.

The optional dependencies are properly structured with:

  • Separate groups for each plugin (html, ansible, golang, etc.)
  • A comprehensive all_plugins group for convenient installation
  • Development dependencies grouped separately

This organization makes it easy for users to install specific plugins as needed.


245-250: LGTM: Package data properly configured.

The package data configuration ensures that non-Python assets (etc, libexec, schemas) are included in the distribution, which is essential for the framework's functionality.

.github/workflows/push_ci.yml (1)

97-97: LGTM: CI build migrated to pip-based installation.

The change from python setup.py develop --user to python -m pip install --user -e . follows modern Python packaging best practices and aligns with the broader migration strategy.

.github/workflows/prerelease.yml (1)

48-49: LGTM: Installation steps modernized.

The workflow now uses pip-based installation for both dependencies and the editable package install, aligning with the repository-wide migration.

.github/workflows/weekly.yml (4)

39-39: LGTM: Installation migrated to pip.

The change to python3 -m pip install --user -e . is consistent with the repository-wide migration to pip-based workflows.


47-47: LGTM: Selftests invocation modernized.

The migration to python3 -m selftests.check follows Python best practices for module execution.


83-83: LGTM: Consistent pip-based installation.

This change mirrors the update in the first job, maintaining consistency across the workflow.


89-89: LGTM: Module-based test execution with plugin exclusions.

The command properly uses module invocation while maintaining the plugin exclusion list for testing without optional plugins.

Comment on lines +33 to 44
run: python3 -m pip install --user -e .
- name: Installing plugins
run: |
python3 setup.py plugin --install=golang --user
python3 setup.py plugin --install=html --user
python3 setup.py plugin --install=result_upload --user
python3 setup.py plugin --install=resultsdb --user
python3 setup.py plugin --install=robot --user
python3 setup.py plugin --install=varianter_cit --user
python3 setup.py plugin --install=varianter_pict --user
python3 setup.py plugin --install=varianter_yaml_to_mux --user
python3 -m pip install ./optional_plugins/golang --user
python3 -m pip install ./optional_plugins/html --user
python3 -m pip install ./optional_plugins/result_upload --user
python3 -m pip install ./optional_plugins/resultsdb --user
python3 -m pip install ./optional_plugins/robot --user
python3 -m pip install ./optional_plugins/varianter_cit --user
python3 -m pip install ./optional_plugins/varianter_pict --user
python3 -m pip install ./optional_plugins/varianter_yaml_to_mux --user
- name: Avocado version
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Ensure PATH includes user base bin for --user installs.

Commands installed to ~/.local/bin may not be on PATH; avocado may not be found.

Add after Python setup (once per job using --user):

- name: Add user base bin to PATH
  run: echo "$(python3 -m site --user-base)/bin" >> "$GITHUB_PATH"

Also applies to: 115-116

🤖 Prompt for AI Agents
.github/workflows/setup.yml around lines 33 to 44 (and also apply same change at
lines 115-116): the workflow uses pip --user to install packages which drop
executables into the per-user bin (e.g. ~/.local/bin) that may not be on PATH;
add a job step right after the Python setup/install step to append the user base
bin to GITHUB_PATH so subsequent steps can find those installed commands (use
python3 -m site --user-base to compute the path and echo "<that>/bin" >>
"$GITHUB_PATH").

"avocado.plugins.init": ["remote = avocado_spawner_remote:RemoteSpawnerInit"],
"avocado.plugins.spawner": ["remote = avocado_spawner_remote:RemoteSpawner"],
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix entry_points to point at the spawner module.

Classes live in avocado_spawner_remote/spawner.py. Update the targets to avoid runtime import errors.

-        "avocado.plugins.init": ["remote = avocado_spawner_remote:RemoteSpawnerInit"],
-        "avocado.plugins.spawner": ["remote = avocado_spawner_remote:RemoteSpawner"],
+        "avocado.plugins.init": ["remote = avocado_spawner_remote.spawner:RemoteSpawnerInit"],
+        "avocado.plugins.spawner": ["remote = avocado_spawner_remote.spawner:RemoteSpawner"],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"avocado.plugins.init": ["remote = avocado_spawner_remote:RemoteSpawnerInit"],
"avocado.plugins.spawner": ["remote = avocado_spawner_remote:RemoteSpawner"],
"avocado.plugins.init": ["remote = avocado_spawner_remote.spawner:RemoteSpawnerInit"],
"avocado.plugins.spawner": ["remote = avocado_spawner_remote.spawner:RemoteSpawner"],
🤖 Prompt for AI Agents
In optional_plugins/spawner_remote/setup.py around lines 35-36, the entry_points
currently point to avocado_spawner_remote:RemoteSpawnerInit and
avocado_spawner_remote:RemoteSpawner but the classes live in
avocado_spawner_remote/spawner.py; fix by updating the entry_points to target
the spawner module (e.g., avocado_spawner_remote.spawner:RemoteSpawnerInit and
avocado_spawner_remote.spawner:RemoteSpawner) so the installer writes correct
import paths and avoids runtime import errors.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
examples/plugins/job-pre-post/sleep/setup.py (1)

1-22: Trim setup.py to a minimal shim
Update examples/plugins/job-pre-post/sleep/setup.py to call setup() without arguments—remove duplicated metadata now defined in pyproject.toml.

optional_plugins/ansible/setup.py (1)

24-46: Add explicit packages for egg builds
Include packages=find_packages(include=['avocado_ansible*']) in the setup() call of optional_plugins/ansible/setup.py to ensure the avocado_ansible modules are discovered.

optional_plugins/varianter_pict/setup.py (1)

28-42: Add explicit packages to setup()

The setup() call in optional_plugins/varianter_pict/setup.py doesn’t auto-discover any packages; add
packages=find_packages(include=['avocado_varianter_pict*'])
to ensure the plugin modules are included in egg builds.

optional_plugins/result_upload/setup.py (1)

28-42: Add packages directive to setup.py
Setuptools isn’t discovering any packages (find_packages returned empty). In optional_plugins/result_upload/setup.py, update the setup() call to include

packages=find_packages(include=['avocado_result_upload*'])

so your plugin code is properly packaged.

optional_plugins/resultsdb/setup.py (1)

28-49: Add namespace package discovery to setup.py
setup() omits packages, so bdist_egg won’t include the avocado_resultsdb code. Since avocado_resultsdb/ is a PEP 420 namespace (no __init__.py), add:

from setuptools import find_namespace_packagessetup(
    …,
    packages=find_namespace_packages(include=['avocado_resultsdb*']),
    …,
)

Or add an __init__.py under avocado_resultsdb/ and switch to find_packages.

♻️ Duplicate comments (22)
examples/plugins/test-pre-post/hello/setup.py (1)

1-16: Same inconsistency as in job-pre-post/sleep plugin.

The header comments indicate configuration moved to pyproject.toml, but the setup() call still contains full configuration. Apply the same resolution as the previous plugin.

optional_plugins/resultsdb/setup.py (1)

1-1: Shebang path still needs correction.

The shebang uses /bin/env instead of the more portable /usr/bin/env. While past review comments indicate this was addressed, the issue remains in the current code.

Apply this diff:

-#!/bin/env python3
+#!/usr/bin/env python3
optional_plugins/result_upload/setup.py (1)

1-1: Shebang path still needs correction.

The shebang uses /bin/env instead of /usr/bin/env. This portability issue should be fixed even though past review comments indicate it was addressed.

Apply this diff:

-#!/bin/env python3
+#!/usr/bin/env python3
optional_plugins/ansible/setup.py (1)

1-1: Shebang path still needs correction.

The shebang uses /bin/env instead of /usr/bin/env, affecting portability.

Apply this diff:

-#!/bin/env python3
+#!/usr/bin/env python3
optional_plugins/varianter_pict/setup.py (1)

1-1: Shebang path still needs correction.

The shebang uses /bin/env instead of /usr/bin/env, which is less portable.

Apply this diff:

-#!/bin/env python3
+#!/usr/bin/env python3
examples/plugins/tests/magic/pyproject.toml (1)

25-25: Complete the package discovery configuration.

The empty [tool.setuptools.packages.find] section may prevent setuptools from discovering the avocado_magic package, which is referenced in the entry points.

Apply this diff:

 [tool.setuptools.packages.find]
+include = ["avocado_magic*"]

This ensures that setuptools will discover and package the avocado_magic module and any subpackages.

setup.py (3)

221-350: Entry_points duplication requires ongoing synchronization.

This 130-line dict duplicates pyproject.toml configuration. While documented as necessary for egg builds (lines 24-25), any plugin changes must be updated in both locations.

Verify synchronization between setup.py and pyproject.toml:

#!/bin/bash
# Compare entry point groups

echo "Entry point groups in setup.py:"
rg -o '^\s+"([^"]+)"\s*:\s*\[' setup.py | sed 's/.*"\([^"]*\)".*/\1/' | sort

echo -e "\nEntry point groups in pyproject.toml:"
rg '^\[project\.entry-points\."([^"]+)"\]' pyproject.toml | sed 's/\[project.entry-points."\([^"]*\)"\]/\1/' | sort

echo -e "\nDifferences (if any):"
diff <(rg -o '^\s+"([^"]+)"\s*:\s*\[' setup.py | sed 's/.*"\([^"]*\)".*/\1/' | sort) \
     <(rg '^\[project\.entry-points\."([^"]+)"\]' pyproject.toml | sed 's/\[project.entry-points."\([^"]*\)"\]/\1/' | sort)

237-237: Ensure scripts package is properly configured.

Line 237 references scripts.external_runner:main. Verify that the scripts/ directory has an __init__.py and is included in the package discovery, or the console script will fail.

Run this verification:

#!/bin/bash
# Verify scripts module packaging

if [ -f "scripts/__init__.py" ]; then
  echo "✓ scripts/__init__.py exists"
else
  echo "✗ scripts/__init__.py missing - add it to make scripts a package"
fi

if [ -f "scripts/external_runner.py" ]; then
  echo "✓ scripts/external_runner.py exists"
  if rg -q 'def main\(' scripts/external_runner.py; then
    echo "✓ main() function found"
  else
    echo "✗ main() function not found"
  fi
else
  echo "✗ scripts/external_runner.py missing"
fi

212-212: Restrict find_packages to the avocado namespace.

The current exclude=("selftests*",) is too broad and may accidentally include packages from optional_plugins/* in the wheel, causing conflicts with separate plugin packages.

Based on past reviews, apply this fix:

-    packages=find_packages(exclude=("selftests*",)),
+    packages=find_packages(include=("avocado", "avocado.*")),
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (8)

38-46: Add key_type=int to timeout settings.

Lines 40 and 45 register numeric timeout options without specifying key_type=int, so they'll be parsed as strings, breaking arithmetic operations downstream.

Apply this fix:

 help_msg = "Test timeout enforced for remote host setup hook"
 settings.register_option(
-    section=section, key="setup_timeout", help_msg=help_msg, default=3600
+    section=section, key="setup_timeout", help_msg=help_msg, default=3600, key_type=int
 )

 help_msg = "Test timeout enforced for sessions (just for this spawner)"
 settings.register_option(
-    section=section, key="test_timeout", help_msg=help_msg, default=14400
+    section=section, key="test_timeout", help_msg=help_msg, default=14400, key_type=int
 )

106-114: Add error handling for slot initialization.

Reading JSON configuration files and creating remote sessions can fail (malformed JSON, missing files, connection errors), but no error handling is present. A single bad slot config will crash spawner initialization.

Add robust error handling:

 if len(RemoteSpawner.slots_cache) == 0:
     for session_slot in self.config.get("spawner.remote.slots"):
         if not session_slot:
             continue
-        with open(session_slot, "r", encoding="utf-8") as f:
-            session_data = json.load(f)
-        session = remote.remote_login(**session_data)
-        RemoteSpawner.slots_cache[session] = False
+        try:
+            with open(session_slot, "r", encoding="utf-8") as f:
+                session_data = json.load(f)
+            session = remote.remote_login(**session_data)
+            RemoteSpawner.slots_cache[session] = False
+        except (FileNotFoundError, json.JSONDecodeError, KeyError) as e:
+            LOG.warning(f"Failed to initialize slot from {session_slot}: {e}")
+        except Exception as e:
+            LOG.error(f"Unexpected error initializing slot from {session_slot}: {e}")

Based on learnings


136-146: Shell-escape task identifier in pgrep command.

Line 144 interpolates runtime_task.task.identifier directly into a shell command without escaping. If the identifier contains special characters (spaces, quotes, shell metacharacters), the command will break or could lead to command injection.

Add shell escaping:

 @staticmethod
 def is_task_alive(runtime_task):
     if runtime_task.spawner_handle is None:
         return False
     session = runtime_task.spawner_handle
+    safe_identifier = shlex.quote(runtime_task.task.identifier)
     status, _ = session.cmd_status_output(
-        f"pgrep -r R,S -f {runtime_task.task.identifier}"
+        f"pgrep -r R,S -f {safe_identifier}"
     )
     return status == 0

182-189: Remove output redirection that discards test results.

Line 182 redirects stdout to /dev/null, discarding all test output, logs, and results. This makes debugging impossible and defeats the purpose of running tests remotely.

Remove the output redirection:

-        cmd = shlex.join(entry_point_args) + " > /dev/null"
+        cmd = shlex.join(entry_point_args)

Or capture to a retrievable file:

-        cmd = shlex.join(entry_point_args) + " > /dev/null"
+        remote_log = f"/tmp/avocado-task-{task.identifier.replace('/', '_')}.log"
+        cmd = shlex.join(entry_point_args) + f" > {shlex.quote(remote_log)} 2>&1"

148-193: spawn_task blocks until task completion.

Line 184 uses run_remote_cmd_async which waits for the command to finish, making spawn_task synchronous. This defeats wait_task/is_task_alive and prevents concurrent task execution.

Launch the remote task in background and return immediately:

-        cmd = shlex.join(entry_point_args) + " > /dev/null"
+        # Launch in background and capture PID
+        remote_log = f"/tmp/avocado-task-{task.identifier.replace('/', '_')}.log"
+        cmd = f"nohup {shlex.join(entry_point_args)} > {shlex.quote(remote_log)} 2>&1 & echo $!"
         timeout = self.config.get("spawner.remote.test_timeout")
         status, output = await RemoteSpawner.run_remote_cmd_async(session, cmd, timeout)
-        LOG.debug(f"Command exited with code {status}")
-        if status != 0:
-            LOG.error(
-                f"Error exit code {status} on {session.host}:{session.port} "
-                f"with output:\n{output}"
-            )
-            return False
-
-        return True
+        if status == 0:
+            # Store PID for later checks
+            runtime_task.spawner_handle_pid = output.strip()
+            return True
+        else:
+            LOG.error(f"Failed to launch task on {session.host}:{session.port}: {output}")
+            return False

Then update is_task_alive to check the stored PID and wait_task to poll until completion.


195-201: Use unique, sanitized remote output directories.

Line 197 hardcodes /tmp/.avocado_task_output_dir, causing concurrent tasks to collide and corrupt logs.

Generate a per-task directory:

 def create_task_output_dir(self, runtime_task):
     output_dir_path = self.task_output_dir(runtime_task)
-    output_lxc_path = "/tmp/.avocado_task_output_dir"
+    # Use task ID for unique remote path
+    task_id = getattr(runtime_task, 'id', runtime_task.task.identifier)
+    # Sanitize for filesystem safety
+    safe_id = "".join(c if c.isalnum() or c in ('-', '_') else '_' for c in task_id)
+    output_lxc_path = f"/tmp/.avocado_task_output_dir_{safe_id}"

     os.makedirs(output_dir_path, exist_ok=True)
     runtime_task.task.setup_output_dir(output_lxc_path)

202-206: Add timeout to wait_task loop.

The infinite loop at lines 203-206 will hang forever if is_task_alive never returns False (due to a hung task or detection bug).

Honor the configured timeout:

 async def wait_task(self, runtime_task):
+    timeout = self.config.get("spawner.remote.test_timeout")
+    start_time = asyncio.get_event_loop().time()
     while True:
         if not RemoteSpawner.is_task_alive(runtime_task):
             return
+        if asyncio.get_event_loop().time() - start_time > timeout:
+            LOG.error(f"Task wait timeout of {timeout}s exceeded")
+            raise asyncio.TimeoutError(f"Task did not complete within {timeout}s")
         await asyncio.sleep(0.1)

208-216: Fix logging and use logging.exception for errors.

Line 215 has an f-string error (missing 'f' prefix) and doesn't log the exception traceback.

Apply this fix:

 async def terminate_task(self, runtime_task):
     session = runtime_task.spawner_handle
     session.sendcontrol("c")
     try:
         session.read_up_to_prompt()
-        return True
     except exceptions.ExpectTimeoutError:
-        LOG.error("Failed to terminate task on {session.host}")
+        LOG.exception(f"Failed to terminate task on {session.host}:{session.port}")
         return False
+    else:
+        return True
pyproject.toml (3)

1-16: Critical: Restore missing runtime dependencies.

Lines 14-16 only list setuptools as a dependency, but the core package imports third-party modules (e.g., psutil, requests, jsonschema) that were previously in install_requires. Without them, pip install avocado-framework will succeed but raise ImportError at runtime.

Restore the full dependency list from the former setup.py:

 dependencies = [
     "setuptools",
+    "psutil>=5.9.5",
+    "requests>=2.32.3",
+    "jsonschema>=3.2.0",
+    # Add other required packages from old install_requires
 ]

Move purely optional features to [project.optional-dependencies].


24-31: Remove unsupported Python 3.14 classifier.

Line 30 includes "Programming Language :: Python :: 3.14", which is not yet published by PyPI. This will cause package uploads to be rejected with "Invalid classifier".

Remove it now and re-add later when PyPI supports it:

     "Programming Language :: Python :: 3.13",
-    "Programming Language :: Python :: 3.14",
 ]

39-55: Verify scripts package is included for console scripts.

Line 54 registers avocado-external-runner = "scripts.external_runner:main", but line 239 excludes scripts* from packaging. This will cause the console script to fail at runtime.

Either remove scripts* from exclusions or move the external_runner module to an included package:

 [tool.setuptools.packages.find]
-exclude = ["selftests*", "docs*", "examples*", "contrib*", "scripts*", "optional_plugins*"]
+exclude = ["selftests*", "docs*", "examples*", "contrib*", "optional_plugins*"]
Makefile.gh (1)

66-68: Add conditional check for pyproject.toml.

The build-wheel target now unconditionally runs python -m build in all plugin directories. This will fail for any plugins that haven't been migrated to pyproject.toml yet.

Add a guard similar to the build-egg target:

 	for PLUGIN in $(AVOCADO_OPTIONAL_PLUGINS); do\
+		if test -f $$PLUGIN/pyproject.toml; then\
 		cd $$PLUGIN;\
 		$(PYTHON) -m build -o ../../PYPI_UPLOAD;\
 		cd -;\
+		fi;\
 	done
.github/workflows/setup.yml (1)

33-44: Verify PATH includes user base bin for --user installs.

The --user flag installs executables to ~/.local/bin (or the platform equivalent), which may not be on PATH by default in GitHub Actions runners. The avocado command at line 45 could fail if the user base bin directory is not accessible.

Verify that the user base bin is on PATH:

#!/bin/bash
# Check if user base bin is typically on PATH in GitHub Actions Ubuntu runners
python3 -c "import site; print(site.USER_BASE + '/bin')"
# Expected: should print something like /home/runner/.local/bin

# Check if this is typically on PATH
echo $PATH | grep -q "\.local/bin" && echo "User bin is on PATH" || echo "User bin NOT on PATH"

If verification shows it's not on PATH, add after the Python setup step:

- name: Add user base bin to PATH
  run: echo "$(python3 -m site --user-base)/bin" >> "$GITHUB_PATH"

Also applies to lines 115-116.

🧹 Nitpick comments (2)
optional_plugins/varianter_cit/setup.py (1)

22-22: Add error handling for VERSION file read.

If the VERSION file is missing or unreadable, setup.py will fail with an unclear error during egg builds.

Consider wrapping in a try/except block:

-VERSION = open("VERSION", "r", encoding="utf-8").read().strip()
+try:
+    VERSION = open("VERSION", "r", encoding="utf-8").read().strip()
+except FileNotFoundError:
+    raise FileNotFoundError("VERSION file not found. Ensure you're running from the plugin directory.")
setup.py (1)

38-41: Consider adding error handling for VERSION file.

If the VERSION file is missing, the error message will be unclear during egg builds.

Add basic error handling:

-with open(os.path.join(BASE_PATH, "VERSION"), "r", encoding="utf-8") as f:
-    VERSION = f.read().strip()
+try:
+    with open(os.path.join(BASE_PATH, "VERSION"), "r", encoding="utf-8") as f:
+        VERSION = f.read().strip()
+except FileNotFoundError:
+    raise FileNotFoundError(f"VERSION file not found at {BASE_PATH}. Run from repository root.")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d4c3bf and f07610d.

📒 Files selected for processing (63)
  • .github/actions/version/action.yml (1 hunks)
  • .github/workflows/ci.yml (13 hunks)
  • .github/workflows/prerelease.yml (1 hunks)
  • .github/workflows/push_ci.yml (1 hunks)
  • .github/workflows/setup.yml (6 hunks)
  • .github/workflows/vmimage.yml (1 hunks)
  • .github/workflows/weekly.yml (2 hunks)
  • Makefile (5 hunks)
  • Makefile.gh (1 hunks)
  • docs/source/guides/contributor/chapters/environment.rst (1 hunks)
  • docs/source/guides/contributor/chapters/plugins.rst (4 hunks)
  • docs/source/guides/writer/chapters/subclassing.rst (2 hunks)
  • examples/plugins/README.rst (1 hunks)
  • examples/plugins/cli-cmd/hello/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_option/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_option/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/setup.py (1 hunks)
  • examples/plugins/job-pre-post/sleep/pyproject.toml (1 hunks)
  • examples/plugins/job-pre-post/sleep/setup.py (1 hunks)
  • examples/plugins/test-pre-post/hello/pyproject.toml (1 hunks)
  • examples/plugins/test-pre-post/hello/setup.py (1 hunks)
  • examples/plugins/tests/README.rst (1 hunks)
  • examples/plugins/tests/magic/pyproject.toml (1 hunks)
  • examples/plugins/tests/magic/setup.py (1 hunks)
  • examples/plugins/tests/rogue/pyproject.toml (1 hunks)
  • examples/plugins/tests/rogue/setup.py (1 hunks)
  • optional_plugins/README.rst (1 hunks)
  • optional_plugins/ansible/README.rst (1 hunks)
  • optional_plugins/ansible/pyproject.toml (1 hunks)
  • optional_plugins/ansible/setup.py (2 hunks)
  • optional_plugins/golang/pyproject.toml (1 hunks)
  • optional_plugins/golang/setup.py (1 hunks)
  • optional_plugins/html/pyproject.toml (1 hunks)
  • optional_plugins/html/setup.py (1 hunks)
  • optional_plugins/mail/pyproject.toml (1 hunks)
  • optional_plugins/mail/setup.py (1 hunks)
  • optional_plugins/result_upload/pyproject.toml (1 hunks)
  • optional_plugins/result_upload/setup.py (1 hunks)
  • optional_plugins/resultsdb/pyproject.toml (1 hunks)
  • optional_plugins/resultsdb/setup.py (1 hunks)
  • optional_plugins/robot/avocado_robot/robot.py (1 hunks)
  • optional_plugins/robot/pyproject.toml (1 hunks)
  • optional_plugins/robot/setup.py (1 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/__init__.py (0 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1 hunks)
  • optional_plugins/spawner_remote/pyproject.toml (1 hunks)
  • optional_plugins/spawner_remote/setup.py (1 hunks)
  • optional_plugins/varianter_cit/pyproject.toml (1 hunks)
  • optional_plugins/varianter_cit/setup.py (1 hunks)
  • optional_plugins/varianter_pict/README.rst (1 hunks)
  • optional_plugins/varianter_pict/pyproject.toml (1 hunks)
  • optional_plugins/varianter_pict/setup.py (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/README.rst (4 hunks)
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/setup.py (1 hunks)
  • pyproject.toml (1 hunks)
  • python-avocado.spec (6 hunks)
  • setup.py (3 hunks)
  • spell.ignore (1 hunks)
💤 Files with no reviewable changes (1)
  • optional_plugins/spawner_remote/avocado_spawner_remote/init.py
✅ Files skipped from review due to trivial changes (2)
  • examples/plugins/cli-cmd/hello_priority/setup.py
  • optional_plugins/varianter_pict/pyproject.toml
🚧 Files skipped from review as they are similar to previous changes (28)
  • optional_plugins/varianter_yaml_to_mux/README.rst
  • examples/plugins/cli-cmd/hello_option/setup.py
  • optional_plugins/result_upload/pyproject.toml
  • examples/plugins/cli-cmd/hello/pyproject.toml
  • examples/plugins/tests/README.rst
  • examples/plugins/tests/rogue/setup.py
  • .github/workflows/push_ci.yml
  • docs/source/guides/contributor/chapters/environment.rst
  • examples/plugins/test-pre-post/hello/pyproject.toml
  • optional_plugins/ansible/pyproject.toml
  • optional_plugins/varianter_pict/README.rst
  • optional_plugins/resultsdb/pyproject.toml
  • .github/workflows/prerelease.yml
  • spell.ignore
  • examples/plugins/job-pre-post/sleep/pyproject.toml
  • optional_plugins/spawner_remote/setup.py
  • examples/plugins/tests/magic/setup.py
  • optional_plugins/html/pyproject.toml
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml
  • examples/plugins/cli-cmd/hello/setup.py
  • examples/plugins/tests/rogue/pyproject.toml
  • examples/plugins/cli-cmd/hello_parser/setup.py
  • optional_plugins/varianter_cit/pyproject.toml
  • examples/plugins/README.rst
  • .github/workflows/vmimage.yml
  • .github/workflows/weekly.yml
  • optional_plugins/html/setup.py
  • optional_plugins/ansible/README.rst
🧰 Additional context used
🧬 Code graph analysis (2)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (5)
avocado/core/plugin_interfaces.py (2)
  • Init (23-28)
  • Spawner (321-441)
avocado/core/spawners/common.py (3)
  • SpawnerMixin (35-47)
  • SpawnMethod (6-18)
  • task_output_dir (46-47)
avocado/core/settings.py (3)
  • section (137-138)
  • register_option (642-799)
  • key (141-142)
avocado/core/nrunner/runnable.py (1)
  • pick_runner_module_from_entry_point_kind (630-643)
avocado/core/nrunner/task.py (1)
  • setup_output_dir (171-174)
optional_plugins/robot/avocado_robot/robot.py (1)
avocado/core/test.py (1)
  • name (367-373)
🪛 checkmake (0.2.2)
Makefile

[warning] 124-124: Missing required phony target "all"

(minphony)


[warning] 124-124: Missing required phony target "test"

(minphony)

🪛 Ruff (0.14.0)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py

73-73: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


74-74: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


126-129: Avoid specifying long messages outside the exception class

(TRY003)


197-197: Probable insecure usage of temporary file or directory: "/tmp/.avocado_task_output_dir"

(S108)


213-213: Consider moving this statement to an else block

(TRY300)


215-215: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


219-219: Unused static method argument: runtime_task

(ARG004)


224-224: Unused static method argument: runtime_task

(ARG004)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (36)
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: Fedora selftests
  • GitHub Check: Podman spawner with 3rd party runner plugin
  • GitHub Check: Windows with Python 3.13
  • GitHub Check: Version task ubuntu:24.04
  • GitHub Check: Smokecheck on Linux with Python 3.10
  • GitHub Check: Smokecheck on Linux with Python 3.11
  • GitHub Check: macOS with Python 3.11
  • GitHub Check: Static checks
🔇 Additional comments (55)
.github/actions/version/action.yml (1)

9-12: LGTM! Modern pip-based installation workflow.

The migration from python setup.py develop to pip install -e . is appropriate and aligns with modern Python packaging best practices. The PIP_BREAK_SYSTEM_PACKAGES=1 environment variable is suitable for CI environments where you control the Python installation.

optional_plugins/robot/avocado_robot/robot.py (1)

41-44: Good defensive coding for API compatibility.

The change properly handles both the old string-based API and the new TestCase object API for Robot Framework tests. Using hasattr to check for the name attribute before accessing it is appropriate and maintains backward compatibility.

optional_plugins/README.rst (1)

10-34: Excellent documentation update for modern packaging workflow.

The documentation now correctly guides developers to use pip-based installation (pip install -e) instead of the deprecated python setup.py develop command. The examples are clear, include both installation and uninstallation commands, and cover both individual plugin management and bulk operations via Makefile targets.

optional_plugins/golang/setup.py (2)

33-33: LGTM! Python version constraint aligns with project requirements.

The python_requires=">=3.9" constraint is appropriate given that Python 3.8 support has been dropped from the project (as noted in the PR comments). This ensures compatibility and prevents installation on unsupported Python versions.


16-18: Entry points are synchronized with pyproject.toml.

All console_scripts and plugin entry-points in setup.py exactly match the sections in pyproject.toml. No further action required.

docs/source/guides/writer/chapters/subclassing.rst (2)

20-47: Excellent modernization of packaging examples.

The documentation now teaches users modern Python packaging practices using pyproject.toml with PEP 621 metadata. The example demonstrates:

  • Proper build system configuration with setuptools backend
  • Dynamic version sourcing from a VERSION file
  • Clear project metadata structure
  • Dependency specification

This is a significant improvement that will help users avoid deprecated patterns.


88-101: Installation instructions reflect modern pip workflow.

The updated installation example correctly demonstrates using pip install -e . for editable installs instead of the deprecated python setup.py develop command. The output shown is realistic and helps users understand what to expect during installation.

optional_plugins/spawner_remote/pyproject.toml (1)

14-16: Bump aexpect minimum version to 1.8.0

In optional_plugins/spawner_remote/pyproject.toml change aexpect>=1.6.2 to aexpect>=1.8.0 to pick up Pyro5 compatibility, portability fixes, and rsync enhancements. Verify that remote.remote_login() and run_remote_cmd_async() continue to work as expected.

docs/source/guides/contributor/chapters/plugins.rst (4)

97-102: LGTM! Documentation correctly updated for pyproject.toml workflow.

The documentation accurately reflects the migration from setup.py to pyproject.toml-based packaging, including the modern pip-based installation command (pip install -e .).


111-114: LGTM! Entry-points syntax correctly updated to TOML format.

The documentation correctly demonstrates the new entry-points syntax in pyproject.toml format.


170-173: LGTM! Settings entry-points example correctly formatted.

The example properly demonstrates the pyproject.toml-based settings entry-points configuration.


325-327: LGTM! Installation command properly updated.

The documentation correctly instructs users to use pip install -e . for development mode installation, which is the modern approach for pyproject.toml-based packages.

examples/plugins/cli-cmd/hello_option/pyproject.toml (4)

1-3: LGTM! Build system configuration is correct.

The build-system configuration properly specifies setuptools>=61.0 and wheel with the standard setuptools.build_meta backend.


5-8: LGTM! Project metadata is appropriate for an example plugin.

The project metadata is correctly defined with name, version, and description suitable for this example CLI plugin.


10-11: LGTM! Entry-point correctly registered.

The entry-point properly registers the hello_option CLI command under the avocado.plugins.cli.cmd namespace.


13-14: LGTM! Module packaging correctly configured.

The py-modules configuration appropriately specifies the single hello_option module for packaging.

optional_plugins/resultsdb/setup.py (1)

16-18: LGTM! Clear documentation of the minimal setup.py approach.

The comment helpfully explains the rationale for keeping a minimal setup.py and reminds maintainers to keep critical fields synchronized with pyproject.toml.

optional_plugins/result_upload/setup.py (1)

16-18: LGTM! Clear documentation of the minimal setup.py approach.

The comment effectively explains the backward compatibility rationale and the need to keep entry_points synchronized with pyproject.toml.

examples/plugins/tests/magic/pyproject.toml (4)

1-3: LGTM! Build system configuration is correct.

The build-system configuration properly specifies the required dependencies and backend.


5-8: LGTM! Project metadata is appropriate.

The project metadata is correctly configured for this test plugin example.


10-11: LGTM! Console script correctly registered.

The avocado-runner-magic console script is properly configured to enable command-line usage of the magic runner.


13-23: LGTM! Plugin entry-points correctly registered.

All necessary plugin entry-points (init, resolver, discoverer, and runner) are properly configured for the magic test type.

optional_plugins/ansible/setup.py (1)

16-18: LGTM! Clear documentation of the minimal setup.py approach.

The comment consistently explains the backward compatibility strategy across all optional plugins.

optional_plugins/varianter_pict/setup.py (1)

16-18: LGTM! Clear documentation of the minimal setup.py approach.

The comment maintains consistency across all optional plugins in explaining the backward compatibility rationale.

optional_plugins/golang/pyproject.toml (5)

1-3: LGTM! Build system configuration is correct.

The build-system configuration properly specifies setuptools and wheel with the standard backend.


5-25: LGTM! Comprehensive project metadata.

The project metadata is well-structured with dynamic version/readme, appropriate classifiers, and consistent Python version requirements.


27-37: LGTM! Entry-points correctly configured.

The project URLs and entry-points are properly defined, including the console script for the golang runner and the plugin registrations.


39-43: LGTM! Package discovery properly configured.

The setuptools configuration correctly specifies package discovery with the appropriate include pattern for avocado_golang packages.


45-47: LGTM! Dynamic sources correctly configured.

The dynamic version and readme sources are properly configured, with the readme content-type appropriately specified as text/x-rst.

optional_plugins/varianter_cit/setup.py (1)

16-18: LGTM!

The comment clearly documents the backward compatibility purpose and explains the minimal setup.py approach for egg builds.

python-avocado.spec (4)

8-8: LGTM!

The gittar macro updates correctly reflect the new avocado_framework- naming convention for tarballs, consistent with the broader packaging modernization.

Also applies to: 18-18


102-104: LGTM!

The %setup directive updates correctly align with the new avocado_framework tarball naming.


220-220: LGTM!

The migration from selftests/check.py to -m selftests.check aligns with the module-based execution pattern adopted across the repository.


259-263: Conditional exclusion logic is correct.

The nested conditionals properly scope the exclusions to non-RHEL and specific Fedora versions where certain plugins should not be included.

examples/plugins/cli-cmd/hello_parser/pyproject.toml (1)

1-14: LGTM!

The pyproject.toml configuration is well-structured and follows best practices:

  • Modern build system with setuptools>=61.0
  • Clear project metadata
  • Proper entry point registration for the CLI plugin
  • Appropriate py-modules configuration

This aligns with the repository-wide migration to pyproject.toml-based packaging.

setup.py (1)

16-26: LGTM!

The docstring clearly explains the backward compatibility purpose and documents the entry_points duplication issue. This helps maintainers understand the temporary nature of this setup.

pyproject.toml (2)

56-166: LGTM!

The entry points are comprehensive and well-organized across all plugin namespaces. Ensure these stay synchronized with the duplicate definitions in setup.py for egg build compatibility (as noted in setup.py comments).


168-232: LGTM!

The optional dependencies are well-structured, providing fine-grained plugin installation via extras and an all_plugins convenience group.

optional_plugins/varianter_yaml_to_mux/pyproject.toml (1)

1-48: LGTM!

The pyproject.toml configuration is well-structured:

  • Modern build system with setuptools>=61.0
  • Complete project metadata with dynamic version/readme
  • Proper dependency declaration including avocado-framework and PyYAML
  • Three entry points correctly configured for init, CLI, and varianter plugins
  • Package discovery properly scoped to avocado_varianter_yaml_to_mux

This aligns with the repository-wide migration to pyproject.toml-based packaging.

optional_plugins/varianter_yaml_to_mux/setup.py (1)

16-18: LGTM: Minimal setup.py configuration aligns with pyproject.toml migration.

The addition of explanatory comments and python_requires=">=3.9" appropriately supports the backward compatibility goal while primary configuration moves to pyproject.toml.

Also applies to: 33-33

optional_plugins/mail/setup.py (1)

16-18: LGTM: Minimal setup.py configuration aligns with pyproject.toml migration.

The addition of explanatory comments and python_requires=">=3.9" appropriately supports the backward compatibility goal.

Also applies to: 33-33

.github/workflows/setup.yml (3)

121-121: LGTM: Manpage generation migrated to make-based command.

The change from setup.py to make man aligns with the build system modernization.

Also applies to: 157-157


123-123: LGTM: Test execution migrated to module-based invocation.

Using python3 -m selftests.check instead of setup.py test aligns with modern Python packaging best practices.

Also applies to: 159-159


181-194: LGTM: Virtualenv workflow migrated to pip-based installs.

The virtualenv setup now uses python3 -m venv and pip install commands, which is the modern approach replacing setup.py invocations.

optional_plugins/robot/setup.py (1)

16-18: LGTM: Minimal setup.py configuration aligns with pyproject.toml migration.

The addition of explanatory comments and python_requires=">=3.9" appropriately supports the backward compatibility goal.

Also applies to: 36-36

optional_plugins/mail/pyproject.toml (1)

1-48: LGTM: Standardized pyproject.toml structure for plugin packaging.

The pyproject.toml follows setuptools build backend conventions with appropriate metadata, entry points, and dynamic version/readme handling. The structure aligns well with the broader migration effort.

.github/workflows/ci.yml (3)

32-32: LGTM: Test execution migrated to module-based invocation.

The migration from setup.py test to python3 -m selftests.check with appropriate skip/select flags aligns with modern Python packaging practices and the broader PR objectives.

Also applies to: 99-99, 143-143


89-89: LGTM: Python version display flag corrected.

Using python -VV (verbose version info) instead of the redundant python3 -V --version is the correct approach.

Also applies to: 131-131, 167-167


93-93: LGTM: Dependency installation migrated to module-based pip.

Using python3 -m pip install ensures the pip associated with the correct Python interpreter is used.

optional_plugins/robot/pyproject.toml (1)

1-48: LGTM: Standardized pyproject.toml structure for Robot plugin packaging.

The pyproject.toml follows setuptools build backend conventions with appropriate metadata, entry points, and dynamic version/readme handling. The structure aligns well with the broader migration effort.

Makefile (5)

9-10: LGTM: New plugin development targets enhance developer workflow.

The addition of develop-plugins and develop-plugin targets with clear usage messages and implementation simplifies the development workflow for optional plugins.

Also applies to: 91-105


34-61: LGTM: Comprehensive clean target covers all artifacts.

The expanded clean target now properly removes build artifacts, caches, and generated files from the main project, optional plugins, and example plugins.


64-64: LGTM: Install and uninstall migrated to pip-based commands.

Using python -m pip install and python -m pip uninstall aligns with modern Python packaging practices and the broader migration from setup.py to pip-based workflows.

Also applies to: 67-67


80-80: LGTM: Check target migrated to module-based test invocation.

Using python -m selftests.check instead of directly executing the script aligns with the migration objectives.


109-114: LGTM: Man page generation includes defensive check for rst2man.

The conditional check for rst2man availability prevents build failures with a clear error message when the tool is missing.

Comment on lines +14 to +16
dependencies = [
"avocado-framework",
]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Synchronize avocado-framework version constraint with setup.py.

The setup.py comment at line 17 states "Keep install_requires and entry_points in sync with pyproject.toml for egg build compatibility." However, there's a version constraint mismatch:

  • setup.py line 32: pins to exact version f"avocado-framework=={VERSION}"
  • pyproject.toml line 15: no version constraint

This inconsistency can cause different dependency resolution between wheel/sdist builds (using pyproject.toml) and egg builds (using setup.py).

Apply this diff to synchronize the version constraint:

 dependencies = [
-    "avocado-framework",
+    "avocado-framework=={VERSION}",
 ]

Note: You'll need to add logic to read VERSION from the VERSION file and substitute it, or use dynamic dependencies. Alternatively, use a version range that both configs can share, such as "avocado-framework" in both if exact pinning isn't required for eggs.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In optional_plugins/mail/pyproject.toml around lines 14 to 16, the pyproject
dependency for avocado-framework lacks the exact version pin present in setup.py
(setup.py pins f"avocado-framework=={VERSION}"). Update pyproject.toml to match
setup.py by either: 1) programmatically substituting the VERSION value into
pyproject's dependencies (read the VERSION file at build time and set
"avocado-framework==<VERSION>"), or 2) change setup.py to avoid hard-pinning and
use a shared range (e.g., "avocado-framework>=x.y,<x.y+1") that you place in
both files; ensure both configs use the same exact constraint so wheel/sdist and
egg builds resolve the same avocado-framework version.

Comment on lines +14 to +17
dependencies = [
"avocado-framework",
"robotframework"
]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Synchronize and update robotframework version constraint.

Two issues need addressing:

  1. Version constraint mismatch: The setup.py comment at line 17 states "Keep install_requires and entry_points in sync with pyproject.toml for egg build compatibility." However:

    • setup.py line 34: "robotframework>=4.1, <7.0; python_version >= '3.9'"
    • pyproject.toml line 16: "robotframework" (no constraint)
  2. Outdated upper bound: The <7.0 constraint in setup.py excludes Robot Framework 7.x releases (latest is 7.3.2 as of October 2025).

Apply this diff to synchronize and update the version constraint:

 dependencies = [
     "avocado-framework",
-    "robotframework"
+    "robotframework>=4.1,<8.0; python_version >= '3.9'"
 ]

Also update setup.py line 34 to match:

-        "robotframework>=4.1, <7.0; python_version >= '3.9'",
+        "robotframework>=4.1,<8.0; python_version >= '3.9'",

This ensures both configs use the same constraint and supports the current 7.x release series.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
dependencies = [
"avocado-framework",
"robotframework"
]
dependencies = [
"avocado-framework",
"robotframework>=4.1,<8.0; python_version >= '3.9'"
]
🤖 Prompt for AI Agents
In optional_plugins/robot/pyproject.toml (lines 14–17) and setup.py (line 34),
the robotframework version constraints are out of sync and the upper bound
excludes the 7.x series; update both to the same constraint allowing current 7.x
releases, e.g. change the pyproject dependency entry to
"robotframework>=4.1,<8.0" (adding the same python_version marker if your
project uses environment markers in pyproject), and update setup.py line 34 to
"robotframework>=4.1, <8.0; python_version >= '3.9'" so both files match and
permit Robot Framework 7.x.

Comment on lines 24 to 29
setup(
name="avocado-framework-plugin-varianter-cit",
version=open("VERSION", "r", encoding="utf-8").read().strip(),
description="Varianter with combinatorial capabilities",
long_description=get_long_description(),
long_description_content_type="text/x-rst",
author="Avocado Developers",
author_email="avocado-devel@redhat.com",
url="http://avocado-framework.github.io/",
packages=packages,
version=VERSION,
include_package_data=True,
install_requires=[f"avocado-framework=={VERSION}"],
python_requires=">=3.9",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Verify install_requires synchronization with pyproject.toml.

Line 28 hardcodes the dependency avocado-framework=={VERSION}. Ensure this remains synchronized with the corresponding entry in optional_plugins/varianter_cit/pyproject.toml.

Run this script to compare dependencies:


🏁 Script executed:

#!/bin/bash
# Compare install_requires between setup.py and pyproject.toml dependencies

echo "Dependencies in setup.py:"
rg -A2 'install_requires' optional_plugins/varianter_cit/setup.py

echo -e "\nDependencies in pyproject.toml:"
rg -A5 '^\[project\]' optional_plugins/varianter_cit/pyproject.toml | rg -A3 'dependencies'

Length of output: 305


🏁 Script executed:

#!/bin/bash
echo "Dependencies block in pyproject.toml:"
rg -n 'dependencies' optional_plugins/varianter_cit/pyproject.toml

echo -e "\nLooking for avocado-framework entry:"
rg -n 'avocado-framework' optional_plugins/varianter_cit/pyproject.toml

Length of output: 225


Pin avocado-framework in pyproject.toml to match setup.py
In optional_plugins/varianter_cit/pyproject.toml change "avocado-framework" to "avocado-framework=={VERSION}" under [project].dependencies.

🤖 Prompt for AI Agents
In optional_plugins/varianter_cit/pyproject.toml (update the
[project].dependencies entry), replace the unpinned "avocado-framework"
dependency with the pinned form "avocado-framework=={VERSION}" so it matches the
install_requires in setup.py; ensure the dependency string is quoted and present
as an element in the dependencies array exactly as
"avocado-framework=={VERSION}".

Comment on lines 243 to 258
%exclude %{python3_sitelib}/contrib*
%exclude %{python3_sitelib}/scripts*
%exclude %{python3_sitelib}/avocado_result_html*
%if ! 0%{?rhel}
%if ! 0%{?fedora} > 35
%exclude %{python3_sitelib}/avocado_resultsdb*
%endif
%endif
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify the exclusion paths match installed package structure.

Lines 243-245 add exclusions for contrib*, scripts*, and avocado_result_html*. Since these are broad globs at the %{python3_sitelib} level, confirm they match the actual installation paths.

Run this script to check what's actually installed at these paths:


🏁 Script executed:

#!/bin/bash
# Check if these patterns match actual installation locations

echo "Checking for patterns in site-packages:"
echo "1. Looking for contrib*:"
fd -t d 'contrib' --max-depth 2

echo -e "\n2. Looking for scripts*:"
fd -t d 'scripts' --max-depth 2

echo -e "\n3. Looking for avocado_result_html*:"
fd -t d 'avocado_result_html' --max-depth 3

Length of output: 256


Fix avocado_result_html exclusion path
The avocado_result_html plugin lives in optional_plugins/html, so %exclude %{python3_sitelib}/avocado_result_html* won’t match. Update it to:

%exclude %{python3_sitelib}/optional_plugins/html/avocado_result_html*
🤖 Prompt for AI Agents
In python-avocado.spec around lines 243 to 250, the %exclude for the
avocado_result_html plugin points at %{python3_sitelib}/avocado_result_html* but
the plugin is installed under optional_plugins/html; update the exclusion path
to %{python3_sitelib}/optional_plugins/html/avocado_result_html* so the RPM
excludes the actual installed files.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
optional_plugins/result_upload/setup.py (1)

28-41: Egg build is empty without packages; also align dependency pin with pyproject.

  • setup() lacks packages, so bdist_egg will not include code.
  • pyproject uses unpinned avocado-framework, but setup.py pins it, causing inconsistent dependency resolution.

Apply:

-from setuptools import setup
+from setuptools import setup, find_packages
@@
 setup(
@@
-    install_requires=[f"avocado-framework=={VERSION}"],
+    install_requires=["avocado-framework"],
+    packages=find_packages(include=["avocado_result_upload*"]),
optional_plugins/resultsdb/setup.py (1)

28-49: Make egg builds include code; remove dead urllib3 marker; align dependency pin.

  • Missing packages => empty egg.
  • urllib3<2.3.0; python_version < '3.9' is unreachable since python_requires=">=3.9".
  • Pinning avocado-framework here but not in pyproject causes divergence.

Apply:

-from setuptools import setup
+from setuptools import setup, find_packages
@@
-    install_requires=[
-        f"avocado-framework=={VERSION}",
-        "resultsdb-api==2.1.5",
-        "urllib3<2.3.0; python_version < '3.9'",
-    ],
+    install_requires=[
+        "avocado-framework",
+        "resultsdb-api==2.1.5",
+    ],
+    packages=find_packages(include=["avocado_resultsdb*"]),
♻️ Duplicate comments (21)
optional_plugins/spawner_remote/setup.py (2)

34-37: Entry points target wrong module (ImportError at runtime).

The entry points reference avocado_spawner_remote:RemoteSpawnerInit and avocado_spawner_remote:RemoteSpawner, but the classes reside in avocado_spawner_remote.spawner. This will cause import failures when the plugin is loaded.

Apply this diff to correct the module paths:

     entry_points={
-        "avocado.plugins.init": ["remote = avocado_spawner_remote:RemoteSpawnerInit"],
-        "avocado.plugins.spawner": ["remote = avocado_spawner_remote:RemoteSpawner"],
+        "avocado.plugins.init": ["remote = avocado_spawner_remote.spawner:RemoteSpawnerInit"],
+        "avocado.plugins.spawner": ["remote = avocado_spawner_remote.spawner:RemoteSpawner"],
     },

28-33: Egg builds will omit plugin code without packages argument.

The setup() call lacks a packages argument, causing bdist_egg to skip the plugin modules. Add explicit package discovery.

Apply this diff:

-from setuptools import setup
+from setuptools import setup, find_packages
 
 BASE_PATH = os.path.dirname(__file__)
 with open(os.path.join(BASE_PATH, "VERSION"), "r", encoding="utf-8") as version_file:
     VERSION = version_file.read().strip()
 
 setup(
     name="avocado-framework-plugin-spawner-remote",
     version=VERSION,
     include_package_data=True,
+    packages=find_packages(include=["avocado_spawner_remote*"]),
     install_requires=[f"avocado-framework=={VERSION}", "aexpect>=1.6.2"],
     python_requires=">=3.9",
optional_plugins/ansible/setup.py (1)

24-37: Add packages argument for egg build compatibility.

The setup() call lacks a packages argument, so egg builds won't include the ansible plugin modules.

Apply this diff:

-from setuptools import setup
+from setuptools import setup, find_packages
 
 VERSION = open("VERSION", "r", encoding="utf-8").read().strip()
 
 setup(
     name="avocado-framework-plugin-ansible",
     version=VERSION,
     include_package_data=True,
+    packages=find_packages(include=["avocado_ansible*"]),
     install_requires=[
         f"avocado-framework=={VERSION}",
         "cffi==1.17.1; python_version<'3.10'",
         "cffi; python_version>='3.10'",
         "cryptography<46.0.0; python_version<'3.10'",
         "pycparser",
         "ansible-core",
         "markupsafe<3.0.0",
     ],
     python_requires=">=3.9",
examples/plugins/tests/magic/pyproject.toml (1)

25-25: Complete package discovery configuration.

The empty [tool.setuptools.packages.find] section won't discover the avocado_magic package referenced by the entry points. Specify which packages to include.

Apply this diff:

 [tool.setuptools.packages.find]
+include = ["avocado_magic*"]
.github/workflows/setup.yml (1)

33-44: Ensure ~/.local/bin is on PATH for --user installs.

The workflow uses pip install --user which installs executables to ~/.local/bin. This directory may not be on PATH, causing subsequent avocado commands to fail.

Add a step to update PATH after Python setup:

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - name: Add user base bin to PATH
        run: echo "$(python3 -m site --user-base)/bin" >> "$GITHUB_PATH"
      - name: Installing Avocado
        run: python3 -m pip install --user -e .

Apply this same fix to lines 115-116 and other jobs using --user.

optional_plugins/ansible/pyproject.toml (1)

14-19: Sync dependencies with setup.py to avoid divergent installs.

The pyproject.toml dependencies are missing environment-specific version constraints and packages present in setup.py. This causes different dependency resolutions for wheel vs egg installs.

Apply this diff to match setup.py:

 dependencies = [
     "avocado-framework",
-    "cffi",
+    "cffi==1.17.1; python_version<'3.10'",
+    "cffi; python_version>='3.10'",
+    "cryptography<46.0.0; python_version<'3.10'",
     "pycparser",
     "ansible-core",
+    "markupsafe<3.0.0",
 ]

Based on learnings

optional_plugins/result_upload/setup.py (1)

1-1: Fix non-portable shebang or drop it.

Use /usr/bin/env, or remove the shebang since this file isn’t meant to be executed directly. Current shebang also triggers EXE001 if not executable.

Apply one:

-#!/bin/env python3
+#!/usr/bin/env python3

Or remove:

-#!/bin/env python3
optional_plugins/resultsdb/setup.py (1)

1-1: Fix non-portable shebang or remove it.

Use /usr/bin/env or drop the shebang to avoid EXE001 on non-executable files.

-#!/bin/env python3
+#!/usr/bin/env python3

Or:

-#!/bin/env python3
setup.py (1)

212-212: find_packages() scope remains too broad despite past review.

A previous review recommended restricting find_packages() to the avocado namespace to prevent accidentally bundling optional_plugins packages. The current code still uses find_packages(exclude=("selftests*",)), which could include unintended packages.

Verify which packages are actually discovered:

#!/bin/bash
# List packages that would be discovered by find_packages
python3 -c "
from setuptools import find_packages
packages = find_packages(exclude=('selftests*',))
print('Packages found:')
for pkg in sorted(packages):
    print(f'  - {pkg}')
print()
print('Checking for optional_plugins packages:')
for pkg in packages:
    if 'optional_plugins' in pkg or pkg.startswith('avocado_'):
        print(f'  WARNING: {pkg} might be an optional plugin')
"
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (8)

136-146: Verify task identifier is properly shell-escaped.

Past reviews flagged that the task identifier in line 144 should be shell-escaped to prevent command injection or breakage with special characters. This was marked as addressed in commits 60f4aa9 to 56fcda5.

Verify the current implementation properly escapes the identifier:

#!/bin/bash
# Check if shlex is imported and used for shell escaping
rg -n "import shlex" optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py
rg -n "shlex.quote.*identifier" optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py
# Check the actual pgrep command construction
rg -A2 -B2 "pgrep.*identifier" optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py

38-46: Add key_type=int to timeout option registrations.

The timeout options (setup_timeout and test_timeout) are registered with integer defaults (3600, 14400) but without key_type=int. This causes values to be parsed as strings, breaking arithmetic operations downstream.

Apply this diff:

 help_msg = "Test timeout enforced for remote host setup hook"
 settings.register_option(
-    section=section, key="setup_timeout", help_msg=help_msg, default=3600
+    section=section, key="setup_timeout", help_msg=help_msg, default=3600, key_type=int
 )

 help_msg = "Test timeout enforced for sessions (just for this spawner)"
 settings.register_option(
-    section=section, key="test_timeout", help_msg=help_msg, default=14400
+    section=section, key="test_timeout", help_msg=help_msg, default=14400, key_type=int
 )

106-114: Add error handling for slot initialization failures.

Reading JSON slot configurations can fail (malformed JSON, missing files, connection errors), but no error handling is present. A single bad slot configuration will crash the spawner.

Apply this diff:

         if len(RemoteSpawner.slots_cache) == 0:
             for session_slot in self.config.get("spawner.remote.slots"):
                 if not session_slot:
                     continue
-                with open(session_slot, "r", encoding="utf-8") as f:
-                    session_data = json.load(f)
-                session = remote.remote_login(**session_data)
-                RemoteSpawner.slots_cache[session] = False
+                try:
+                    with open(session_slot, "r", encoding="utf-8") as f:
+                        session_data = json.load(f)
+                    session = remote.remote_login(**session_data)
+                    RemoteSpawner.slots_cache[session] = False
+                except (FileNotFoundError, json.JSONDecodeError, KeyError) as e:
+                    LOG.warning(f"Failed to initialize slot from {session_slot}: {e}")
+                except Exception as e:
+                    LOG.error(f"Unexpected error initializing slot from {session_slot}: {e}")

148-193: spawn_task blocks until task completion, defeating spawner semantics.

The spawn_task method uses run_remote_cmd_async which waits for command completion (line 184), making it synchronous. This defeats the purpose of wait_task/is_task_alive and prevents concurrent task execution.

To fix:

  1. Launch the remote task in background (e.g., nohup ... >/tmp/<taskid>.log 2>&1 & echo $!)
  2. Capture and store the PID in runtime_task.spawner_handle
  3. Return True immediately without waiting
  4. Update is_task_alive to check the stored PID
  5. Update wait_task to poll until the process exits

182-182: Output redirection to /dev/null loses all test results.

Redirecting output to /dev/null discards stdout/stderr, making debugging impossible and losing test results.

Remove the redirection or capture to a retrievable file:

-        cmd = shlex.join(entry_point_args) + " > /dev/null"
+        cmd = shlex.join(entry_point_args)

Or capture to a log file:

-        cmd = shlex.join(entry_point_args) + " > /dev/null"
+        remote_log = f"/tmp/avocado-task-{task.identifier.replace('/', '_')}.log"
+        cmd = shlex.join(entry_point_args) + f" > {shlex.quote(remote_log)} 2>&1"

197-197: Use unique, sanitized remote output directory per task.

Every task writes to the same fixed path /tmp/.avocado_task_output_dir, causing collisions and race conditions for concurrent tasks.

Generate a unique, sanitized path per task:

     def create_task_output_dir(self, runtime_task):
         output_dir_path = self.task_output_dir(runtime_task)
-        output_lxc_path = "/tmp/.avocado_task_output_dir"
+        # Sanitize task identifier for use in path
+        safe_id = runtime_task.task.identifier.replace('/', '_').replace(' ', '_')
+        output_lxc_path = f"/tmp/.avocado_task_output_dir_{safe_id}"

         os.makedirs(output_dir_path, exist_ok=True)
         runtime_task.task.setup_output_dir(output_lxc_path)

202-206: Add timeout to wait_task to prevent infinite blocking.

wait_task loops forever if is_task_alive never returns False (due to a hang or detection bug).

Honor spawner.remote.test_timeout to break out:

     async def wait_task(self, runtime_task):
+        timeout = self.config.get("spawner.remote.test_timeout")
+        start_time = asyncio.get_event_loop().time()
         while True:
             if not RemoteSpawner.is_task_alive(runtime_task):
                 return
+            if asyncio.get_event_loop().time() - start_time > timeout:
+                raise asyncio.TimeoutError(f"Task did not complete within {timeout}s")
             await asyncio.sleep(0.1)

208-216: Fix logging and use logging.exception for proper traceback.

Line 215 has an f-string formatting issue and should use logging.exception to include traceback.

Apply this diff:

     async def terminate_task(self, runtime_task):
         session = runtime_task.spawner_handle
         session.sendcontrol("c")
         try:
             session.read_up_to_prompt()
-            return True
         except exceptions.ExpectTimeoutError:
-            LOG.error("Failed to terminate task on {session.host}")
+            LOG.exception(f"Failed to terminate task on {session.host}:{session.port}")
             return False
+        else:
+            return True
pyproject.toml (4)

54-54: Verify scripts.external_runner module is packaged.

The console script avocado-external-runner = "scripts.external_runner:main" requires the scripts package, but line 239 excludes scripts* from packaging.

Verify if scripts package is needed and properly excluded:

#!/bin/bash
# Check if scripts directory exists and contains Python modules
if [ -d "scripts" ]; then
    echo "scripts directory exists"
    find scripts -name "*.py" -type f | head -10
    echo ""
    # Check if external_runner.py exists
    if [ -f "scripts/external_runner.py" ]; then
        echo "scripts/external_runner.py found"
        rg -n "def main" scripts/external_runner.py
    else
        echo "scripts/external_runner.py NOT found"
    fi
else
    echo "scripts directory does NOT exist"
fi

238-239: scripts exclusion may break avocado-external-runner entry point.*

Past review noted that excluding scripts* from packaging (line 239) will prevent the scripts.external_runner module from being included, breaking the console script at line 54.

Either:

  1. Remove scripts* from the exclude list if it's meant to be packaged
  2. Move the external_runner functionality to a packaged module and update the entry point

14-16: Restore missing runtime dependencies to prevent ImportError.

The dependencies list contains only "setuptools", but the core package imports third-party modules that were previously in setup.py's install_requires. Past review flagged that modules like psutil, requests, and jsonschema are imported but not listed, causing ImportError after installation.

Verify which modules are actually imported:

#!/bin/bash
# Search for third-party imports in the avocado package
echo "Checking for third-party imports..."
rg -g "avocado/**/*.py" "^import (psutil|requests|jsonschema|aexpect)" --no-filename | sort -u
rg -g "avocado/**/*.py" "^from (psutil|requests|jsonschema|aexpect)" --no-filename | sort -u

30-30: Remove unsupported Python 3.14 classifier.

The classifier "Programming Language :: Python :: 3.14" is not yet published by PyPI and will cause upload rejections.

Apply this diff:

     "Programming Language :: Python :: 3.11",
     "Programming Language :: Python :: 3.12",
     "Programming Language :: Python :: 3.13",
-    "Programming Language :: Python :: 3.14",
 ]
🧹 Nitpick comments (4)
optional_plugins/robot/avocado_robot/robot.py (1)

42-42: Consider using isinstance() for more explicit type checking.

While hasattr(test_case, "name") works for duck typing, it could match unintended objects that happen to have a name attribute. For more robust type safety, consider checking the type explicitly or using a try/except block.

Example using try/except:

-test_name = test_case.name if hasattr(test_case, "name") else test_case
+try:
+    test_name = test_case.name
+except AttributeError:
+    test_name = test_case

Or, if you know the expected types from Robot Framework's API:

+from robot.model import TestCase
+# ... in find_tests function:
-test_name = test_case.name if hasattr(test_case, "name") else test_case
+test_name = test_case.name if isinstance(test_case, TestCase) else test_case
.github/workflows/ci.yml (1)

27-32: Consider migrating develop install to pip for consistency.

While the test invocation correctly uses python3 -m selftests.check, line 28 still uses python3 setup.py develop --user. For consistency with the rest of the PR, consider migrating to python3 -m pip install --user -e ..

Apply this diff for consistency:

       - name: Installing Avocado in develop mode
-        run: python3 setup.py develop --user
+        run: python3 -m pip install --user -e .
.github/workflows/weekly.yml (1)

39-39: Ensure pip user bin is on PATH in GitHub Actions.

--user installs scripts to ~/.local/bin, which may not be on PATH in all runners. Make it explicit.

Apply this step after installation:

       - name: Installing Avocado in develop mode
         run: python3 -m pip install --user -e .
+      - name: Ensure pip user bin on PATH
+        run: echo "$HOME/.local/bin" >> $GITHUB_PATH

Also applies to: 83-83

optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1)

73-74: Add ClassVar type annotations to mutable class attributes.

Static analysis flags that METHODS and slots_cache should be annotated with typing.ClassVar to clarify they are class-level attributes.

+from typing import ClassVar
+
 class RemoteSpawner(Spawner, SpawnerMixin):

     description = "Remote (host) based spawner"
-    METHODS = [SpawnMethod.STANDALONE_EXECUTABLE]
-    slots_cache = {}
+    METHODS: ClassVar = [SpawnMethod.STANDALONE_EXECUTABLE]
+    slots_cache: ClassVar = {}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f07610d and b537d6c.

📒 Files selected for processing (62)
  • .github/actions/version/action.yml (1 hunks)
  • .github/workflows/ci.yml (13 hunks)
  • .github/workflows/prerelease.yml (1 hunks)
  • .github/workflows/setup.yml (6 hunks)
  • .github/workflows/vmimage.yml (1 hunks)
  • .github/workflows/weekly.yml (2 hunks)
  • Makefile (5 hunks)
  • Makefile.gh (1 hunks)
  • docs/source/guides/contributor/chapters/environment.rst (1 hunks)
  • docs/source/guides/contributor/chapters/plugins.rst (4 hunks)
  • docs/source/guides/writer/chapters/subclassing.rst (2 hunks)
  • examples/plugins/README.rst (1 hunks)
  • examples/plugins/cli-cmd/hello/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_option/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_option/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/setup.py (1 hunks)
  • examples/plugins/job-pre-post/sleep/pyproject.toml (1 hunks)
  • examples/plugins/job-pre-post/sleep/setup.py (1 hunks)
  • examples/plugins/test-pre-post/hello/pyproject.toml (1 hunks)
  • examples/plugins/test-pre-post/hello/setup.py (1 hunks)
  • examples/plugins/tests/README.rst (1 hunks)
  • examples/plugins/tests/magic/pyproject.toml (1 hunks)
  • examples/plugins/tests/magic/setup.py (1 hunks)
  • examples/plugins/tests/rogue/pyproject.toml (1 hunks)
  • examples/plugins/tests/rogue/setup.py (1 hunks)
  • optional_plugins/README.rst (1 hunks)
  • optional_plugins/ansible/README.rst (1 hunks)
  • optional_plugins/ansible/pyproject.toml (1 hunks)
  • optional_plugins/ansible/setup.py (2 hunks)
  • optional_plugins/golang/pyproject.toml (1 hunks)
  • optional_plugins/golang/setup.py (1 hunks)
  • optional_plugins/html/pyproject.toml (1 hunks)
  • optional_plugins/html/setup.py (1 hunks)
  • optional_plugins/mail/pyproject.toml (1 hunks)
  • optional_plugins/mail/setup.py (1 hunks)
  • optional_plugins/result_upload/pyproject.toml (1 hunks)
  • optional_plugins/result_upload/setup.py (1 hunks)
  • optional_plugins/resultsdb/pyproject.toml (1 hunks)
  • optional_plugins/resultsdb/setup.py (1 hunks)
  • optional_plugins/robot/avocado_robot/robot.py (1 hunks)
  • optional_plugins/robot/pyproject.toml (1 hunks)
  • optional_plugins/robot/setup.py (1 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/__init__.py (0 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1 hunks)
  • optional_plugins/spawner_remote/pyproject.toml (1 hunks)
  • optional_plugins/spawner_remote/setup.py (1 hunks)
  • optional_plugins/varianter_cit/pyproject.toml (1 hunks)
  • optional_plugins/varianter_cit/setup.py (1 hunks)
  • optional_plugins/varianter_pict/README.rst (1 hunks)
  • optional_plugins/varianter_pict/pyproject.toml (1 hunks)
  • optional_plugins/varianter_pict/setup.py (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/README.rst (4 hunks)
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/setup.py (1 hunks)
  • pyproject.toml (1 hunks)
  • python-avocado.spec (7 hunks)
  • setup.py (3 hunks)
  • spell.ignore (1 hunks)
💤 Files with no reviewable changes (1)
  • optional_plugins/spawner_remote/avocado_spawner_remote/init.py
✅ Files skipped from review due to trivial changes (2)
  • examples/plugins/tests/magic/setup.py
  • examples/plugins/cli-cmd/hello_parser/setup.py
🚧 Files skipped from review as they are similar to previous changes (30)
  • examples/plugins/README.rst
  • optional_plugins/varianter_pict/README.rst
  • examples/plugins/test-pre-post/hello/pyproject.toml
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml
  • examples/plugins/cli-cmd/hello_option/pyproject.toml
  • examples/plugins/tests/rogue/pyproject.toml
  • .github/workflows/vmimage.yml
  • optional_plugins/varianter_cit/pyproject.toml
  • optional_plugins/robot/pyproject.toml
  • optional_plugins/golang/pyproject.toml
  • examples/plugins/tests/README.rst
  • optional_plugins/spawner_remote/pyproject.toml
  • .github/workflows/prerelease.yml
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml
  • examples/plugins/job-pre-post/sleep/pyproject.toml
  • examples/plugins/cli-cmd/hello/setup.py
  • optional_plugins/varianter_cit/setup.py
  • optional_plugins/robot/setup.py
  • optional_plugins/varianter_pict/setup.py
  • spell.ignore
  • .github/actions/version/action.yml
  • examples/plugins/job-pre-post/sleep/setup.py
  • examples/plugins/cli-cmd/hello_option/setup.py
  • optional_plugins/html/setup.py
  • examples/plugins/test-pre-post/hello/setup.py
  • docs/source/guides/contributor/chapters/plugins.rst
  • docs/source/guides/contributor/chapters/environment.rst
  • optional_plugins/ansible/README.rst
  • Makefile.gh
  • optional_plugins/mail/pyproject.toml
🧰 Additional context used
🧬 Code graph analysis (2)
optional_plugins/robot/avocado_robot/robot.py (1)
avocado/core/test.py (1)
  • name (367-373)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (5)
avocado/core/plugin_interfaces.py (2)
  • Init (23-28)
  • Spawner (321-441)
avocado/core/spawners/common.py (3)
  • SpawnerMixin (35-47)
  • SpawnMethod (6-18)
  • task_output_dir (46-47)
avocado/core/settings.py (3)
  • section (137-138)
  • register_option (642-799)
  • key (141-142)
avocado/core/nrunner/runnable.py (1)
  • pick_runner_module_from_entry_point_kind (630-643)
avocado/core/nrunner/task.py (1)
  • setup_output_dir (171-174)
🪛 checkmake (0.2.2)
Makefile

[warning] 124-124: Missing required phony target "all"

(minphony)


[warning] 124-124: Missing required phony target "test"

(minphony)

🪛 Ruff (0.14.0)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py

73-73: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


74-74: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


126-129: Avoid specifying long messages outside the exception class

(TRY003)


197-197: Probable insecure usage of temporary file or directory: "/tmp/.avocado_task_output_dir"

(S108)


213-213: Consider moving this statement to an else block

(TRY300)


215-215: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


219-219: Unused static method argument: runtime_task

(ARG004)


224-224: Unused static method argument: runtime_task

(ARG004)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (39)
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: Fedora develop install/uninstall task
  • GitHub Check: Fedora selftests
  • GitHub Check: Podman spawner with 3rd party runner plugin
  • GitHub Check: macOS with Python 3.11
  • GitHub Check: Smokecheck on Linux with Python 3.10
  • GitHub Check: Smokecheck on Linux with Python 3.11
  • GitHub Check: Static checks
  • GitHub Check: Code Coverage (3.11)
🔇 Additional comments (42)
optional_plugins/robot/avocado_robot/robot.py (1)

41-44: Verify that this API compatibility change is intentional for this PR.

This change handles compatibility between old and new Robot Framework APIs, which seems unrelated to the PR's stated objective of migrating from setup.py to pyproject.toml. Please confirm whether:

  1. This change is required due to updated Robot Framework dependencies pulled in during the migration.
  2. This is an intentional bundled fix for a known issue.
  3. This should be split into a separate PR.

Understanding the relationship helps ensure changes are appropriately scoped and documented.

examples/plugins/cli-cmd/hello_priority/setup.py (1)

1-2: LGTM! Clear backward-compatibility header.

The added comments clearly indicate the file's purpose and the migration to pyproject.toml, which aligns with the PR's modernization goals.

optional_plugins/varianter_yaml_to_mux/README.rst (1)

6-6: LGTM! Documentation formatting improvements.

The conversion from Sphinx roles (:mod:, :class:) to inline code literals improves readability and maintains compatibility with various documentation renderers.

Also applies to: 11-11, 14-14, 632-632, 639-639, 649-649, 655-656, 786-786, 790-790, 797-798

examples/plugins/tests/rogue/setup.py (1)

1-2: LGTM! Consistent migration pattern.

The header comments align with the project-wide migration strategy and clearly communicate the file's reduced role.

examples/plugins/cli-cmd/hello/pyproject.toml (1)

1-14: LGTM! Well-structured pyproject.toml.

The packaging configuration follows PEP 621 standards with:

  • Modern build system (setuptools>=61.0)
  • Clear project metadata
  • Proper entry point registration for the CLI plugin
optional_plugins/varianter_pict/pyproject.toml (1)

1-44: LGTM! Comprehensive packaging configuration.

The pyproject.toml demonstrates best practices:

  • Dynamic version and readme from separate files
  • Complete project metadata including classifiers
  • Proper dependency declaration on avocado-framework
  • Well-defined entry points for CLI and varianter plugins
docs/source/guides/writer/chapters/subclassing.rst (2)

20-47: LGTM! Documentation aligns with modern packaging.

The updated examples properly reflect the migration to pyproject.toml and demonstrate:

  • PEP 621-compliant project configuration
  • Dynamic version handling
  • Proper dependency declaration

This will help users adopt the new packaging approach correctly.


88-101: LGTM! Accurate pip install examples.

The updated installation output shows the modern editable install workflow using pip install -e ., which aligns with current best practices and the PR's migration goals.

optional_plugins/varianter_yaml_to_mux/setup.py (2)

16-18: LGTM! Clear migration guidance.

The header comments properly document the file's purpose and remind maintainers to keep install_requires and entry_points synchronized with pyproject.toml for egg build compatibility.


33-33: LGTM! Explicit Python version requirement.

Adding python_requires=">=3.9" improves compatibility checking and aligns with the project's minimum Python version.

optional_plugins/golang/setup.py (2)

16-18: LGTM!

The comment clearly documents the purpose of this minimal setup.py and the synchronization requirement with pyproject.toml.


34-44: Plugin entry points match between setup.py and pyproject.toml. No inconsistencies found.

optional_plugins/spawner_remote/setup.py (1)

16-18: LGTM!

The comment accurately describes the purpose and synchronization requirement.

optional_plugins/ansible/setup.py (2)

16-18: LGTM!

The comment is clear and consistent with other plugin setup files.


38-45: Entry points correctly defined.

The entry_points configuration properly registers the ansible plugin's console scripts and runnable runner.

examples/plugins/tests/magic/pyproject.toml (1)

1-23: Configuration is well-structured.

The build system, project metadata, and entry points are properly defined for the magic plugin example.

optional_plugins/mail/setup.py (2)

16-18: LGTM!

The comment is consistent with the project-wide migration approach.


34-41: Entry points properly configured.

The mail plugin's entry points are correctly defined for the init and job.prepost hooks.

.github/workflows/setup.yml (2)

121-123: Migration to pip and selftests.check is correct.

The workflow properly uses make man for manpage generation and python3 -m selftests.check for tests, aligning with the pyproject.toml migration.


180-194: Virtualenv installation handles PATH correctly.

The virtualenv activation ensures executables are on PATH, so the pip installs and avocado commands will work correctly.

optional_plugins/ansible/pyproject.toml (2)

1-13: Build system and metadata are well-configured.

The setuptools-based build system with dynamic version/readme handling is properly set up.


39-47: Package discovery and dynamic sources properly configured.

The setuptools configuration correctly specifies package inclusion and dynamic version/readme sources.

.github/workflows/ci.yml (3)

93-99: Test migration to selftests.check is correct.

The workflow properly migrates from setup.py-based test invocation to python3 -m selftests.check, aligning with the PR's modernization goals.


167-171: Windows installation properly migrated to pip.

The workflow correctly uses python -m pip install --user -e . for Windows, ensuring cross-platform consistency.


265-359: Distribution-specific Python dependencies properly updated.

The workflow ensures pip is installed for each distribution, supporting the migration to pip-based installation across all tested platforms.

.github/workflows/weekly.yml (2)

47-47: LGTM: switch to module-based selftests.

python3 -m selftests.check is the right direction and matches the project migration.


89-89: LGTM: module-based selftests with plugin disables; please verify plugin IDs.

The invocation is fine. Double-check the disabled plugin IDs remained unchanged after the migration.

python-avocado.spec (2)

108-111: LGTM: force setuptools distutils on RHEL 9.

This is a pragmatic compatibility tweak for entry-point generation in the RPM buildroot.

Also applies to: 154-157


228-228: LGTM: migrate to module-based selftests.

%{python3} -m selftests.check aligns with the new invocation.

optional_plugins/resultsdb/pyproject.toml (1)

5-17: LGTM: modern packaging and entry points look consistent.

Also applies to: 31-39, 43-48

setup.py (3)

16-26: LGTM: Clear documentation of the minimal setup.py purpose.

The module docstring clearly explains that this file is retained for backward compatibility with egg builds and tools that still expect setup.py, with configuration moved to pyproject.toml.


221-350: Entry points duplication is acknowledged and intentional.

The large entry_points dictionary duplicates configuration from pyproject.toml for egg build compatibility, as documented in lines 24-25. While this creates maintenance burden, it's an intentional trade-off for backward compatibility.


31-31: Update clean import for Python 3.12+
distutils.command.clean is removed in Python 3.12; replace with from setuptools.command.clean import clean (or confirm a setuptools-provided shim exists) to maintain compatibility.

Makefile (5)

9-10: LGTM: Clear help text for new plugin targets.

The new develop-plugins and develop-plugin targets are well-documented in the help text.


33-61: Comprehensive clean target implementation.

The expanded clean target now handles:

  • Build artifacts (build/, dist/, *.egg-info)
  • Cache files (pycache, *.pyc)
  • Documentation artifacts
  • Optional plugins and example plugins

The error handling with || true prevents failures from interrupting the clean process.


91-104: LGTM: New plugin development targets with proper guards.

The new develop-plugins and develop-plugin targets:

  • Use pip editable installs (pip install -e)
  • Include proper error handling for missing PLUGIN variable
  • Iterate through optional plugins correctly

These align with the shift to pip-based workflows.


109-114: Enhanced man page generation with rst2man availability check.

The man page target now checks for rst2man availability before attempting to generate the man page, providing a clear error message if the tool is missing. This prevents cryptic failures in environments without rst2man.


64-64: Confirm file layout under DESTDIR with pip install
Run an actual install into a temporary DESTDIR and list installed files:

TMPDIR=$(mktemp -d)
python3 -m pip install . --root "$TMPDIR" --prefix=/usr --no-deps
find "$TMPDIR" -type f | sed 's|^$TMPDIR||' | sort
rm -rf "$TMPDIR"

Verify this matches the layout from setup.py install --root.

optional_plugins/README.rst (2)

10-24: LGTM: Clear pip-based installation documentation.

The updated documentation clearly shows:

  • How to install plugins using pip install -e optional_plugins/<plugin-name>
  • How to uninstall using pip uninstall <plugin-package-name>
  • Explicit examples with actual plugin names (html, ansible)

This aligns with the broader migration to pyproject.toml-based packaging.


30-34: Updated make targets reflect new workflows.

The documentation correctly shows:

  • make develop-plugins to enable all optional plugins
  • make develop-plugin PLUGIN=html to enable a specific plugin

These match the new Makefile targets.

optional_plugins/html/pyproject.toml (1)

1-47: LGTM: Complete pyproject.toml configuration for HTML plugin.

The configuration includes:

  • Proper build system setup (setuptools>=61.0, wheel)
  • Complete project metadata (name, description, authors, license)
  • Python version requirement (>=3.9)
  • Dependencies correctly specified (avocado-framework, jinja2)
  • Comprehensive classifiers
  • Entry points properly configured for three plugin interfaces:
    • avocado.plugins.cli (HTML)
    • avocado.plugins.init (HTMLInit)
    • avocado.plugins.result (HTMLResult)
  • Dynamic version and readme loading from files

This aligns with the project-wide migration to pyproject.toml-based packaging.

pyproject.toml (1)

1-250: Comprehensive pyproject.toml configuration for project-wide migration.

This file establishes the foundation for the project-wide migration from setup.py to pyproject.toml-based packaging, including:

  • Complete build system configuration
  • Extensive entry points for console scripts and plugin interfaces
  • Optional dependencies grouped by feature
  • Package discovery and data inclusion

This aligns with the broader PR goal of modernizing the project's packaging approach.

Comment on lines 31 to 33
setup(
name="avocado-framework-plugin-result-mail",
version=VERSION,
description="Avocado Mail Notification for Jobs",
long_description=get_long_description(),
long_description_content_type="text/x-rst",
author="Avocado Developers",
author_email="avocado-devel@redhat.com",
url="http://avocado-framework.github.io/",
packages=find_packages(),
include_package_data=True,
install_requires=[f"avocado-framework=={VERSION}"],
python_requires=">=3.9",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add packages argument for egg builds.

Without a packages argument, egg builds won't include the mail plugin modules.

Apply this diff:

-from setuptools import setup
+from setuptools import setup, find_packages
 
 BASE_PATH = os.path.dirname(__file__)
 with open(os.path.join(BASE_PATH, "VERSION"), "r", encoding="utf-8") as version_file:
     VERSION = version_file.read().strip()
 
 setup(
     name="avocado-framework-plugin-result-mail",
     version=VERSION,
     include_package_data=True,
+    packages=find_packages(include=["avocado_result_mail*"]),
     install_requires=[f"avocado-framework=={VERSION}"],
     python_requires=">=3.9",
🤖 Prompt for AI Agents
In optional_plugins/mail/setup.py around lines 28 to 33, the setup() call lacks
a packages argument which prevents egg builds from including the mail plugin
modules; add a packages argument (e.g. packages=find_packages()) to the setup()
call and ensure you import find_packages from setuptools at the top of the file
(or explicitly list the package names) so the plugin modules are included in
built eggs while keeping include_package_data=True and the existing metadata.

# data.tests is a list
for test_case in data.tests: # pylint: disable=E1133
# Handle both string (old API) and TestCase object (new API)
test_name = test_case.name if hasattr(test_case, "name") else test_case
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Validate that test_name is not None or empty.

After extracting test_name, there's no check to ensure it's a valid, non-empty value. If test_case.name returns None or an empty string, it will be used directly, potentially causing downstream issues or confusing test identification.

Consider adding validation:

 # Handle both string (old API) and TestCase object (new API)
 test_name = test_case.name if hasattr(test_case, "name") else test_case
+if not test_name:
+    raise ValueError(f"Invalid test name for test case: {test_case}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test_name = test_case.name if hasattr(test_case, "name") else test_case
# Handle both string (old API) and TestCase object (new API)
test_name = test_case.name if hasattr(test_case, "name") else test_case
if not test_name:
raise ValueError(f"Invalid test name for test case: {test_case}")
🤖 Prompt for AI Agents
In optional_plugins/robot/avocado_robot/robot.py around line 42, test_name is
assigned from test_case.name or test_case but there is no validation that it is
non-empty; add a guard after the assignment to verify test_name is not None and
not an empty string (trim whitespace), and if invalid either raise a clear
ValueError or substitute a fallback identifier (e.g., "unknown_test" or derived
repr(test_case)) and log a warning so downstream code always receives a usable
test name.

Comment on lines 8 to 9
%global gittar avocado_framework-%{version}.tar.gz
%else
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Tarball name/directory likely incorrect; build will fail to fetch/extract.

GitHub archives are typically named and unpack to “avocado-…”. Switching to “avocado_framework-…” for both the tarball and %setup -n will likely 404 on Source0 and fail %setup.

Either revert to avocado-… or prove that releases publish avocado_framework-… artifacts and dirs. Suggested fix:

-    %global gittar          avocado_framework-%{version}.tar.gz
+    %global gittar          avocado-%{version}.tar.gz
@@
-    %global gittar          avocado_framework-%{shortcommit}.tar.gz
+    %global gittar          avocado-%{shortcommit}.tar.gz
@@
-%setup -q -n avocado_framework-%{version}
+%setup -q -n avocado-%{version}
@@
-%setup -q -n avocado_framework-%{commit}
+%setup -q -n avocado-%{commit}

Also applies to: 18-19, 102-105


🏁 Script executed:

#!/bin/bash
# Verify which archive names exist for v112.0
set -euo pipefail
for name in avocado_framework-112.0.tar.gz avocado-112.0.tar.gz; do
  url="https://github.com/avocado-framework/avocado/archive/refs/tags/112.0.tar.gz"
  # The downloaded filename differs; check the extracted top-level dir
  curl -sL "$url" | tar -tz | head -1 | sed 's|/.*||' | uniq
done
echo "If top-level dir is 'avocado-112.0', %setup -n must match it."

Length of output: 13


Tarball name/directory mismatch – build will fail

GitHub archives extract into “avocado-”, so using “avocado_framework-…” for Source0 and %setup will fetch a non-existent file and unpack into the wrong directory. Revert to:

-    %global gittar          avocado_framework-%{version}.tar.gz
+    %global gittar          avocado-%{version}.tar.gz
@@
-    %global gittar          avocado_framework-%{shortcommit}.tar.gz
+    %global gittar          avocado-%{shortcommit}.tar.gz
@@
-%setup -q -n avocado_framework-%{version}
+%setup -q -n avocado-%{version}
@@
-%setup -q -n avocado_framework-%{commit}
+%setup -q -n avocado-%{commit}

Apply these changes at lines 8–9, 18–19 and 102–105.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
%global gittar avocado_framework-%{version}.tar.gz
%else
%global gittar avocado-%{version}.tar.gz
%else
🤖 Prompt for AI Agents
In python-avocado.spec around lines 8–9, 18–19 and 102–105, the tarball name and
unpack directory are wrong (using avocado_framework-... but GitHub archives
extract as avocado-<version>), so change the %global gittar/Source0 entries back
to avocado-%{version}.tar.gz and update any %setup or %autosetup invocation(s)
to reference the unpacked directory name (avocado-%{version}) so the build will
find and unpack the correct files; apply these replacements at the specified
line ranges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
optional_plugins/result_upload/setup.py (1)

1-1: Fix the shebang for portability.

The shebang uses /bin/env which is not portable across all systems and the past review comment indicating this was addressed appears incorrect. Use /usr/bin/env instead.

Apply this diff:

-#!/bin/env python3
+#!/usr/bin/env python3

Additionally, either make the file executable or remove the shebang entirely:

chmod +x optional_plugins/result_upload/setup.py
optional_plugins/spawner_remote/setup.py (2)

1-1: Fix the shebang for portability.

The shebang uses /bin/env which is not portable. Use /usr/bin/env instead, or remove the shebang if the file is not meant to be executed directly.

Apply this diff:

-#!/bin/env python3
+#!/usr/bin/env python3

Alternatively, remove the shebang or make the file executable:

chmod +x optional_plugins/spawner_remote/setup.py

34-37: Critical: Entry points missing .spawner module component.

The entry points in setup.py point to avocado_spawner_remote:Class but should point to avocado_spawner_remote.spawner:Class to match the actual module structure. This mismatch with pyproject.toml (which correctly uses .spawner) will cause import errors for egg-based installs.

Apply this diff:

     entry_points={
-        "avocado.plugins.init": ["remote = avocado_spawner_remote:RemoteSpawnerInit"],
-        "avocado.plugins.spawner": ["remote = avocado_spawner_remote:RemoteSpawner"],
+        "avocado.plugins.init": ["remote = avocado_spawner_remote.spawner:RemoteSpawnerInit"],
+        "avocado.plugins.spawner": ["remote = avocado_spawner_remote.spawner:RemoteSpawner"],
     },
♻️ Duplicate comments (18)
Makefile.gh (1)

66-68: Add guard for pyproject.toml existence.

The build-wheel target unconditionally runs python -m build in all plugin directories, which will fail for plugins missing pyproject.toml. The build-egg target (lines 78-83) includes a conditional check for setup.py that should be mirrored here.

Apply this diff to add the guard:

 	for PLUGIN in $(AVOCADO_OPTIONAL_PLUGINS); do\
+		if test -f $$PLUGIN/pyproject.toml; then\
 		cd $$PLUGIN;\
 		$(PYTHON) -m build -o ../../PYPI_UPLOAD;\
 		cd -;\
+		fi;\
 	done
examples/plugins/cli-cmd/hello_priority/pyproject.toml (1)

10-11: Critical: Entry-point name collision persists.

The entry-point name hello collides with examples/plugins/cli-cmd/hello/pyproject.toml. Both plugins register the same key under [project.entry-points."avocado.plugins.cli.cmd"], causing plugin loading conflicts at runtime.

Apply this diff to resolve the collision:

 [project.entry-points."avocado.plugins.cli.cmd"]
-hello = "hello_priority:HelloWorld"
+hello_priority = "hello_priority:HelloWorld"
optional_plugins/mail/pyproject.toml (1)

14-16: Version pinning inconsistency already flagged.

The unpinned "avocado-framework" dependency was already identified in past review comments as inconsistent with the setup.py pinning.

optional_plugins/robot/pyproject.toml (1)

15-17: Bound robotframework dependency and keep in sync with setup.py

Unpinned dependency risks breakage. Recommend supporting RF 7.x.

 dependencies = [
     "avocado-framework",
-    "robotframework"
+    "robotframework>=4.1,<8.0"
 ]

Verify parity with setup.py:

#!/bin/bash
set -euo pipefail
if [ -f optional_plugins/robot/setup.py ]; then
  rg -nP 'install_requires\s*=\s*\[(?s).*?\]' optional_plugins/robot/setup.py -U -n || true
else
  echo "optional_plugins/robot/setup.py not found"
fi
optional_plugins/mail/setup.py (1)

28-34: Egg builds will miss plugin modules without packages=find_packages(...)

setup() lacks packages, so bdist_egg won’t include avocado_result_mail. Import find_packages and include the package.

Apply:

-from setuptools import setup
+from setuptools import setup, find_packages
@@
 setup(
     name="avocado-framework-plugin-result-mail",
     version=VERSION,
     include_package_data=True,
+    packages=find_packages(include=["avocado_result_mail*"]),
     install_requires=[f"avocado-framework=={VERSION}"],
     python_requires=">=3.9",
python-avocado.spec (1)

102-105: Fix %setup -n dir name to match extracted archive

Ensure the directory matches the GitHub archive top-level.

-%setup -q -n avocado_framework-%{version}
+%setup -q -n avocado-%{version}
@@
-%setup -q -n avocado_framework-%{commit}
+%setup -q -n avocado-%{commit}
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (8)

208-216: Improve termination logging

The exception path logs a literal {session.host} string and drops the traceback. Use LOG.exception (or format the f-string with exc_info=True) so the host and stack trace are recorded.

-        except exceptions.ExpectTimeoutError:
-            LOG.error("Failed to terminate task on {session.host}")
-            return False
+        except exceptions.ExpectTimeoutError:
+            LOG.exception(f"Failed to terminate task on {session.host}:{session.port}")
+            return False

38-46: Ensure timeout settings are parsed as integers

Line 40 and Line 45 register numeric options without key_type=int, so the config returns strings. Those strings flow into run_remote_cmd_async() timeouts and break arithmetic/comparisons. Please declare them as integers.

-        settings.register_option(
-            section=section, key="setup_timeout", help_msg=help_msg, default=3600
-        )
+        settings.register_option(
+            section=section,
+            key="setup_timeout",
+            help_msg=help_msg,
+            default=3600,
+            key_type=int,
+        )
...
-        settings.register_option(
-            section=section, key="test_timeout", help_msg=help_msg, default=14400
-        )
+        settings.register_option(
+            section=section,
+            key="test_timeout",
+            help_msg=help_msg,
+            default=14400,
+            key_type=int,
+        )

106-115: Harden slot initialization against missing/invalid JSON

Line 112 opens each slot file without protection. A missing file, malformed JSON, or failed login will raise and abort the spawner. Please wrap this block with targeted exception handling, log the failure, and continue loading the remaining slots.


136-145: Escape task identifiers in remote commands

Line 144 interpolates runtime_task.task.identifier directly into the shell command. Identifiers containing spaces or shell metacharacters break the command, and crafted values open command-injection risks. Quote the identifier before composing the pgrep command.

-        status, _ = session.cmd_status_output(
-            f"pgrep -r R,S -f {runtime_task.task.identifier}"
-        )
+        safe_identifier = shlex.quote(runtime_task.task.identifier)
+        status, _ = session.cmd_status_output(f"pgrep -r R,S -f {safe_identifier}")

148-193: Spawn tasks asynchronously instead of blocking until completion

run_remote_cmd_async() on Line 184 waits for the command to finish, so spawn_task() only returns after the remote job is done. That defeats the spawner contract (no parallelism, wait_task/terminate_task never see a running process). Please launch the remote command in the background (capture PID/handle), store it on runtime_task, and return immediately so later lifecycle calls can manage the running task.


182-190: Preserve remote stdout/stderr

Line 182 appends > /dev/null, discarding every bit of task output and making failures impossible to debug. Drop the redirection (or redirect to a per-task log you can fetch later).

-        cmd = shlex.join(entry_point_args) + " > /dev/null"
+        cmd = shlex.join(entry_point_args)

195-201: Avoid a shared /tmp/.avocado_task_output_dir

Line 197 reuses a fixed path for all tasks. Concurrent runs collide, overwrite artifacts, and trigger the Ruff S108 warning. Derive a sanitized, per-task path (e.g. include the identifier or a UUID) and ensure the remote directory exists before use.


202-206: Honor a timeout while waiting for tasks

wait_task() loops forever if is_task_alive() never flips (hung process or detection bug). Please enforce the configured spawner.remote.test_timeout (or a dedicated wait timeout): track elapsed time, break once exceeded, and surface a timeout error to callers.

pyproject.toml (3)

14-16: Restore runtime dependencies

Line 15 now lists only "setuptools", but the core package still imports modules such as psutil, requests, jsonschema, etc. Without declaring them, pip install avocado-framework succeeds and the code crashes with ImportError. Please port the full install requirements from the previous setup.py into this dependency list (use optional groups for extras).


30-30: Remove unsupported Python 3.14 classifier

PyPI currently rejects uploads containing Programming Language :: Python :: 3.14. Drop this classifier until PyPI publishes it.


238-239: Package the scripts module for registered console scripts

Line 239 excludes scripts*, yet [project.scripts] exposes avocado-external-runner = "scripts.external_runner:main". The wheel/sdist will miss that module, so the entry point fails at runtime. Remove scripts* from the exclusion list (or relocate the module inside an included package).

.github/workflows/prerelease.yml (1)

51-51: Critical typo remains unfixed.

The module name selftets.check should be selftests.check. This typo will cause the workflow to fail with a ModuleNotFoundError.

Apply this diff:

-        run: python3 -m selftets.check --select=pre-release,vmimage
+        run: python3 -m selftests.check --select=pre-release,vmimage
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b537d6c and 1d7f5fe.

📒 Files selected for processing (63)
  • .github/actions/version/action.yml (1 hunks)
  • .github/workflows/ci.yml (13 hunks)
  • .github/workflows/prerelease.yml (1 hunks)
  • .github/workflows/setup.yml (6 hunks)
  • .github/workflows/vmimage.yml (1 hunks)
  • .github/workflows/weekly.yml (2 hunks)
  • Makefile (5 hunks)
  • Makefile.gh (1 hunks)
  • docs/source/guides/contributor/chapters/environment.rst (1 hunks)
  • docs/source/guides/contributor/chapters/plugins.rst (4 hunks)
  • docs/source/guides/writer/chapters/subclassing.rst (2 hunks)
  • examples/plugins/README.rst (1 hunks)
  • examples/plugins/cli-cmd/hello/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_option/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_option/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_parser/setup.py (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/pyproject.toml (1 hunks)
  • examples/plugins/cli-cmd/hello_priority/setup.py (1 hunks)
  • examples/plugins/job-pre-post/sleep/pyproject.toml (1 hunks)
  • examples/plugins/job-pre-post/sleep/setup.py (1 hunks)
  • examples/plugins/test-pre-post/hello/pyproject.toml (1 hunks)
  • examples/plugins/test-pre-post/hello/setup.py (1 hunks)
  • examples/plugins/tests/README.rst (1 hunks)
  • examples/plugins/tests/magic/pyproject.toml (1 hunks)
  • examples/plugins/tests/magic/setup.py (1 hunks)
  • examples/plugins/tests/rogue/pyproject.toml (1 hunks)
  • examples/plugins/tests/rogue/setup.py (1 hunks)
  • optional_plugins/README.rst (1 hunks)
  • optional_plugins/ansible/README.rst (1 hunks)
  • optional_plugins/ansible/pyproject.toml (1 hunks)
  • optional_plugins/ansible/setup.py (2 hunks)
  • optional_plugins/golang/pyproject.toml (1 hunks)
  • optional_plugins/golang/setup.py (1 hunks)
  • optional_plugins/html/pyproject.toml (1 hunks)
  • optional_plugins/html/setup.py (1 hunks)
  • optional_plugins/mail/pyproject.toml (1 hunks)
  • optional_plugins/mail/setup.py (1 hunks)
  • optional_plugins/result_upload/pyproject.toml (1 hunks)
  • optional_plugins/result_upload/setup.py (1 hunks)
  • optional_plugins/resultsdb/pyproject.toml (1 hunks)
  • optional_plugins/resultsdb/setup.py (1 hunks)
  • optional_plugins/robot/avocado_robot/robot.py (1 hunks)
  • optional_plugins/robot/pyproject.toml (1 hunks)
  • optional_plugins/robot/setup.py (1 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/__init__.py (0 hunks)
  • optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (1 hunks)
  • optional_plugins/spawner_remote/pyproject.toml (1 hunks)
  • optional_plugins/spawner_remote/setup.py (1 hunks)
  • optional_plugins/varianter_cit/pyproject.toml (1 hunks)
  • optional_plugins/varianter_cit/setup.py (1 hunks)
  • optional_plugins/varianter_pict/README.rst (1 hunks)
  • optional_plugins/varianter_pict/pyproject.toml (1 hunks)
  • optional_plugins/varianter_pict/setup.py (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/README.rst (4 hunks)
  • optional_plugins/varianter_yaml_to_mux/pyproject.toml (1 hunks)
  • optional_plugins/varianter_yaml_to_mux/setup.py (1 hunks)
  • pyproject.toml (1 hunks)
  • python-avocado.spec (4 hunks)
  • selftests/unit/runner_package.py (2 hunks)
  • setup.py (3 hunks)
  • spell.ignore (1 hunks)
💤 Files with no reviewable changes (1)
  • optional_plugins/spawner_remote/avocado_spawner_remote/init.py
✅ Files skipped from review due to trivial changes (5)
  • examples/plugins/cli-cmd/hello/pyproject.toml
  • examples/plugins/cli-cmd/hello/setup.py
  • examples/plugins/job-pre-post/sleep/setup.py
  • examples/plugins/test-pre-post/hello/setup.py
  • examples/plugins/tests/magic/setup.py
🚧 Files skipped from review as they are similar to previous changes (24)
  • examples/plugins/job-pre-post/sleep/pyproject.toml
  • .github/workflows/weekly.yml
  • examples/plugins/tests/README.rst
  • examples/plugins/README.rst
  • docs/source/guides/writer/chapters/subclassing.rst
  • optional_plugins/README.rst
  • examples/plugins/cli-cmd/hello_option/setup.py
  • optional_plugins/varianter_yaml_to_mux/README.rst
  • examples/plugins/cli-cmd/hello_parser/setup.py
  • optional_plugins/result_upload/pyproject.toml
  • optional_plugins/ansible/README.rst
  • examples/plugins/test-pre-post/hello/pyproject.toml
  • optional_plugins/varianter_pict/README.rst
  • optional_plugins/varianter_pict/pyproject.toml
  • optional_plugins/varianter_cit/pyproject.toml
  • examples/plugins/tests/magic/pyproject.toml
  • .github/workflows/setup.yml
  • .github/actions/version/action.yml
  • examples/plugins/tests/rogue/pyproject.toml
  • optional_plugins/robot/avocado_robot/robot.py
  • optional_plugins/golang/setup.py
  • .github/workflows/vmimage.yml
  • spell.ignore
  • optional_plugins/robot/setup.py
🧰 Additional context used
🧬 Code graph analysis (2)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py (5)
avocado/core/plugin_interfaces.py (2)
  • Init (23-28)
  • Spawner (321-441)
avocado/core/spawners/common.py (3)
  • SpawnerMixin (35-47)
  • SpawnMethod (6-18)
  • task_output_dir (46-47)
avocado/core/settings.py (3)
  • section (137-138)
  • register_option (642-799)
  • key (141-142)
avocado/core/nrunner/runnable.py (1)
  • pick_runner_module_from_entry_point_kind (630-643)
avocado/core/nrunner/task.py (1)
  • setup_output_dir (171-174)
selftests/unit/runner_package.py (2)
avocado/utils/software_manager/manager.py (1)
  • is_capable (48-54)
avocado/plugins/runners/package.py (2)
  • PackageRunner (12-156)
  • _run_software_manager (87-109)
🪛 checkmake (0.2.2)
Makefile

[warning] 124-124: Missing required phony target "all"

(minphony)


[warning] 124-124: Missing required phony target "test"

(minphony)

🪛 Ruff (0.14.0)
optional_plugins/spawner_remote/avocado_spawner_remote/spawner.py

73-73: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


74-74: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


126-129: Avoid specifying long messages outside the exception class

(TRY003)


197-197: Probable insecure usage of temporary file or directory: "/tmp/.avocado_task_output_dir"

(S108)


213-213: Consider moving this statement to an else block

(TRY300)


215-215: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


219-219: Unused static method argument: runtime_task

(ARG004)


224-224: Unused static method argument: runtime_task

(ARG004)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (47)
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:centos-stream-9-x86_64
  • GitHub Check: rpm-build:fedora-43-x86_64
  • GitHub Check: rpm-build:fedora-rawhide-x86_64
  • GitHub Check: rpm-build:fedora-41-s390x
  • GitHub Check: rpm-build:fedora-41-x86_64
  • GitHub Check: rpm-build:fedora-42-x86_64
  • GitHub Check: rpm-build:epel-9-x86_64
  • GitHub Check: rpm-build:fedora-41-ppc64le
  • GitHub Check: rpm-build:fedora-41-aarch64
  • GitHub Check: Egg task ubi:9.2
  • GitHub Check: Version task ubuntu:22.04
  • GitHub Check: Podman spawner with 3rd party runner plugin
  • GitHub Check: Podman Egg task
  • GitHub Check: Fedora selftests
  • GitHub Check: macOS with Python 3.11
  • GitHub Check: Windows with Python 3.13
  • GitHub Check: Windows with Python 3.11
  • GitHub Check: Smokecheck on Linux with Python 3.11
  • GitHub Check: Smokecheck on Linux with Python 3.10
  • GitHub Check: Static checks
  • GitHub Check: Code Coverage (3.11)
🔇 Additional comments (33)
examples/plugins/cli-cmd/hello_priority/setup.py (1)

1-15: LGTM!

The backward compatibility shim correctly retains all necessary metadata for egg builds while documenting the migration to pyproject.toml.

examples/plugins/tests/rogue/setup.py (1)

1-24: LGTM!

The backward compatibility shim follows the same pattern and correctly preserves all entry points and metadata for egg builds.

examples/plugins/cli-cmd/hello_option/pyproject.toml (1)

1-14: LGTM!

The pyproject.toml configuration is well-structured with correct build system requirements, project metadata, and a unique entry point name that avoids collisions.

optional_plugins/spawner_remote/pyproject.toml (1)

1-45: LGTM!

The pyproject.toml configuration is correct with proper entry points referencing the .spawner module, appropriate build system requirements, and dependency specifications including aexpect>=1.6.2.

optional_plugins/result_upload/setup.py (1)

16-42: LGTM – entry points in setup.py and pyproject.toml are synchronized.

optional_plugins/varianter_pict/setup.py (1)

16-18: LGTM! Minimal setup.py configuration looks good.

The migration correctly preserves backward compatibility by keeping essential fields (name, version, install_requires, entry_points) in setup.py while moving metadata to pyproject.toml. The added python_requires=">=3.9" aligns with the project dropping Python 3.8 support.

Also applies to: 33-33

optional_plugins/html/setup.py (1)

16-18: LGTM! Backward compatibility setup maintained correctly.

The minimal setup.py approach with python_requires=">=3.9" is consistent with the migration pattern and project requirements.

Also applies to: 37-37

optional_plugins/varianter_cit/setup.py (1)

29-29: LGTM on python_requires addition.

The python_requires=">=3.9" constraint correctly reflects the project's dropped Python 3.8 support.

optional_plugins/ansible/setup.py (1)

16-18: LGTM! Backward compatibility setup maintained.

The minimal setup.py with python_requires=">=3.9" and conditional dependencies for different Python versions is appropriate for the migration.

Also applies to: 37-37

selftests/unit/runner_package.py (2)

57-71: Mocking approach is correct for unit tests.

The refactored tests properly mock SoftwareManager and directly test the _run_software_manager logic with controlled inputs/outputs. The test coverage for install, already-installed, and failed-install scenarios is comprehensive.

Also applies to: 73-86, 88-102


50-55: Integration tests already cover the full runner.run() flow.
The functional tests in selftests/functional/runner_package.py invoke the package runner CLI, exercise process spawning and queue communication, and assert the “started”→“finished” status sequence, timing fields, and error handling.

python-avocado.spec (1)

220-220: LGTM: use python -m for selftests

Switch to “%{python3} -m selftests.check …” is correct and more robust.

examples/plugins/cli-cmd/hello_parser/pyproject.toml (1)

5-14: LGTM: minimal example plugin packaging looks correct

Name/version/entry-point and py-modules are consistent.

optional_plugins/resultsdb/pyproject.toml (1)

5-48: LGTM: resultsdb plugin pyproject is consistent

Build-system, metadata, deps, and entry points look good.

docs/source/guides/contributor/chapters/plugins.rst (4)

97-101: LGTM! Documentation correctly reflects modern packaging practices.

The updates accurately document the migration from setup.py to pyproject.toml and the use of pip install -e . for editable installs, which aligns with PEP 517/621 standards.


111-114: LGTM! Entry points format correctly updated.

The documentation now correctly shows the TOML-based entry points syntax ([project.entry-points."avocado.plugins.job.prepost"]) which is the standard for pyproject.toml files.


170-173: LGTM! Settings plugin registration correctly documented.

The documentation correctly shows how to register settings plugins using the TOML format in pyproject.toml, maintaining consistency with the overall migration approach.


325-327: LGTM! Magic plugin example correctly updated.

The documentation properly references pyproject.toml and uses pip install -e . for installation, providing developers with accurate guidance for plugin development.

optional_plugins/varianter_yaml_to_mux/setup.py (2)

28-46: LGTM! Minimal setup.py correctly configured for backward compatibility.

The simplified setup.py:

  • Correctly adds python_requires=">=3.9" (Python 3.8 was dropped per PR comments)
  • Retains essential install_requires and entry_points for egg build compatibility
  • Appropriately removes metadata fields now handled by pyproject.toml

16-18: setup.py comments accurately reflect pyproject.toml

pyproject.toml defines matching entry-points and dependencies for this plugin.

.github/workflows/prerelease.yml (1)

48-49: LGTM! Installation commands modernized appropriately.

The changes correctly:

  • Use python3 -m pip to ensure the correct pip is invoked
  • Replace setup.py develop --user with pip install --user -e . (the modern, recommended approach)
.github/workflows/ci.yml (5)

32-32: LGTM! Static checks correctly updated to use module invocation.

The change from python setup.py test to python3 -m selftests.check is appropriate and aligns with modern Python packaging practices.


89-99: LGTM! Linux test configuration correctly modernized.

The updates appropriately:

  • Use python -VV for verbose version display (correctly addressed previous redundant flag issue)
  • Invoke pip via python3 -m pip to ensure correct pip instance
  • Use python3 -m selftests.check for test execution

131-143: LGTM! macOS test configuration consistent with other platforms.

The changes maintain consistency across platforms by using python -VV and python3 -m selftests.check.


167-171: LGTM! Windows configuration correctly updated.

The changes properly:

  • Use python -VV consistently with other platforms
  • Install setuptools via python -m pip if missing (correctly fixed from past review)
  • Use python -m pip install --user -e . for editable install

265-356: LGTM! Comprehensive dependency updates across all distros.

The updates systematically ensure that python3-pip (and python3-venv where needed) are installed across all tested distributions (Fedora, UBI, Debian, Ubuntu). This is essential for the pip-based installation workflow.

Notable additions:

  • ca-certificates for Ubuntu environments (needed for pip over HTTPS)
  • python3-venv for Debian/Ubuntu (required for virtual environments)
Makefile (7)

9-10: LGTM! New plugin development targets improve developer experience.

The addition of develop-plugins and develop-plugin targets provides convenient workflows for plugin development, aligning well with the project's plugin architecture.


33-61: LGTM! Comprehensive cleanup implementation.

The expanded clean target now:

  • Removes all build artifacts (build/, dist/, *.egg-info, etc.)
  • Cleans optional plugins and example plugins
  • Removes Python cache files and temporary directories
  • Provides informative echo messages during execution

63-67: LGTM! Installation targets modernized to use pip module invocation.

Both install and uninstall targets now use python -m pip which ensures:

  • The correct pip instance is used (matching the Python interpreter)
  • Consistency with other pip invocations across the project

78-80: LGTM! Check target updated to use module invocation.

The change from direct script execution to python -m selftests.check maintains consistency with the workflow updates across the project.


91-104: LGTM! Well-implemented plugin development targets.

The implementations:

  • Use python -m pip install -e for editable plugin installs (correct approach)
  • Include proper error handling (ifndef PLUGIN)
  • Provide clear user feedback with echo statements
  • Support both batch installation and individual plugin selection

109-114: LGTM! Man page generation properly guarded.

The addition of a command existence check for rst2man provides:

  • Clear error messaging when the tool is missing
  • Prevents silent failures during the build process
  • Better developer experience

124-124: LGTM! PHONY targets correctly updated.

The .PHONY declaration properly includes the newly added develop-plugins and develop-plugin targets, ensuring these targets are always executed regardless of file timestamps.

Comment on lines +14 to +19
dependencies = [
"avocado-framework",
"cffi",
"pycparser",
"ansible-core",
]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Sync dependency markers with legacy setup.py (avoid drift)

Confirm whether setup.py had pins/markers (e.g., cffi, cryptography, markupsafe). Keep both files aligned or drop pins in both.

Run to diff constraints:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "pyproject deps:"
rg -n '^dependencies\s*=\s*\[' -n -n -A20 optional_plugins/ansible/pyproject.toml | sed -n '1,40p'
echo
echo "setup.py install_requires (if present):"
if [ -f optional_plugins/ansible/setup.py ]; then
  rg -nP 'install_requires\s*=\s*\[(?s).*?\]' optional_plugins/ansible/setup.py -n -N -U -C2 || true
else
  echo "optional_plugins/ansible/setup.py not found"
fi

Length of output: 1068


Align dependency constraints between pyproject.toml and setup.py
optional_plugins/ansible/pyproject.toml: update dependencies to mirror the pins and markers in optional_plugins/ansible/setup.py (avocado-framework=={VERSION}, cffi==1.17.1; python_version<'3.10', cffi; python_version>='3.10', cryptography<46.0.0; python_version<'3.10', pycparser, ansible-core, markupsafe<3.0.0) or remove them from install_requires to avoid drift.

🤖 Prompt for AI Agents
In optional_plugins/ansible/pyproject.toml around lines 14 to 19, the declared
dependencies must be aligned with the pins and environment markers currently
present in optional_plugins/ansible/setup.py; update the dependencies list to
mirror those exact constraints (e.g. avocado-framework=={VERSION}, cffi==1.17.1;
add conditional markers: "cffi; python_version < '3.10'", "cryptography<46.0.0;
python_version >= '3.10'", "pycparser; python_version < '3.10'", "ansible-core",
"markupsafe<3.0.0" where applicable) or alternatively remove these packages from
setup.py install_requires so pyproject.toml becomes the single source of
truth—make the change so both files contain identical pinned versions and
markers to avoid drift.

Comment on lines +14 to +16
dependencies = [
"avocado-framework",
]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Version pinning inconsistency with setup.py.

The comment in setup.py files states "Keep install_requires and entry_points in sync with pyproject.toml for egg build compatibility." However, this file uses an unpinned "avocado-framework" dependency, while the corresponding setup.py likely pins it as f"avocado-framework=={VERSION}".

This inconsistency causes different dependency resolution between builds:

  • Wheel/sdist builds (via pyproject.toml): any avocado-framework version
  • Egg builds (via setup.py): exact version pinned

Run this script to compare the dependency specifications:

Consider either:

  1. Adding dynamic dependency substitution to match the VERSION file
  2. Using a consistent version range in both files (e.g., "avocado-framework>=X.Y,<X.Y+1")

🏁 Script executed:

#!/bin/bash
# Description: Compare avocado-framework dependency between setup.py and pyproject.toml

echo "=== golang/setup.py install_requires ==="
rg -A5 "install_requires" optional_plugins/golang/setup.py

echo -e "\n=== golang/pyproject.toml dependencies ==="
rg -A3 "^dependencies" optional_plugins/golang/pyproject.toml

Length of output: 537


Sync avocado-framework version pin between pyproject.toml and setup.py
In optional_plugins/golang/pyproject.toml, change the unpinned "avocado-framework" dependency to "avocado-framework=={VERSION}" to match setup.py and ensure consistent builds.

🤖 Prompt for AI Agents
In optional_plugins/golang/pyproject.toml around lines 14 to 16, the dependency
"avocado-framework" is not version-pinned; update it to
"avocado-framework=={VERSION}" to match setup.py and ensure consistent
builds—replace the unpinned entry with the exact version used in setup.py (or
reference the same version constant), keep the same quoting and comma style as
other entries.

Comment on lines 35 to 37
"urllib3<2.3.0; python_version < '3.9'",
],
python_requires=">=3.9",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove dead conditional dependency.

Line 35 specifies urllib3<2.3.0; python_version < '3.9', but line 37 sets python_requires=">=3.9". This condition can never be satisfied because the package cannot be installed on Python < 3.9.

Apply this diff to remove the dead constraint:

     install_requires=[
         f"avocado-framework=={VERSION}",
         "resultsdb-api==2.1.5",
-        "urllib3<2.3.0; python_version < '3.9'",
     ],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"urllib3<2.3.0; python_version < '3.9'",
],
python_requires=">=3.9",
install_requires=[
f"avocado-framework=={VERSION}",
"resultsdb-api==2.1.5",
],
python_requires=">=3.9",
🤖 Prompt for AI Agents
In optional_plugins/resultsdb/setup.py around lines 35 to 37, the dependency
line "urllib3<2.3.0; python_version < '3.9'" is dead because
python_requires=">=3.9" prevents installing on Python <3.9; remove that
conditional specifier (delete the entire "urllib3<2.3.0; python_version < '3.9'"
entry) so the requirements list only contains relevant constraints, or if
urllib3 is needed for all supported Pythons add a non-conditional urllib3 spec
instead.

readme_contents = readme.read()
return readme_contents

VERSION = open("VERSION", "r", encoding="utf-8").read().strip()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix resource leak: file handle not closed.

Line 22 opens the VERSION file but never closes it, causing a resource leak. The file handle remains open until garbage collection.

Apply this diff to use a context manager:

-VERSION = open("VERSION", "r", encoding="utf-8").read().strip()
+BASE_PATH = os.path.dirname(__file__)
+with open(os.path.join(BASE_PATH, "VERSION"), "r", encoding="utf-8") as version_file:
+    VERSION = version_file.read().strip()

Or match the pattern used in other setup.py files in this PR (e.g., varianter_pict/setup.py lines 24-26).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
VERSION = open("VERSION", "r", encoding="utf-8").read().strip()
# before, at line 22:
# VERSION = open("VERSION", "r", encoding="utf-8").read().strip()
BASE_PATH = os.path.dirname(__file__)
with open(os.path.join(BASE_PATH, "VERSION"), "r", encoding="utf-8") as version_file:
VERSION = version_file.read().strip()
🤖 Prompt for AI Agents
In optional_plugins/varianter_cit/setup.py around line 22, the VERSION file is
opened with open(...) and read without closing the file; change this to use a
context manager (with open("VERSION", "r", encoding="utf-8") as f: VERSION =
f.read().strip()) or use the same pattern as varianter_pict/setup.py (e.g.,
Path("VERSION").read_text(encoding="utf-8").strip()) so the file handle is
closed immediately and no resource leak occurs.

# Settings used for build from snapshots.
%if 0%{?rel_build}
%global gittar avocado-%{version}.tar.gz
%global gittar avocado_framework-%{version}.tar.gz
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Tarball name/directory mismatch will break source fetch and %setup

GitHub archives extract to avocado-, not avocado_framework-. This will 404/mis-unpack.

Apply:

-%global gittar          avocado_framework-%{version}.tar.gz
+%global gittar          avocado-%{version}.tar.gz
@@
-%global gittar          avocado_framework-%{shortcommit}.tar.gz
+%global gittar          avocado-%{shortcommit}.tar.gz

Also applies to: 18-18

🤖 Prompt for AI Agents
In python-avocado.spec around line 8 (and also at line 18), the %global gittar
value uses avocado_framework-%{version}.tar.gz which mismatches GitHub archive
names (they extract to avocado-%{tag}), causing source fetch and %setup to fail;
update the %global gittar to avocado-%{version}.tar.gz and ensure any %setup -c
or %setup -n references use the extracted directory name (avocado-%{version}) so
the source archive and unpacked directory match.

Comment on lines +207 to +356
# For egg builds, we need to specify packages and entry_points explicitly
# Note: This duplicates configuration from pyproject.toml
setup(
name="avocado-framework",
version=VERSION,
packages=find_packages(exclude=("selftests*",)),
include_package_data=True,
zip_safe=False,
# Keep install_requires in sync with pyproject.toml dependencies
# for backward compatibility with older tools
install_requires=[
"setuptools",
],
python_requires=">=3.9",
entry_points={
"console_scripts": [
"avocado = avocado.core.main:main",
"avocado-runner-noop = avocado.plugins.runners.noop:main",
"avocado-runner-dry-run = avocado.plugins.runners.dry_run:main",
"avocado-runner-exec-test = avocado.plugins.runners.exec_test:main",
"avocado-runner-python-unittest = avocado.plugins.runners.python_unittest:main",
"avocado-runner-avocado-instrumented = avocado.plugins.runners.avocado_instrumented:main",
"avocado-runner-tap = avocado.plugins.runners.tap:main",
"avocado-runner-asset = avocado.plugins.runners.asset:main",
"avocado-runner-package = avocado.plugins.runners.package:main",
"avocado-runner-pip = avocado.plugins.runners.pip:main",
"avocado-runner-vmimage = avocado.plugins.runners.vmimage:main",
"avocado-runner-podman-image = avocado.plugins.runners.podman_image:main",
"avocado-runner-sysinfo = avocado.plugins.runners.sysinfo:main",
"avocado-software-manager = avocado.utils.software_manager.main:main",
"avocado-external-runner = scripts.external_runner:main",
],
"avocado.plugins.init": [
"xunit = avocado.plugins.xunit:XUnitInit",
"jsonresult = avocado.plugins.jsonresult:JSONInit",
"tmt = avocado.plugins.tmtresult:TMTInit",
"sysinfo = avocado.plugins.sysinfo:SysinfoInit",
"tap = avocado.plugins.tap:TAPInit",
"jobscripts = avocado.plugins.jobscripts:JobScriptsInit",
"dict_variants = avocado.plugins.dict_variants:DictVariantsInit",
"json_variants = avocado.plugins.json_variants:JsonVariantsInit",
"run = avocado.plugins.run:RunInit",
"podman = avocado.plugins.spawners.podman:PodmanSpawnerInit",
"lxc = avocado.plugins.spawners.lxc:LXCSpawnerInit",
"nrunner = avocado.plugins.runner_nrunner:RunnerInit",
"testlogsui = avocado.plugins.testlogs:TestLogsUIInit",
"human = avocado.plugins.human:HumanInit",
"exec-runnables-recipe = avocado.plugins.resolvers:ExecRunnablesRecipeInit",
],
"avocado.plugins.cli": [
"xunit = avocado.plugins.xunit:XUnitCLI",
"json = avocado.plugins.jsonresult:JSONCLI",
"tmt = avocado.plugins.tmtresult:TMTCLI",
"journal = avocado.plugins.journal:Journal",
"tap = avocado.plugins.tap:TAP",
"zip_archive = avocado.plugins.archive:ArchiveCLI",
"json_variants = avocado.plugins.json_variants:JsonVariantsCLI",
"nrunner = avocado.plugins.runner_nrunner:RunnerCLI",
"podman = avocado.plugins.spawners.podman:PodmanCLI",
],
"avocado.plugins.cli.cmd": [
"config = avocado.plugins.config:Config",
"distro = avocado.plugins.distro:Distro",
"exec-path = avocado.plugins.exec_path:ExecPath",
"variants = avocado.plugins.variants:Variants",
"list = avocado.plugins.list:List",
"run = avocado.plugins.run:Run",
"sysinfo = avocado.plugins.sysinfo:SysInfo",
"plugins = avocado.plugins.plugins:Plugins",
"diff = avocado.plugins.diff:Diff",
"vmimage = avocado.plugins.vmimage:VMimage",
"assets = avocado.plugins.assets:Assets",
"jobs = avocado.plugins.jobs:Jobs",
"replay = avocado.plugins.replay:Replay",
"cache = avocado.plugins.cache:Cache",
],
"avocado.plugins.job.prepost": [
"jobscripts = avocado.plugins.jobscripts:JobScripts",
"teststmpdir = avocado.plugins.teststmpdir:TestsTmpDir",
"human = avocado.plugins.human:HumanJob",
"testlogsui = avocado.plugins.testlogs:TestLogsUI",
"suite-dependency = avocado.plugins.dependency:SuiteDependency",
],
"avocado.plugins.test.pre": [
"dependency = avocado.plugins.dependency:DependencyResolver",
"sysinfo = avocado.plugins.sysinfo:SysInfoTest",
],
"avocado.plugins.test.post": [
"sysinfo = avocado.plugins.sysinfo:SysInfoTest",
],
"avocado.plugins.result": [
"xunit = avocado.plugins.xunit:XUnitResult",
"json = avocado.plugins.jsonresult:JSONResult",
"tmt = avocado.plugins.tmtresult:TMTResult",
"zip_archive = avocado.plugins.archive:Archive",
],
"avocado.plugins.result_events": [
"human = avocado.plugins.human:Human",
"tap = avocado.plugins.tap:TAPResult",
"journal = avocado.plugins.journal:JournalResult",
"fetchasset = avocado.plugins.assets:FetchAssetJob",
"sysinfo = avocado.plugins.sysinfo:SysInfoJob",
"testlogging = avocado.plugins.testlogs:TestLogging",
"bystatus = avocado.plugins.bystatus:ByStatusLink",
"beaker = avocado.plugins.beaker_result:BeakerResult",
],
"avocado.plugins.varianter": [
"json_variants = avocado.plugins.json_variants:JsonVariants",
"dict_variants = avocado.plugins.dict_variants:DictVariants",
],
"avocado.plugins.resolver": [
"exec-test = avocado.plugins.resolvers:ExecTestResolver",
"python-unittest = avocado.plugins.resolvers:PythonUnittestResolver",
"avocado-instrumented = avocado.plugins.resolvers:AvocadoInstrumentedResolver",
"tap = avocado.plugins.resolvers:TapResolver",
"runnable-recipe = avocado.plugins.resolvers:RunnableRecipeResolver",
"runnables-recipe = avocado.plugins.resolvers:RunnablesRecipeResolver",
"exec-runnables-recipe = avocado.plugins.resolvers:ExecRunnablesRecipeResolver",
],
"avocado.plugins.suite.runner": [
"nrunner = avocado.plugins.runner_nrunner:Runner",
],
"avocado.plugins.runnable.runner": [
"avocado-instrumented = avocado.plugins.runners.avocado_instrumented:AvocadoInstrumentedTestRunner",
"tap = avocado.plugins.runners.tap:TAPRunner",
"noop = avocado.plugins.runners.noop:NoOpRunner",
"dry-run = avocado.plugins.runners.dry_run:DryRunRunner",
"exec-test = avocado.plugins.runners.exec_test:ExecTestRunner",
"python-unittest = avocado.plugins.runners.python_unittest:PythonUnittestRunner",
"asset = avocado.plugins.runners.asset:AssetRunner",
"package = avocado.plugins.runners.package:PackageRunner",
"pip = avocado.plugins.runners.pip:PipRunner",
"podman-image = avocado.plugins.runners.podman_image:PodmanImageRunner",
"vmimage = avocado.plugins.runners.vmimage:VMImageRunner",
"sysinfo = avocado.plugins.runners.sysinfo:SysinfoRunner",
],
"avocado.plugins.spawner": [
"process = avocado.plugins.spawners.process:ProcessSpawner",
"podman = avocado.plugins.spawners.podman:PodmanSpawner",
"lxc = avocado.plugins.spawners.lxc:LXCSpawner",
],
"avocado.plugins.cache": [
"requirement = avocado.plugins.requirement_cache:RequirementCache",
],
packages=find_packages(exclude=("selftests*",)),
include_package_data=True,
entry_points={
"console_scripts": [
"avocado = avocado.core.main:main",
"avocado-runner-noop = avocado.plugins.runners.noop:main",
"avocado-runner-dry-run = avocado.plugins.runners.dry_run:main",
"avocado-runner-exec-test = avocado.plugins.runners.exec_test:main",
"avocado-runner-python-unittest = avocado.plugins.runners.python_unittest:main",
"avocado-runner-avocado-instrumented = avocado.plugins.runners.avocado_instrumented:main",
"avocado-runner-tap = avocado.plugins.runners.tap:main",
"avocado-runner-asset = avocado.plugins.runners.asset:main",
"avocado-runner-package = avocado.plugins.runners.package:main",
"avocado-runner-pip = avocado.plugins.runners.pip:main",
"avocado-runner-vmimage = avocado.plugins.runners.vmimage:main",
"avocado-runner-podman-image = avocado.plugins.runners.podman_image:main",
"avocado-runner-sysinfo = avocado.plugins.runners.sysinfo:main",
"avocado-software-manager = avocado.utils.software_manager.main:main",
"avocado-external-runner = scripts.external_runner:main",
],
"avocado.plugins.init": [
"xunit = avocado.plugins.xunit:XUnitInit",
"jsonresult = avocado.plugins.jsonresult:JSONInit",
"tmt = avocado.plugins.tmtresult:TMTInit",
"sysinfo = avocado.plugins.sysinfo:SysinfoInit",
"tap = avocado.plugins.tap:TAPInit",
"jobscripts = avocado.plugins.jobscripts:JobScriptsInit",
"dict_variants = avocado.plugins.dict_variants:DictVariantsInit",
"json_variants = avocado.plugins.json_variants:JsonVariantsInit",
"run = avocado.plugins.run:RunInit",
"podman = avocado.plugins.spawners.podman:PodmanSpawnerInit",
"lxc = avocado.plugins.spawners.lxc:LXCSpawnerInit",
"nrunner = avocado.plugins.runner_nrunner:RunnerInit",
"testlogsui = avocado.plugins.testlogs:TestLogsUIInit",
"human = avocado.plugins.human:HumanInit",
"exec-runnables-recipe = avocado.plugins.resolvers:ExecRunnablesRecipeInit",
],
"avocado.plugins.cli": [
"xunit = avocado.plugins.xunit:XUnitCLI",
"json = avocado.plugins.jsonresult:JSONCLI",
"tmt = avocado.plugins.tmtresult:TMTCLI",
"journal = avocado.plugins.journal:Journal",
"tap = avocado.plugins.tap:TAP",
"zip_archive = avocado.plugins.archive:ArchiveCLI",
"json_variants = avocado.plugins.json_variants:JsonVariantsCLI",
"nrunner = avocado.plugins.runner_nrunner:RunnerCLI",
"podman = avocado.plugins.spawners.podman:PodmanCLI",
],
"avocado.plugins.cli.cmd": [
"config = avocado.plugins.config:Config",
"distro = avocado.plugins.distro:Distro",
"exec-path = avocado.plugins.exec_path:ExecPath",
"variants = avocado.plugins.variants:Variants",
"list = avocado.plugins.list:List",
"run = avocado.plugins.run:Run",
"sysinfo = avocado.plugins.sysinfo:SysInfo",
"plugins = avocado.plugins.plugins:Plugins",
"diff = avocado.plugins.diff:Diff",
"vmimage = avocado.plugins.vmimage:VMimage",
"assets = avocado.plugins.assets:Assets",
"jobs = avocado.plugins.jobs:Jobs",
"replay = avocado.plugins.replay:Replay",
"cache = avocado.plugins.cache:Cache",
],
"avocado.plugins.job.prepost": [
"jobscripts = avocado.plugins.jobscripts:JobScripts",
"teststmpdir = avocado.plugins.teststmpdir:TestsTmpDir",
"human = avocado.plugins.human:HumanJob",
"testlogsui = avocado.plugins.testlogs:TestLogsUI",
"suite-dependency = avocado.plugins.dependency:SuiteDependency",
],
"avocado.plugins.test.pre": [
"dependency = avocado.plugins.dependency:DependencyResolver",
"sysinfo = avocado.plugins.sysinfo:SysInfoTest",
],
"avocado.plugins.test.post": [
"sysinfo = avocado.plugins.sysinfo:SysInfoTest",
],
"avocado.plugins.result": [
"xunit = avocado.plugins.xunit:XUnitResult",
"json = avocado.plugins.jsonresult:JSONResult",
"tmt = avocado.plugins.tmtresult:TMTResult",
"zip_archive = avocado.plugins.archive:Archive",
],
"avocado.plugins.result_events": [
"human = avocado.plugins.human:Human",
"tap = avocado.plugins.tap:TAPResult",
"journal = avocado.plugins.journal:JournalResult",
"fetchasset = avocado.plugins.assets:FetchAssetJob",
"sysinfo = avocado.plugins.sysinfo:SysInfoJob",
"testlogging = avocado.plugins.testlogs:TestLogging",
"bystatus = avocado.plugins.bystatus:ByStatusLink",
"beaker = avocado.plugins.beaker_result:BeakerResult",
],
"avocado.plugins.varianter": [
"json_variants = avocado.plugins.json_variants:JsonVariants",
"dict_variants = avocado.plugins.dict_variants:DictVariants",
],
"avocado.plugins.resolver": [
"exec-test = avocado.plugins.resolvers:ExecTestResolver",
"python-unittest = avocado.plugins.resolvers:PythonUnittestResolver",
"avocado-instrumented = avocado.plugins.resolvers:AvocadoInstrumentedResolver",
"tap = avocado.plugins.resolvers:TapResolver",
"runnable-recipe = avocado.plugins.resolvers:RunnableRecipeResolver",
"runnables-recipe = avocado.plugins.resolvers:RunnablesRecipeResolver",
"exec-runnables-recipe = avocado.plugins.resolvers:ExecRunnablesRecipeResolver",
],
"avocado.plugins.suite.runner": [
"nrunner = avocado.plugins.runner_nrunner:Runner",
],
"avocado.plugins.runnable.runner": [
(
"avocado-instrumented = avocado.plugins."
"runners.avocado_instrumented:AvocadoInstrumentedTestRunner"
),
"tap = avocado.plugins.runners.tap:TAPRunner",
"noop = avocado.plugins.runners.noop:NoOpRunner",
"dry-run = avocado.plugins.runners.dry_run:DryRunRunner",
"exec-test = avocado.plugins.runners.exec_test:ExecTestRunner",
"python-unittest = avocado.plugins.runners.python_unittest:PythonUnittestRunner",
"asset = avocado.plugins.runners.asset:AssetRunner",
"package = avocado.plugins.runners.package:PackageRunner",
"pip = avocado.plugins.runners.pip:PipRunner",
"podman-image = avocado.plugins.runners.podman_image:PodmanImageRunner",
"vmimage = avocado.plugins.runners.vmimage:VMImageRunner",
"sysinfo = avocado.plugins.runners.sysinfo:SysinfoRunner",
],
"avocado.plugins.spawner": [
"process = avocado.plugins.spawners.process:ProcessSpawner",
"podman = avocado.plugins.spawners.podman:PodmanSpawner",
"lxc = avocado.plugins.spawners.lxc:LXCSpawner",
],
"avocado.plugins.cache": [
"requirement = avocado.plugins.requirement_cache:RequirementCache",
],
},
zip_safe=False,
python_requires=">=3.9",
cmdclass={
"clean": Clean,
"develop": Develop,
"lint": Linter,
"man": Man,
"plugin": Plugin,
"test": Test,
},
install_requires=["setuptools"],
)
},
cmdclass={
"clean": Clean,
"develop": Develop,
},
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Restrict package discovery to avocado namespace (avoid bundling optional_plugins)

find_packages(exclude=("selftests*",)) will pick up optional_plugins/* packages, causing conflicts with separate plugin wheels/RPMs.

-    packages=find_packages(exclude=("selftests*",)),
+    packages=find_packages(include=("avocado", "avocado.*")),

Ensure avocado-external-runner import is package-resolved

Entry point “scripts.external_runner:main” requires scripts/ to be a package and included. Please confirm scripts/init.py exists and scripts is packaged, or move this module under avocado.*.

install_requires not in sync with pyproject

Only “setuptools” is listed, which breaks egg installs (missing runtime deps). Either sync this with pyproject’s [project.dependencies] or drop egg support. At minimum, mirror core runtime deps here.

Future-proof clean command for Python 3.12+

distutils is removed on 3.12. Switch to setuptools.command.clean.clean.

Outside the selected lines, update the import:

-from distutils.command.clean import clean  # pylint: disable=W0402
+from setuptools.command.clean import clean

Optional: add a CI check to compare entry_points between setup.py and pyproject.toml to prevent drift.


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "Check scripts package:"
test -f scripts/external_runner.py && echo "✓ scripts/external_runner.py exists" || echo "✗ missing"
test -f scripts/__init__.py && echo "✓ scripts/__init__.py exists" || echo "✗ scripts/__init__.py missing"
rg -nP 'packages\s*=\s*find_packages\([^)]*\)' setup.py -n || true

Length of output: 145


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "pyproject dependencies (root):"
if [ -f pyproject.toml ]; then rg -n '^dependencies\s*=\s*\[' -n -A20 pyproject.toml | sed -n '1,80p'; else echo "pyproject.toml not found"; fi
echo
echo "setup.py install_requires:"
rg -nP 'install_requires\s*=\s*\[(?s).*?\]' setup.py -U -n || true

Length of output: 888


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "Check import of distutils.command.clean in setup.py:"
rg -nP 'distutils\.command\.clean.*import' -n setup.py || echo "No import found"
echo
echo "Check import of setuptools.command.clean in setup.py:"
rg -nP 'setuptools\.command\.clean.*import' -n setup.py || echo "No import found"

Length of output: 193


Restrict find_packages, ensure scripts package, and update clean import

  • Replace

    -    packages=find_packages(exclude=("selftests*",)),
    +    packages=find_packages(include=("avocado", "avocado.*")),

    to avoid bundling optional_plugins.

  • Add scripts/__init__.py (or move external_runner.py under avocado/…) so scripts.external_runner:main is importable.

  • Replace

    -from distutils.command.clean import clean  # pylint: disable=W0402
    +from setuptools.command.clean import clean

    for Python 3.12+ compatibility.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In setup.py around lines 207-356, tighten package discovery and fix imports:
update find_packages(...) to exclude optional_plugins (e.g., add
"optional_plugins*" to the exclude tuple) so optional plugins aren't bundled;
ensure scripts.external_runner is importable by adding scripts/__init__.py (or
moving external_runner.py into the avocado package and adjusting the entry_point
path); and replace the distutils-based Clean import/usage with the
setuptools-compatible variant for Python 3.12+ (swap the import/implementation
to use setuptools' clean command or an equivalent implementation) so cmdclass
remains compatible.

@PaulYuuu PaulYuuu marked this pull request as draft October 15, 2025 13:52
This commit modernizes the project packaging from setup.py to
pyproject.toml-based configuration following PEP 517/518/621 standards.

Changes include:
- Add pyproject.toml for main avocado-framework package
- Add pyproject.toml for all optional plugins (ansible, golang, html,
  mail, result_upload, resultsdb, robot, spawner_remote, varianter_cit,
  varianter_pict, varianter_yaml_to_mux)
- Add pyproject.toml for all example plugins
- Update GitHub Actions workflows to use pip install instead of setup.py
- Modernize Makefile to use pip commands and improve clean targets
- Update documentation to reflect new installation methods
- Simplify setup.py files to act as compatibility shims
- Refactor spawner_remote plugin structure (move code from __init__.py
  to spawner.py)
- Update python-avocado.spec for RPM packaging compatibility

All installation commands now use standard pip commands:
- Development install: pip install -e .
- Plugin install: pip install ./optional_plugins/<plugin>
- Uninstall: pip uninstall avocado-framework

Testing and CI workflows have been updated accordingly.

Signed-off-by: Yihuang Yu <yihyu@redhat.com>
@PaulYuuu
Copy link
Contributor Author

Hello @clebergnu, after much coding and debugging, I made the CI all green. But unfortunately, there are some different kinds of issue to enable pyproject.toml.

  1. The compatibility of egg
  2. pkg_resources vs importlib.metadata
  3. custom setup.py usage
  4. spec file uses some deprecated macros and some will be removed at fc44.
  5. selftests dir is not a pkg module, the install behavior between setup.py develop and pip install -e are different.
  6. Something I forgot.

Based on the above, I convert this to draft as a tracker, and coderabbit AI made some dirty comments. Will use separate PRs to fix issues step by step.
The first thing is introducing pyproject.toml in the repo, but keeping setup.py for backward compatibility, what do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Review Requested

Development

Successfully merging this pull request may close these issues.

2 participants