From 5973e898f0df1179bc1bff425d06f93fdcc44c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Doktor?= Date: Tue, 26 Apr 2016 06:37:52 +0200 Subject: [PATCH] avocado: Implement serialized test ids MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit implements the serialized test ids described in the Introduce proper test IDs RFC. https://www.redhat.com/archives/avocado-devel/2016-March/msg00024.html It implements `TestName` class, which contains the test uid, test name and the variant uid and allows querying for the file-system-friendly name. The workflow is: 1. tests are discovered, name is translated to "Test Name" by loader 2. test_suite is passed to the runner (new) along with the number of tests+variants to be executed (used to get number of digits) 3. the Mux (params generator) yields the template + (new) variant id 4. the runner replaces template['name'] to TestName(uid, test_name, variant_id); where uid is currently no executed tests, test_name is the original name from Loader and variant_id is either None or the variant index. This commit makes the `tag` argument unused. To avoid problems a warning is issued on it's usage so we can remove it in the upcoming releases. Signed-off-by: Lukáš Doktor --- avocado/core/html.py | 2 +- avocado/core/job.py | 6 +- avocado/core/jsonresult.py | 4 +- avocado/core/multiplexer.py | 10 +-- avocado/core/remote/runner.py | 9 +- avocado/core/remote/test.py | 1 - avocado/core/result.py | 10 ++- avocado/core/runner.py | 11 ++- avocado/core/test.py | 128 +++++++++++++++++--------- avocado/core/xunit.py | 8 +- avocado/plugins/journal.py | 2 +- selftests/functional/test_basic.py | 11 +-- selftests/functional/test_streams.py | 5 +- selftests/unit/test_loader.py | 6 ++ selftests/unit/test_remote.py | 18 ++-- selftests/unit/test_test.py | 129 +++++++++++++++------------ 16 files changed, 219 insertions(+), 141 deletions(-) diff --git a/avocado/core/html.py b/avocado/core/html.py index 0b0c0699..43bfd320 100644 --- a/avocado/core/html.py +++ b/avocado/core/html.py @@ -217,7 +217,7 @@ class HTMLTestResult(TestResult): :type state: dict """ TestResult.end_test(self, state) - t = {'test': state.get('tagged_name', ""), + t = {'test': str(state.get('name', "")), 'url': state.get('name', ""), 'time_start': state.get('time_start', -1), 'time_end': state.get('time_end', -1), diff --git a/avocado/core/job.py b/avocado/core/job.py index 30670fa8..be234454 100644 --- a/avocado/core/job.py +++ b/avocado/core/job.py @@ -499,9 +499,9 @@ class Job(object): self._log_job_debug_info(mux) replay.record(self.args, self.logdir, mux, self.urls) replay_map = getattr(self.args, 'replay_map', None) - summary = self.test_runner.run_suite(test_suite, mux, - timeout=self.timeout, - replay_map=replay_map) + summary = self.test_runner.run_suite(test_suite, mux, self.timeout, + replay_map, + self.args.test_result_total) self.__stop_job_logging() # If it's all good so far, set job status to 'PASS' if self.status == 'RUNNING': diff --git a/avocado/core/jsonresult.py b/avocado/core/jsonresult.py index 7df76a99..d37248ff 100644 --- a/avocado/core/jsonresult.py +++ b/avocado/core/jsonresult.py @@ -61,8 +61,8 @@ class JSONTestResult(TestResult): TestResult.end_test(self, state) if 'job_id' not in self.json: self.json['job_id'] = state.get('job_unique_id', "") - t = {'test': state.get('tagged_name', ""), - 'url': state.get('name', ""), + t = {'test': str(state.get('name', "")), + 'url': str(state.get('name', "")), 'start': state.get('time_start', -1), 'end': state.get('time_end', -1), 'time': state.get('time_elapsed', -1), diff --git a/avocado/core/multiplexer.py b/avocado/core/multiplexer.py index 380b6767..81bd64fe 100644 --- a/avocado/core/multiplexer.py +++ b/avocado/core/multiplexer.py @@ -415,18 +415,16 @@ class Mux(object): """ if self.variants: # Copy template and modify it's params i = None - for i, variant in enumerate(self.variants): + for i, variant in enumerate(self.variants, 1): test_factory = [template[0], template[1].copy()] - if self._has_multiple_variants: - test_factory[1]['tag'] = "variant%s" % (i + 1) if "params" in test_factory[1]: msg = ("Unable to multiplex test %s, params are already " "present in test factory: %s" % (test_factory[0], test_factory[1])) raise ValueError(msg) test_factory[1]['params'] = (variant, self._mux_path) - yield test_factory + yield test_factory, i if self._has_multiple_variants else None if i is None: # No variants, use template - yield template + yield template, None else: # No variants, use template - yield template + yield template, None diff --git a/avocado/core/remote/runner.py b/avocado/core/remote/runner.py index b8d1c753..8a6c31b7 100644 --- a/avocado/core/remote/runner.py +++ b/avocado/core/remote/runner.py @@ -29,6 +29,7 @@ from .. import virt from .. import exceptions from .. import status from ..runner import TestRunner +from ..test import TestName from ...utils import astring from ...utils import archive from ...utils import stacktrace @@ -180,7 +181,7 @@ class RemoteTestRunner(TestRunner): return json_result - def run_suite(self, test_suite, mux, timeout, replay_map=None): + def run_suite(self, test_suite, mux, timeout, replay_map=None, test_result_total=0): """ Run one or more tests and report with test result. @@ -191,6 +192,7 @@ class RemoteTestRunner(TestRunner): """ del test_suite # using self.job.urls instead del mux # we're not using multiplexation here + del test_result_total # evaluated by the remote avocado if not timeout: # avoid timeout = 0 timeout = None summary = set() @@ -229,7 +231,10 @@ class RemoteTestRunner(TestRunner): remote_log_dir = os.path.dirname(results['debuglog']) self.result.start_tests() for tst in results['tests']: - test = RemoteTest(name=tst['test'], + name = tst['test'].split('-', 1) + name = [name[0]] + name[1].split(';') + name = TestName(*name, no_digits=-1) + test = RemoteTest(name=name, time=tst['time'], start=tst['start'], end=tst['end'], diff --git a/avocado/core/remote/test.py b/avocado/core/remote/test.py index 7b918b9c..2d4516a6 100644 --- a/avocado/core/remote/test.py +++ b/avocado/core/remote/test.py @@ -25,7 +25,6 @@ class RemoteTest(object): logfile): note = "Not supported yet" self.name = name - self.tagged_name = name self.status = status self.time_elapsed = time self.time_start = start diff --git a/avocado/core/result.py b/avocado/core/result.py index 638bb1a1..648de511 100644 --- a/avocado/core/result.py +++ b/avocado/core/result.py @@ -235,8 +235,14 @@ class HumanTestResult(TestResult): def start_test(self, state): super(HumanTestResult, self).start_test(state) - self.log.debug(' (%s/%s) %s: ', self.tests_run, self.tests_total, - state.get("tagged_name", ""), + if "name" in state: + name = state["name"] + uid = name.str_uid + name = name.name + name.str_variant + else: + name = "" + uid = '?' + self.log.debug(' (%s/%s) %s: ', uid, self.tests_total, name, extra={"skip_newline": True}) def end_test(self, state): diff --git a/avocado/core/runner.py b/avocado/core/runner.py index cbff80d9..c692b25e 100644 --- a/avocado/core/runner.py +++ b/avocado/core/runner.py @@ -403,7 +403,8 @@ class TestRunner(object): return False return True - def run_suite(self, test_suite, mux, timeout=0, replay_map=None): + def run_suite(self, test_suite, mux, timeout=0, replay_map=None, + test_result_total=0): """ Run one or more tests and report with test result. @@ -423,14 +424,20 @@ class TestRunner(object): else: deadline = None + no_digits = len(str(test_result_total)) + index = -1 for test_template in test_suite: test_template[1]['base_logdir'] = self.job.logdir test_template[1]['job'] = self.job break_loop = False - for test_factory in mux.itertests(test_template): + for test_factory, variant in mux.itertests(test_template): index += 1 test_parameters = test_factory[1] + name = test_parameters.get("name") + test_parameters["name"] = test.TestName(index + 1, name, + variant, + no_digits) if deadline is not None and time.time() > deadline: summary.add('INTERRUPTED') if 'methodName' in test_parameters: diff --git a/avocado/core/test.py b/avocado/core/test.py index 3a7ee9b6..479b9b46 100644 --- a/avocado/core/test.py +++ b/avocado/core/test.py @@ -46,6 +46,65 @@ else: import unittest +class TestName(object): + + """ + Test name representation + """ + + def __init__(self, uid, name, variant=None, no_digits=None): + """ + Test name according to avocado specification + + :param uid: unique test id (within the job) + :param name: test name (identifies the executed test) + :param variant: variant id + :param no_digits: number of digits of the test uid + """ + self.uid = uid + if no_digits >= 0: + self.str_uid = str(uid).zfill(no_digits if no_digits else 3) + else: + self.str_uid = str(uid) + self.name = name or "" + self.variant = variant + self.str_variant = "" if variant is None else ";" + str(variant) + + def __str__(self): + return "%s-%s%s" % (self.str_uid, self.name, self.str_variant) + + def __repr__(self): + return repr(str(self)) + + def __eq__(self, other): + if isinstance(other, basestring): + return str(self) == other + else: + return self.__dict__ == other.__dict__ + + def str_filesystem(self): + """ + File-system friendly representation of the test name + """ + name = str(self) + fsname = astring.string_to_safe_path(name) + if len(name) == len(fsname): # everything fits in + return fsname + # 001-mytest;aaa + # 001-mytest;a + # 001-myte;aaa + idx_fit_variant = len(fsname) - len(self.str_variant) + if idx_fit_variant > len(self.str_uid): # full uid+variant + return (fsname[:idx_fit_variant] + + astring.string_to_safe_path(self.str_variant)) + elif len(self.str_uid) <= len(fsname): # full uid + return astring.string_to_safe_path(self.str_uid + self.str_variant) + else: # not even uid could be stored in fs + raise AssertionError("Test uid is too long to be stored on the " + "filesystem: %s\nFull test name is %s" + % (self.str_uid, str(self))) + + class Test(unittest.TestCase): """ @@ -81,10 +140,14 @@ class Test(unittest.TestCase): self.__log_warn_used = True return original_log_warn(*args, **kwargs) - if name is not None: + _incorrect_name = None + if isinstance(name, basestring): # TODO: Remove in release 0.37 + _incorrect_name = True + self.name = TestName(0, name) + elif name is not None: self.name = name else: - self.name = self.__class__.__name__ + self.name = TestName(0, self.__class__.__name__) self.tag = tag self.job = job @@ -101,7 +164,12 @@ class Test(unittest.TestCase): if base_logdir is None: base_logdir = data_dir.create_job_logs_dir() base_logdir = os.path.join(base_logdir, 'test-results') - self.tagged_name, self.logdir = self._init_logdir(base_logdir) + logdir = os.path.join(base_logdir, self.name.str_filesystem()) + if os.path.exists(logdir): + raise exceptions.TestSetupFail("Log dir already exists, this " + "should never happen: %s" + % logdir) + self.logdir = utils_path.init_dir(logdir) # Replace '/' with '_' to avoid splitting name into multiple dirs genio.set_log_file_dir(self.logdir) @@ -119,6 +187,13 @@ class Test(unittest.TestCase): original_log_warn = self.log.warning self.__log_warn_used = False self.log.warn = self.log.warning = record_and_warn + if _incorrect_name is not None: + self.log.warn("The 'name' argument has to be TestName instance, " + "not string. In the upcomming releases this will " + "become an exception. (%s)", self.name.name) + if tag is not None: # TODO: Remove in release 0.37 + self.log.warn("The 'tag' argument is not supported and will be " + "removed in the upcoming releases. (%s)", tag) mux_path = ['/test/*'] if isinstance(params, dict): @@ -135,7 +210,7 @@ class Test(unittest.TestCase): default_timeout = getattr(self, "timeout", None) self.timeout = self.params.get("timeout", default=default_timeout) - self.log.info('START %s', self.tagged_name) + self.log.info('START %s', self.name) self.debugdir = None self.resultsdir = None @@ -209,7 +284,7 @@ class Test(unittest.TestCase): return str(self.name) def __repr__(self): - return "Test(%r)" % self.tagged_name + return "Test(%r)" % self.name def _tag_start(self): self.running = True @@ -245,7 +320,7 @@ class Test(unittest.TestCase): preserve_attr = ['basedir', 'debugdir', 'depsdir', 'fail_reason', 'logdir', 'logfile', 'name', 'resultsdir', 'srcdir', 'status', 'sysinfodir', - 'tag', 'tagged_name', 'text_output', 'time_elapsed', + 'tag', 'text_output', 'time_elapsed', 'traceback', 'workdir', 'whiteboard', 'time_start', 'time_end', 'running', 'paused', 'paused_msg', 'fail_class', 'params', "timeout"] @@ -296,39 +371,6 @@ class Test(unittest.TestCase): self.log.removeHandler(self.file_handler) logging.getLogger('paramiko').removeHandler(self._ssh_fh) - def _init_logdir(self, logdir): - """ - Initialize log dir - - Combines name + tag (if present) to obtain unique name. When associated - directory already exists, appends ".$number" until unused name - is generated to avoid clashes. - - :param logdir: Log directory being in use for result storage. - - :return: Unique test name and the logdir - """ - name = self.name - if self.tag is not None: - name += ".%s" % self.tag - tag = 0 - tagged_name = name - # The maximal length on ext4+python2.7 is 255 chars. - safe_tagged_name = astring.string_to_safe_path(tagged_name[:250]) - for i in xrange(9999): - if not os.path.isdir(os.path.join(logdir, safe_tagged_name)): - break - tag += 1 - tagged_name = "%s.%s" % (name, tag) - safe_tagged_name = astring.string_to_safe_path("%s.%s" - % (name[:250], tag)) - else: - raise exceptions.TestSetupFail("Unable to find unique name in %s " - "iterations (%s).", i, - safe_tagged_name) - self.tag = "%s.%s" % (self.tag, tag) if self.tag else str(tag) - return tagged_name, utils_path.init_dir(logdir, safe_tagged_name) - def _record_reference_stdout(self): if self.datadir is not None: utils_path.init_dir(self.datadir) @@ -520,7 +562,7 @@ class Test(unittest.TestCase): """ if self.fail_reason is not None: self.log.error("%s %s -> %s: %s", self.status, - self.tagged_name, + self.name, self.fail_class, self.fail_reason) @@ -528,7 +570,7 @@ class Test(unittest.TestCase): if self.status is None: self.status = 'INTERRUPTED' self.log.info("%s %s", self.status, - self.tagged_name) + self.name) def fail(self, message=None): """ @@ -608,7 +650,7 @@ class SimpleTest(Test): """ Returns the name of the file (path) that holds the current test """ - return os.path.abspath(self.name) + return os.path.abspath(self.name.name) def _log_detailed_cmd_info(self, result): """ @@ -654,7 +696,7 @@ class ExternalRunnerTest(SimpleTest): self.external_runner = external_runner super(ExternalRunnerTest, self).__init__(name, params, base_logdir, tag, job) - self._command = external_runner.runner + " " + name + self._command = external_runner.runner + " " + self.name.name @property def filename(self): diff --git a/avocado/core/xunit.py b/avocado/core/xunit.py index d7f94b88..1f6a1368 100644 --- a/avocado/core/xunit.py +++ b/avocado/core/xunit.py @@ -88,7 +88,7 @@ class XmlResult(object): """ tc = '\t' values = {'class': self._escape_attr(state.get('class_name', "")), - 'name': self._escape_attr(state.get('tagged_name', "")), + 'name': self._escape_attr(state.get('name', "")), 'time': state.get('time_elapsed', -1)} self.testcases.append(tc.format(**values)) @@ -103,7 +103,7 @@ class XmlResult(object): \t\t \t''' values = {'class': self._escape_attr(state.get('class_name', "")), - 'name': self._escape_attr(state.get('tagged_name', "")), + 'name': self._escape_attr(state.get('name', "")), 'time': state.get('time_elapsed', -1)} self.testcases.append(tc.format(**values)) @@ -119,7 +119,7 @@ class XmlResult(object): \t\t \t''' values = {'class': self._escape_attr(state.get('class_name', "")), - 'name': self._escape_attr(state.get('tagged_name', "")), + 'name': self._escape_attr(state.get('name', "")), 'time': state.get('time_elapsed', -1), 'type': self._escape_attr(state.get('fail_class', "")), 'traceback': self._escape_cdata(state.get('traceback', "")), @@ -139,7 +139,7 @@ class XmlResult(object): \t\t \t''' values = {'class': self._escape_attr(state.get('class_name', "")), - 'name': self._escape_attr(state.get('tagged_name', "")), + 'name': self._escape_attr(state.get('name', "")), 'time': state.get('time_elapsed', -1), 'type': self._escape_attr(state.get('fail_class', "")), 'traceback': self._escape_cdata(state.get('traceback', "")), diff --git a/avocado/plugins/journal.py b/avocado/plugins/journal.py index d4a4716e..67048dfc 100644 --- a/avocado/plugins/journal.py +++ b/avocado/plugins/journal.py @@ -91,7 +91,7 @@ class TestResultJournal(TestResult): status = None self.journal_cursor.execute(sql, - (state['tagged_name'], + (str(state['name']), datetime.datetime(1, 1, 1).now().isoformat(), action, status)) diff --git a/selftests/functional/test_basic.py b/selftests/functional/test_basic.py index 800ba461..3272285c 100644 --- a/selftests/functional/test_basic.py +++ b/selftests/functional/test_basic.py @@ -385,7 +385,8 @@ class RunnerOperationTest(unittest.TestCase): self.assertEqual(result.exit_status, expected_rc, "Avocado did not return rc %d:\n%s" % (expected_rc, result)) - self.assertIn('MyTest.test_my_name -> TestError', result.stdout) + self.assertIn('1-%s:MyTest.test_my_name -> TestError' % test, + result.stdout) def tearDown(self): shutil.rmtree(self.tmpdir) @@ -452,7 +453,7 @@ class RunnerHumanOutputTest(unittest.TestCase): self.assertIn('[stdout] foo', result.stdout, result) self.assertIn('[stdout] \'"', result.stdout, result) self.assertIn('[stdout] bar/baz', result.stdout, result) - self.assertIn('PASS /bin/echo -ne foo\\\\n\\\'\\"\\\\nbar/baz', + self.assertIn('PASS 1-/bin/echo -ne foo\\\\n\\\'\\"\\\\nbar/baz', result.stdout, result) # logdir name should escape special chars (/) test_dirs = glob.glob(os.path.join(self.tmpdir, 'latest', @@ -461,7 +462,7 @@ class RunnerHumanOutputTest(unittest.TestCase): " test-results dir, but only one test was executed: " "%s" % (test_dirs)) self.assertEqual(os.path.basename(test_dirs[0]), - '_bin_echo -ne foo\\\\n\\\'\\"\\\\nbar_baz') + '1-_bin_echo -ne foo\\\\n\\\'\\"\\\\nbar_baz') def test_replay_skip_skipped(self): result = process.run("./scripts/avocado run skiponsetup --json -") @@ -913,10 +914,10 @@ class PluginsJSONTest(AbsPluginsTest, unittest.TestCase): 0, 0) # The executed test should be this self.assertEqual(data['tests'][0]['url'], - '/bin/echo -ne foo\\\\n\\\'\\"\\\\nbar/baz') + '1-/bin/echo -ne foo\\\\n\\\'\\"\\\\nbar/baz') # logdir name should escape special chars (/) self.assertEqual(os.path.basename(data['tests'][0]['logdir']), - '_bin_echo -ne foo\\\\n\\\'\\"\\\\nbar_baz') + '1-_bin_echo -ne foo\\\\n\\\'\\"\\\\nbar_baz') def tearDown(self): shutil.rmtree(self.tmpdir) diff --git a/selftests/functional/test_streams.py b/selftests/functional/test_streams.py index dfd9d7d1..97e4f78c 100644 --- a/selftests/functional/test_streams.py +++ b/selftests/functional/test_streams.py @@ -82,8 +82,9 @@ class StreamsTest(unittest.TestCase): result.stderr) self.assertIn("Command line: %s" % cmd, result.stdout) - self.assertIn("START passtest", result.stdout) - self.assertIn("PASS passtest", result.stdout) + self.assertIn("\nSTART 1-passtest.py:PassTest.test", + result.stdout) + self.assertIn("PASS 1-passtest.py:PassTest.test", result.stdout) self.assertEqual('', result.stderr) def test_none_success(self): diff --git a/selftests/unit/test_loader.py b/selftests/unit/test_loader.py index 0be82fb3..750584ac 100644 --- a/selftests/unit/test_loader.py +++ b/selftests/unit/test_loader.py @@ -148,6 +148,7 @@ class LoaderTest(unittest.TestCase): test_class, test_parameters = ( self.loader.discover(simple_test.path, True)[0]) self.assertTrue(test_class == test.SimpleTest, test_class) + test_parameters['name'] = test.TestName(0, test_parameters['name']) tc = test_class(**test_parameters) tc.test() # Load with params @@ -165,6 +166,7 @@ class LoaderTest(unittest.TestCase): test_class, test_parameters = ( self.loader.discover(simple_test.path, True)[0]) self.assertTrue(test_class == test.NotATest, test_class) + test_parameters['name'] = test.TestName(0, test_parameters['name']) tc = test_class(**test_parameters) self.assertRaises(exceptions.NotATestError, tc.test) simple_test.remove() @@ -188,6 +190,7 @@ class LoaderTest(unittest.TestCase): test_class, test_parameters = ( self.loader.discover(avocado_not_a_test.path, True)[0]) self.assertTrue(test_class == test.NotATest, test_class) + test_parameters['name'] = test.TestName(0, test_parameters['name']) tc = test_class(**test_parameters) self.assertRaises(exceptions.NotATestError, tc.test) avocado_not_a_test.remove() @@ -199,6 +202,7 @@ class LoaderTest(unittest.TestCase): test_class, test_parameters = ( self.loader.discover(avocado_not_a_test.path, True)[0]) self.assertTrue(test_class == test.SimpleTest, test_class) + test_parameters['name'] = test.TestName(0, test_parameters['name']) tc = test_class(**test_parameters) # The test can't be executed (no shebang), raising an OSError # (OSError: [Errno 8] Exec format error) @@ -213,6 +217,7 @@ class LoaderTest(unittest.TestCase): test_class, test_parameters = ( self.loader.discover(avocado_simple_test.path, True)[0]) self.assertTrue(test_class == test.SimpleTest) + test_parameters['name'] = test.TestName(0, test_parameters['name']) tc = test_class(**test_parameters) tc.test() avocado_simple_test.remove() @@ -226,6 +231,7 @@ class LoaderTest(unittest.TestCase): test_class, test_parameters = ( self.loader.discover(avocado_simple_test.path, True)[0]) self.assertTrue(test_class == test.NotATest) + test_parameters['name'] = test.TestName(0, test_parameters['name']) tc = test_class(**test_parameters) self.assertRaises(exceptions.NotATestError, tc.test) avocado_simple_test.remove() diff --git a/selftests/unit/test_remote.py b/selftests/unit/test_remote.py index 4b738d06..7b3a098f 100644 --- a/selftests/unit/test_remote.py +++ b/selftests/unit/test_remote.py @@ -13,7 +13,7 @@ import logging cwd = os.getcwd() JSON_RESULTS = ('Something other than json\n' - '{"tests": [{"test": "sleeptest.1", "url": "sleeptest", ' + '{"tests": [{"test": "1-sleeptest;0", "url": "sleeptest", ' '"fail_reason": "None", ' '"status": "PASS", "time": 1.23, "start": 0, "end": 1.23}],' '"debuglog": "/home/user/avocado/logs/run-2014-05-26-15.45.' @@ -44,7 +44,7 @@ class RemoteTestRunnerTest(unittest.TestCase): log.should_receive("info") job = flexmock(args=Args, log=log, urls=['/tests/sleeptest', '/tests/other/test', - 'passtest'], unique_id='sleeptest.1', + 'passtest'], unique_id='1-sleeptest;0', logdir="/local/path") flexmock(remote.RemoteTestRunner).should_receive('__init__') @@ -56,7 +56,7 @@ class RemoteTestRunnerTest(unittest.TestCase): flexmock(logging).should_receive("FileHandler").and_return(filehandler) test_results = flexmock(stdout=JSON_RESULTS, exit_status=0) - stream = flexmock(job_unique_id='sleeptest.1', + stream = flexmock(job_unique_id='1-sleeptest;0', debuglog='/local/path/dirname') Remote = flexmock() Remoter = flexmock(remoter.Remote) @@ -102,7 +102,7 @@ _=/usr/bin/env''', exit_status=0) .with_args(args, ignore_status=True, timeout=60) .once().and_return(urls_result)) - args = ("cd ~/avocado/tests; avocado run --force-job-id sleeptest.1 " + args = ("cd ~/avocado/tests; avocado run --force-job-id 1-sleeptest;0 " "--json - --archive /tests/sleeptest /tests/other/test " "passtest --multiplex ~/avocado/tests/foo.yaml " "~/avocado/tests/bar/baz.yaml --dry-run") @@ -116,14 +116,14 @@ _=/usr/bin/env''', exit_status=0) dry_run=True)) Results.should_receive('start_tests').once().ordered() args = {'status': u'PASS', 'whiteboard': '', 'time_start': 0, - 'name': u'sleeptest.1', 'class_name': 'RemoteTest', + 'name': '1-sleeptest;0', 'class_name': 'RemoteTest', 'traceback': 'Not supported yet', 'text_output': 'Not supported yet', 'time_end': 1.23, - 'tagged_name': u'sleeptest.1', 'time_elapsed': 1.23, + 'time_elapsed': 1.23, 'fail_class': 'Not supported yet', 'job_unique_id': '', - 'fail_reason': 'None', - 'logdir': '/local/path/test-results/sleeptest.1', - 'logfile': '/local/path/test-results/sleeptest.1/debug.log'} + 'fail_reason': u'None', + 'logdir': u'/local/path/test-results/1-sleeptest;0', + 'logfile': u'/local/path/test-results/1-sleeptest;0/debug.log'} Results.should_receive('start_test').once().with_args(args).ordered() Results.should_receive('check_test').once().with_args(args).ordered() (Remote.should_receive('receive_files') diff --git a/selftests/unit/test_test.py b/selftests/unit/test_test.py index d8585589..1c64d243 100644 --- a/selftests/unit/test_test.py +++ b/selftests/unit/test_test.py @@ -39,19 +39,20 @@ class TestClassTestUnit(unittest.TestCase): def testUglyName(self): def run(name, path_name): """ Initialize test and check the dirs were created """ - test = DummyTest("test", name, base_logdir=self.tmpdir) - self.assertEqual(os.path.basename(test.logdir), path_name) - self.assertTrue(os.path.exists(test.logdir)) - self.assertEqual(os.path.dirname(os.path.dirname(test.logdir)), + tst = DummyTest("test", test.TestName(1, name), + base_logdir=self.tmpdir) + self.assertEqual(os.path.basename(tst.logdir), path_name) + self.assertTrue(os.path.exists(tst.logdir)) + self.assertEqual(os.path.dirname(os.path.dirname(tst.logdir)), self.tmpdir) - run("/absolute/path", "_absolute_path") - run("./relative/path", "__relative_path") + run("/absolute/path", "1-_absolute_path") + run("./relative/path", "1-._relative_path") run("../../multi_level/relative/path", - "_._.._multi_level_relative_path") + "1-.._.._multi_level_relative_path") # Greek word 'kosme' run("\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5", - "\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5") + "1-\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5") # Particularly problematic noncharacters in 16-bit applications name = ("\xb7\x95\xef\xb7\x96\xef\xb7\x97\xef\xb7\x98\xef\xb7\x99" "\xef\xb7\x9a\xef\xb7\x9b\xef\xb7\x9c\xef\xb7\x9d\xef\xb7" @@ -59,31 +60,48 @@ class TestClassTestUnit(unittest.TestCase): "\xb7\xa3\xef\xb7\xa4\xef\xb7\xa5\xef\xb7\xa6\xef\xb7\xa7" "\xef\xb7\xa8\xef\xb7\xa9\xef\xb7\xaa\xef\xb7\xab\xef\xb7" "\xac\xef\xb7\xad\xef\xb7\xae\xef\xb7\xaf") - run(name, name) + run(name, "1-" + name) def testLongName(self): - test = DummyTest("test", "a" * 256, base_logdir=self.tmpdir) - self.assertEqual(os.path.basename(test.logdir), "a" * 250) - test = DummyTest("test", "a" * 256, base_logdir=self.tmpdir) - self.assertEqual(os.path.basename(test.logdir), "a" * 250 + ".1") - self.assertEqual(os.path.basename(test.workdir), - os.path.basename(test.logdir)) - flexmock(test) - test.should_receive('filename').and_return(os.path.join(self.tmpdir, - "a"*250)) + def check(uid, name, variant, exp_logdir): + tst = DummyTest("test", test.TestName(uid, name, variant)) + self.assertEqual(os.path.basename(tst.logdir), exp_logdir) + return tst + + # Everything fits + check(1, "a" * 253, None, "1-" + ("a" * 253)) + check(2, "a" * 251, 1, "2-" + ("a" * 251) + ";1") + check(99, "a" * 249, 88, "99-" + ("a" * 249) + ";88") + # Shrink name + check(3, "a" * 252, 1, "3-" + ('a' * 251) + ";1") + # Shrink variant + check("a" * 253, "whatever", 99, "a" * 253 + ";9") + check("a" * 254, "whatever", 99, "a" * 254 + ";") + # No variant + tst = check("a" * 255, "whatever", "whatever-else", "a" * 255) + # Impossible to store (uid does not fit + self.assertRaises(AssertionError, check, "a" * 256, "whatever", "else", + None) + + self.assertEqual(os.path.basename(tst.workdir), + os.path.basename(tst.logdir)) + flexmock(tst) + tst.should_receive('filename').and_return(os.path.join(self.tmpdir, + "a"*250)) self.assertEqual(os.path.join(self.tmpdir, "a"*250 + ".data"), - test.datadir) - test.should_receive('filename').and_return("a"*251) - self.assertFalse(test.datadir) - test._record_reference_stdout # Should does nothing - test._record_reference_stderr # Should does nothing - test._record_reference_stdout() - test._record_reference_stderr() + tst.datadir) + tst.should_receive('filename').and_return("a"*251) + self.assertFalse(tst.datadir) + tst._record_reference_stdout # Should does nothing + tst._record_reference_stderr # Should does nothing + tst._record_reference_stdout() + tst._record_reference_stderr() def testAllDirsExistsNoHang(self): flexmock(os.path) - os.path.should_receive('isdir').and_return(True) - self.assertRaises(exceptions.TestSetupFail, DummyTest, "test", "name") + os.path.should_receive('exists').and_return(True) + self.assertRaises(exceptions.TestSetupFail, DummyTest, "test", + test.TestName(1, "name")) class TestClassTest(unittest.TestCase): @@ -99,11 +117,9 @@ class TestClassTest(unittest.TestCase): self.base_logdir = tempfile.mkdtemp(prefix='avocado_' + __name__) self.tst_instance_pass = AvocadoPass(base_logdir=self.base_logdir) self.tst_instance_pass.run_avocado() - self.tst_instance_pass_new = AvocadoPass(base_logdir=self.base_logdir) - self.tst_instance_pass_new.run_avocado() def testClassAttributesName(self): - self.assertEqual(self.tst_instance_pass.name, 'AvocadoPass') + self.assertEqual(self.tst_instance_pass.name, '0-AvocadoPass') def testClassAttributesStatus(self): self.assertEqual(self.tst_instance_pass.status, 'PASS') @@ -112,10 +128,7 @@ class TestClassTest(unittest.TestCase): self.assertIsInstance(self.tst_instance_pass.time_elapsed, float) def testClassAttributesTag(self): - self.assertEqual(self.tst_instance_pass.tag, "0") - - def testClassAttributesTaggedName(self): - self.assertEqual(self.tst_instance_pass.tagged_name, "AvocadoPass") + self.assertEqual(self.tst_instance_pass.tag, None) def testWhiteboardSave(self): whiteboard_file = os.path.join( @@ -125,13 +138,14 @@ class TestClassTest(unittest.TestCase): whiteboard_contents = whiteboard_file_obj.read().strip() self.assertTrue(whiteboard_contents, 'foo') - def testTaggedNameNewTests(self): - """ - New test instances should have crescent tag instances. - """ - self.assertEqual( - self.tst_instance_pass_new.tagged_name, "AvocadoPass.1") - self.assertEqual(self.tst_instance_pass_new.tag, "1") + def testRunningTestTwiceWithTheSameUidFailure(self): + class AvocadoPass(test.Test): + + def test(self): + pass + + self.assertRaises(exceptions.TestSetupFail, AvocadoPass, + base_logdir=self.base_logdir) def tearDown(self): shutil.rmtree(self.base_logdir) @@ -154,12 +168,12 @@ class SimpleTestClassTest(unittest.TestCase): self.fail_script.save() self.tst_instance_pass = test.SimpleTest( - name=self.pass_script.path, + name=test.TestName(1, self.pass_script.path), base_logdir=self.tmpdir) self.tst_instance_pass.run_avocado() self.tst_instance_fail = test.SimpleTest( - name=self.fail_script.path, + name=test.TestName(1, self.fail_script.path), base_logdir=self.tmpdir) self.tst_instance_fail.run_avocado() @@ -186,47 +200,46 @@ class SkipTest(unittest.TestCase): self.assertRaises(exceptions.TestSkipError, self.tests[-1].setUp) self.assertRaises(RuntimeError, self.tests[-1].test) # Positional - self.tests.append(test.SkipTest("test", "my_name", {}, None, "1", + self.tests.append(test.SkipTest("test", test.TestName(1, "my_name"), + {}, None, "1", None, None, "extra_param1", "extra_param2")) - self.assertEqual(self.tests[-1].name, "my_name") - self.assertEqual(self.tests[-1].tagged_name, "my_name.1") + self.assertEqual(self.tests[-1].name, "1-my_name") # Kwargs - self.tests.append(test.SkipTest(methodName="test", name="my_name2", + self.tests.append(test.SkipTest(methodName="test", + name=test.TestName(1, "my_name2"), params={}, base_logdir=None, tag="a", job=None, runner_queue=None, extra1="extra_param1", extra2="extra_param2")) - self.assertEqual(self.tests[-1].name, "my_name2") - self.assertEqual(self.tests[-1].tagged_name, "my_name2.a") + self.assertEqual(self.tests[-1].name, "1-my_name2") # both (theoretically impossible in python, but valid for nasty tests) # keyword args are used as they explicitly represent what they mean self.tests.append(test.SkipTest("not used", "who cares", {}, None, "0", None, None, "extra_param1", "extra_param2", - methodName="test", name="my_name3", + methodName="test", + name=test.TestName(1, "my_name3"), params={}, base_logdir=None, tag="3", job=None, runner_queue=None, extra1="extra_param3", extra2="extra_param4")) - self.assertEqual(self.tests[-1].name, "my_name3") - self.assertEqual(self.tests[-1].tagged_name, "my_name3.3") + self.assertEqual(self.tests[-1].name, "1-my_name3") # combination - self.tests.append(test.SkipTest("test", "my_name4", tag="321", + self.tests.append(test.SkipTest("test", test.TestName(1, "my_name4"), + tag="321", other_param="Whatever")) - self.assertEqual(self.tests[-1].name, "my_name4") - self.assertEqual(self.tests[-1].tagged_name, "my_name4.321") + self.assertEqual(self.tests[-1].name, "1-my_name4") # ugly combination (positional argument overrides kwargs, this only # happens when the substituted class reorders the positional arguments. # We try to first match keyword args and then fall-back to positional # ones. name = "positional_method_name_becomes_test_name" tag = "positional_base_logdir_becomes_tag" - self.tests.append(test.SkipTest(name, None, None, tag, + self.tests.append(test.SkipTest(test.TestName(1, name), None, None, tag, methodName="test", other_param="Whatever")) - self.assertEqual(self.tests[-1].name, name) - self.assertEqual(self.tests[-1].tagged_name, "%s.%s" % (name, tag)) + self.assertEqual(self.tests[-1].name, "1-" + name) def tearDown(self): for tst in self.tests: -- GitLab