[ews-build] Parse and display layout test failures
https://bugs.webkit.org/show_bug.cgi?id=199709

Rubber-stamped by Jonathan Bedard.

* BuildSlaveSupport/ews-build/steps.py:
(RunWebKitTests.start): Initialize log_observer.
(RunWebKitTests._strip_python_logging_prefix): Copied from similar code in build.webkit.org buildbot config.
(RunWebKitTests._parseRunWebKitTestsOutput): Ditto.
(RunWebKitTests.commandComplete): Gather and parse the stdout and stderr logs.
(RunWebKitTests.evaluateResult): Analyze the results and decide build status.
(RunWebKitTests.getResultSummary): Update build and step summary.
* BuildSlaveSupport/ews-build/steps_unittest.py: Added and updated unit-tests.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@247433 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Tools/BuildSlaveSupport/ews-build/steps.py b/Tools/BuildSlaveSupport/ews-build/steps.py
index d16b14a..5fb86d1 100644
--- a/Tools/BuildSlaveSupport/ews-build/steps.py
+++ b/Tools/BuildSlaveSupport/ews-build/steps.py
@@ -844,6 +844,10 @@
                WithProperties('--%(configuration)s')]
 
     def start(self):
+        self.log_observer = logobserver.BufferLogObserver(wantStderr=True)
+        self.addLogObserver('stdio', self.log_observer)
+
+        self.incorrectLayoutLines = []
         platform = self.getProperty('platform')
         appendCustomBuildFlags(self, platform, self.getProperty('fullPlatform'))
         additionalArguments = self.getProperty('additionalArguments')
@@ -855,9 +859,76 @@
             self.setCommand(self.command + additionalArguments)
         return shell.Test.start(self)
 
+    # FIXME: This will break if run-webkit-tests changes its default log formatter.
+    nrwt_log_message_regexp = re.compile(r'\d{2}:\d{2}:\d{2}(\.\d+)?\s+\d+\s+(?P<message>.*)')
+
+    def _strip_python_logging_prefix(self, line):
+        match_object = self.nrwt_log_message_regexp.match(line)
+        if match_object:
+            return match_object.group('message')
+        return line
+
+    def _parseRunWebKitTestsOutput(self, logText):
+        incorrectLayoutLines = []
+        expressions = [
+            ('flakes', re.compile(r'Unexpected flakiness.+\((\d+)\)')),
+            ('new passes', re.compile(r'Expected to .+, but passed:\s+\((\d+)\)')),
+            ('missing results', re.compile(r'Regressions: Unexpected missing results\s+\((\d+)\)')),
+            ('failures', re.compile(r'Regressions: Unexpected.+\((\d+)\)')),
+        ]
+        testFailures = {}
+
+        for line in logText.splitlines():
+            if line.find('Exiting early') >= 0 or line.find('leaks found') >= 0:
+                incorrectLayoutLines.append(self._strip_python_logging_prefix(line))
+                continue
+            for name, expression in expressions:
+                match = expression.search(line)
+
+                if match:
+                    testFailures[name] = testFailures.get(name, 0) + int(match.group(1))
+                    break
+
+                # FIXME: Parse file names and put them in results
+
+        for name in testFailures:
+            incorrectLayoutLines.append(str(testFailures[name]) + ' ' + name)
+
+        self.incorrectLayoutLines = incorrectLayoutLines
+
+    def commandComplete(self, cmd):
+        shell.Test.commandComplete(self, cmd)
+        logText = self.log_observer.getStdout() + self.log_observer.getStderr()
+        self._parseRunWebKitTestsOutput(logText)
+
+    def evaluateResult(self, cmd):
+        result = SUCCESS
+
+        if self.incorrectLayoutLines:
+            if len(self.incorrectLayoutLines) == 1:
+                line = self.incorrectLayoutLines[0]
+                if line.find('were new') >= 0 or line.find('was new') >= 0 or line.find(' leak') >= 0:
+                    return WARNINGS
+
+            for line in self.incorrectLayoutLines:
+                if line.find('flakes') >= 0 or line.find('new passes') >= 0 or line.find('missing results') >= 0:
+                    result = WARNINGS
+                else:
+                    return FAILURE
+
+        # Return code from Tools/Scripts/layout_tests/run_webkit_tests.py.
+        # This means that an exception was raised when running run-webkit-tests and
+        # was never handled.
+        if cmd.rc == 254:
+            return RETRY
+        if cmd.rc != 0:
+            return FAILURE
+
+        return result
+
     def evaluateCommand(self, cmd):
-        rc = super(RunWebKitTests, self).evaluateCommand(cmd)
-        if rc == SUCCESS:
+        rc = self.evaluateResult(cmd)
+        if rc == SUCCESS or rc == WARNINGS:
             message = 'Passed layout tests'
             self.descriptionDone = message
             self.build.results = SUCCESS
@@ -866,6 +937,15 @@
             self.build.addStepsAfterCurrentStep([ArchiveTestResults(), UploadTestResults(), ExtractTestResults(), ReRunWebKitTests()])
         return rc
 
+    def getResultSummary(self):
+        status = self.name
+
+        if self.results != SUCCESS and self.incorrectLayoutLines:
+            status = u' '.join(self.incorrectLayoutLines)
+            return {u'step': status}
+
+        return super(RunWebKitTests, self).getResultSummary()
+
 
 class ReRunWebKitTests(RunWebKitTests):
     name = 're-run-layout-tests'
diff --git a/Tools/BuildSlaveSupport/ews-build/steps_unittest.py b/Tools/BuildSlaveSupport/ews-build/steps_unittest.py
index aa77223..e9f6d89 100644
--- a/Tools/BuildSlaveSupport/ews-build/steps_unittest.py
+++ b/Tools/BuildSlaveSupport/ews-build/steps_unittest.py
@@ -975,6 +975,39 @@
         self.expectOutcome(result=SUCCESS, state_string='Passed layout tests')
         return self.runStep()
 
+    def test_warnings(self):
+        self.setupStep(RunWebKitTests())
+        self.setProperty('fullPlatform', 'ios-simulator')
+        self.setProperty('configuration', 'release')
+        self.expectRemoteCommands(
+            ExpectShell(workdir='wkdir',
+                        logfiles={'json': self.jsonFileName},
+                        command=['python', 'Tools/Scripts/run-webkit-tests', '--no-build', '--no-new-test-results', '--no-show-results', '--exit-after-n-failures', '30', '--skip-failing-tests', '--release', '--results-directory', 'layout-test-results', '--debug-rwt-logging'],
+                        )
+            + 0
+            + ExpectShell.log('stdio', stdout='''Unexpected flakiness: timeouts (2)
+                              imported/blink/storage/indexeddb/blob-valid-before-commit.html [ Timeout Pass ]
+                              storage/indexeddb/modern/deleteindex-2.html [ Timeout Pass ]'''),
+        )
+        self.expectOutcome(result=WARNINGS, state_string='2 flakes')
+        return self.runStep()
+
+    def test_unexpected_error(self):
+        self.setupStep(RunWebKitTests())
+        self.setProperty('fullPlatform', 'mac-highsierra')
+        self.setProperty('configuration', 'debug')
+        self.expectRemoteCommands(
+            ExpectShell(workdir='wkdir',
+                        logfiles={'json': self.jsonFileName},
+                        command=['python', 'Tools/Scripts/run-webkit-tests', '--no-build', '--no-new-test-results', '--no-show-results', '--exit-after-n-failures', '30', '--skip-failing-tests', '--debug', '--results-directory', 'layout-test-results', '--debug-rwt-logging'],
+                        )
+            + ExpectShell.log('stdio', stdout='Unexpected error.')
+            + 254,
+        )
+        self.expectOutcome(result=RETRY, state_string='layout-tests (retry)')
+        return self.runStep()
+
+
     def test_failure(self):
         self.setupStep(RunWebKitTests())
         self.setProperty('fullPlatform', 'ios-simulator')
diff --git a/Tools/ChangeLog b/Tools/ChangeLog
index 728c0fa..b9767cf 100644
--- a/Tools/ChangeLog
+++ b/Tools/ChangeLog
@@ -1,3 +1,19 @@
+2019-07-15  Aakash Jain  <aakash_jain@apple.com>
+
+        [ews-build] Parse and display layout test failures
+        https://bugs.webkit.org/show_bug.cgi?id=199709
+
+        Rubber-stamped by Jonathan Bedard.
+
+        * BuildSlaveSupport/ews-build/steps.py:
+        (RunWebKitTests.start): Initialize log_observer.
+        (RunWebKitTests._strip_python_logging_prefix): Copied from similar code in build.webkit.org buildbot config.
+        (RunWebKitTests._parseRunWebKitTestsOutput): Ditto.
+        (RunWebKitTests.commandComplete): Gather and parse the stdout and stderr logs.
+        (RunWebKitTests.evaluateResult): Analyze the results and decide build status.
+        (RunWebKitTests.getResultSummary): Update build and step summary.
+        * BuildSlaveSupport/ews-build/steps_unittest.py: Added and updated unit-tests.
+
 2019-07-15  Commit Queue  <commit-queue@webkit.org>
 
         Unreviewed, rolling out r247393.