Skip to content

Commit 9753898

Browse files
committed
Merge remote-tracking branch 'richtja/suite_dependency'
Signed-off-by: Cleber Rosa <[email protected]>
2 parents 1c66ae2 + c24a90d commit 9753898

File tree

8 files changed

+155
-4
lines changed

8 files changed

+155
-4
lines changed

avocado/core/extension_manager.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import pkg_resources
2828

29+
from avocado.core.exceptions import JobBaseException
2930
from avocado.utils import stacktrace
3031

3132
# This is also defined in avocado.core.output, but this avoids a
@@ -188,6 +189,8 @@ def map_method_with_return(self, method_name, *args, **kwargs):
188189
raise
189190
except KeyboardInterrupt:
190191
raise
192+
except JobBaseException:
193+
raise
191194
except: # catch any exception pylint: disable=W0702
192195
LOG_UI.error(
193196
'Error running method "%s" of plugin "%s": %s',
@@ -214,6 +217,8 @@ def map_method(self, method_name, *args):
214217
raise
215218
except KeyboardInterrupt:
216219
raise
220+
except JobBaseException:
221+
raise
217222
except: # catch any exception pylint: disable=W0702
218223
LOG_UI.error(
219224
'Error running method "%s" of plugin "%s": %s',

avocado/core/job.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,6 @@ def run(self):
599599
self.pre_tests()
600600
return self.run_tests()
601601
except exceptions.JobBaseException as details:
602-
self.status = details.status
603602
fail_class = details.__class__.__name__
604603
self.log.error("\nAvocado job failed: %s: %s", fail_class, details)
605604
self.exitcode |= exit_codes.AVOCADO_JOB_FAIL

avocado/plugins/dependency.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,17 @@
1111
#
1212
# Copyright: Red Hat Inc. 2021
1313
# Authors: Willian Rampazzo <[email protected]>
14+
import json
1415

15-
from avocado.core.plugin_interfaces import PreTest
16+
from avocado.core.dependencies.dependency import Dependency
17+
from avocado.core.exceptions import JobBaseException
18+
from avocado.core.plugin_interfaces import JobPre, PreTest
19+
20+
21+
class DependencyResolverError(JobBaseException):
22+
"""
23+
Error raised when issue occurred during dependency file resolution.
24+
"""
1625

1726

1827
class DependencyResolver(PreTest):
@@ -36,3 +45,33 @@ def pre_test_runnables(test_runnable, suite_config=None): # pylint: disable=W02
3645
for dependency in unique_dependencies:
3746
dependency_runnables.append(dependency.to_runnable(test_runnable.config))
3847
return dependency_runnables
48+
49+
50+
class SuiteDependency(JobPre):
51+
name = "suite-dependency"
52+
description = "Applies a set of dependencies to every test within the suite"
53+
54+
def pre(self, job):
55+
for suite in job.test_suites:
56+
dependency_file_path = suite.config.get("job.run.dependency")
57+
if dependency_file_path:
58+
try:
59+
with open(
60+
dependency_file_path, encoding="utf-8"
61+
) as dependency_file:
62+
try:
63+
dependencies_dict = json.load(dependency_file)
64+
except json.JSONDecodeError as e:
65+
raise DependencyResolverError(
66+
f"Issue with parsing dependency file at {dependency_file_path}: {e}"
67+
)
68+
except FileNotFoundError as e:
69+
raise DependencyResolverError(
70+
f"Dependency file not found at {dependency_file_path}: {e}"
71+
)
72+
dependencies = []
73+
for dependency in dependencies_dict:
74+
dependencies.append(Dependency.from_dictionary(dependency))
75+
for runnable in suite.tests:
76+
dependencies.extend(runnable.dependencies)
77+
runnable.dependencies = dependencies

avocado/plugins/run.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,19 @@ def configure(self, parser):
170170
long_arg="--job-category",
171171
)
172172

173+
help_msg = (
174+
"Path to the json file with list of dependencies needed for each test."
175+
)
176+
settings.register_option(
177+
section="job.run",
178+
key="dependency",
179+
help_msg=help_msg,
180+
parser=parser,
181+
default=None,
182+
metavar="FILE",
183+
long_arg="--job-dependency",
184+
)
185+
173186
settings.add_argparser_to_option(
174187
namespace="job.run.timeout",
175188
metavar="SECONDS",

docs/source/guides/user/chapters/dependencies.rst

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,52 @@ Defining a dependency in the class docstring will fulfill the dependency for
9393
every test within a test class. Defining a dependency in the test docstring
9494
will fulfill the dependency for that single test only.
9595

96+
Defining a job dependency
97+
-------------------------
98+
99+
Managing dependencies across multiple tests within a job can be streamlined
100+
using the Job Dependency Feature. If your tests share common dependencies,
101+
there's no need to specify them individually for each test. Instead, you can
102+
utilize the Job Dependency Feature to apply a set of dependencies to every
103+
test within the job.
104+
105+
Once it is enabled, Avocado will read the list of common dependencies from
106+
the specified JSON file and automatically apply them to each test within
107+
the job.
108+
109+
Enabling the Job Dependency
110+
+++++++++++++++++++++++++++
111+
112+
To enable this feature, you have two options:
113+
114+
1. Command-line Option:
115+
Use of the `--job-dependency` option of the avocado run command
116+
to specify the path to the dependency JSON file::
117+
118+
avocado run --job-dependency=dependencies.json tests/
119+
120+
2. Configuration File:
121+
Alternatively, you can set the path to the dependency JSON file
122+
using the `job.run.dependency` configuration option in the Avocado
123+
configuration file.
124+
125+
.. tip:: If you're using the Job API, you have the flexibility to define
126+
different dependencies for each suite. Simply modify the
127+
`job.run.dependency` value in the suite configuration during suite
128+
creation.
129+
130+
Dependency File Format
131+
++++++++++++++++++++++
132+
133+
The dependency file follows the same format as the test dependency defined
134+
in the docstring. If you have multiple dependencies, ensure to encapsulate
135+
them within a JSON list::
136+
137+
[
138+
{"type": "package", "name": "hello"},
139+
{"type": "package", "name": "bash"}
140+
]
141+
96142
Supported types of dependencies
97143
-------------------------------
98144

selftests/check.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@
2626
"job-api-6": 4,
2727
"job-api-7": 1,
2828
"nrunner-interface": 70,
29-
"nrunner-requirement": 20,
29+
"nrunner-requirement": 24,
3030
"unit": 669,
3131
"jobs": 11,
3232
"functional-parallel": 302,
33-
"functional-serial": 5,
33+
"functional-serial": 6,
3434
"optional-plugins": 0,
3535
"optional-plugins-golang": 2,
3636
"optional-plugins-html": 3,

selftests/functional/serial/requirements.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,32 @@ def test_a(self):
104104
self.check_hello()
105105
'''
106106

107+
TEST_WITHOUT_DEPENDENCY = """from avocado import Test
108+
from avocado.utils import process
109+
110+
class SuccessTest(Test):
111+
112+
def check_hello(self):
113+
result = process.run("hello", ignore_status=True)
114+
self.assertEqual(result.exit_status, 0)
115+
self.assertIn('Hello, world!', result.stdout_text,)
116+
117+
def test_a(self):
118+
self.check_hello()
119+
120+
def test_b(self):
121+
self.check_hello()
122+
123+
def test_c(self):
124+
self.check_hello()
125+
"""
126+
127+
DEPENDENCY_FILE = """
128+
[
129+
{"type": "package", "name": "hello"}
130+
]
131+
"""
132+
107133

108134
class BasicTest(TestCaseTmpDir, Test):
109135
"""
@@ -246,3 +272,25 @@ def test_dependency_duplicates(self):
246272
"PASS 1",
247273
result.stdout_text,
248274
)
275+
276+
@skipUnless(os.getenv("CI"), skip_install_message)
277+
def test_job_dependency(self):
278+
with script.Script(
279+
os.path.join(self.tmpdir.name, "test_multiple_success.py"),
280+
TEST_WITHOUT_DEPENDENCY,
281+
) as test:
282+
command = self.get_command(test.path)
283+
with script.Script(
284+
os.path.join(self.tmpdir.name, "dependency.json"), DEPENDENCY_FILE
285+
) as dependency_config:
286+
command = f"{command} --job-dependency={dependency_config.path}"
287+
result = process.run(command, ignore_status=True)
288+
self.assertEqual(result.exit_status, exit_codes.AVOCADO_ALL_OK)
289+
self.assertIn(
290+
"PASS 3",
291+
result.stdout_text,
292+
)
293+
self.assertNotIn(
294+
"vim-common",
295+
result.stdout_text,
296+
)

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ def run(self):
419419
"teststmpdir = avocado.plugins.teststmpdir:TestsTmpDir",
420420
"human = avocado.plugins.human:HumanJob",
421421
"testlogsui = avocado.plugins.testlogs:TestLogsUI",
422+
"suite-dependency = avocado.plugins.dependency:SuiteDependency",
422423
],
423424
"avocado.plugins.test.pre": [
424425
"dependency = avocado.plugins.dependency:DependencyResolver",

0 commit comments

Comments
 (0)