diff --git a/avocado/core/job.py b/avocado/core/job.py index 1ff79a9cd73200a08ac8a35ffa0a89f5d5c7dd98..def66da163edd1356da6151431af3dd7f7f1e236 100644 --- a/avocado/core/job.py +++ b/avocado/core/job.py @@ -134,6 +134,16 @@ class Job(object): self.test_suite = None self.test_runner = None + #: Placeholder for test parameters (related to --test-parameters command + #: line option). They're kept in the job because they will be prepared + #: only once, since they are read only and will be shared acrross all + #: tests of a job. + self.test_parameters = None + if "test_parameters" in self.args: + self.test_parameters = {} + for parameter_name, parameter_value in self.args.test_parameters: + self.test_parameters[parameter_name] = parameter_value + # The result events dispatcher is shared with the test runner. # Because of our goal to support using the phases of a job # freely, let's get the result events dispatcher ready early. diff --git a/avocado/core/runner.py b/avocado/core/runner.py index 48001f7af806696376705636c8a707d414d0d9b8..360c95150d3d04ca685396d3440cfb705df3f6c6 100644 --- a/avocado/core/runner.py +++ b/avocado/core/runner.py @@ -25,6 +25,7 @@ import sys import time from . import test +from . import tree from . import exceptions from . import output from . import status @@ -505,8 +506,7 @@ class TestRunner(object): return False return True - @staticmethod - def _template_to_factory(template, variant): + def _template_to_factory(self, template, variant): """ Applies test params from variant to the test template @@ -520,19 +520,25 @@ class TestRunner(object): """ var = variant.get("variant") paths = variant.get("paths") + empty_variants = varianter.is_empty_variant(var) if "params" not in template[1]: factory = [template[0], template[1].copy()] + if self.job.test_parameters and empty_variants: + var[0] = tree.TreeNode().get_node("/", True) + var[0].value = self.job.test_parameters + paths = ["/"] factory[1]["params"] = (var, paths) return factory, variant - if not varianter.is_empty_variant(var): + if not empty_variants: raise NotImplementedError("Specifying test params from test loader " "and from varianter at the same time is " "not yet supported. Please remove either " "variants defined by the varianter (%s) " - "or make the test loader oftest %s to not " - "to fill variants." % (variant, template)) + "or make the test loader of test %s to " + "not to fill variants." % (variant, + template)) return template, {"variant": var, "variant_id": varianter.generate_variant_id(var), diff --git a/avocado/plugins/run.py b/avocado/plugins/run.py index b594ce060e9b91f0aedc99a9f0af35309d5b7e8a..b457500221ba2b94de65b6a5215af100dcaa676a 100644 --- a/avocado/plugins/run.py +++ b/avocado/plugins/run.py @@ -42,6 +42,15 @@ class Run(CLICmd): description = ("Runs one or more tests (native test, test alias, binary " "or script)") + @staticmethod + def _test_parameter(string): + param_name_value = string.split('=', 1) + if len(param_name_value) < 2: + msg = ('Invalid --test-parameter option: "%s". Valid option must ' + 'be a "NAME=VALUE" like expression' % string) + raise argparse.ArgumentTypeError(msg) + return param_name_value + def configure(self, parser): """ Add the subparser for the run action. @@ -54,6 +63,15 @@ class Run(CLICmd): metavar="TEST_REFERENCE", help='List of test references (aliases or paths)') + parser.add_argument("-p", "--test-parameter", action="append", + dest='test_parameters', default=[], + metavar="NAME_VALUE", type=self._test_parameter, + help="Parameter name and value to pass to all " + "tests. This is only applicable when not using a " + "varianter plugin. This option format must be " + "given in the NAME=VALUE format, and may be given " + "any number of times, or per parameter.") + parser.add_argument("-d", "--dry-run", action="store_true", help="Instead of running the test only " "list them and log their params.") diff --git a/docs/source/TestParameters.rst b/docs/source/TestParameters.rst index 539bdd833f12daf565cdaad9426eb3773dda32b4..c2f7700975cd1646d6ce948c79e52521a5730b89 100644 --- a/docs/source/TestParameters.rst +++ b/docs/source/TestParameters.rst @@ -26,16 +26,17 @@ Overall picture of how the params handling works is: .. code-block:: c +-----------+ - | | // Test uses variant to produce AvocadoParams - | Test | - | | + | | // Test uses AvocadoParams, with content either from + | Test | // a variant or from the test parameters given by + | | // "--test-parameters" +-----^-----+ - | // single variant is passed to Test + | | +-----------+ | Runner | // iterates through tests and variants to run all - +-----^-----+ // desired combinations specified by "--execution-order" - | + +-----^-----+ // desired combinations specified by "--execution-order". + | // if no variants are produced by varianter plugins, + | // use the test parameters given by "--test-parameters" | +-------------------+ provide variants +-----------------------+ | |<-----------------| | @@ -273,6 +274,33 @@ Where: * path - the location of this parameter. When the path does not exists yet, it's created out of `TreeNode`_. +Test parameters +~~~~~~~~~~~~~~~ + +This is an Avocado core feature, that is, it's not dependent on any +varianter plugin. In fact, it's only active when no Varianter plugin +is used and produces a valid variant. + +Avocado will use those simple parameters, and will pass them to all +tests in a job execution. This is done on the command line via +``--test-parameters``, or simply, ``-p``. It can be given multiple +times for multiple parameters. + +Because Avocado parameters do not have a mechanism to define their +types, test code should always consider that a parameter value is a +string, and convert it to the appropriate type. + +.. note:: Some varianter plugins would implicitly set parameters + with different data types, but given that the same test can be + used with different, or none, varianter plugins, it's safer if + the test does an explicit check or type conversion. + +Because the :class:`avocado.core.varianter.AvocadoParams` mandates the +concept of a parameter path (a legacy of the tree based Multiplexer) +and these test parameters are flat, those test parameters are placed +in the ``/`` path. This is to ensure maximum compatibility with tests +that do not choose an specific parameter location. + Varianter plugins ~~~~~~~~~~~~~~~~~ diff --git a/examples/tests/sleeptest.py b/examples/tests/sleeptest.py index 99b04abf36f27c1bc19081f43377864eb6be89d9..a465b9ba8979c8536419b13ca105c3985cf1a35f 100755 --- a/examples/tests/sleeptest.py +++ b/examples/tests/sleeptest.py @@ -18,7 +18,7 @@ class SleepTest(Test): """ Sleep for length seconds. """ - sleep_length = self.params.get('sleep_length', default=1) + sleep_length = float(self.params.get('sleep_length', default=1)) self.log.debug("Sleeping for %.2f seconds", sleep_length) time.sleep(sleep_length) diff --git a/selftests/functional/test_basic.py b/selftests/functional/test_basic.py index c53c5475ce7c2ccb69c97b2f36ac310873923d01..4963f215299e5f7eaab02cd5a602d79a7ef213b7 100644 --- a/selftests/functional/test_basic.py +++ b/selftests/functional/test_basic.py @@ -554,6 +554,18 @@ class RunnerOperationTest(unittest.TestCase): self.assertEqual(result.exit_status, 1, "Expected exit status is 1\n%s" % result) + def test_runner_test_parameters(self): + cmd_line = ('%s --show=test run --sysinfo=off --job-results-dir %s ' + '-p "sleep_length=0.01" -- sleeptest.py ' % (AVOCADO, + self.tmpdir)) + result = process.run(cmd_line, ignore_status=True) + expected_rc = exit_codes.AVOCADO_ALL_OK + self.assertEqual(result.exit_status, expected_rc, + "Avocado did not return rc %d:\n%s" % (expected_rc, result)) + self.assertIn(b"PARAMS (key=sleep_length, path=*, default=1) => '0.01'", + result.stdout) + self.assertIn(b"Sleeping for 0.01 seconds", result.stdout) + def tearDown(self): shutil.rmtree(self.tmpdir)