[zorg] r174138 - LitTestCommand: Fix some log parsing bugs.

David Blaikie dblaikie at gmail.com
Fri Feb 1 08:36:41 PST 2013


On Thu, Jan 31, 2013 at 5:39 PM, Daniel Dunbar <daniel at zuster.org> wrote:
> Author: ddunbar
> Date: Thu Jan 31 19:39:53 2013
> New Revision: 174138
>
> URL: http://llvm.org/viewvc/llvm-project?rev=174138&view=rev
> Log:
> LitTestCommand: Fix some log parsing bugs.
>
>  - The last test failure would not get recorded properly.
>
>  - The log names were wrong sometimes because of a variable shadowing bug.
>
>  - Also, always parse verbose log info even if it isn't associated with a
>    failing code.

Where/when/why does this happen? & what does your change cause us to
do with such logs? Log them or just parse & discard them? (the latter
seems to lack fidelity with the original log - seems we should
probably log them in the same way)

>
> Modified:
>     zorg/trunk/zorg/buildbot/commands/LitTestCommand.py
>
> Modified: zorg/trunk/zorg/buildbot/commands/LitTestCommand.py
> URL: http://llvm.org/viewvc/llvm-project/zorg/trunk/zorg/buildbot/commands/LitTestCommand.py?rev=174138&r1=174137&r2=174138&view=diff
> ==============================================================================
> --- zorg/trunk/zorg/buildbot/commands/LitTestCommand.py (original)
> +++ zorg/trunk/zorg/buildbot/commands/LitTestCommand.py Thu Jan 31 19:39:53 2013
> @@ -8,54 +8,93 @@ from buildbot.process.buildstep import L
>  from buildbot.steps.shell import Test
>
>  class LitLogObserver(LogLineObserver):
> -  kTestLineRE = re.compile(r'([^ ]*): (.*) \(.*.*\)')
> -  kTestFailureLogStartRE = re.compile(r"""\*{4,80} TEST '(.*)' .*""")
> -  kTestFailureLogStopRE = re.compile(r"""\*{10,80}""")
> +  # Regular expressions for a regular test line.
> +  kTestLineRE = re.compile(r'([^ ]*): (.*) \(.*\)')
> +
> +  # Regular expressions for verbose log start and stop markers. Verbose log
> +  # output always immediately follow a test.
> +  kTestVerboseLogStartRE = re.compile(r"""\*{4,80} TEST '(.*)' .*""")
> +  kTestVerboseLogStopRE = re.compile(r"""\*{10,80}""")
> +
> +  # These are the codes for which we will include the log output in the buildbot
> +  # step results.
>    failingCodes = set(['FAIL', 'XPASS', 'KPASS', 'UNRESOLVED'])
> +
>    def __init__(self):
>      LogLineObserver.__init__(self)
>      self.resultCounts = {}
> -    self.inFailure = None
> -    self.inFailureContext = False
> +
> +    # If non-null, a tuple of the last test code and name.
> +    self.lastTestResult = None
> +
> +    # If non-null, a list of lines in the current log.
> +    self.activeVerboseLog = None

& if we did, this variable would presumably get rolled into the
previous one, since they'd have the same kind of lifetime

>
>    def hadFailure(self):
>      for code in self.failingCodes:
>        if self.resultCounts.get(code):
>          return True
>
> +  def handleVerboseLogLine(self, line):
> +    # If this isn't a stop marker, just append it and continue.
> +    if not self.kTestVerboseLogStopRE.match(line):
> +      self.activeVerboseLog.append(line)
> +      return
> +
> +    # Otherwise, the log and test info are finished.
> +    self.testInfoFinished()
> +
> +  def testInfoFinished(self):
> +    # We have finished getting information for one test, handle it.
> +    code, name = self.lastTestResult
> +
> +    # If the test failed, add a log entry for it.
> +    if code in self.failingCodes:
> +      # If a verbose log was not provided, just add a one line description.
> +      if self.activeVerboseLog is None:
> +        self.activeVerboseLog = ['%s: %s' % (code, name)]

Why not just always include the status line at the start? - I thought
that's what I had this setup to do once you decided to support
context-less failures.

> +
> +      # Add the log to the build status.
> +      self.step.addCompleteLog(name.replace('/', '__'),
> +                               '\n'.join(self.activeVerboseLog))
> +
> +    # Reset the current state.
> +    self.lastTestResult = None
> +    self.activeVerboseLog = None
> +
>    def outLineReceived(self, line):
> -    # See if we are inside a failure log.
> -    if self.inFailureContext:
> -      self.inFailure[1].append(line)
> -      if self.kTestFailureLogStopRE.match(line):
> -        self.inFailureContext = False
> +    # If we are inside a verbose log, just accumulate lines until we reach the
> +    # stop marker.
> +    if self.activeVerboseLog is not None:
> +      self.handleVerboseLogLine(line)
>        return
>
> -    line = line.strip()
> -    if not line:
> +    # Check for the test verbose log start marker.
> +    m = self.kTestVerboseLogStartRE.match(line.strip())
> +    if m:
> +      self.activeVerboseLog = []
> +      if m.group(1) != self.lastTestResult[1]:
> +        # This is bogus, the verbose log test name doesn't match what we
> +        # expect. Just note it in the log but otherwise accumulate as normal.
> +        self.activeVerboseLog.append(
> +          "error: verbose log output name didn't match expected test name")
>        return
>
> -    # Check for test failure logs.
> -    m = self.kTestFailureLogStartRE.match(line)
> -    if m and self.inFailure:
> -      if m.group(1)==self.inFailure[0]:
> -          self.inFailure[1].append(line)
> -          self.inFailureContext = True
> -          return
> -      else:
> -          msg = 'm[0]: %r\ninFailure[0]: %r' % (m.group(0), self.inFailure[0])
> -          raise ValueError, msg
> -    # Otherwise expect a test status line.
> -    m = self.kTestLineRE.match(line)
> +    # Otherwise, if we had any previous test consider it finished.
> +    #
> +    # FIXME: This assumes that we will always have at least one line following
> +    # the last test result to properly record each test; we could fix this if
> +    # buildbot provided us a hook for when the log is done.
> +    if self.lastTestResult:
> +      self.testInfoFinished()
> +
> +    # Check for a new test status line.
> +    m = self.kTestLineRE.match(line.strip())
>      if m:
> -      code, name = m.groups()
> -      if self.inFailure and name != self.inFailure[0]:
> -        name,log = self.inFailure
> -        self.step.addCompleteLog(name.replace('/', '__'), '\n'.join(log))
> -        self.inFailure = None
> -      if code in self.failingCodes:
> -        self.inFailure = (name, [line])
> +      # Remember the last test result and update the result counts.
> +      self.lastTestResult = (code, name) = m.groups()
>        self.resultCounts[code] = self.resultCounts.get(code, 0) + 1
> +      return
>
>  class LitTestCommand(Test):
>    resultNames = {'FAIL':'unexpected failures',
> @@ -124,7 +163,23 @@ PASS: test-three (3 of 3)
>  """)
>
>      self.assertEqual(obs.resultCounts, { 'FAIL' : 1, 'PASS' : 2 })
> -    self.assertEqual(obs.step.logs, [('test-two', 'FAIL: test-two (2 of 3)')])
> +    self.assertEqual(obs.step.logs, [('test-two', 'FAIL: test-two')])
> +
> +  def test_verbose_logs(self):
> +    obs = self.parse_log("""
> +FAIL: test-one (1 of 3)
> +FAIL: test-two (2 of 3)
> +**** TEST 'test-two' ****
> +bla bla bla
> +**********
> +FAIL: test-three (3 of 3)
> +""")
> +
> +    self.assertEqual(obs.resultCounts, { 'FAIL' : 3 })
> +    self.assertEqual(obs.step.logs, [
> +        ('test-one', 'FAIL: test-one'),
> +        ('test-two', 'bla bla bla'),
> +        ('test-three', 'FAIL: test-three')])
>
>  class TestCommand(unittest.TestCase):
>    def parse_log(self, text):

All in all these changes seem to have made this code much more
complicated for cases I don't really understand & don't think I've
ever seen come from lit. It might be best to keep this simple & break
out a separate command for lit-like things that have those
complications (LNT? what else?) or fix those things to be more like
lit.



More information about the llvm-commits mailing list