Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce "exec-runnables-recipe" resolver #6032

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions avocado/plugins/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,21 @@ def configure(self, parser):
allow_multiple=True,
)

settings.add_argparser_to_option(
namespace="resolver.run_executables",
parser=parser,
long_arg="--resolver-run-executables",
allow_multiple=True,
)

settings.add_argparser_to_option(
namespace="resolver.exec_runnables_recipe.arguments",
metavar="ARGS",
parser=parser,
long_arg="--resolver-exec-arguments",
allow_multiple=True,
)

help_msg = "Writes runnable recipe files to a directory."
settings.register_option(
section="list.recipes",
Expand Down
143 changes: 122 additions & 21 deletions avocado/plugins/resolvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
import json
import os
import re
import shlex
import subprocess

from avocado.core.extension_manager import PluginPriority
from avocado.core.nrunner.runnable import Runnable
from avocado.core.plugin_interfaces import Resolver
from avocado.core.plugin_interfaces import Init, Resolver
from avocado.core.references import reference_split
from avocado.core.resolver import (
ReferenceResolution,
Expand All @@ -31,16 +33,12 @@
get_file_assets,
)
from avocado.core.safeloader import find_avocado_tests, find_python_unittests
from avocado.core.settings import settings


class ExecTestResolver(Resolver):

name = "exec-test"
description = "Test resolver for executable files to be handled as tests"
priority = PluginPriority.VERY_LOW

def resolve(self, reference):

class BaseExec:
@staticmethod
def check_exec(reference):
criteria_check = check_file(
reference,
reference,
Expand All @@ -52,6 +50,18 @@ def resolve(self, reference):
if criteria_check is not True:
return criteria_check


class ExecTestResolver(BaseExec, Resolver):

name = "exec-test"
description = "Test resolver for executable files to be handled as tests"
priority = PluginPriority.VERY_LOW

def resolve(self, reference):
exec_criteria = self.check_exec(reference)
if exec_criteria is not None:
return exec_criteria

runnable = Runnable("exec-test", reference, assets=get_file_assets(reference))
return ReferenceResolution(
reference, ReferenceResolutionResult.SUCCESS, [runnable]
Expand Down Expand Up @@ -121,24 +131,16 @@ def resolve(self, reference):
)


class TapResolver(Resolver):
class TapResolver(BaseExec, Resolver):

name = "tap"
description = "Test resolver for executable files to be handled as TAP tests"
priority = PluginPriority.LAST_RESORT

def resolve(self, reference):

criteria_check = check_file(
reference,
reference,
suffix=None,
type_name="executable file",
access_check=os.R_OK | os.X_OK,
access_name="executable",
)
if criteria_check is not True:
return criteria_check
exec_criteria = self.check_exec(reference)
if exec_criteria is not None:
return exec_criteria

runnable = Runnable("tap", reference, assets=get_file_assets(reference))
return ReferenceResolution(
Expand Down Expand Up @@ -196,3 +198,102 @@ def resolve(self, reference):
return criteria_check

return self._validate_and_load_runnables(reference)


class ExecRunnablesRecipeInit(Init):
name = "exec-runnables-recipe"
description = 'Configuration for resolver plugin "exec-runnables-recipe" plugin'

def initialize(self):
help_msg = (
'Whether resolvers (such as "exec-runnables-recipe") should '
"execute files given as test references that have executable "
"permissions. This is disabled by default due to security "
"implications of running executables that may not be trusted."
)
settings.register_option(
section="resolver",
key="run_executables",
key_type=bool,
default=False,
help_msg=help_msg,
)

help_msg = (
"Command line options (space separated) that will be added "
"to the executable when executing it as a producer of "
"runnables-recipe JSON content."
)
settings.register_option(
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure if this is the best solution, what would happen when there would be more than one run-executables-resolver, all of them should get the same arguments?

What about have the arguments as part of the reference behind :. Like this:

$ avocado list --resolver-run-executables examples/nrunner/resolvers/exec_runnables_recipe_kind.sh:tap

section="resolver.exec_runnables_recipe",
key="arguments",
key_type=str,
default="",
help_msg=help_msg,
)


class ExecRunnablesRecipeResolver(BaseExec, Resolver):
name = "exec-runnables-recipe"
description = "Test resolver for executables that output JSON runnable recipes"
priority = PluginPriority.LOW

def resolve(self, reference):
if not self.config.get("resolver.run_executables"):
return ReferenceResolution(
reference,
ReferenceResolutionResult.NOTFOUND,
info=(
"Running executables is not enabled. Refer to "
'"resolver.run_executables" configuration option'
),
)

exec_criteria = self.check_exec(reference)
if exec_criteria is not None:
return exec_criteria

args = self.config.get("resolver.exec_runnables_recipe.arguments")
if args:
cmd = [reference] + shlex.split(args)
else:
cmd = reference
try:
process = subprocess.Popen(
cmd,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
except (FileNotFoundError, PermissionError) as exc:
return ReferenceResolution(
reference,
ReferenceResolutionResult.NOTFOUND,
info=(f'Failure while running running executable "{reference}": {exc}'),
)

content, _ = process.communicate()
try:
runnables = json.loads(content)
Copy link
Contributor

Choose a reason for hiding this comment

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

This whole part from 258 to 280 is duplication of RunnablesRecipeResolver code. IMO, it would be better to call the RunnablesRecipeResolver or create some Base class for holding the json validation a resolving code.

except json.JSONDecodeError:
return ReferenceResolution(
reference,
ReferenceResolutionResult.NOTFOUND,
info=f'Content generated by running executable "{reference}" is not JSON',
)

if not (
isinstance(runnables, list)
and all([isinstance(r, dict) for r in runnables])
):
return ReferenceResolution(
reference,
ReferenceResolutionResult.NOTFOUND,
info=f"Content generated by running executable {reference} does not look like a runnables recipe JSON content",
)

return ReferenceResolution(
reference,
ReferenceResolutionResult.SUCCESS,
[Runnable.from_dict(r) for r in runnables],
)
15 changes: 15 additions & 0 deletions avocado/plugins/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,21 @@ def configure(self, parser):
long_arg="--log-test-data-directories",
)

settings.add_argparser_to_option(
namespace="resolver.run_executables",
parser=parser,
long_arg="--resolver-run-executables",
allow_multiple=True,
)

settings.add_argparser_to_option(
namespace="resolver.exec_runnables_recipe.arguments",
metavar="ARGS",
parser=parser,
long_arg="--resolver-exec-arguments",
allow_multiple=True,
)

parser_common_args.add_tag_filter_args(parser)

def run(self, config):
Expand Down
43 changes: 43 additions & 0 deletions docs/source/guides/writer/chapters/recipes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,46 @@ That will be parsed by the ``runnables-recipe`` resolver, like in

exec-test /bin/true
exec-test /bin/false

Using dynamically generated recipes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The ``exec-runnables-recipe`` resolver allows a user to point to a
file that will be executed, and that is expected to generate (on its
``STDOUT``) content compatible with the Runnable recipe format
mentioned previously.

.. note:: For security reasons, Avocado won't execute files
indiscriminately when looking for tests (at the resolution
phase). One must set the ``--resolver-run-executables``
command line option (or the underlying
``resolver.run_executables`` configuration option) to allow
running executables at the resolver stage.

A script such as:

.. literalinclude:: ../../../../../examples/nrunner/resolvers/exec_runnables_recipe.sh

Will output JSON that is compatible with the runnable recipe format.
That can be used directly via either ``avocado list`` or ``avocado
run``. Example::

$ avocado list --resolver-run-executables examples/nrunner/resolvers/exec_runnables_recipe.sh

exec-test true-test
exec-test false-test

If the executable to be run needs arguments, you can pass it via the
``--resolver-exec-arguments`` or the underlying
``resolver.exec_runnable_recipe.arguments`` option. The following
script receives an optional parameter that can change the type of the
tests it generates:

.. literalinclude:: ../../../../../examples/nrunner/resolvers/exec_runnables_recipe_kind.sh

In order to have those tests resolved as ``tap`` tests, one can run::

$ avocado list --resolver-run-executables --resolver-exec-arguments tap examples/nrunner/resolvers/exec_runnables_recipe_kind.sh

tap true-test
tap false-test
2 changes: 2 additions & 0 deletions examples/nrunner/resolvers/exec_runnables_recipe.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/sh
echo '[{"kind": "exec-test","uri": "/bin/true","identifier": "true-test"},{"kind": "exec-test","uri": "/bin/false","identifier": "false-test"}]'
3 changes: 3 additions & 0 deletions examples/nrunner/resolvers/exec_runnables_recipe_kind.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh
kind=${1:-exec-test}
echo "[{\"kind\": \"$kind\",\"uri\": \"/bin/true\",\"identifier\": \"true-test\"},{\"kind\": \"$kind\",\"uri\": \"/bin/false\",\"identifier\": \"false-test\"}]"
35 changes: 0 additions & 35 deletions selftests/.data/whiteboard.py

This file was deleted.

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": 309,
"functional-parallel": 312,
"functional-serial": 7,
"optional-plugins": 0,
"optional-plugins-golang": 2,
Expand Down
Loading
Loading