Skip to content

Commit

Permalink
PIP runner introduction
Browse files Browse the repository at this point in the history
This commit introduces a new dependency runner called `pip`. With this
runner, avocado will be able to manipulate with python packages in test
environment based on the test dependency configuration. The runner will
install pip into the test environment, and then it can call `pip
install` or `pip uninstall` commands. For example, this feature can be
used for running `coverage.py` inside different environments than
process.

Signed-off-by: Jan Richter <[email protected]>
  • Loading branch information
richtja committed Nov 27, 2024
1 parent 7c4af2d commit 38d12f6
Show file tree
Hide file tree
Showing 8 changed files with 162 additions and 1 deletion.
84 changes: 84 additions & 0 deletions avocado/plugins/runners/pip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import sys
import traceback
from multiprocessing import set_start_method

Check warning on line 3 in avocado/plugins/runners/pip.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/pip.py#L1-L3

Added lines #L1 - L3 were not covered by tests

from avocado.core.nrunner.app import BaseRunnerApp
from avocado.core.nrunner.runner import BaseRunner
from avocado.core.utils import messages
from avocado.utils import process

Check warning on line 8 in avocado/plugins/runners/pip.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/pip.py#L5-L8

Added lines #L5 - L8 were not covered by tests


class PipRunner(BaseRunner):

Check warning on line 11 in avocado/plugins/runners/pip.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/pip.py#L11

Added line #L11 was not covered by tests
"""Runner for dependencies of type pip
This runner handles, the installation, verification and removal of
packages using the pip.
Runnable attributes usage:
* kind: 'pip'
* uri: not used
* args: not used
* kwargs:
- name: the package name (required)
- action: one of 'install' or 'uninstall' (optional, defaults
to 'install')
"""

name = "pip"
description = "Runner for dependencies of type pip"

Check warning on line 32 in avocado/plugins/runners/pip.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/pip.py#L31-L32

Added lines #L31 - L32 were not covered by tests

def run(self, runnable):
try:
yield messages.StartedMessage.get()

Check warning on line 36 in avocado/plugins/runners/pip.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/pip.py#L34-L36

Added lines #L34 - L36 were not covered by tests
# check if there is a valid 'action' argument
cmd = runnable.kwargs.get("action", "install")

Check warning on line 38 in avocado/plugins/runners/pip.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/pip.py#L38

Added line #L38 was not covered by tests
# avoid invalid arguments
if cmd not in ["install", "uninstall"]:
stderr = f"Invalid action {cmd}. Use one of 'install' or 'remove'"
yield messages.StderrMessage.get(stderr.encode())
yield messages.FinishedMessage.get("error")
return

Check warning on line 44 in avocado/plugins/runners/pip.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/pip.py#L40-L44

Added lines #L40 - L44 were not covered by tests

package = runnable.kwargs.get("name")

Check warning on line 46 in avocado/plugins/runners/pip.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/pip.py#L46

Added line #L46 was not covered by tests
# if package was passed correctly, run python -m pip
if package is not None:
try:
cmd = f"python3 -m ensurepip && python3 -m pip {cmd} {package}"
result = process.run(cmd, shell=True)
except Exception as e:
yield messages.StderrMessage.get(str(e))
yield messages.FinishedMessage.get("error")
return

Check warning on line 55 in avocado/plugins/runners/pip.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/pip.py#L48-L55

Added lines #L48 - L55 were not covered by tests

yield messages.StdoutMessage.get(result.stdout)
yield messages.StderrMessage.get(result.stderr)
yield messages.FinishedMessage.get("pass")
except Exception as e:
yield messages.StderrMessage.get(traceback.format_exc())
yield messages.FinishedMessage.get(

Check warning on line 62 in avocado/plugins/runners/pip.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/pip.py#L57-L62

Added lines #L57 - L62 were not covered by tests
"error",
fail_reason=str(e),
fail_class=e.__class__.__name__,
traceback=traceback.format_exc(),
)


class RunnerApp(BaseRunnerApp):
PROG_NAME = "avocado-runner-pip"
PROG_DESCRIPTION = "nrunner application for dependencies of type pip"
RUNNABLE_KINDS_CAPABLE = ["pip"]

Check warning on line 73 in avocado/plugins/runners/pip.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/pip.py#L70-L73

Added lines #L70 - L73 were not covered by tests


def main():
if sys.platform == "darwin":
set_start_method("fork")
app = RunnerApp(print)
app.run()

Check warning on line 80 in avocado/plugins/runners/pip.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/pip.py#L76-L80

Added lines #L76 - L80 were not covered by tests


if __name__ == "__main__":
main()

Check warning on line 84 in avocado/plugins/runners/pip.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/pip.py#L83-L84

Added lines #L83 - L84 were not covered by tests
11 changes: 11 additions & 0 deletions docs/source/guides/user/chapters/dependencies.rst
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,17 @@ Following is an example of a test using the Package dependency:

.. literalinclude:: ../../../../../examples/tests/passtest_with_dependency.py

Pip
+++

Support managing python packages via pip. The
parameters available to use the asset `type` of dependencies are:

* `type`: `pip`
* `name`: the package name (required)
* `action`: `install` or `uninstall`
(optional, defaults to `install`)

Asset
+++++

Expand Down
1 change: 1 addition & 0 deletions examples/nrunner/recipes/runnable/pip_coverage.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"kind": "pip", "kwargs": {"action": "install", "name": "coverage"}}
1 change: 1 addition & 0 deletions python-avocado.spec
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ PATH=%{buildroot}%{_bindir}:%{buildroot}%{_libexecdir}/avocado:$PATH \
%{_bindir}/avocado-runner-tap
%{_bindir}/avocado-runner-asset
%{_bindir}/avocado-runner-package
%{_bindir}/avocado-runner-pip
%{_bindir}/avocado-runner-podman-image
%{_bindir}/avocado-runner-sysinfo
%{_bindir}/avocado-software-manager
Expand Down
2 changes: 1 addition & 1 deletion selftests/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"nrunner-requirement": 28,
"unit": 678,
"jobs": 11,
"functional-parallel": 314,
"functional-parallel": 317,
"functional-serial": 7,
"optional-plugins": 0,
"optional-plugins-golang": 2,
Expand Down
1 change: 1 addition & 0 deletions selftests/functional/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ def test_runnables_recipe(self):
exec-test: 3
noop: 3
package: 1
pip: 1
python-unittest: 1
sysinfo: 1"""
cmd_line = f"{AVOCADO} -V list {runnables_recipe_path}"
Expand Down
61 changes: 61 additions & 0 deletions selftests/functional/runner_pip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import os
import sys
import unittest

from avocado.utils import process
from selftests.utils import BASEDIR

RUNNER = f"{sys.executable} -m avocado.plugins.runners.pip"


class RunnableRun(unittest.TestCase):
def test_no_kwargs(self):
res = process.run(f"{RUNNER} runnable-run -k pip", ignore_status=True)
self.assertIn(b"'status': 'started'", res.stdout)
self.assertIn(b"'status': 'finished'", res.stdout)
self.assertIn(b"'time': ", res.stdout)
self.assertEqual(res.exit_status, 0)

@unittest.skipUnless(
os.getenv("CI"),
"This test runs on CI environments"
" only as it depends on the system package manager,"
" and some environments don't have it available.",
)
def test_recipe(self):
recipe = os.path.join(
BASEDIR,
"examples",
"nrunner",
"recipes",
"runnable",
"pip_coverage.json",
)
cmd = f"{RUNNER} runnable-run-recipe {recipe}"
res = process.run(cmd, ignore_status=True)
lines = res.stdout_text.splitlines()
if len(lines) == 1:
first_status = final_status = lines[0]
else:
first_status = lines[0]
final_status = lines[-1]
self.assertIn("'status': 'started'", first_status)
self.assertIn("'time': ", first_status)
self.assertIn("'status': 'finished'", final_status)
self.assertIn("'time': ", final_status)
self.assertEqual(res.exit_status, 0)


class TaskRun(unittest.TestCase):
def test_no_kwargs(self):
res = process.run(
f"{RUNNER} task-run -i XXXreq-pacXXX -k pip", ignore_status=True
)
self.assertIn(b"'status': 'finished'", res.stdout)
self.assertIn(b"'result': 'error'", res.stdout)
self.assertIn(b"'id': 'XXXreq-pacXXX'", res.stdout)
self.assertEqual(res.exit_status, 0)


if __name__ == "__main__":
unittest.main()
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ def run(self):
"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-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",
Expand Down Expand Up @@ -479,6 +480,7 @@ def run(self):
"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",
"sysinfo = avocado.plugins.runners.sysinfo:SysinfoRunner",
],
Expand Down

0 comments on commit 38d12f6

Please sign in to comment.