diff --git a/topfails/log_parser.py b/topfails/log_parser.py index a86c0cc..13857c8 100644 --- a/topfails/log_parser.py +++ b/topfails/log_parser.py @@ -36,17 +36,58 @@ # # ***** END LICENSE BLOCK ***** +# TODO: document what all of the cases are, e.g +# +# - harness completes and automation.py hangs: +# "'62521 INFO TEST-PASS | /tests/content/xbl/test/test_bug542406.xhtml | Field three readonly?\n', +# '62523 INFO Passed: 60569\n', +# '62524 INFO Failed: 44\n', +# '62525 INFO Todo: 770\n', +# '62526 INFO SimpleTest FINISHED\n', +# 'TEST-UNEXPECTED-FAIL | automation.py | application timed out after 330 seconds with no output\n', +# "Can't trigger Breakpad, just killing process\n", +# 'INFO | automation.py | Application ran for: 0:24:44.270038\n', +# 'INFO | automation.py | Reading PID log: /var/folders/H5/H5TD8hgwEqKq9hgKlayjWU+++TM/-Tmp-/tmpEjNEf2pidlog\n', +# "WARNING | automationutils.processLeakLog() | refcount logging is off, so leaks can't be detected!\n", +# '\n', +# 'INFO | runtests.py | Running tests: end.\n', +# 'program finished with exit code 247\n', +# 'elapsedTime=1496.639870\n'," +# +# - leak at harness closure: see sample-logs/leaks-log.txt + import re class LogParser(object): """abstract base class for parsing unittest logs""" # 'TestFailed' expected log format is "result | test | optional text". - testfailedRe = re.compile(r"(TEST-UNEXPECTED-.*) \| (.*) \|(.*)") - + testfailedRe = re.compile(r"(TEST-UNEXPECTED-.*|PROCESS-CRASH) \| (.*) \|(.*)") + def get_potentialTestName(self, line): """return potential test name [None by default]""" return None + + def processTestName(self, test, reason, potentialTestName): + """substitute the potential name for the test (if applicable)""" + + # for process crash, take the test-runner (automation) as the test failure + # (as already reported in test) and reset the potentialTestName to None + if 'PROCESS-CRASH' in reason: + return test + + # an automation.py failure will ALWAYS be followed by a + # automationutils.processLeakLog line; so send a None here + # which will cause the parsing to continue and don't record this failure + if 'automation.py' in test: + return None + + if 'automationutils.processLeakLog' and (potentialTestName is not None): + return potentialTestName + + # if these conditions are not met, return + # the test name and potentialTestName untouched + return test # no name substitution def parse(self, fp): """ @@ -58,7 +99,10 @@ class LogParser(object): failures = [] lines = fp.readlines() potentialTestName = None - for idx, line in enumerate(lines): + + idx = 0 + while idx < len(lines): + line = lines[idx] # get the potential real name for reporting # a test for an automation.py or automationutils.processLeakLog failure @@ -69,16 +113,29 @@ class LogParser(object): if not m: continue + # reason for failure [TEST-UNEXPECTED-.* or PROCESS-CRASH] + reason = m.group(1).rstrip() + # name of the test test = m.group(2).strip() or "[unittest-log.py: no logged test]" - # substitute potentialTestName for the test name if - # test is automation.py or automationutils.processLeakLog - if 'automation.py' in test or 'automationutils.processLeakLog' in test: - if potentialTestName is not None: - test = potentialTestName - potentialTestName = None + # fail log text + text = m.group(3).strip() or "[unittest-log.py: no logged text]" + # test to see if the harness hangs after a run completion + if lines[idx-1].strip().endswith('FINISHED'): + text = 'harness hangs after end of test run (or something)' + else: + # substitute potentialTestName for the test name if + # test is automation.py or automationutils.processLeakLog + test = self.processPotentialTestName(test, reason, potentialTestName) + + if test is None: # don't add this test + continue + + # reset potentialTestName + potentialTestName = None + # Code bits below try to change back slash to forward slash # and get rid of varibale prepends to the /test/../.. names if test.find('\\') != -1: @@ -90,12 +147,12 @@ class LogParser(object): else : test=tup[0] - # fail log text - text = m.group(3).strip() or "[unittest-log.py: no logged text]" - # append interesting data to failures return value - failures.append({'test': test, 'text': text, 'reason': m.group(1).rstrip()}) - + failures.append({'test': test, 'text': text, 'reason': reason}) + + # increment the line counter + idx += 1 + return failures class ReftestParser(LogParser): @@ -104,11 +161,6 @@ class ReftestParser(LogParser): - Reftest - Crashtest - JSReftest - - TODO: - - look for PROCESS-CRASH as well as UNEXPECTED-FAIL - [PROCESS-CRASH is a harness crash] - - need an actual log file with TEST-UNEXPECTED-FAIL with automation.py """ def get_potentialTestName(self, line): @@ -127,23 +179,6 @@ class MochitestParser(LogParser): - Mochitest-chrome - Mochitest-browserchrome - Mochitest-a11y - - TODO: unhandled cases: - - harness completes and automation.py hangs: - "'62521 INFO TEST-PASS | /tests/content/xbl/test/test_bug542406.xhtml | Field three readonly?\n', - '62523 INFO Passed: 60569\n', - '62524 INFO Failed: 44\n', - '62525 INFO Todo: 770\n', - '62526 INFO SimpleTest FINISHED\n', - 'TEST-UNEXPECTED-FAIL | automation.py | application timed out after 330 seconds with no output\n', - "Can't trigger Breakpad, just killing process\n", - 'INFO | automation.py | Application ran for: 0:24:44.270038\n', - 'INFO | automation.py | Reading PID log: /var/folders/H5/H5TD8hgwEqKq9hgKlayjWU+++TM/-Tmp-/tmpEjNEf2pidlog\n', - "WARNING | automationutils.processLeakLog() | refcount logging is off, so leaks can't be detected!\n", - '\n', - 'INFO | runtests.py | Running tests: end.\n', - 'program finished with exit code 247\n', - 'elapsedTime=1496.639870\n'," """ def get_potentialTestName(self, line):