Skip to content

Commit

Permalink
Merge branch 'clebergnu-job_phases_v3'
Browse files Browse the repository at this point in the history
  • Loading branch information
apahim committed Oct 26, 2016
2 parents 3301f1c + 3e34ef0 commit 3c10b4d
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 39 deletions.
87 changes: 52 additions & 35 deletions avocado/core/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,11 @@ def __init__(self, args=None):
if unique_id is None:
unique_id = job_id.create_unique_job_id()
self.unique_id = unique_id
#: The log directory for this job, also known as the job results
#: directory. If it's set to None, it means that the job results
#: directory has not yet been created.
self.logdir = None
self._setup_job_results()
raw_log_level = settings.get_value('job.output', 'loglevel',
default='debug')
mapping = {'info': logging.INFO,
Expand All @@ -102,28 +106,31 @@ def __init__(self, args=None):
else:
self.loglevel = logging.DEBUG

self.test_dir = data_dir.get_test_dir()
self.test_index = 1
self.status = "RUNNING"
self.result_proxy = result.ResultProxy()
self.sysinfo = None
self.timeout = getattr(self.args, 'job_timeout', 0)
self.__logging_handlers = {}
self.__start_job_logging()
self.funcatexit = data_structures.CallbackRegister("JobExit %s"
% self.unique_id,
_TEST_LOGGER)
self.stdout_stderr = None
self._stdout_stderr = None
self.replay_sourcejob = getattr(self.args, 'replay_sourcejob', None)
self.exitcode = exit_codes.AVOCADO_ALL_OK
#: The list of discovered/resolved tests that will be attempted to
#: be run by this job. If set to None, it means that test resolution
#: has not been attempted. If set to an empty list, it means that no
#: test was found during resolution.
self.test_suite = None
self.job_pre_post_dispatcher = dispatcher.JobPrePostDispatcher()
output.log_plugin_failures(self.job_pre_post_dispatcher.load_failures)

# A job may not have a dispatcher for pre/post tests execution plugins
self._job_pre_post_dispatcher = None

def _setup_job_results(self):
"""
Prepares a job result directory, also known as logdir, for this job
"""
logdir = getattr(self.args, 'logdir', None)
if self.standalone:
if logdir is not None:
Expand All @@ -139,9 +146,11 @@ def _setup_job_results(self):
logdir = os.path.abspath(logdir)
self.logdir = data_dir.create_job_logs_dir(logdir=logdir,
unique_id=self.unique_id)
if not (self.standalone or getattr(self.args, "dry_run", False)):
self._update_latest_link()
self.logfile = os.path.join(self.logdir, "job.log")
self.idfile = os.path.join(self.logdir, "id")
with open(self.idfile, 'w') as id_file_obj:
idfile = os.path.join(self.logdir, "id")
with open(idfile, 'w') as id_file_obj:
id_file_obj.write("%s\n" % self.unique_id)

def __start_job_logging(self):
Expand Down Expand Up @@ -181,7 +190,7 @@ def __start_job_logging(self):
enabled_logs = getattr(self.args, "show", [])
if ('test' in enabled_logs and
'early' not in enabled_logs):
self.stdout_stderr = sys.stdout, sys.stderr
self._stdout_stderr = sys.stdout, sys.stderr
# Enable std{out,err} but redirect booth to stderr
sys.stdout = STD_OUTPUT.stdout
sys.stderr = STD_OUTPUT.stdout
Expand All @@ -194,8 +203,8 @@ def __start_job_logging(self):
self.__logging_handlers[test_handler] = ["avocado.test", ""]

def __stop_job_logging(self):
if self.stdout_stderr:
sys.stdout, sys.stderr = self.stdout_stderr
if self._stdout_stderr:
sys.stdout, sys.stderr = self._stdout_stderr
for handler, loggers in self.__logging_handlers.iteritems():
for logger in loggers:
logging.getLogger(logger).removeHandler(handler)
Expand Down Expand Up @@ -239,9 +248,6 @@ def _start_sysinfo(self):
sysinfo_dir = path.init_dir(self.logdir, 'sysinfo')
self.sysinfo = sysinfo.SysInfo(basedir=sysinfo_dir)

def _remove_job_results(self):
shutil.rmtree(self.logdir, ignore_errors=True)

def _make_test_runner(self):
if hasattr(self.args, 'test_runner'):
test_runner_class = self.args.test_runner
Expand Down Expand Up @@ -290,10 +296,8 @@ def _make_test_suite(self, urls=None):
try:
suite = loader.loader.discover(urls)
except loader.LoaderUnhandledUrlError as details:
self._remove_job_results()
raise exceptions.OptionValidationError(details)
except KeyboardInterrupt:
self._remove_job_results()
raise exceptions.JobError('Command interrupted by user...')

if not getattr(self.args, "dry_run", False):
Expand Down Expand Up @@ -410,19 +414,12 @@ def _log_job_debug_info(self, mux):
self._log_mux_variants(mux)
self._log_job_id()

def _run(self):
def create_test_suite(self):
"""
Unhandled job method. Runs a list of test URLs to its completion.
Creates the test suite for this Job
:return: Integer with overall job status. See
:mod:`avocado.core.exit_codes` for more information.
:raise: Any exception (avocado crashed), or
:class:`avocado.core.exceptions.JobBaseException` errors,
that configure a job failure.
This is a public Job API as part of the documented Job phases
"""
self._setup_job_results()
self.__start_job_logging()

if (getattr(self.args, 'remote_hostname', False) and
getattr(self.args, 'remote_no_copy', False)):
self.test_suite = [(None, {})]
Expand All @@ -431,13 +428,21 @@ def _run(self):
self.test_suite = self._make_test_suite(self.urls)
except loader.LoaderError as details:
stacktrace.log_exc_info(sys.exc_info(), 'avocado.app.debug')
self._remove_job_results()
raise exceptions.OptionValidationError(details)

self.job_pre_post_dispatcher.map_method('pre', self)
def pre_tests(self):
"""
Run the pre tests execution hooks
By default this runs the plugins that implement the
:class:`avocado.core.plugin_interfaces.JobPre` interface.
"""
self._job_pre_post_dispatcher = dispatcher.JobPrePostDispatcher()
output.log_plugin_failures(self._job_pre_post_dispatcher.load_failures)
self._job_pre_post_dispatcher.map_method('pre', self)

def run_tests(self):
if not self.test_suite:
self._remove_job_results()
if self.urls:
e_msg = ("No tests found for given urls, try 'avocado list -V "
"%s' for details" % " ".join(self.urls))
Expand All @@ -459,8 +464,6 @@ def _run(self):
self.args.test_result_total = mux.get_number_of_tests(self.test_suite)

self._make_old_style_test_result()
if not (self.standalone or getattr(self.args, "dry_run", False)):
self._update_latest_link()
self._make_test_runner()
self._start_sysinfo()

Expand Down Expand Up @@ -490,19 +493,33 @@ def _run(self):

return self.exitcode

def post_tests(self):
"""
Run the post tests execution hooks
By default this runs the plugins that implement the
:class:`avocado.core.plugin_interfaces.JobPost` interface.
"""
if self._job_pre_post_dispatcher is None:
self._job_pre_post_dispatcher = dispatcher.JobPrePostDispatcher()
output.log_plugin_failures(self._job_pre_post_dispatcher.load_failures)
self._job_pre_post_dispatcher.map_method('post', self)

def run(self):
"""
Handled main job method. Runs a list of test URLs to its completion.
Runs all job phases, returning the test execution results.
The test runner figures out which tests need to be run on an empty urls
list by assuming the first component of the shortname is the test url.
This method is supposed to be the simplified interface for
jobs, that is, they run all phases of a job.
:return: Integer with overall job status. See
:mod:`avocado.core.exit_codes` for more information.
"""
runtime.CURRENT_JOB = self
try:
return self._run()
self.create_test_suite()
self.pre_tests()
return self.run_tests()
except exceptions.JobBaseException as details:
self.status = details.status
fail_class = details.__class__.__name__
Expand All @@ -529,7 +546,7 @@ def run(self):
self.exitcode |= exit_codes.AVOCADO_FAIL
return self.exitcode
finally:
self.job_pre_post_dispatcher.map_method('post', self)
self.post_tests()
if not settings.get_value('runner.behavior', 'keep_tmp_files',
key_type=bool, default=False):
data_dir.clean_tmp_files()
Expand Down
4 changes: 0 additions & 4 deletions selftests/functional/test_argument_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,5 @@ def test_whacky_option(self):
self.run_but_fail_before_create_job_dir('--whacky-option passtest',
exit_codes.AVOCADO_FAIL)

def test_empty_option(self):
self.run_but_fail_before_create_job_dir('',
exit_codes.AVOCADO_JOB_FAIL)

if __name__ == '__main__':
unittest.main()
122 changes: 122 additions & 0 deletions selftests/unit/test_job.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import argparse
import os
import sys
if sys.version_info[:2] == (2, 6):
import unittest2 as unittest
else:
import unittest

from avocado.core import test
from avocado.core import job
from avocado.core import exit_codes
from avocado.utils import path as utils_path


class JobTest(unittest.TestCase):

@staticmethod
def _find_simple_test_candidates(candidates=['true', 'time', 'uptime']):
found = []
for candidate in candidates:
try:
found.append(utils_path.find_command(candidate))
except utils_path.CmdNotFoundError:
pass
return found

def test_job_empty_suite(self):
args = argparse.Namespace()
empty_job = job.Job(args)
self.assertIsNone(empty_job.test_suite)

def test_job_empty_has_id(self):
args = argparse.Namespace()
empty_job = job.Job(args)
self.assertIsNotNone(empty_job.unique_id)

def test_job_test_suite_not_created(self):
args = argparse.Namespace()
myjob = job.Job(args)
self.assertIsNone(myjob.test_suite)

def test_job_create_test_suite_empty(self):
args = argparse.Namespace()
myjob = job.Job(args)
myjob.create_test_suite()
self.assertEqual(myjob.test_suite, [])

def test_job_create_test_suite_simple(self):
simple_tests_found = self._find_simple_test_candidates()
args = argparse.Namespace(url=simple_tests_found)
myjob = job.Job(args)
myjob.create_test_suite()
self.assertEqual(len(simple_tests_found), len(myjob.test_suite))

def test_job_pre_tests(self):
class JobFilterTime(job.Job):
def pre_tests(self):
filtered_test_suite = []
for test_factory in self.test_suite:
if test_factory[0] is test.SimpleTest:
if not test_factory[1].get('name', '').endswith('time'):
filtered_test_suite.append(test_factory)
self.test_suite = filtered_test_suite
super(JobFilterTime, self).pre_tests()
simple_tests_found = self._find_simple_test_candidates()
args = argparse.Namespace(url=simple_tests_found)
myjob = JobFilterTime(args)
myjob.create_test_suite()
myjob.pre_tests()
self.assertLessEqual(len(myjob.test_suite), 1)

def test_job_run_tests(self):
simple_tests_found = self._find_simple_test_candidates(['true'])
args = argparse.Namespace(url=simple_tests_found)
myjob = job.Job(args)
myjob.create_test_suite()
self.assertEqual(myjob.run_tests(),
exit_codes.AVOCADO_ALL_OK)

def test_job_post_tests(self):
class JobLogPost(job.Job):
def post_tests(self):
with open(os.path.join(self.logdir, "reversed_id"), "w") as f:
f.write(self.unique_id[::-1])
super(JobLogPost, self).post_tests()
simple_tests_found = self._find_simple_test_candidates()
args = argparse.Namespace(url=simple_tests_found)
myjob = JobLogPost(args)
myjob.create_test_suite()
myjob.pre_tests()
myjob.run_tests()
myjob.post_tests()
self.assertEqual(myjob.unique_id[::-1],
open(os.path.join(myjob.logdir, "reversed_id")).read())

@unittest.skip("Issue described at https://trello.com/c/qgSTIK0Y")
def test_job_run(self):
class JobFilterLog(job.Job):
def pre_tests(self):
filtered_test_suite = []
for test_factory in self.test_suite:
if test_factory[0] is test.SimpleTest:
if not test_factory[1].get('name', '').endswith('time'):
filtered_test_suite.append(test_factory)
self.test_suite = filtered_test_suite
super(JobFilterLog, self).pre_tests()

def post_tests(self):
with open(os.path.join(self.logdir, "reversed_id"), "w") as f:
f.write(self.unique_id[::-1])
super(JobFilterLog, self).post_tests()
simple_tests_found = self._find_simple_test_candidates()
args = argparse.Namespace(url=simple_tests_found)
myjob = JobFilterLog(args)
self.assertEqual(myjob.run(),
exit_codes.AVOCADO_ALL_OK)
self.assertLessEqual(len(myjob.test_suite), 1)
self.assertEqual(myjob.unique_id[::-1],
open(os.path.join(myjob.logdir, "reversed_id")).read())

if __name__ == '__main__':
unittest.main()

0 comments on commit 3c10b4d

Please sign in to comment.