aboutsummaryrefslogtreecommitdiff
path: root/pw_presubmit/py/pw_presubmit/ninja_parser.py
diff options
context:
space:
mode:
Diffstat (limited to 'pw_presubmit/py/pw_presubmit/ninja_parser.py')
-rw-r--r--pw_presubmit/py/pw_presubmit/ninja_parser.py115
1 files changed, 88 insertions, 27 deletions
diff --git a/pw_presubmit/py/pw_presubmit/ninja_parser.py b/pw_presubmit/py/pw_presubmit/ninja_parser.py
index 131137df6..487af1754 100644
--- a/pw_presubmit/py/pw_presubmit/ninja_parser.py
+++ b/pw_presubmit/py/pw_presubmit/ninja_parser.py
@@ -16,40 +16,81 @@
# https://fuchsia.googlesource.com/infra/recipes/+/336933647862a1a9718b4ca18f0a67e89c2419f8/recipe_modules/ninja/resources/ninja_wrapper.py
"""Extracts a concise error from a ninja log."""
+import argparse
+import logging
from pathlib import Path
import re
+from typing import List, IO
+import sys
-_RULE_RE = re.compile(r'^\s*\[\d+/\d+\] (\S+)')
-_FAILED_RE = re.compile(r'^\s*FAILED: (.*)$')
-_FAILED_END_RE = re.compile(r'^\s*ninja: build stopped:.*')
+_LOG: logging.Logger = logging.getLogger(__name__)
+# Assume any of these lines could be prefixed with ANSI color codes.
+_COLOR_CODES_PREFIX = r'^(?:\x1b)?(?:\[\d+m\s*)?'
-def parse_ninja_stdout(ninja_stdout: Path) -> str:
- """Extract an error summary from ninja output."""
+_GOOGLETEST_FAILED, _GOOGLETEST_RUN, _GOOGLETEST_OK, _GOOGLETEST_DISABLED = (
+ '[ FAILED ]',
+ '[ RUN ]',
+ '[ OK ]',
+ '[ DISABLED ]',
+)
- failure_begins = False
- failure_lines = []
- last_line = ''
- with ninja_stdout.open() as ins:
- for line in ins:
- # Trailing whitespace isn't significant, as it doesn't affect the
- # way the line shows up in the logs. However, leading whitespace may
- # be significant, especially for compiler error messages.
- line = line.rstrip()
- if failure_begins:
- if not _RULE_RE.match(line) and not _FAILED_END_RE.match(line):
- failure_lines.append(line)
- else:
- # Output of failed step ends, save its info.
- failure_begins = False
+def _remove_passing_tests(failure_lines: List[str]) -> List[str]:
+ test_lines: List[str] = []
+ result = []
+ for line in failure_lines:
+ if test_lines:
+ if _GOOGLETEST_OK in line:
+ test_lines = []
+ elif _GOOGLETEST_FAILED in line:
+ result.extend(test_lines)
+ test_lines = []
+ result.append(line)
else:
- failed_nodes_match = _FAILED_RE.match(line)
- failure_begins = False
- if failed_nodes_match:
- failure_begins = True
- failure_lines.extend([last_line, line])
- last_line = line
+ test_lines.append(line)
+ elif _GOOGLETEST_RUN in line:
+ test_lines.append(line)
+ elif _GOOGLETEST_DISABLED in line:
+ pass
+ else:
+ result.append(line)
+ result.extend(test_lines)
+ return result
+
+
+def _parse_ninja(ins: IO) -> str:
+ failure_lines: List[str] = []
+ last_line: str = ''
+
+ for line in ins:
+ _LOG.debug('processing %r', line)
+ # Trailing whitespace isn't significant, as it doesn't affect the
+ # way the line shows up in the logs. However, leading whitespace may
+ # be significant, especially for compiler error messages.
+ line = line.rstrip()
+
+ if failure_lines:
+ _LOG.debug('inside failure block')
+
+ if re.match(_COLOR_CODES_PREFIX + r'\[\d+/\d+\] (\S+)', line):
+ _LOG.debug('next rule started, ending failure block')
+ break
+
+ if re.match(_COLOR_CODES_PREFIX + r'ninja: build stopped:.*', line):
+ _LOG.debug('ninja build stopped, ending failure block')
+ break
+ failure_lines.append(line)
+
+ else:
+ if re.match(_COLOR_CODES_PREFIX + r'FAILED: (.*)$', line):
+ _LOG.debug('starting failure block')
+ failure_lines.extend([last_line, line])
+ elif 'FAILED' in line:
+ _LOG.debug('not a match')
+ last_line = line
+
+ failure_lines = _remove_passing_tests(failure_lines)
# Remove "Requirement already satisfied:" lines since many of those might
# be printed during Python installation, and they usually have no relevance
@@ -60,5 +101,25 @@ def parse_ninja_stdout(ninja_stdout: Path) -> str:
if not x.lstrip().startswith('Requirement already satisfied:')
]
- result = '\n'.join(failure_lines)
+ result: str = '\n'.join(failure_lines)
return re.sub(r'\n+', '\n', result)
+
+
+def parse_ninja_stdout(ninja_stdout: Path) -> str:
+ """Extract an error summary from ninja output."""
+ with ninja_stdout.open() as ins:
+ return _parse_ninja(ins)
+
+
+def main(argv=None):
+ parser = argparse.ArgumentParser()
+ parser.add_argument('input', type=argparse.FileType('r'))
+ parser.add_argument('output', type=argparse.FileType('w'))
+ args = parser.parse_args(argv)
+
+ for line in _parse_ninja(args.input):
+ args.output.write(line)
+
+
+if __name__ == '__main__':
+ sys.exit(main())