Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(138)

Unified Diff: client/common_lib/base_job.py

Issue 6246035: Merge remote branch 'cros/upstream' into master (Closed) Base URL: ssh://git@gitrw.chromium.org:9222/autotest.git@master
Patch Set: patch Created 9 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: client/common_lib/base_job.py
diff --git a/client/common_lib/base_job.py b/client/common_lib/base_job.py
index 3c77d386680a161a886e925428756761aba74939..c5f55f8d42f3816c8a8114e844a8c10a4f3d0b0f 100644
--- a/client/common_lib/base_job.py
+++ b/client/common_lib/base_job.py
@@ -1,6 +1,6 @@
import os, copy, logging, errno, fcntl, time, re, weakref, traceback
+import tarfile
import cPickle as pickle
-
from autotest_lib.client.common_lib import autotemp, error, log
@@ -422,6 +422,9 @@ class status_log_entry(object):
TIMESTAMP_FIELD = 'timestamp'
LOCALTIME_FIELD = 'localtime'
+ # non-space whitespace is forbidden in any fields
+ BAD_CHAR_REGEX = re.compile(r'[\t\n\r\v\f]')
+
def __init__(self, status_code, subdir, operation, message, fields,
timestamp=None):
"""Construct a status.log entry.
@@ -439,18 +442,16 @@ class status_log_entry(object):
@raise ValueError: if any of the parameters are invalid
"""
- # non-space whitespace is forbidden in any fields
- bad_char_regex = r'[\t\n\r\v\f]'
if not log.is_valid_status(status_code):
raise ValueError('status code %r is not valid' % status_code)
self.status_code = status_code
- if subdir and re.search(bad_char_regex, subdir):
+ if subdir and self.BAD_CHAR_REGEX.search(subdir):
raise ValueError('Invalid character in subdir string')
self.subdir = subdir
- if operation and re.search(bad_char_regex, operation):
+ if operation and self.BAD_CHAR_REGEX.search(operation):
raise ValueError('Invalid character in operation string')
self.operation = operation
@@ -460,7 +461,7 @@ class status_log_entry(object):
message_lines = message.split('\n')
self.message = message_lines[0].replace('\t', ' ' * 8)
self.extra_message_lines = message_lines[1:]
- if re.search(bad_char_regex, self.message):
+ if self.BAD_CHAR_REGEX.search(self.message):
raise ValueError('Invalid character in message %r' % self.message)
if not fields:
@@ -468,7 +469,7 @@ class status_log_entry(object):
else:
self.fields = fields.copy()
for key, value in self.fields.iteritems():
- if re.search(bad_char_regex, key + value):
+ if self.BAD_CHAR_REGEX.search(key + value):
raise ValueError('Invalid character in %r=%r field'
% (key, value))
@@ -574,7 +575,8 @@ class status_logger(object):
@property subdir_filename: The filename to write subdir-level logs to.
"""
def __init__(self, job, indenter, global_filename='status',
- subdir_filename='status', record_hook=None):
+ subdir_filename='status', record_hook=None,
+ tap_writer=None):
"""Construct a logger instance.
@param job: A reference to the job object this is logging for. Only a
@@ -589,12 +591,18 @@ class status_logger(object):
@param record_hook: An optional function to be called before an entry
is logged. The function should expect a single parameter, a
copy of the status_log_entry object.
+ @param tap_writer: An instance of the class TAPReport for addionally
+ writing TAP files
"""
self._jobref = weakref.ref(job)
self._indenter = indenter
self.global_filename = global_filename
self.subdir_filename = subdir_filename
self._record_hook = record_hook
+ if tap_writer is None:
+ self._tap_writer = TAPReport(None)
+ else:
+ self._tap_writer = tap_writer
def render_entry(self, log_entry):
@@ -647,6 +655,10 @@ class status_logger(object):
finally:
fileobj.close()
+ # write to TAPRecord instance
+ if log_entry.is_end() and self._tap_writer.do_tap_report:
+ self._tap_writer.record(log_entry, self._indenter.indent, log_files)
+
# adjust the indentation if this was a START or END entry
if log_entry.is_start():
self._indenter.increment()
@@ -654,6 +666,191 @@ class status_logger(object):
self._indenter.decrement()
+class TAPReport(object):
+ """
+ Deal with TAP reporting for the Autotest client.
+ """
+
+ job_statuses = {
+ "TEST_NA": False,
+ "ABORT": False,
+ "ERROR": False,
+ "FAIL": False,
+ "WARN": False,
+ "GOOD": True,
+ "START": True,
+ "END GOOD": True,
+ "ALERT": False,
+ "RUNNING": False,
+ "NOSTATUS": False
+ }
+
+
+ def __init__(self, enable, resultdir=None, global_filename='status'):
+ """
+ @param enable: Set self.do_tap_report to trigger TAP reporting.
+ @param resultdir: Path where the TAP report files will be written.
+ @param global_filename: File name of the status files .tap extensions
+ will be appended.
+ """
+ self.do_tap_report = enable
+ if resultdir is not None:
+ self.resultdir = os.path.abspath(resultdir)
+ self._reports_container = {}
+ self._keyval_container = {} # {'path1': [entries],}
+ self.global_filename = global_filename
+
+
+ @classmethod
+ def tap_ok(self, success, counter, message):
+ """
+ return a TAP message string.
+
+ @param success: True for positive message string.
+ @param counter: number of TAP line in plan.
+ @param message: additional message to report in TAP line.
+ """
+ if success:
+ message = "ok %s - %s" % (counter, message)
+ else:
+ message = "not ok %s - %s" % (counter, message)
+ return message
+
+
+ def record(self, log_entry, indent, log_files):
+ """
+ Append a job-level status event to self._reports_container. All
+ events will be written to TAP log files at the end of the test run.
+ Otherwise, it's impossilble to determine the TAP plan.
+
+ @param log_entry: A string status code describing the type of status
+ entry being recorded. It must pass log.is_valid_status to be
+ considered valid.
+ @param indent: Level of the log_entry to determine the operation if
+ log_entry.operation is not given.
+ @param log_files: List of full path of files the TAP report will be
+ written to at the end of the test.
+ """
+ for log_file in log_files:
+ log_file_path = os.path.dirname(log_file)
+ key = log_file_path.split(self.resultdir, 1)[1].strip(os.sep)
+ if not key:
+ key = 'root'
+
+ if not self._reports_container.has_key(key):
+ self._reports_container[key] = []
+
+ if log_entry.operation:
+ operation = log_entry.operation
+ elif indent == 1:
+ operation = "job"
+ else:
+ operation = "unknown"
+ entry = self.tap_ok(
+ self.job_statuses.get(log_entry.status_code, False),
+ len(self._reports_container[key]) + 1, operation + "\n"
+ )
+ self._reports_container[key].append(entry)
+
+
+ def record_keyval(self, path, dictionary, type_tag=None):
+ """
+ Append a key-value pairs of dictionary to self._keyval_container in
+ TAP format. Once finished write out the keyval.tap file to the file
+ system.
+
+ If type_tag is None, then the key must be composed of alphanumeric
+ characters (or dashes + underscores). However, if type-tag is not
+ null then the keys must also have "{type_tag}" as a suffix. At
+ the moment the only valid values of type_tag are "attr" and "perf".
+
+ @param path: The full path of the keyval.tap file to be created
+ @param dictionary: The keys and values.
+ @param type_tag: The type of the values
+ """
+ self._keyval_container.setdefault(path, [0, []])
+ self._keyval_container[path][0] += 1
+
+ if type_tag is None:
+ key_regex = re.compile(r'^[-\.\w]+$')
+ else:
+ if type_tag not in ('attr', 'perf'):
+ raise ValueError('Invalid type tag: %s' % type_tag)
+ escaped_tag = re.escape(type_tag)
+ key_regex = re.compile(r'^[-\.\w]+\{%s\}$' % escaped_tag)
+ self._keyval_container[path][1].extend([
+ self.tap_ok(True, self._keyval_container[path][0], "results"),
+ "\n ---\n",
+ ])
+ try:
+ for key in sorted(dictionary.keys()):
+ if not key_regex.search(key):
+ raise ValueError('Invalid key: %s' % key)
+ self._keyval_container[path][1].append(
+ ' %s: %s\n' % (key.replace('{', '_').rstrip('}'),
+ dictionary[key])
+ )
+ finally:
+ self._keyval_container[path][1].append(" ...\n")
+ self._write_keyval()
+
+
+ def _write_reports(self):
+ """
+ Write TAP reports to file.
+ """
+ for key in self._reports_container.keys():
+ if key == 'root':
+ sub_dir = ''
+ else:
+ sub_dir = key
+ tap_fh = open(os.sep.join(
+ [self.resultdir, sub_dir, self.global_filename]
+ ) + ".tap", 'w')
+ tap_fh.write('1..' + str(len(self._reports_container[key])) + '\n')
+ tap_fh.writelines(self._reports_container[key])
+ tap_fh.close()
+
+
+ def _write_keyval(self):
+ """
+ Write the self._keyval_container key values to a file.
+ """
+ for path in self._keyval_container.keys():
+ tap_fh = open(path + ".tap", 'w')
+ tap_fh.write('1..' + str(self._keyval_container[path][0]) + '\n')
+ tap_fh.writelines(self._keyval_container[path][1])
+ tap_fh.close()
+
+
+ def write(self):
+ """
+ Write the TAP reports to files.
+ """
+ self._write_reports()
+
+
+ def _write_tap_archive(self):
+ """
+ Write a tar archive containing all the TAP files and
+ a meta.yml containing the file names.
+ """
+ os.chdir(self.resultdir)
+ tap_files = []
+ for rel_path, d, files in os.walk('.'):
+ tap_files.extend(["/".join(
+ [rel_path, f]) for f in files if f.endswith('.tap')])
+ meta_yaml = open('meta.yml', 'w')
+ meta_yaml.write('file_order:\n')
+ tap_tar = tarfile.open(self.resultdir + '/tap.tar.gz', 'w:gz')
+ for f in tap_files:
+ meta_yaml.write(" - " + f.lstrip('./') + "\n")
+ tap_tar.add(f)
+ meta_yaml.close()
+ tap_tar.add('meta.yml')
+ tap_tar.close()
+
+
class base_job(object):
"""An abstract base class for the various autotest job classes.
@@ -799,6 +996,11 @@ class base_job(object):
# initialize all the job state
self._state = self._job_state()
+ # initialize tap reporting
+ if dargs.has_key('options'):
+ self._tap = self._tap_init(dargs['options'].tap_report)
+ else:
+ self._tap = self._tap_init(False)
@classmethod
def _find_base_directories(cls):
@@ -959,6 +1161,11 @@ class base_job(object):
subdir, e)
raise error.TestError('%s directory creation failed' % subdir)
+ def _tap_init(self, enable):
+ """Initialize TAP reporting
+ """
+ return TAPReport(enable, resultdir=self.resultdir)
+
def record(self, status_code, subdir, operation, status='',
optional_fields=None):

Powered by Google App Engine
This is Rietveld 408576698