def testAddKeyWord(self):
     signal = FailureSignal()
     signal.AddKeyword(' ')
     signal.AddKeyword('a')
     signal.AddKeyword('b')
     signal.AddKeyword('a')
     self.assertEqual({'a': 2, 'b': 1}, signal.keywords)
Пример #2
0
 def testAddEdge(self):
     signal = FailureSignal()
     signal.AddEdge({
         'rule': "CXX",
         'output_nodes': ['b.o'],
         'dependencies': ['b.h']
     })
     signal.AddEdge({
         'rule': "CXX",
         'output_nodes': ['a.o', 'aa.o'],
         'dependencies': ['a.h', 'a.c']
     })
     signal.AddEdge({
         'rule': "CXX",
         'output_nodes': ['a.o', 'aa.o'],
         'dependencies': ['a.h', 'a.c']
     })
     self.assertEqual([{
         "rule": 'CXX',
         "output_nodes": ['b.o'],
         'dependencies': ['b.h']
     }, {
         'rule': 'CXX',
         'output_nodes': ['a.o', 'aa.o'],
         'dependencies': ['a.h', 'a.c']
     }], signal.failed_edges)
    def testCheckNinjaDependencies(self):
        failed_edges = [{
            'dependencies': [
                'src/a/b/f1.cc', 'd/e/a2_test.cc', 'b/c/f2.cc', 'd/e/f3.h',
                'x/y/f4.py', 'f5_impl.cc'
            ]
        }]

        failure_signal = FailureSignal()
        failure_signal.failed_edges = failed_edges
        change_log_json = {
            'revision':
            '12',
            'touched_files': [
                {
                    'change_type': ChangeType.ADD,
                    'old_path': '/dev/null',
                    'new_path': 'a/b/f1.cc'
                },
                {
                    'change_type': ChangeType.ADD,
                    'old_path': '/dev/null',
                    'new_path': 'd/e/a2.cc'
                },
                {
                    'change_type': ChangeType.MODIFY,
                    'old_path': 'a/b/c/f2.h',
                    'new_path': 'a/b/c/f2.h'
                },
                {
                    'change_type': ChangeType.MODIFY,
                    'old_path': 'd/e/f3.h',
                    'new_path': 'd/e/f3.h'
                },
                {
                    'change_type': ChangeType.DELETE,
                    'old_path': 'x/y/f4.py',
                    'new_path': '/dev/null'
                },
                {
                    'change_type': ChangeType.DELETE,
                    'old_path': 'h/f5.h',
                    'new_path': '/dev/null'
                },
                {
                    'change_type': ChangeType.RENAME,
                    'old_path': 't/y/x.cc',
                    'new_path': 's/z/x.cc'
                },
            ]
        }
        deps_info = {}

        justification = build_failure_analysis.CheckFiles(
            failure_signal, ChangeLogFromDict(change_log_json), deps_info,
            True)
        self.assertIsNotNone(justification)
        # The score is 2 because:
        # CL only touches dependencies
        self.assertEqual(2, justification['score'])
 def testAddTarget(self):
     signal = FailureSignal()
     signal.AddTarget({'target': 'a.exe'})
     signal.AddTarget({'target': 'b.o', 'source': 'b.cpp'})
     signal.AddTarget({'target': 'b.o', 'source': 'b.cpp'})
     self.assertEqual([{
         'target': 'a.exe'
     }, {
         'target': 'b.o',
         'source': 'b.cpp'
     }], signal.failed_targets)
    def testMergeFrom(self):
        test_signals = [
            {
                'files': {
                    'a.cc': [2],
                    'd.cc': []
                },
                'keywords': {},
                'failed_targets': [{
                    'target': 'a.o',
                    'source': 'a.cc'
                }, {
                    'target': 'b.o',
                    'source': 'b.cc'
                }]
            },
            {
                'files': {
                    'a.cc': [2, 3, 4],
                    'b.cc': [],
                    'd.cc': [1]
                },
                'keywords': {},
                'failed_targets': [{
                    'target': 'a.o',
                    'source': 'a.cc'
                }, {
                    'target': 'c.exe'
                }]
            },
        ]
        step_signal = FailureSignal()

        for test_signal in test_signals:
            step_signal.MergeFrom(test_signal)

        expected_step_signal_files = {
            'a.cc': [2, 3, 4],
            'd.cc': [1],
            'b.cc': []
        }
        expected_step_failed_targets = [{
            'target': 'a.o',
            'source': 'a.cc'
        }, {
            'target': 'b.o',
            'source': 'b.cc'
        }, {
            'target': 'c.exe'
        }]

        self.assertEqual(expected_step_signal_files, step_signal.files)
        self.assertEqual(expected_step_failed_targets,
                         step_signal.failed_targets)
Пример #6
0
    def Extract(self, failure_log, *_):
        signal = FailureSignal()
        failure_log_lines = failure_log.splitlines()

        i = 0
        end_index = len(failure_log_lines)

        while i < end_index:
            line = failure_log_lines[i]
            cpp_stacktrace_match = extractor_util.CPP_STACK_TRACE_FRAME_PATTERN.match(
                line)
            if cpp_stacktrace_match:
                # Handle cpp failure stacktraces.
                start = i
                for line in failure_log_lines[start:]:  # pragma: no cover
                    if extractor_util.CPP_STACK_TRACE_FRAME_PATTERN.match(
                            line):
                        i += 1
                    else:
                        break
                end = i

                if (start >= 1 and self.INDIRECT_LEAK_MARKER_PATTERN.match(
                        failure_log_lines[start - 1])):
                    # Ignore stack trace of an indirect leak.
                    continue

                cpp_stacktrace_frames = failure_log_lines[start:end]
                self._ExtractCppFiles(cpp_stacktrace_frames, signal)
            elif extractor_util.PYTHON_STACK_TRACE_START_PATTERN.search(line):
                # Handle python failure stacktraces.
                i += 1
                start = i
                while i < end_index:  # pragma: no cover
                    line = failure_log_lines[i]
                    if (extractor_util.PYTHON_STACK_TRACE_FRAME_PATTERN_1.
                            search(line) or extractor_util.
                            PYTHON_STACK_TRACE_FRAME_PATTERN_2.search(line)):
                        i += 2
                    else:
                        break
                end = i
                python_stacktrace_frames = failure_log_lines[start:end]
                self._ExtractPythonFiles(python_stacktrace_frames, signal)
            elif 'GMOCK WARNING' in line:
                # Ignore GMOCK WARNING statements.
                start = i
                for l in failure_log_lines[start:]:  # pragma: no cover
                    if ('You can safely ignore the above warning unless this call '
                            'should not happen.') in l:
                        # The end line in GMOCK WARNING statements.
                        break
                    i += 1
            else:
                if line and not extractor_util.ShouldIgnoreLine(line):
                    self.ExtractFiles(line, signal)

            i += 1

        return signal
Пример #7
0
    def Extract(self, failure_log, *_):
        signal = FailureSignal()

        for line in reversed(failure_log.splitlines()):
            if line.startswith('FAILED'):  # pragma: no cover
                # This is where the failure message starts.
                # As we do reverse check, we should stop here.
                break

            # Extract files.
            for match in extractor_util.FILE_PATH_LINE_PATTERN.finditer(line):
                file_path, line_number = match.groups()
                signal.AddFile(extractor_util.NormalizeFilePath(file_path),
                               line_number)

        return signal
Пример #8
0
 def testToFromDict(self):
     data = {
         'files': {
             'a.cc': [2],
             'd.cc': []
         },
         'tests': ['suite.name'],
         'keywords': {
             'k1': 3
         }
     }
     signal = FailureSignal.FromDict(data)
     self.assertEqual(data, signal.ToDict())
Пример #9
0
  def testExtractFiles(self):
    cases = {
        'a/b/c.h:1 at d/e/f.cpp(2)': {
            'a/b/c.h': [1],
            'd/e/f.cpp': [2]
        },
        'blabla telemetry/decorators.py:55': {
            'telemetry/decorators.py': [55]
        },
        'File "telemetry/core/web_contents.py", line 78, in pythonMethod': {
            'telemetry/core/web_contents.py': [78]
        },
        'File "/b/build/slave/bot/build/src/a/b.py", line 246, in RunPage': {
            'a/b.py': [246]
        }
    }

    extractor = Extractor()
    for case in cases:
      signal = FailureSignal()
      extractor.ExtractFiles(case, signal)
      self.assertEqual(cases[case], signal.ToDict()['files'])
Пример #10
0
    def _ExtractNinjaOutputJson(self, ninja_output, bot_name, master_name):
        signal = FailureSignal()
        strict_regex = waterfall_config.EnableStrictRegexForCompileLinkFailures(
            master_name, bot_name)
        failed_output_nodes = []
        for failure in ninja_output['failures']:
            lines = failure['output'].splitlines()
            del failure['output']
            failure['dependencies'] = map(extractor_util.NormalizeFilePath,
                                          failure['dependencies'])
            signal.AddEdge(failure)
            if lines:
                if strict_regex:
                    self.ExtractFailedOutputNodes(lines[0], signal)
                else:
                    self.GetFailedTarget(lines[0], signal)
                for line in lines[1:]:
                    self.ExtractFiles(line, signal)
            failed_output_nodes.extend(failure['output_nodes'])

        signal.failed_output_nodes = sorted(set(failed_output_nodes))
        return signal
Пример #11
0
 def testToFromDict(self):
     data = {
         'files': {
             'a.cc': [2],
             'd.cc': []
         },
         'keywords': {
             'k1': 3
         },
         'failed_output_nodes': [
             'obj/path/to/file.o',
         ],
     }
     signal = FailureSignal.FromDict(data)
     self.assertEqual(data, signal.ToDict())
Пример #12
0
 def testToFromDictWithFailedTargets(self):
     data = {
         'files': {
             'a.cc': [2],
             'd.cc': []
         },
         'keywords': {
             'k1': 3
         },
         'failed_targets': [{
             'target': 'a.o',
             'source': 'b/a.cc'
         }]
     }
     signal = FailureSignal.FromDict(data)
     self.assertEqual(data, signal.ToDict())
Пример #13
0
    def Extract(self, failure_log, *_):
        signal = FailureSignal()
        log_lines = failure_log.splitlines()

        index = len(log_lines) - 1

        while index >= 0:  # pragma: no cover
            # Start at the bottom of the log and read up.
            line = log_lines[index]
            if line:
                if self.STOP_MARKER.search(line):
                    break

                if not self._ShouldIgnoreLine(line):
                    self.ExtractFiles(line, signal)

            index -= 1
        return signal
Пример #14
0
    def Extract(self, failure_log, test_name, step_name, bot_name,
                master_name):
        signal = FailureSignal()

        failure_started = False
        if (master_name == self.MAC_MASTER_NAME_FOR_COMPILE
                and bot_name in self.IOS_BUILDER_NAMES_FOR_COMPILE):
            error_lines = []
            for line in reversed(failure_log.splitlines()):
                if (not failure_started
                        and self.ERROR_LINE_END_PATTERN.match(line)):
                    failure_started = True
                    continue

                if failure_started:
                    if line.startswith(self.IOS_ERROR_LINE_START_PREFIX):
                        failure_started = False
                        for l in error_lines[:-4]:
                            self.ExtractFiles(l, signal)
                        error_lines = []
                    else:
                        error_lines.append(line)

        else:
            for line in failure_log.splitlines():
                if line.startswith(self.FAILURE_START_LINE_PREFIX):
                    if not failure_started:
                        failure_started = True
                    continue  # pragma: no cover
                elif failure_started and self.ERROR_LINE_END_PATTERN.match(
                        line):
                    failure_started = False
                elif failure_started and line.startswith(
                        self.NINJA_FAILURE_END_LINE_PREFIX
                ):  # pragma: no cover
                    break

                if failure_started or line.startswith(
                        self.NINJA_ERROR_LINE_PREFIX):
                    # either within the compile errors or is a ninja error.
                    self.ExtractFiles(line, signal)

        return signal
Пример #15
0
 def testToFromDict(self):
     data = {
         'files': {
             'a.cc': [2],
             'd.cc': []
         },
         'keywords': {
             'k1': 3
         },
         'failed_output_nodes': [
             'obj/path/to/file.o',
         ],
         'failed_edges': [{
             'rule': 'CXX',
             'output_nodes': ['a.o', 'aa.o'],
             'dependencies': ['a.h', 'a.c']
         }]
     }
     signal = FailureSignal.FromDict(data)
     self.assertEqual(data, signal.ToDict())
Пример #16
0
 def testCheckFilesAgainstDEPSRollWithUnrelatedLinesChanged(self):
     failure_signal_json = {
         'files': {
             'src/third_party/dep1/f.cc': [123],
         }
     }
     change_log_json = {
         'revision':
         '12',
         'touched_files': [
             {
                 'change_type': ChangeType.MODIFY,
                 'old_path': 'DEPS',
                 'new_path': 'DEPS'
             },
         ]
     }
     deps_info = {
         'deps_rolls': {
             '12': [
                 {
                     'path': 'src/third_party/dep1',
                     'repo_url': 'https://url_dep1',
                     'old_revision': '7',
                     'new_revision': '9',
                 },
             ]
         }
     }
     self.mock(CachedGitilesRepository, 'GetChangeLog',
               self._MockGetChangeLog)
     self.mock(CachedGitilesRepository, 'GetChangeLogs',
               self._MockGetChangeLogs)
     self.mock(CachedGitilesRepository, 'GetBlame', self._MockGetBlame)
     justification = build_failure_analysis.CheckFiles(
         FailureSignal.FromDict(failure_signal_json),
         ChangeLogFromDict(change_log_json), deps_info)
     self.assertIsNotNone(justification)
     # The score is 1 because:
     # +1 rolled third_party/dep1/ and src/third_party/dep1/f.cc was in log.
     self.assertEqual(1, justification['score'])
Пример #17
0
    def Extract(self, failure_log, *_):
        signal = FailureSignal()
        failure_started = False

        for line in failure_log.splitlines():
            if line.startswith(self.BEGINNING_MARKER):
                failure_started = True
                continue

            # Skip hints.
            if line.startswith(self.HINT_MARKER):
                continue

            if self.END_MARKER.match(line):
                failure_started = False
                continue

            # Begin extracting file names.
            if failure_started:
                self.ExtractFiles(line, signal)

        return signal
Пример #18
0
    def Extract(self, failure_log, *_):
        signal = FailureSignal()
        log_lines = failure_log.splitlines()

        index = 0
        start = index
        end = len(log_lines)

        while index < end:  # pragma: no cover
            line = log_lines[index]
            if line.startswith(self.TEST_START_MARKER):
                start = index + 1
            elif line.startswith(self.TEST_FAILED_MARKER):
                # Extract the test that failed as a possible signal.
                match = self.JAVA_TEST_NAME_PATTERN.search(line)
                self.ExtractJavaFileMatch(match, signal)

                # Extract the rest of the stacks associated with this failure.
                test_failure_lines = log_lines[start:index]
                self.ProcessTestFailure(test_failure_lines, signal)
            index += 1
        return signal
Пример #19
0
  def testCheckFilesAgainstUnrelatedCL(self):
    failure_signal_json = {
        'files': {
            'src/a/b/f.cc': [],
        }
    }
    change_log_json = {
        'revision': 'rev',
        'touched_files': [
            {
                'change_type': ChangeType.ADD,
                'old_path': '/dev/null',
                'new_path': 'a/d/f1.cc'
            },
        ]
    }
    deps_info = {}

    justification = build_failure_analysis._CheckFiles(
        FailureSignal.FromDict(failure_signal_json),
        change_log_json, deps_info)
    self.assertIsNone(justification)
Пример #20
0
    def Extract(self, failure_log, *_):
        signal = FailureSignal()
        failure_started = False
        in_failure_stacktrace_within_range = False
        java_stack_frame_index = 0

        for line in failure_log.splitlines():  # pragma: no cover
            if not failure_started and line.endswith('Detailed Logs'):
                failure_started = True
                continue

            if failure_started:
                if (not in_failure_stacktrace_within_range and
                        self.JAVA_STACK_TRACE_BEGINNING_MARKER.match(line)):
                    in_failure_stacktrace_within_range = True
                    java_stack_frame_index = 0
                    continue

                if line.endswith('Summary'):
                    break

                if in_failure_stacktrace_within_range:
                    match = extractor_util.JAVA_STACK_TRACE_FRAME_PATTERN.search(
                        line)

                    if match:
                        self.ExtractJavaFileMatch(match, signal)
                        java_stack_frame_index += 1

                        # Only extract the top several frames of each stack.
                        if (java_stack_frame_index >=
                                extractor_util.JAVA_MAXIMUM_NUMBER_STACK_FRAMES
                            ):
                            in_failure_stacktrace_within_range = False

        return signal
Пример #21
0
def AnalyzeCompileFailure(failure_info, change_logs, deps_info,
                          failure_signals):
  """Analyzes given failure signals, and figure out culprits of compile failure.

  Args:
    failure_info (CompileFailureInfo): Output of pipeline
      DetectFirstFailurePipeline.
    change_logs (dict): Output of pipeline PullChangelogPipeline.
    deps_info (dict): Output of pipeline ExtractDEPSInfoPipeline.
    failure_signals (dict): Output of pipeline ExtractSignalPipeline.

  Returns:
    A dict with the following form:
    {
      'failures': [
        {
          'step_name': 'compile',
          'supported': True
          'first_failure': 230,
          'last_pass': 229,
          'suspected_cls': [
            {
              'build_number': 230,
              'repo_name': 'chromium',
              'revision': 'a_git_hash',
              'commit_position': 56789,
              'score': 11,
              'hints': {
                'add a/b/x.cc': 5,
                'delete a/b/y.cc': 5,
                'modify e/f/z.cc': 1,
                ...
              }
            },
            ...
          ],
        },
        ...
      ]
    }

    And a list of suspected_cls format as below:
    [
        {
            'repo_name': 'chromium',
            'revision': 'r98_1',
            'commit_position': None,
            'url': None,
            'failures': {
                'b': ['Unittest2.Subtest1', 'Unittest3.Subtest2']
            },
            'top_score': 4
        },
        ...
    ]
  """
  analysis_result = {'failures': []}
  cl_failure_map = defaultdict(build_failure_analysis.CLInfo)

  step_name = constants.COMPILE_STEP_NAME

  if not failure_signals:
    logging.debug('No failure signals when analyzing a compile failure.')
    return analysis_result, []

  if step_name not in failure_info.failed_steps:
    logging.debug('No failed compile step when analyzing a compile failure.')
    return analysis_result, []

  builds = failure_info.builds
  compile_failure_info = failure_info.failed_steps[step_name]

  failed_build_number = compile_failure_info.current_failure
  start_build_number = build_failure_analysis.GetLowerBoundForAnalysis(
      compile_failure_info)
  step_analysis_result = build_failure_analysis.InitializeStepLevelResult(
      step_name, compile_failure_info)

  if not step_analysis_result['supported']:
    return analysis_result, []

  failure_signal = FailureSignal.FromDict(failure_signals[step_name])
  _Analyze(start_build_number, failed_build_number, builds, step_name,
           failure_signal, change_logs, deps_info, step_analysis_result,
           cl_failure_map)

  if waterfall_config.GetDownloadBuildDataSettings().get(
      'use_ninja_output_log'):
    step_analysis_result['new_compile_suspected_cls'] = []
    _Analyze(
        start_build_number,
        failed_build_number,
        builds,
        step_name,
        failure_signal,
        change_logs,
        deps_info,
        step_analysis_result,
        cl_failure_map,
        use_ninja_output=True)

    if (not step_analysis_result['suspected_cls'] and
        step_analysis_result.get('new_compile_suspected_cls')):
      step_analysis_result['use_ninja_dependencies'] = True
      step_analysis_result['suspected_cls'] = step_analysis_result[
          'new_compile_suspected_cls']
      for new_suspected_cl_dict in step_analysis_result['suspected_cls']:
        # Top score for new heuristic is always 2.
        build_failure_analysis.SaveFailureToMap(
            cl_failure_map, new_suspected_cl_dict, step_name, None, 2)

  # TODO(stgao): sort CLs by score.
  analysis_result['failures'].append(step_analysis_result)

  suspected_cls = build_failure_analysis.ConvertCLFailureMapToList(
      cl_failure_map)

  return analysis_result, suspected_cls
Пример #22
0
    def testCheckFilesAgainstSuspectedCL(self):
        failure_signal_json = {
            'files': {
                'src/a/b/f1.cc': [],
                'd/e/a2_test.cc': [],
                'b/c/f2.cc': [10, 20],
                'd/e/f3.h': [],
                'x/y/f4.py': [],
                'f5_impl.cc': []
            }
        }
        change_log_json = {
            'revision':
            '12',
            'touched_files': [
                {
                    'change_type': ChangeType.ADD,
                    'old_path': '/dev/null',
                    'new_path': 'a/b/f1.cc'
                },
                {
                    'change_type': ChangeType.ADD,
                    'old_path': '/dev/null',
                    'new_path': 'd/e/a2.cc'
                },
                {
                    'change_type': ChangeType.MODIFY,
                    'old_path': 'a/b/c/f2.h',
                    'new_path': 'a/b/c/f2.h'
                },
                {
                    'change_type': ChangeType.MODIFY,
                    'old_path': 'd/e/f3.h',
                    'new_path': 'd/e/f3.h'
                },
                {
                    'change_type': ChangeType.DELETE,
                    'old_path': 'x/y/f4.py',
                    'new_path': '/dev/null'
                },
                {
                    'change_type': ChangeType.DELETE,
                    'old_path': 'h/f5.h',
                    'new_path': '/dev/null'
                },
                {
                    'change_type': ChangeType.RENAME,
                    'old_path': 't/y/x.cc',
                    'new_path': 's/z/x.cc'
                },
            ]
        }
        deps_info = {}

        justification = build_failure_analysis.CheckFiles(
            FailureSignal.FromDict(failure_signal_json),
            ChangeLogFromDict(change_log_json), deps_info)
        self.assertIsNotNone(justification)
        # The score is 15 because:
        # +5 added a/b/f1.cc (same file src/a/b/f1.cc in failure_signal log)
        # +1 added d/e/a2.cc (related file a2_test.cc in failure_signal log)
        # +1 modified b/c/f2.h (related file a/b/c/f2.cc in failure_signal log)
        # +2 modified d/e/f3.h (same file d/e/f3.h in failure_signal log)
        # +5 deleted x/y/f4.py (same file x/y/f4.py in failure_signal log)
        # +1 deleted h/f5.h (related file f5_impl.cc in failure_signal log)
        # +0 renamed t/y/x.cc -> s/z/x.cc (no related file in failure_signal log)
        self.assertEqual(15, justification['score'])
def AnalyzeTestFailure(failure_info, change_logs, deps_info, failure_signals):
  """Analyzes given failure signals, and figure out culprits of test failure.

  Args:
    failure_info (TestFailureInfo): Output of pipeline
      DetectFirstFailurePipeline.
    change_logs (dict): Output of pipeline PullChangelogPipeline.
    deps_info (dict): Output of pipeline ExtractDEPSInfoPipeline.
    failure_signals (dict): Output of pipeline ExtractSignalPipeline.

  Returns:
    A dict with the following form:
    {
      'failures': [
        {
          'step_name': 'compile',
          'supported': True
          'first_failure': 230,
          'last_pass': 229,
          'suspected_cls': [
            {
              'build_number': 230,
              'repo_name': 'chromium',
              'revision': 'a_git_hash',
              'commit_position': 56789,
              'score': 11,
              'hints': {
                'add a/b/x.cc': 5,
                'delete a/b/y.cc': 5,
                'modify e/f/z.cc': 1,
                ...
              }
            },
            ...
          ],
        },
        ...
      ]
    }

    And a list of suspected_cls format as below:
    [
        {
            'repo_name': 'chromium',
            'revision': 'r98_1',
            'commit_position': None,
            'url': None,
            'failures': {
                'b': ['Unittest2.Subtest1', 'Unittest3.Subtest2']
            },
            'top_score': 4
        },
        ...
    ]
  """
  analysis_result = {'failures': []}

  if not failure_signals:
    logging.debug('No failure signals when analyzing a test failure.')
    return analysis_result, []

  failed_steps = failure_info.failed_steps
  builds = failure_info.builds

  cl_failure_map = defaultdict(build_failure_analysis.CLInfo)

  for step_name, step_failure_info in failed_steps.iteritems():
    is_test_level = step_failure_info.tests is not None

    failed_build_number = step_failure_info.current_failure
    start_build_number = (
        build_failure_analysis.GetLowerBoundForAnalysis(step_failure_info))
    step_analysis_result = (
        build_failure_analysis.InitializeStepLevelResult(
            step_name, step_failure_info))

    if is_test_level:
      step_analysis_result['tests'] = []
      tests = step_failure_info.tests or {}
      for test_name, test_failure in tests.iteritems():
        test_analysis_result = {
            'test_name': test_name,
            'first_failure': test_failure.first_failure,
            'last_pass': test_failure.last_pass,
            'suspected_cls': [],
        }
        step_analysis_result['tests'].append(test_analysis_result)

    if step_analysis_result['supported']:
      step_failure_signal = FailureSignal.FromDict(failure_signals[step_name])
      for build_number, build in builds.iteritems():
        if (build_number > failed_build_number or
            build_number < start_build_number):
          continue

        for revision in build.blame_list:
          # TODO(crbug/842980): Deprecate blame_list in builds.
          if not change_logs.get(revision):
            continue

          if is_test_level:
            # Checks files at test level.
            for test_analysis_result in step_analysis_result['tests']:
              test_name = test_analysis_result['test_name']
              test_signal = FailureSignal.FromDict(
                  failure_signals[step_name]['tests'].get(test_name) or {})

              _AnalyzeTestFailureOnOneBuild(build_number, step_name, test_name,
                                            test_signal, change_logs[revision],
                                            deps_info, test_analysis_result,
                                            cl_failure_map)

          # Checks Files on step level using step level signals
          # regardless of test level signals so we can make sure
          # no duplicate justifications added to the step result.
          _AnalyzeTestFailureOnOneBuild(
              build_number,
              step_name,
              None,
              step_failure_signal,
              change_logs[revision],
              deps_info,
              step_analysis_result,
              cl_failure_map,
              has_lower_level_info=is_test_level)

    # TODO(stgao): sort CLs by score.
    analysis_result['failures'].append(step_analysis_result)

  suspected_cls = build_failure_analysis.ConvertCLFailureMapToList(
      cl_failure_map)

  return analysis_result, suspected_cls
    def run(self, failure_info):
        """Extracts failure signals from failed steps.

    Args:
      failure_info (dict): Output of pipeline DetectFirstFailurePipeline.run().

    Returns:
      A dict like below:
      {
        'step_name1': waterfall.failure_signal.FailureSignal.ToDict(),
        ...
      }
    """
        signals = {}
        if not failure_info['failed'] or not failure_info['chromium_revision']:
            # Bail out if no failed step or no chromium revision.
            return signals

        # Bail out on infra failure
        if failure_info.get('failure_type') == failure_type.INFRA:
            return signals

        master_name = failure_info['master_name']
        builder_name = failure_info['builder_name']
        build_number = failure_info['build_number']

        for step_name in failure_info.get('failed_steps', []):
            if not waterfall_config.StepIsSupportedForMaster(
                    step_name, master_name):
                # Bail out if the step is not supported.
                continue

            step = WfStep.Get(master_name, builder_name, build_number,
                              step_name)
            if step and step.log_data:
                failure_log = step.log_data
            else:
                # TODO: do test-level analysis instead of step-level.
                # TODO: Use swarming test result instead of archived gtest results
                gtest_result = buildbot.GetGtestResultLog(
                    master_name, builder_name, build_number, step_name)
                if gtest_result:
                    failure_log = _GetReliableTestFailureLog(gtest_result)

                if gtest_result is None or failure_log == 'invalid':
                    if not lock_util.WaitUntilDownloadAllowed(
                            master_name):  # pragma: no cover
                        raise pipeline.Retry(
                            'Failed to pull log of step %s of master %s' %
                            (step_name, master_name))
                    try:
                        failure_log = buildbot.GetStepLog(
                            master_name, builder_name, build_number, step_name,
                            self.HTTP_CLIENT)
                    except ResponseTooLargeError:  # pragma: no cover.
                        logging.exception(
                            'Log of step "%s" is too large for urlfetch.',
                            step_name)
                        # If the stdio log of a step is too large, we don't want to pull it
                        # again in next run, because that might lead to DDoS to the master.
                        # TODO: Use archived stdio logs in Google Storage instead.
                        failure_log = 'Stdio log is too large for urlfetch.'

                    if not failure_log:  # pragma: no cover
                        raise pipeline.Retry(
                            'Failed to pull stdio of step %s of master %s' %
                            (step_name, master_name))

                # Save step log in datastore and avoid downloading again during retry.
                if not step:  # pragma: no cover
                    step = WfStep.Create(master_name, builder_name,
                                         build_number, step_name)

                step.log_data = _ExtractStorablePortionOfLog(failure_log)

                try:
                    step.put()
                except Exception as e:  # pragma: no cover
                    # Sometimes, the step log is too large to save in datastore.
                    logging.exception(e)

            # TODO: save result in datastore?
            if step.isolated:
                try:
                    json_failure_log = (json.loads(failure_log)
                                        if failure_log != 'flaky' else {})
                except ValueError:  # pragma: no cover
                    json_failure_log = {}
                    logging.warning('failure_log %s is not valid JSON.' %
                                    failure_log)

                signals[step_name] = {'tests': {}}
                step_signal = FailureSignal()

                for test_name, test_failure_log in json_failure_log.iteritems(
                ):
                    signals[step_name]['tests'][
                        test_name] = extractors.ExtractSignal(
                            master_name, builder_name, step_name, test_name,
                            base64.b64decode(test_failure_log)).ToDict()

                    # Save signals in test failure log to step level.
                    step_signal.MergeFrom(
                        signals[step_name]['tests'][test_name])

                signals[step_name]['files'] = step_signal.files
                signals[step_name]['keywords'] = step_signal.keywords
            else:
                signals[step_name] = extractors.ExtractSignal(
                    master_name, builder_name, step_name, None,
                    failure_log).ToDict()

        return signals
def AnalyzeBuildFailure(failure_info, change_logs, deps_info, failure_signals):
    """Analyzes the given failure signals, and figure out culprit CLs.

  Args:
    failure_info (dict): Output of pipeline DetectFirstFailurePipeline.
    change_logs (dict): Output of pipeline PullChangelogPipeline.
    deps_info (dict): Output of pipeline ExtractDEPSInfoPipeline.
    failure_signals (dict): Output of pipeline ExtractSignalPipeline.

  Returns:
    A dict with the following form:
    {
      'failures': [
        {
          'step_name': 'compile',
          'supported': True
          'first_failure': 230,
          'last_pass': 229,
          'suspected_cls': [
            {
              'build_number': 230,
              'repo_name': 'chromium',
              'revision': 'a_git_hash',
              'commit_position': 56789,
              'score': 11,
              'hints': {
                'add a/b/x.cc': 5,
                'delete a/b/y.cc': 5,
                'modify e/f/z.cc': 1,
                ...
              }
            },
            ...
          ],
        },
        ...
      ]
    }

    And a list of suspected_cls format as below:
    [
        {
            'repo_name': 'chromium',
            'revision': 'r98_1',
            'commit_position': None,
            'url': None,
            'failures': {
                'b': ['Unittest2.Subtest1', 'Unittest3.Subtest2']
            },
            'top_score': 4
        },
        ...
    ]
  """
    analysis_result = {'failures': []}

    if not failure_info['failed'] or not failure_info['chromium_revision']:
        # Bail out if no failed step or no chromium revision.
        return analysis_result, []

    # Bail out on infra failure
    if failure_info.get('failure_type') == failure_type.INFRA:
        return analysis_result, []

    def CreateCLInfoDict(justification_dict, build_number, change_log):
        # TODO(stgao): remove hard-coded 'chromium' when DEPS file parsing is
        # supported.
        cl_info = {
            'build_number':
            build_number,
            'repo_name':
            'chromium',
            'revision':
            change_log['revision'],
            'commit_position':
            change_log.get('commit_position'),
            'url':
            change_log.get('code_review_url') or change_log.get('commit_url'),
        }

        cl_info.update(justification_dict)
        return cl_info

    failed_steps = failure_info['failed_steps']
    builds = failure_info['builds']
    master_name = failure_info['master_name']

    cl_failure_map = defaultdict(_CLInfo)

    for step_name, step_failure_info in failed_steps.iteritems():
        is_test_level = step_failure_info.get('tests') is not None

        failed_build_number = step_failure_info['current_failure']
        if step_failure_info.get('last_pass') is not None:
            start_build_number = step_failure_info.get('last_pass') + 1
        else:
            start_build_number = step_failure_info['first_failure']
        step_analysis_result = {
            'step_name':
            step_name,
            'first_failure':
            step_failure_info['first_failure'],
            'last_pass':
            step_failure_info.get('last_pass'),
            'suspected_cls': [],
            'supported':
            waterfall_config.StepIsSupportedForMaster(step_name, master_name)
        }

        if is_test_level:
            step_analysis_result['tests'] = []
            for test_name, test_failure in step_failure_info[
                    'tests'].iteritems():
                test_analysis_result = {
                    'test_name': test_name,
                    'first_failure': test_failure['first_failure'],
                    'last_pass': test_failure.get('last_pass'),
                    'suspected_cls': [],
                }
                step_analysis_result['tests'].append(test_analysis_result)

        if step_analysis_result['supported']:
            for build_number in range(start_build_number,
                                      failed_build_number + 1):
                for revision in builds[str(build_number)]['blame_list']:
                    if is_test_level:
                        # Checks files at test level.
                        for test_analysis_result in step_analysis_result[
                                'tests']:
                            test_name = test_analysis_result['test_name']
                            test_signal = FailureSignal.FromDict(
                                failure_signals[step_name]['tests'].get(
                                    test_name, {}))

                            justification_dict = _CheckFiles(
                                test_signal, change_logs[revision], deps_info)

                            if not justification_dict:
                                continue

                            new_suspected_cl_dict = CreateCLInfoDict(
                                justification_dict, build_number,
                                change_logs[revision])
                            test_analysis_result['suspected_cls'].append(
                                new_suspected_cl_dict)

                            _SaveFailureToMap(
                                cl_failure_map, new_suspected_cl_dict,
                                step_name, test_name,
                                max(justification_dict['hints'].values()))

                    # Checks Files on step level using step level signals
                    # regardless of test level signals so we can make sure
                    # no duplicate justifications added to the step result.
                    failure_signal = FailureSignal.FromDict(
                        failure_signals[step_name])
                    justification_dict = _CheckFiles(failure_signal,
                                                     change_logs[revision],
                                                     deps_info)

                    if not justification_dict:
                        continue

                    new_suspected_cl_dict = CreateCLInfoDict(
                        justification_dict, build_number,
                        change_logs[revision])
                    step_analysis_result['suspected_cls'].append(
                        new_suspected_cl_dict)

                    if not is_test_level:
                        _SaveFailureToMap(
                            cl_failure_map, new_suspected_cl_dict, step_name,
                            None, max(justification_dict['hints'].values()))

        # TODO(stgao): sort CLs by score.
        analysis_result['failures'].append(step_analysis_result)

    suspected_cls = _ConvertCLFailureMapToList(cl_failure_map)

    return analysis_result, suspected_cls
Пример #26
0
    def testMergeFrom(self):
        test_signals = [
            {
                'files': {
                    'a.cc': [2],
                    'd.cc': []
                },
                'keywords': {},
                'failed_targets': [{
                    'target': 'a.o',
                    'source': 'a.cc'
                }, {
                    'target': 'b.o',
                    'source': 'b.cc'
                }],
                'failed_edges': [{
                    'rule': 'CXX',
                    'output_nodes': ['a.o', 'aa.o'],
                    'dependencies': ['a.h', 'a.c']
                }]
            },
            {
                'files': {
                    'a.cc': [2, 3, 4],
                    'b.cc': [],
                    'd.cc': [1]
                },
                'keywords': {},
                'failed_targets': [{
                    'target': 'a.o',
                    'source': 'a.cc'
                }, {
                    'target': 'c.exe'
                }],
                'failed_edges': [{
                    'rule': 'LINK',
                    'output_nodes': ['b.o'],
                    'dependencies': []
                }]
            },
        ]
        step_signal = FailureSignal()

        for test_signal in test_signals:
            step_signal.MergeFrom(test_signal)

        expected_step_signal_files = {
            'a.cc': [2, 3, 4],
            'd.cc': [1],
            'b.cc': []
        }
        expected_step_failed_targets = [{
            'target': 'a.o',
            'source': 'a.cc'
        }, {
            'target': 'b.o',
            'source': 'b.cc'
        }, {
            'target': 'c.exe'
        }]
        expected_step_failed_edges = [{
            'rule': 'CXX',
            'output_nodes': ['a.o', 'aa.o'],
            'dependencies': ['a.h', 'a.c']
        }, {
            'rule': 'LINK',
            'output_nodes': ['b.o'],
            'dependencies': []
        }]
        self.assertEqual(expected_step_signal_files, step_signal.files)
        self.assertEqual(expected_step_failed_targets,
                         step_signal.failed_targets)
        self.assertEqual(expected_step_failed_edges, step_signal.failed_edges)
Пример #27
0
 def testAddFileWithoutLineNumber(self):
     signal = FailureSignal()
     signal.AddFile('a.cc', None)
     self.assertEqual({'a.cc': []}, signal.files)
Пример #28
0
 def testAddFileWithLineNumber(self):
     signal = FailureSignal()
     signal.AddFile('a.cc', 1)
     signal.AddFile('a.cc', 11)
     signal.AddFile('a.cc', 11)
     self.assertEqual({'a.cc': [1, 11]}, signal.files)
Пример #29
0
def AnalyzeBuildFailure(
    failure_info, change_logs, deps_info, failure_signals):
  """Analyze the given failure signals, and figure out culprit CLs.

  Args:
    failure_info (dict): Output of pipeline DetectFirstFailurePipeline.
    change_logs (dict): Output of pipeline PullChangelogPipeline.
    deps_info (dict): Output of pipeline ExtractDEPSInfoPipeline.
    failure_signals (dict): Output of pipeline ExtractSignalPipeline.

  Returns:
    A dict with the following form:
    {
      'failures': [
        {
          'step_name': 'compile',
          'first_failure': 230,
          'last_pass': 229,
          'suspected_cls': [
            {
              'build_number': 230,
              'repo_name': 'chromium',
              'revision': 'a_git_hash',
              'commit_position': 56789,
              'score': 11,
              'hints': {
                'add a/b/x.cc': 5,
                'delete a/b/y.cc': 5,
                'modify e/f/z.cc': 1,
                ...
              }
            },
            ...
          ],
        },
        ...
      ]
    }
  """
  analysis_result = {
      'failures': []
  }

  if not failure_info['failed'] or not failure_info['chromium_revision']:
    # Bail out if no failed step or no chromium revision.
    return analysis_result

  def CreateCLInfoDict(justification_dict, build_number, change_log):
    # TODO(stgao): remove hard-coded 'chromium' when DEPS file parsing is
    # supported.
    cl_info = {
        'build_number': build_number,
        'repo_name': 'chromium',
        'revision': change_log['revision'],
        'commit_position': change_log.get('commit_position'),
        'url':
            change_log.get('code_review_url') or change_log.get('commit_url'),
    }

    cl_info.update(justification_dict)
    return cl_info

  failed_steps = failure_info['failed_steps']
  builds = failure_info['builds']
  for step_name, step_failure_info in failed_steps.iteritems():
    failure_signal = FailureSignal.FromDict(failure_signals[step_name])
    failed_build_number = step_failure_info['current_failure']

    if step_failure_info.get('last_pass') is not None:
      build_number = step_failure_info.get('last_pass') + 1
    else:
      build_number = step_failure_info['first_failure']

    step_analysis_result = {
        'step_name': step_name,
        'first_failure': step_failure_info['first_failure'],
        'last_pass': step_failure_info.get('last_pass'),
        'suspected_cls': [],
    }

    while build_number <= failed_build_number:
      for revision in builds[str(build_number)]['blame_list']:
        justification_dict = _CheckFiles(
            failure_signal, change_logs[revision], deps_info)

        if not justification_dict:
          continue

        step_analysis_result['suspected_cls'].append(
            CreateCLInfoDict(justification_dict, build_number,
                             change_logs[revision]))

      build_number += 1

    # TODO(stgao): sort CLs by score.
    analysis_result['failures'].append(step_analysis_result)

  return analysis_result
Пример #30
0
 def testAddTest(self):
     signal = FailureSignal()
     signal.AddTest('suite.name1')
     signal.AddTest('suite.name2')
     self.assertEqual(['suite.name1', 'suite.name2'], signal.tests)