Repository: kudu
Updated Branches:
  refs/heads/master c93f08393 -> f9f16fa53


http://git-wip-us.apache.org/repos/asf/kudu/blob/602b451a/build-support/build-support-test-data/ubsan-failure-out.txt
----------------------------------------------------------------------
diff --git a/build-support/build-support-test-data/ubsan-failure-out.txt 
b/build-support/build-support-test-data/ubsan-failure-out.txt
new file mode 100644
index 0000000..7c2a9c0
--- /dev/null
+++ b/build-support/build-support-test-data/ubsan-failure-out.txt
@@ -0,0 +1 @@
+Patterns/CacheBench.RunBench/0: SUMMARY: UndefinedBehaviorSanitizer: 
undefined-behavior 
/home/jenkins-slave/workspace/kudu-master/0/src/kudu/util/cache.cc:294:20 in 

http://git-wip-us.apache.org/repos/asf/kudu/blob/602b451a/build-support/build-support-test-data/ubsan-failure-out.xml
----------------------------------------------------------------------
diff --git a/build-support/build-support-test-data/ubsan-failure-out.xml 
b/build-support/build-support-test-data/ubsan-failure-out.xml
new file mode 100644
index 0000000..ab335fd
--- /dev/null
+++ b/build-support/build-support-test-data/ubsan-failure-out.xml
@@ -0,0 +1,10 @@
+<testsuites>
+  <testsuite name="Patterns/CacheBench">
+    <testcase name="RunBench/0" classname="Patterns/CacheBench">
+      <error message="SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior 
/home/jenkins-slave/workspace/kudu-master/0/src/kudu/util/cache.cc:294:20 in ">
+<![CDATA[
+SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior 
/home/jenkins-slave/workspace/kudu-master/0/src/kudu/util/cache.cc:294:20 in ]]>
+      </error>
+    </testcase>
+  </testsuite>
+</testsuites>

http://git-wip-us.apache.org/repos/asf/kudu/blob/602b451a/build-support/build-support-test-data/ubsan-failure.txt
----------------------------------------------------------------------
diff --git a/build-support/build-support-test-data/ubsan-failure.txt 
b/build-support/build-support-test-data/ubsan-failure.txt
new file mode 100644
index 0000000..303db74
--- /dev/null
+++ b/build-support/build-support-test-data/ubsan-failure.txt
@@ -0,0 +1,8 @@
+[==========] Running 4 tests from 1 test case.
+[----------] Global test environment set-up.
+[----------] 4 tests from Patterns/CacheBench
+[ RUN      ] Patterns/CacheBench.RunBench/0
+WARNING: Logging before InitGoogleLogging() is written to STDERR
+I0425 23:59:43.137094 30971 cache-bench.cc:176] Warming up...
+/home/jenkins-slave/workspace/kudu-master/0/src/kudu/util/cache.cc:294:20: 
runtime error: negation of 4096 cannot be represented in type 'size_t' (aka 
'unsigned long')
+SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior 
/home/jenkins-slave/workspace/kudu-master/0/src/kudu/util/cache.cc:294:20 in 

http://git-wip-us.apache.org/repos/asf/kudu/blob/602b451a/build-support/parse_test_failure.py
----------------------------------------------------------------------
diff --git a/build-support/parse_test_failure.py 
b/build-support/parse_test_failure.py
index c0ca766..dcb6cfc 100755
--- a/build-support/parse_test_failure.py
+++ b/build-support/parse_test_failure.py
@@ -22,8 +22,10 @@
 
 from xml.sax.saxutils import quoteattr
 import argparse
+import os
 import re
 import sys
+import unittest
 
 # Read at most 100MB of a test log.
 # Rarely would this be exceeded, but we don't want to end up
@@ -32,9 +34,10 @@ MAX_MEMORY = 100 * 1024 * 1024
 
 START_TESTCASE_RE = re.compile(r'\[ RUN\s+\] (.+)$')
 END_TESTCASE_RE = re.compile(r'\[\s+(?:OK|FAILED)\s+\] (.+)$')
-ASAN_ERROR_RE = re.compile('ERROR: AddressSanitizer')
+ASAN_ERROR_RE = re.compile('ERROR: (AddressSanitizer|LeakSanitizer)')
 TSAN_ERROR_RE = re.compile('WARNING: ThreadSanitizer.*')
 END_TSAN_ERROR_RE = re.compile('SUMMARY: ThreadSanitizer.*')
+UBSAN_ERROR_RE = re.compile(r'SUMMARY: UndefinedBehaviorSanitizer')
 FATAL_LOG_RE = re.compile(r'^F\d\d\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d\s+\d+ 
(.*)')
 LINE_RE = re.compile(r"^.*$", re.MULTILINE)
 STACKTRACE_ELEM_RE = re.compile(r'^    @')
@@ -43,126 +46,169 @@ IGNORED_STACKTRACE_ELEM_RE = re.compile(
 TEST_FAILURE_RE = re.compile(r'.*\d+: Failure$')
 GLOG_LINE_RE = re.compile(r'^[WIEF]\d\d\d\d \d\d:\d\d:\d\d')
 
-def consume_rest(line_iter):
-  """ Consume and return the rest of the lines in the iterator. """
-  return [l.group(0) for l in line_iter]
 
-def consume_until(line_iter, end_re):
+class ParsedTest(object):
   """
-  Consume and return lines from the iterator until one matches 'end_re'.
-  The line matching 'end_re' will not be returned, but will be consumed.
+  The LogParser creates one instance of this class for each test that is 
discovered
+  while parsing the log.
   """
-  ret = []
-  for l in line_iter:
-    line = l.group(0)
-    if end_re.search(line):
-      break
-    ret.append(line)
-  return ret
-
-def remove_glog_lines(lines):
-  """ Remove any lines from the list of strings which appear to be GLog 
messages. """
-  return [l for l in lines if not GLOG_LINE_RE.search(l)]
-
-def record_error(errors, name, error):
-  errors.setdefault(name, []).append(error)
-
-def extract_failures(log_text):
-  cur_test_case = None
-  tests_seen = set()
-  tests_seen_in_order = list()
-  errors_by_test = dict()
-
-  # Iterate over the lines, using finditer instead of .split()
-  # so that we don't end up doubling memory usage.
-  line_iter = LINE_RE.finditer(log_text)
-  for match in line_iter:
-    line = match.group(0)
-
-    # Track the currently-running test case
-    m = START_TESTCASE_RE.search(line)
-    if m:
-      cur_test_case = m.group(1)
-      if cur_test_case not in tests_seen:
-        tests_seen.add(cur_test_case)
-        tests_seen_in_order.append(cur_test_case)
-
-    m = END_TESTCASE_RE.search(line)
-    if m:
-      cur_test_case = None
-
-    # Look for ASAN errors.
-    m = ASAN_ERROR_RE.search(line)
-    if m:
-      error_signature = line + "\n"
-      asan_lines = remove_glog_lines(consume_rest(line_iter))
-      error_signature += "\n".join(asan_lines)
-      record_error(errors_by_test, cur_test_case, error_signature)
-
-    # Look for TSAN errors
-    m = TSAN_ERROR_RE.search(line)
-    if m:
-      error_signature = m.group(0)
-      error_signature += "\n".join(remove_glog_lines(
-          consume_until(line_iter, END_TSAN_ERROR_RE)))
-      record_error(errors_by_test, cur_test_case, error_signature)
-
-    # Look for test failures
-    # - slight micro-optimization to check for substring before running the 
regex
-    m = 'Failure' in line and TEST_FAILURE_RE.search(line)
-    if m:
-      error_signature = m.group(0) + "\n"
-      error_signature += "\n".join(remove_glog_lines(
-          consume_until(line_iter, END_TESTCASE_RE)))
-      record_error(errors_by_test, cur_test_case, error_signature)
-
-    # Look for fatal log messages (including CHECK failures)
-    # - slight micro-optimization to check for 'F' before running the regex
-    m = line and line[0] == 'F' and FATAL_LOG_RE.search(line)
-    if m:
-      error_signature = m.group(1) + "\n"
-      remaining_lines = consume_rest(line_iter)
-      remaining_lines = [l for l in remaining_lines if 
STACKTRACE_ELEM_RE.search(l)
-                         and not IGNORED_STACKTRACE_ELEM_RE.search(l)]
-      error_signature += "\n".join(remaining_lines)
-      record_error(errors_by_test, cur_test_case, error_signature)
-
-  # Sometimes we see crashes that the script doesn't know how to parse.
-  # When that happens, we leave a generic message to be picked up by Jenkins.
-  if cur_test_case and cur_test_case not in errors_by_test:
-    record_error(errors_by_test, cur_test_case, "Unrecognized error type. 
Please see the error log for more information.")
-
-  return (tests_seen_in_order, errors_by_test)
-
-# Return failure summary formatted as text.
-def text_failure_summary(tests, errors_by_test):
-  msg = ''
-  for test_name in tests:
-    if test_name not in errors_by_test:
-      continue
-    for error in errors_by_test[test_name]:
-      if msg: msg += "\n"
-      msg += "%s: %s\n" % (test_name, error)
-  return msg
+  def __init__(self, test_name):
+    self.test_name = test_name
+    self.errors = []
 
-# Parse log lines and return failure summary formatted as text.
-#
-# This helper function is part of a public API called from 
test_result_server.py
-def extract_failure_summary(log_text):
-  (tests, errors_by_test) = extract_failures(log_text)
-  return text_failure_summary(tests, errors_by_test)
 
-# Print failure summary based on desired output format.
-# 'tests' is a list of all tests run (in order), not just the failed ones.
-# This allows us to print the test results in the order they were run.
-# 'errors_by_test' is a dict of lists, keyed by test name.
-def print_failure_summary(tests, errors_by_test, is_xml):
-  # Plain text dump.
-  if not is_xml:
-    sys.stdout.write(text_failure_summary(tests, errors_by_test))
+class LogParser(object):
+  """
+  Parser for textual gtest output
+  """
+  def __init__(self):
+    self._tests = []
+    self._cur_test = None
 
-  # Fake a JUnit report file.
-  else:
+  @staticmethod
+  def _consume_rest(line_iter):
+    """ Consume and return the rest of the lines in the iterator. """
+    return [l for l in line_iter]
+
+  @staticmethod
+  def _consume_until(line_iter, end_re):
+    """
+    Consume and return lines from the iterator until one matches 'end_re'.
+    The line matching 'end_re' will not be returned, but will be consumed.
+    """
+    ret = []
+    for line in line_iter:
+      if end_re.search(line):
+        break
+      ret.append(line)
+    return ret
+
+  @staticmethod
+  def _remove_glog_lines(lines):
+    """ Remove any lines from the list of strings which appear to be GLog 
messages. """
+    return [l for l in lines if not GLOG_LINE_RE.search(l)]
+
+  def _record_error(self, error):
+    if self._cur_test is None:
+      # TODO(todd) would be nice to have a more specific name indicating which
+      # test file caused the issue.
+      self._start_test("General.OutsideOfAnyTestCase")
+      self._record_error(error)
+      self._end_test()
+    else:
+      self._cur_test.errors.append(error)
+
+  def _start_test(self, test_name):
+    assert test_name is not None
+    self._cur_test = ParsedTest(test_name)
+    self._tests.append(self._cur_test)
+
+  def _end_test(self):
+    self._cur_test = None
+
+  @staticmethod
+  def _fast_re(substr, regexp, line):
+    """
+    Implements a micro-optimization: returns true if 'line' matches 'regexp,
+    but short-circuited by a much faster check whether 'line' contains 
'substr'.
+    This provides a big speed-up since substring searches execute much more
+    quickly than regexp matches.
+    """
+    if substr not in line:
+      return None
+    return regexp.search(line)
+
+  def parse_text(self, log_text):
+    # Iterate over the lines, using finditer instead of .split()
+    # so that we don't end up doubling memory usage.
+    def line_iter():
+      for match in LINE_RE.finditer(log_text):
+        yield match.group(0)
+    self.parse_lines(line_iter())
+
+  def parse_lines(self, line_iter):
+    """
+    Arguments:
+      lines: generator which should yield lines of log output
+    """
+    for line in line_iter:
+      # Track the currently-running test case
+      m = self._fast_re('RUN', START_TESTCASE_RE, line)
+      if m:
+        self._start_test(m.group(1))
+        continue
+
+      m = self._fast_re('[', END_TESTCASE_RE, line)
+      if m:
+        self._end_test()
+        continue
+
+      # Look for ASAN errors.
+      m = self._fast_re('ERROR', ASAN_ERROR_RE, line)
+      if m:
+        error_signature = line + "\n"
+        # ASAN errors kill the process, so we consume the rest of the log
+        # and remove any lines that don't look like part of the stack trace
+        asan_lines = self._remove_glog_lines(self._consume_rest(line_iter))
+        error_signature += "\n".join(asan_lines)
+        self._record_error(error_signature)
+        continue
+
+      # Look for TSAN errors
+      m = self._fast_re('ThreadSanitizer', TSAN_ERROR_RE, line)
+      if m:
+        error_signature = m.group(0)
+        error_signature += "\n".join(self._remove_glog_lines(
+          self._consume_until(line_iter, END_TSAN_ERROR_RE)))
+        self._record_error(error_signature)
+        continue
+
+      # Look for UBSAN errors
+      m = self._fast_re('UndefinedBehavior', UBSAN_ERROR_RE, line)
+      if m:
+        # UBSAN errors are a single line.
+        # TODO: there is actually some info on the previous line but there
+        # is no obvious prefix to look for.
+        self._record_error(line)
+        continue
+
+      # Look for test failures
+      # - slight micro-optimization to check for substring before running the 
regex
+      m = self._fast_re('Failure', TEST_FAILURE_RE, line)
+      if m:
+        error_signature = m.group(0) + "\n"
+        error_signature += "\n".join(self._remove_glog_lines(
+          self._consume_until(line_iter, END_TESTCASE_RE)))
+        self._record_error(error_signature)
+        self._end_test()
+        continue
+
+      # Look for fatal log messages (including CHECK failures)
+      # - slight micro-optimization to check for 'F' before running the regex
+      m = line and line[0] == 'F' and FATAL_LOG_RE.search(line)
+      if m:
+        error_signature = m.group(1) + "\n"
+        remaining_lines = self._consume_rest(line_iter)
+        remaining_lines = [l for l in remaining_lines if 
STACKTRACE_ELEM_RE.search(l) and
+                           not IGNORED_STACKTRACE_ELEM_RE.search(l)]
+        error_signature += "\n".join(remaining_lines)
+        self._record_error(error_signature)
+
+    # Sometimes we see crashes that the script doesn't know how to parse.
+    # When that happens, we leave a generic message to be picked up by Jenkins.
+    if self._cur_test and not self._cur_test.errors:
+      self._record_error("Unrecognized error type. Please see the error log 
for more information.")
+
+  # Return failure summary formatted as text.
+  def text_failure_summary(self):
+    msgs = []
+    for test in self._tests:
+      for error in test.errors:
+        msgs.append("%s: %s\n" % (test.test_name, error))
+
+    return "\n".join(msgs)
+
+  def xml_failure_summary(self):
     # Example format:
     """
     <testsuites>
@@ -175,43 +221,92 @@ def print_failure_summary(tests, errors_by_test, is_xml):
       </testsuite>
     </testsuites>
     """
+    ret = ""
+
     cur_test_suite = None
-    print '<testsuites>'
+    ret += '<testsuites>\n'
 
     found_test_suites = False
-    for test_name in tests:
-      if test_name not in errors_by_test:
+    for test in self._tests:
+      if not test.errors:
         continue
 
-      (test_suite, test_case) = test_name.split(".")
+      (test_suite, test_case) = test.test_name.split(".")
 
       # Test suite initialization or name change.
       if test_suite and test_suite != cur_test_suite:
         if cur_test_suite:
-          print '  </testsuite>'
+          ret += '  </testsuite>\n'
         cur_test_suite = test_suite
-        print '  <testsuite name="%s">' % cur_test_suite
+        ret += '  <testsuite name="%s">\n' % cur_test_suite
         found_test_suites = True
 
       # Print each test case.
-      print '    <testcase name="%s" classname="%s">' % (test_case, 
cur_test_suite)
-      errors = "\n\n".join(errors_by_test[test_name])
+      ret += '    <testcase name="%s" classname="%s">\n' % (test_case, 
cur_test_suite)
+      errors = "\n\n".join(test.errors)
       first_line = re.sub("\n.*", '', errors)
-      print '      <error message=%s>' % quoteattr(first_line)
-      print '<![CDATA['
-      print errors
-      print ']]>'
-      print '      </error>'
-      print '    </testcase>'
+      ret += '      <error message=%s>\n' % quoteattr(first_line)
+      ret += '<![CDATA[\n'
+      ret += errors
+      ret += ']]>\n'
+      ret += '      </error>\n'
+      ret += '    </testcase>\n'
 
     if found_test_suites:
-      print '  </testsuite>'
-    print '</testsuites>'
+      ret += '  </testsuite>\n'
+    ret += '</testsuites>\n'
+    return ret
 
-def main():
 
+# Parse log lines and return failure summary formatted as text.
+#
+# Print failure summary based on desired output format.
+# 'tests' is a list of all tests run (in order), not just the failed ones.
+# This allows us to print the test results in the order they were run.
+# 'errors_by_test' is a dict of lists, keyed by test name.
+#
+# This helper function is part of a public API called from 
test_result_server.py
+def extract_failure_summary(log_text, format='text'):
+  p = LogParser()
+  p.parse_text(log_text)
+  if format == 'text':
+    return p.text_failure_summary()
+  else:
+    return p.xml_failure_summary()
+
+
+class Test(unittest.TestCase):
+  _TEST_DIR = os.path.join(os.path.dirname(__file__), 
"build-support-test-data")
+
+  def __init__(self, *args, **kwargs):
+    super(Test, self).__init__(*args, **kwargs)
+    self.regenerate = os.environ.get('REGENERATE_TEST_EXPECTATIONS') == '1'
+
+  def test_all(self):
+    for child in os.listdir(self._TEST_DIR):
+      if not child.endswith(".txt") or '-out' in child:
+        continue
+      base, _ = os.path.splitext(child)
+
+      p = LogParser()
+      p.parse_text(file(os.path.join(self._TEST_DIR, child)).read())
+      self._do_test(p.text_failure_summary(), base + "-out.txt")
+      self._do_test(p.xml_failure_summary(), base + "-out.xml")
+
+  def _do_test(self, got_value, filename):
+    path = os.path.join(self._TEST_DIR, filename)
+    if self.regenerate:
+      print("Regenerating %s" % path)
+      with file(path, "w") as f:
+        f.write(got_value)
+    else:
+      self.assertEquals(got_value, file(path).read())
+
+
+def main():
   parser = argparse.ArgumentParser()
-  parser.add_argument("-x", "--xml", help="Print output in JUnit report XML 
format (default: plain text)",
+  parser.add_argument("-x", "--xml",
+                      help="Print output in JUnit report XML format (default: 
plain text)",
                       action="store_true")
   parser.add_argument("path", nargs="?", help="File to parse. If not provided, 
parses stdin")
   args = parser.parse_args()
@@ -221,8 +316,9 @@ def main():
   else:
     in_file = sys.stdin
   log_text = in_file.read(MAX_MEMORY)
-  (tests, errors_by_test) = extract_failures(log_text)
-  print_failure_summary(tests, errors_by_test, args.xml)
+  format = args.xml and 'xml' or 'text'
+  sys.stdout.write(extract_failure_summary(log_text, format))
+
 
 if __name__ == "__main__":
   main()

http://git-wip-us.apache.org/repos/asf/kudu/blob/602b451a/build-support/release/rat_exclude_files.txt
----------------------------------------------------------------------
diff --git a/build-support/release/rat_exclude_files.txt 
b/build-support/release/rat_exclude_files.txt
index 79d7595..ec24bdb 100644
--- a/build-support/release/rat_exclude_files.txt
+++ b/build-support/release/rat_exclude_files.txt
@@ -8,6 +8,7 @@ pax_global_header
 *.bib
 *.pdf
 version.txt
+build-support/build-support-test-data/*
 build-support/iwyu/__init__.py
 build-support/iwyu/fix_includes.py
 build-support/iwyu/iwyu_tool.py

Reply via email to