diff --git a/avocado/core/exceptions.py b/avocado/core/exceptions.py index 3f11a0962637b24c56036efea614f59a9c807a57..88349eb1f0b0ac7a7619e612a294a85afe6a4696 100644 --- a/avocado/core/exceptions.py +++ b/avocado/core/exceptions.py @@ -36,6 +36,14 @@ class JobError(Exception): status = "ERROR" +class OptionValidationError(Exception): + + """ + An invalid option was passed to the test runner + """ + status = "ERROR" + + class TestBaseException(Exception): """ diff --git a/avocado/job.py b/avocado/job.py index 15fae08d64860e335f7f0da280e7488ba4e8cb96..d4d82f74abf561ac40ed8100db14a24f5eb4573e 100644 --- a/avocado/job.py +++ b/avocado/job.py @@ -69,28 +69,44 @@ class TestRunner(object): :type params: dict :return: an instance of :class:`avocado.test.Test`. """ - shortname = params.get('shortname') - url = shortname.split('.')[0] - path_attempt = os.path.abspath(shortname) - if os.path.exists(path_attempt): - test_class = test.DropinTest - test_instance = test_class(path=path_attempt, - base_logdir=self.job.logdir, - job=self.job) - else: + t_id = params.get('id') + test_path = os.path.abspath(t_id) + path_analyzer = path.PathInspector(test_path) + module_name = os.path.basename(test_path).split('.')[0] + if not os.path.exists(test_path): + # Try to resolve test ID (keep compatibility) + test_path = os.path.join(data_dir.get_test_dir(), '%s.py' % t_id) + if os.path.exists(test_path): + path_analyzer = path.PathInspector(test_path) + else: + test_class = test.MissingTest + test_instance = test_class(name=t_id, + base_logdir=self.job.logdir, + params=params, + job=self.job) + return test_instance + + if path_analyzer.is_python(): try: - test_module_dir = os.path.join(self.job.test_dir, url) - f, p, d = imp.find_module(url, [test_module_dir]) - test_module = imp.load_module(url, f, p, d) + test_module_dir = os.path.dirname(test_path) + f, p, d = imp.find_module(module_name, [test_module_dir]) + test_module = imp.load_module(module_name, f, p, d) f.close() - test_class = getattr(test_module, url) + test_class = getattr(test_module, module_name) except ImportError: test_class = test.MissingTest finally: - test_instance = test_class(name=url, + test_instance = test_class(name=t_id, base_logdir=self.job.logdir, params=params, job=self.job) + + else: + test_class = test.DropinTest + test_instance = test_class(path=test_path, + base_logdir=self.job.logdir, + job=self.job) + return test_instance def run_test(self, instance, queue): @@ -287,7 +303,7 @@ class Job(object): if urls is not None: for url in urls: - params_list.append({'shortname': url}) + params_list.append({'id': url}) if multiplex_file is None: if self.args and self.args.multiplex_file is not None: @@ -299,18 +315,19 @@ class Job(object): params_list = [] if urls is not None: for url in urls: + test_module = os.path.basename(url).split('.')[0] parser = multiplex_config.Parser(multiplex_file) - parser.only_filter(url) + parser.only_filter(test_module) dcts = [d for d in parser.get_dicts()] if dcts: for dct in dcts: + dct['id'] = url params_list.append(dct) else: - params_list.append({'shortname': url}) + params_list.append({'id': url}) else: - parser = multiplex_config.Parser(multiplex_file) - for dct in parser.get_dicts(): - params_list.append(dct) + e_msg = "Empty test ID. A test path or alias must be provided" + raise exceptions.OptionValidationError(e_msg) if self.args is not None: self.args.test_result_total = len(params_list) @@ -371,6 +388,10 @@ class Job(object): self.output_manager.log_fail_header('Avocado job failed: %s: %s' % (fail_class, details)) return error_codes.numeric_status['AVOCADO_JOB_FAIL'] + except exceptions.OptionValidationError, details: + self.output_manager.log_fail_header(str(details)) + return error_codes.numeric_status['AVOCADO_JOB_FAIL'] + except Exception, details: self.status = "ERROR" exc_type, exc_value, exc_traceback = sys.exc_info() diff --git a/avocado/plugins/runner.py b/avocado/plugins/runner.py index 8f28cefe1f4e17a6ebd962462ee5cf2439ef6683..2b28a423f2fa1a09e44ff40dee1927232c9fcbd3 100644 --- a/avocado/plugins/runner.py +++ b/avocado/plugins/runner.py @@ -21,6 +21,7 @@ import os from avocado.plugins import plugin from avocado.core import data_dir from avocado.core import output +from avocado.utils import path from avocado import sysinfo from avocado import job @@ -53,7 +54,12 @@ class TestLister(plugin.Plugin): """ bcolors = output.colors pipe = output.get_paginator() - test_dirs = os.listdir(data_dir.get_test_dir()) + test_files = os.listdir(data_dir.get_test_dir()) + test_dirs = [] + for t in test_files: + inspector = path.PathInspector(path=t) + if inspector.is_python(): + test_dirs.append(t.split('.')[0]) pipe.write(bcolors.header_str('Tests available:')) pipe.write("\n") for test_dir in test_dirs: diff --git a/avocado/test.py b/avocado/test.py index e378fca4cc85d7fdc460a55f70c7780305781b34..b28e97157c69d22f4b338cf5fa692ebac9db40af 100644 --- a/avocado/test.py +++ b/avocado/test.py @@ -17,6 +17,7 @@ Contains the base test implementation, used as a base for the actual framework tests. """ +import inspect import logging import os import sys @@ -115,14 +116,22 @@ class Test(unittest.TestCase): s_tag = ".".join(split_shortname[1:]) self.tag = tag or s_tag self.job = job - self.basedir = os.path.join(data_dir.get_test_dir(), self.name) - self.datadir = os.path.join(self.basedir, 'data') - self.workdir = path.init_dir(data_dir.get_tmp_dir(), self.name) + + basename = os.path.basename(self.name) + + self.basedir = os.path.dirname(inspect.getfile(self.__class__)) + self.datadir = os.path.join(self.basedir, '%s.data' % basename) + self.workdir = path.init_dir(data_dir.get_tmp_dir(), basename) self.srcdir = path.init_dir(self.workdir, 'src') if base_logdir is None: base_logdir = data_dir.get_job_logs_dir() self.tagged_name = self.get_tagged_name(base_logdir) - self.logdir = path.init_dir(base_logdir, self.tagged_name) + # We need log directory names to be unique + tagged_name = self.tagged_name.replace('/', '.') + if tagged_name.startswith('.'): + tagged_name = tagged_name[1:] + + self.logdir = path.init_dir(base_logdir, tagged_name) self.logfile = os.path.join(self.logdir, 'debug.log') self.outputdir = path.init_dir(self.logdir, 'data') self.sysinfodir = path.init_dir(self.logdir, 'sysinfo') @@ -386,10 +395,8 @@ class DropinTest(Test): """ def __init__(self, path, params=None, base_logdir=None, tag=None, job=None): - basename = os.path.basename(path) - name = basename.split(".")[0] self.path = os.path.abspath(path) - super(DropinTest, self).__init__(name=name, base_logdir=base_logdir, + super(DropinTest, self).__init__(name=path, base_logdir=base_logdir, params=params, tag=tag, job=job) def _log_detailed_cmd_info(self, result): diff --git a/avocado/utils/path.py b/avocado/utils/path.py index d175265a2354557e1961b98e631ba501479f5172..a0f0d8baf7d2a4c715c9b3417373db3c907cd95f 100644 --- a/avocado/utils/path.py +++ b/avocado/utils/path.py @@ -17,6 +17,10 @@ Avocado path related functions. """ import os +import stat + +PY_EXTENSIONS = ['.py'] +SHEBANG = '#!' def init_dir(*args): @@ -32,3 +36,42 @@ def init_dir(*args): if not os.path.isdir(directory): os.makedirs(directory) return directory + + +class PathInspector(object): + + def __init__(self, path): + self.path = path + + def get_first_line(self): + first_line = "" + if os.path.isfile(self.path): + checked_file = open(self.path, "r") + first_line = checked_file.readline() + checked_file.close() + return first_line + + def has_exec_permission(self): + mode = os.stat(self.path)[stat.ST_MODE] + return mode & stat.S_IXUSR + + def is_empty(self): + size = os.stat(self.path)[stat.ST_SIZE] + return size == 0 + + def is_script(self, language=None): + first_line = self.get_first_line() + if first_line: + if first_line.startswith(SHEBANG): + if language is None: + return True + elif language in first_line: + return True + return False + + def is_python(self): + for extension in PY_EXTENSIONS: + if self.path.endswith(extension): + return True + + return self.is_script(language='python') diff --git a/selftests/all/functional/avocado/basic_tests.py b/selftests/all/functional/avocado/basic_tests.py index 06259c1cc4affa03c9c51d7d846d8457e7308e28..5521795e9379f93906c20665dc8c73f197b62830 100644 --- a/selftests/all/functional/avocado/basic_tests.py +++ b/selftests/all/functional/avocado/basic_tests.py @@ -46,6 +46,11 @@ class RunnerOperationTest(unittest.TestCase): cmd_line = './scripts/avocado run "sleeptest sleeptest"' process.run(cmd_line) + def test_runner_noalias(self): + os.chdir(basedir) + cmd_line = "./scripts/avocado run 'tests/sleeptest.py tests/sleeptest.py'" + process.run(cmd_line) + def test_runner_tests_fail(self): os.chdir(basedir) cmd_line = './scripts/avocado run "sleeptest failtest sleeptest"' diff --git a/selftests/all/functional/avocado/multiplex_tests.py b/selftests/all/functional/avocado/multiplex_tests.py index 73aed7b9836b33d5bb65a2357d2df1199249e3c1..df7ef94c3fd00bc8eb1e473916c15f10e318fb50 100644 --- a/selftests/all/functional/avocado/multiplex_tests.py +++ b/selftests/all/functional/avocado/multiplex_tests.py @@ -60,7 +60,7 @@ class MultiplexTests(unittest.TestCase): "%d:\n%s" % (cmd_line, expected_rc, result)) def test_mplex_plugin(self): - cmd_line = './scripts/avocado multiplex tests/sleeptest/sleeptest.mplx' + cmd_line = './scripts/avocado multiplex tests/sleeptest.py.data/sleeptest.mplx' expected_rc = 0 self.run_and_check(cmd_line, expected_rc) @@ -69,8 +69,22 @@ class MultiplexTests(unittest.TestCase): expected_rc = 2 self.run_and_check(cmd_line, expected_rc) + def test_run_mplex_noid(self): + cmd_line = './scripts/avocado run --multiplex tests/sleeptest.py.data/sleeptest.mplx' + expected_rc = 0 + self.run_and_check(cmd_line, 2) + def test_run_mplex_sleeptest(self): - cmd_line = './scripts/avocado run sleeptest --multiplex tests/sleeptest/sleeptest.mplx' + cmd_line = './scripts/avocado run sleeptest --multiplex tests/sleeptest.py.data/sleeptest.mplx' + expected_rc = 0 + # A typical sleeptest has about 14 lines of output, + # so we expect the full job log has at least 3 times + # this value. If that is not the case, something is wrong with + # the output. + self.run_and_check(cmd_line, expected_rc, 14*3) + + def test_run_mplex_noalias_sleeptest(self): + cmd_line = './scripts/avocado run tests/sleeptest.py --multiplex tests/sleeptest.py.data/sleeptest.mplx' expected_rc = 0 # A typical sleeptest has about 14 lines of output, # so we expect the full job log has at least 3 times @@ -79,12 +93,12 @@ class MultiplexTests(unittest.TestCase): self.run_and_check(cmd_line, expected_rc, 14*3) def test_run_mplex_doublesleep(self): - cmd_line = './scripts/avocado run "sleeptest sleeptest" --multiplex tests/sleeptest/sleeptest.mplx' + cmd_line = './scripts/avocado run "sleeptest sleeptest" --multiplex tests/sleeptest.py.data/sleeptest.mplx' expected_rc = 0 self.run_and_check(cmd_line, expected_rc) def test_run_mplex_failtest(self): - cmd_line = './scripts/avocado run "sleeptest failtest" --multiplex tests/sleeptest/sleeptest.mplx' + cmd_line = './scripts/avocado run "sleeptest failtest" --multiplex tests/sleeptest.py.data/sleeptest.mplx' expected_rc = 1 self.run_and_check(cmd_line, expected_rc) diff --git a/selftests/all/functional/avocado/standalone_tests.py b/selftests/all/functional/avocado/standalone_tests.py index a6b7c605dd4846f547acd7c8459bd705453e3151..a0efffca52d2fa01b94302657e70ed2232513f2f 100644 --- a/selftests/all/functional/avocado/standalone_tests.py +++ b/selftests/all/functional/avocado/standalone_tests.py @@ -44,27 +44,27 @@ class StandaloneTests(unittest.TestCase): "%d:\n%s" % (tstname, expected_rc, result)) def test_sleeptest(self): - cmd_line = './tests/sleeptest/sleeptest.py' + cmd_line = './tests/sleeptest.py' expected_rc = 0 self.run_and_check(cmd_line, expected_rc, 'sleeptest') def test_skiptest(self): - cmd_line = './tests/skiptest/skiptest.py' + cmd_line = './tests/skiptest.py' expected_rc = 0 self.run_and_check(cmd_line, expected_rc, 'skiptest') def test_failtest(self): - cmd_line = './tests/failtest/failtest.py' + cmd_line = './tests/failtest.py' expected_rc = 1 self.run_and_check(cmd_line, expected_rc, 'failtest') def test_errortest(self): - cmd_line = './tests/errortest/errortest.py' + cmd_line = './tests/errortest.py' expected_rc = 1 self.run_and_check(cmd_line, expected_rc, 'errortest') def test_warntest(self): - cmd_line = './tests/warntest/warntest.py' + cmd_line = './tests/warntest.py' expected_rc = 1 self.run_and_check(cmd_line, expected_rc, 'warntest') diff --git a/tests/doublefail/doublefail.py b/tests/doublefail.py similarity index 100% rename from tests/doublefail/doublefail.py rename to tests/doublefail.py diff --git a/tests/doublefree/doublefree.py b/tests/doublefree.py similarity index 100% rename from tests/doublefree/doublefree.py rename to tests/doublefree.py diff --git a/tests/doublefree/data/doublefree.c b/tests/doublefree.py.data/doublefree.c similarity index 100% rename from tests/doublefree/data/doublefree.c rename to tests/doublefree.py.data/doublefree.c diff --git a/tests/errortest/errortest.py b/tests/errortest.py similarity index 100% rename from tests/errortest/errortest.py rename to tests/errortest.py diff --git a/tests/failtest/failtest.py b/tests/failtest.py similarity index 100% rename from tests/failtest/failtest.py rename to tests/failtest.py diff --git a/tests/fiotest/fiotest.py b/tests/fiotest.py similarity index 100% rename from tests/fiotest/fiotest.py rename to tests/fiotest.py diff --git a/tests/fiotest/data/fio-2.1.7.tar.bz2 b/tests/fiotest.py.data/fio-2.1.7.tar.bz2 similarity index 100% rename from tests/fiotest/data/fio-2.1.7.tar.bz2 rename to tests/fiotest.py.data/fio-2.1.7.tar.bz2 diff --git a/tests/fiotest/data/fio-mixed.job b/tests/fiotest.py.data/fio-mixed.job similarity index 100% rename from tests/fiotest/data/fio-mixed.job rename to tests/fiotest.py.data/fio-mixed.job diff --git a/tests/gendata/gendata.py b/tests/gendata.py old mode 100644 new mode 100755 similarity index 100% rename from tests/gendata/gendata.py rename to tests/gendata.py diff --git a/tests/linuxbuild/linuxbuild.py b/tests/linuxbuild.py similarity index 100% rename from tests/linuxbuild/linuxbuild.py rename to tests/linuxbuild.py diff --git a/tests/linuxbuild/data/config b/tests/linuxbuild.py.data/config similarity index 100% rename from tests/linuxbuild/data/config rename to tests/linuxbuild.py.data/config diff --git a/tests/multiplextest/multiplextest.py b/tests/multiplextest.py old mode 100644 new mode 100755 similarity index 100% rename from tests/multiplextest/multiplextest.py rename to tests/multiplextest.py diff --git a/tests/multiplextest/multiplextest.mplx b/tests/multiplextest.py.data/multiplextest.mplx similarity index 100% rename from tests/multiplextest/multiplextest.mplx rename to tests/multiplextest.py.data/multiplextest.mplx diff --git a/tests/skiptest/skiptest.py b/tests/skiptest.py similarity index 100% rename from tests/skiptest/skiptest.py rename to tests/skiptest.py diff --git a/tests/sleeptenmin/sleeptenmin.py b/tests/sleeptenmin.py similarity index 100% rename from tests/sleeptenmin/sleeptenmin.py rename to tests/sleeptenmin.py diff --git a/tests/sleeptest/sleeptest.py b/tests/sleeptest.py similarity index 100% rename from tests/sleeptest/sleeptest.py rename to tests/sleeptest.py diff --git a/tests/sleeptest/sleeptest.mplx b/tests/sleeptest.py.data/sleeptest.mplx similarity index 100% rename from tests/sleeptest/sleeptest.mplx rename to tests/sleeptest.py.data/sleeptest.mplx diff --git a/tests/synctest/synctest.py b/tests/synctest.py similarity index 100% rename from tests/synctest/synctest.py rename to tests/synctest.py diff --git a/tests/synctest/synctest.mplx b/tests/synctest.py.data/synctest.mplx similarity index 100% rename from tests/synctest/synctest.mplx rename to tests/synctest.py.data/synctest.mplx diff --git a/tests/synctest/data/synctest.tar.bz2 b/tests/synctest.py.data/synctest.tar.bz2 similarity index 100% rename from tests/synctest/data/synctest.tar.bz2 rename to tests/synctest.py.data/synctest.tar.bz2 diff --git a/tests/timeouttest/timeouttest.py b/tests/timeouttest.py similarity index 100% rename from tests/timeouttest/timeouttest.py rename to tests/timeouttest.py diff --git a/tests/trinity/trinity.py b/tests/trinity.py old mode 100644 new mode 100755 similarity index 100% rename from tests/trinity/trinity.py rename to tests/trinity.py diff --git a/tests/trinity/data/trinity-1.4.tar.bz2 b/tests/trinity.py.data/trinity-1.4.tar.bz2 similarity index 100% rename from tests/trinity/data/trinity-1.4.tar.bz2 rename to tests/trinity.py.data/trinity-1.4.tar.bz2 diff --git a/tests/warntest/warntest.py b/tests/warntest.py similarity index 100% rename from tests/warntest/warntest.py rename to tests/warntest.py diff --git a/tests/whiteboard/whiteboard.py b/tests/whiteboard.py old mode 100644 new mode 100755 similarity index 100% rename from tests/whiteboard/whiteboard.py rename to tests/whiteboard.py diff --git a/tests/whiteboard/whiteboard.mplx b/tests/whiteboard.py.data/whiteboard.mplx similarity index 100% rename from tests/whiteboard/whiteboard.mplx rename to tests/whiteboard.py.data/whiteboard.mplx