OLD | NEW |
1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2014 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 import os | 5 import os |
6 from threading import Lock, Thread | 6 from threading import Lock, Thread |
7 | 7 |
8 import blame | 8 import blame |
9 from common import utils | 9 from common import utils |
10 import component_dictionary | 10 import component_dictionary |
11 import crash_utils | 11 import crash_utils |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
72 (diff_url, changed_line_numbers, changed_line_contents) = ( | 72 (diff_url, changed_line_numbers, changed_line_contents) = ( |
73 repository_parser.ParseLineDiff( | 73 repository_parser.ParseLineDiff( |
74 file_path, component_path, file_change_type, revision_number)) | 74 file_path, component_path, file_change_type, revision_number)) |
75 | 75 |
76 # Ignore this match if the component is not supported for svn. | 76 # Ignore this match if the component is not supported for svn. |
77 if not diff_url: | 77 if not diff_url: |
78 return | 78 return |
79 | 79 |
80 # Find the intersection between the lines that this file crashed on and | 80 # Find the intersection between the lines that this file crashed on and |
81 # the changed lines. | 81 # the changed lines. |
82 (line_number_intersection, stack_frame_index_intersection) = ( | 82 (line_number_intersection, stack_frame_index_intersection, functions) = ( |
83 crash_utils.Intersection( | 83 crash_utils.Intersection( |
84 crashed_line_numbers, stack_frame_indices, changed_line_numbers)) | 84 crashed_line_numbers, stack_frame_indices, changed_line_numbers, |
| 85 function)) |
85 | 86 |
86 # Find the minimum distance between the changed lines and crashed lines. | 87 # Find the minimum distance between the changed lines and crashed lines. |
87 min_distance = crash_utils.FindMinLineDistance(crashed_line_numbers, | 88 (min_distance, min_crashed_line, min_changed_line) = \ |
88 changed_line_numbers) | 89 crash_utils.FindMinLineDistance(crashed_line_numbers, |
| 90 changed_line_numbers) |
89 | 91 |
90 # Check whether this CL changes the crashed lines or not. | 92 # Check whether this CL changes the crashed lines or not. |
91 if line_number_intersection: | 93 if line_number_intersection: |
92 priority = LINE_CHANGE_PRIORITY | 94 priority = LINE_CHANGE_PRIORITY |
93 else: | 95 else: |
94 priority = FILE_CHANGE_PRIORITY | 96 priority = FILE_CHANGE_PRIORITY |
95 | 97 |
96 # Add the parsed information to the object. | 98 # Add the parsed information to the object. |
97 with matches.matches_lock: | 99 with matches.matches_lock: |
98 match.crashed_line_numbers.append(line_number_intersection) | 100 match.crashed_line_numbers.append(line_number_intersection) |
99 | 101 |
100 file_name = file_path.split('/')[-1] | 102 file_name = file_path.split('/')[-1] |
101 match.changed_files.append(file_name) | 103 match.changed_files.append(file_name) |
102 | 104 |
103 # Update the min distance only if it is less than the current one. | 105 # Update the min distance only if it is less than the current one. |
104 if min_distance < match.min_distance: | 106 if min_distance < match.min_distance: |
105 match.min_distance = min_distance | 107 match.min_distance = min_distance |
| 108 match.min_distance_info = (file_name, min_crashed_line, min_changed_line) |
106 | 109 |
107 # If this CL does not change the crashed line, all occurrence of this | 110 # If this CL does not change the crashed line, all occurrence of this |
108 # file in the stack has the same priority. | 111 # file in the stack has the same priority. |
109 if not stack_frame_index_intersection: | 112 if not stack_frame_index_intersection: |
110 stack_frame_index_intersection = stack_frame_indices | 113 stack_frame_index_intersection = stack_frame_indices |
| 114 functions = function |
111 match.stack_frame_indices.append(stack_frame_index_intersection) | 115 match.stack_frame_indices.append(stack_frame_index_intersection) |
112 match.changed_file_urls.append(diff_url) | 116 match.changed_file_urls.append(diff_url) |
113 match.priorities.append(priority) | 117 match.priorities.append(priority) |
114 match.function_list.append(function) | 118 match.function_list.append(functions) |
115 | 119 |
116 | 120 |
117 def FindMatch(revisions_info_map, file_to_revision_info, file_to_crash_info, | 121 def FindMatch(revisions_info_map, file_to_revision_info, file_to_crash_info, |
118 component_path, component_name, repository_parser, | 122 component_path, component_name, repository_parser, |
119 codereview_api_url): | 123 codereview_api_url): |
120 """Finds a CL that modifies file in the stacktrace. | 124 """Finds a CL that modifies file in the stacktrace. |
121 | 125 |
122 Args: | 126 Args: |
123 revisions_info_map: A dictionary mapping revision number to the CL | 127 revisions_info_map: A dictionary mapping revision number to the CL |
124 information. | 128 information. |
(...skipping 13 matching lines...) Expand all Loading... |
138 threads = [] | 142 threads = [] |
139 | 143 |
140 # Iterate through the crashed files in the stacktrace. | 144 # Iterate through the crashed files in the stacktrace. |
141 for crashed_file_path in file_to_crash_info: | 145 for crashed_file_path in file_to_crash_info: |
142 # Ignore header file. | 146 # Ignore header file. |
143 if crashed_file_path.endswith('.h'): | 147 if crashed_file_path.endswith('.h'): |
144 continue | 148 continue |
145 | 149 |
146 # If the file in the stacktrace is not changed in any commits, continue. | 150 # If the file in the stacktrace is not changed in any commits, continue. |
147 for changed_file_path in file_to_revision_info: | 151 for changed_file_path in file_to_revision_info: |
148 changed_file_name = changed_file_path.split('/')[-1] | 152 changed_file_name = changed_file_path.split('/')[-1].lower() |
149 crashed_file_name = crashed_file_path.split('/')[-1] | 153 crashed_file_name = crashed_file_path.split('/')[-1].lower() |
150 | |
151 if changed_file_name != crashed_file_name: | 154 if changed_file_name != crashed_file_name: |
152 continue | 155 continue |
153 | 156 |
154 if not crash_utils.GuessIfSameSubPath( | 157 if not crash_utils.GuessIfSameSubPath( |
155 changed_file_path, crashed_file_path): | 158 changed_file_path.lower(), crashed_file_path.lower()): |
156 continue | 159 continue |
157 | 160 |
158 crashed_line_numbers = file_to_crash_info.GetCrashedLineNumbers( | 161 crashed_line_numbers = file_to_crash_info.GetCrashedLineNumbers( |
159 crashed_file_path) | 162 crashed_file_path) |
160 stack_frame_nums = file_to_crash_info.GetCrashStackFrameIndices( | 163 stack_frame_nums = file_to_crash_info.GetCrashStackFrameIndices( |
161 crashed_file_path) | 164 crashed_file_path) |
162 functions = file_to_crash_info.GetCrashFunctions(crashed_file_path) | 165 functions = file_to_crash_info.GetCrashFunctions(crashed_file_path) |
163 | 166 |
164 # Iterate through the CLs that this file path is changed. | 167 # Iterate through the CLs that this file path is changed. |
165 for (cl, file_change_type) in file_to_revision_info[changed_file_path]: | 168 for (cl, file_change_type) in file_to_revision_info[changed_file_path]: |
(...skipping 183 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
349 match_with_stack_priority: A match object, with the CL it is from and what | 352 match_with_stack_priority: A match object, with the CL it is from and what |
350 callstack it is from. | 353 callstack it is from. |
351 | 354 |
352 Returns: | 355 Returns: |
353 A sort key. | 356 A sort key. |
354 """ | 357 """ |
355 (stack_priority, _, match) = match_with_stack_priority | 358 (stack_priority, _, match) = match_with_stack_priority |
356 | 359 |
357 return (min(match.priorities), | 360 return (min(match.priorities), |
358 stack_priority, | 361 stack_priority, |
| 362 match.min_distance, |
359 crash_utils.FindMinStackFrameNumber(match.stack_frame_indices, | 363 crash_utils.FindMinStackFrameNumber(match.stack_frame_indices, |
360 match.priorities), | 364 match.priorities), |
361 -len(match.changed_files), match.min_distance) | 365 -len(match.changed_files)) |
362 | 366 |
363 | 367 |
364 def SortAndFilterMatches(matches, num_important_frames=5): | 368 def SortAndFilterMatches(matches, num_important_frames=5): |
365 """Filters the list of potential culprit CLs to remove noise. | 369 """Filters the list of potential culprit CLs to remove noise. |
366 | 370 |
367 Args: | 371 Args: |
368 matches: A list containing match results. | 372 matches: A list containing match results. |
369 num_important_frames: A number of frames on the top of the frame to Check | 373 num_important_frames: A number of frames on the top of the frame to Check |
370 for when filtering the results. A match with a file | 374 for when filtering the results. A match with a file |
371 that is in top num_important_frames of the stacktrace | 375 that is in top num_important_frames of the stacktrace |
372 is regarded more probable then others. | 376 is regarded more probable then others. |
373 | 377 |
374 Returns: | 378 Returns: |
375 Filtered match results. | 379 Filtered match results. |
376 """ | 380 """ |
377 new_matches = [] | 381 new_matches = [] |
378 line_changed = False | 382 line_changed = False |
379 is_important_frame = False | 383 is_important_frame = False |
380 highest_priority_stack = crash_utils.INFINITY | 384 highest_priority_stack = crash_utils.INFINITY |
381 matches.sort(key=SortMatchesFunction) | 385 matches.sort(key=SortMatchesFunction) |
382 | |
383 # Iterate through the matches to find out what results are significant. | 386 # Iterate through the matches to find out what results are significant. |
384 for stack_priority, cl, match in matches: | 387 for stack_priority, cl, match in matches: |
385 # Check if the current match changes crashed line. | 388 # Check if the current match changes crashed line. |
386 is_line_change = (min(match.priorities) == LINE_CHANGE_PRIORITY) | 389 is_line_change = (min(match.priorities) == LINE_CHANGE_PRIORITY) |
387 | 390 |
388 # Check which stack this match is from, and finds the highest priority | 391 # Check which stack this match is from, and finds the highest priority |
389 # callstack up to this point. | 392 # callstack up to this point. |
390 current_stack = stack_priority | 393 current_stack = stack_priority |
391 if current_stack < highest_priority_stack: | 394 if current_stack < highest_priority_stack: |
392 highest_priority_stack = current_stack | 395 highest_priority_stack = current_stack |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
427 matches: A list of match objects. | 430 matches: A list of match objects. |
428 """ | 431 """ |
429 # Iterate through the matches in the list. | 432 # Iterate through the matches in the list. |
430 for i, _, match in matches: | 433 for i, _, match in matches: |
431 reason = [] | 434 reason = [] |
432 | 435 |
433 # Zip the files in the match by the reason they are suspected | 436 # Zip the files in the match by the reason they are suspected |
434 # (how the file is modified). | 437 # (how the file is modified). |
435 match_by_priority = zip( | 438 match_by_priority = zip( |
436 match.priorities, match.crashed_line_numbers, match.changed_files, | 439 match.priorities, match.crashed_line_numbers, match.changed_files, |
437 match.changed_file_urls) | 440 match.stack_frame_indices, match.function_list) |
438 | 441 |
439 # Sort the zipped changed files in the match by their priority so that the | 442 # Sort the zipped changed files in the match by their priority so that the |
440 # changed lines comes first in the reason. | 443 # changed lines comes first in the reason. |
441 match_by_priority.sort( | 444 match_by_priority.sort( |
442 key=lambda (priority, crashed_line_number, file_name, url): priority) | 445 key=lambda (priority, crashed_line_numbers, file_name, |
| 446 stack_frame_indices, function_list): priority) |
443 | 447 |
444 # Iterate through the sorted match. | 448 # Iterate through the sorted match. |
445 for i in range(len(match_by_priority)): | 449 for i in range(len(match_by_priority)): |
446 (priority, crashed_line_number, file_name, file_url) = \ | 450 (priority, crashed_line_numbers, file_name, stack_frame_indices, |
447 match_by_priority[i] | 451 function_list) = match_by_priority[i] |
448 | 452 |
449 # If the file in the match is a line change, append a explanation. | 453 # If the file in the match is a line change, append a explanation. |
450 if priority == LINE_CHANGE_PRIORITY: | 454 if priority == LINE_CHANGE_PRIORITY: |
| 455 crashed_line_numbers = [crashed_line_number |
| 456 for lines in crashed_line_numbers |
| 457 for crashed_line_number in lines] |
451 reason.append( | 458 reason.append( |
452 'Line %s of file %s which potentially caused the crash ' | 459 'Line %s of file %s which potentially caused the crash ' |
453 'according to the stacktrace, is changed in this cl.\n' % | 460 'according to the stacktrace, is changed in this cl' |
454 (crash_utils.PrettifyList(crashed_line_number), | 461 ' (From stack frame %s, function %s).' % |
455 crash_utils.PrettifyFiles([(file_name, file_url)]))) | 462 (crash_utils.PrettifyList(crashed_line_numbers), |
| 463 file_name, |
| 464 crash_utils.PrettifyList(stack_frame_indices), |
| 465 crash_utils.PrettifyList(function_list))) |
456 | 466 |
457 else: | 467 else: |
458 # Get all the files that are not line change. | 468 # Get all the files that are not line change. |
459 rest_of_the_files = match_by_priority[i:] | 469 rest_of_the_files = match_by_priority[i:] |
460 | 470 |
461 if len(rest_of_the_files) == 1: | 471 if len(rest_of_the_files) == 1: |
462 file_string = 'File %s is changed in this cl.\n' | 472 file_string = 'File %s is changed in this cl ' |
463 else: | 473 else: |
464 file_string = 'Files %s are changed in this cl.\n' | 474 file_string = 'Files %s are changed in this cl ' |
465 | 475 |
466 # Create a list of file name and its url, and prettify the list. | 476 # Create a list of file names, and prettify the list. |
467 file_name_with_url = [(file_name, file_url) | 477 file_names = [ |
468 for (_, _, file_name, file_url) | 478 file_name for (_, _, file_name, _, _) in rest_of_the_files] |
469 in rest_of_the_files] | 479 pretty_file_names = crash_utils.PrettifyList(file_names) |
470 pretty_file_name_url = crash_utils.PrettifyFiles(file_name_with_url) | |
471 | 480 |
472 # Add the reason, break because we took care of the rest of the files. | 481 # Add the reason, break because we took care of the rest of the files. |
473 reason.append(file_string % pretty_file_name_url) | 482 file_string += ('(From stack frames %s, functions %s)' % |
| 483 (crash_utils.PrettifyList(stack_frame_indices), |
| 484 crash_utils.PrettifyList(function_list))) |
| 485 reason.append(file_string % pretty_file_names) |
474 break | 486 break |
475 | 487 |
476 # Set the reason as string. | 488 # Set the reason as string. |
477 match.reason = ''.join(reason) | 489 match.reason = '\n'.join(reason) |
478 | 490 |
479 | 491 |
480 def CombineMatches(matches): | 492 def CombineMatches(matches): |
481 """Combine possible duplicates in matches. | 493 """Combine possible duplicates in matches. |
482 | 494 |
483 Args: | 495 Args: |
484 matches: A list of matches object, along with its callstack priority and | 496 matches: A list of matches object, along with its callstack priority and |
485 CL it is from. | 497 CL it is from. |
486 Returns: | 498 Returns: |
487 A combined list of matches. | 499 A combined list of matches. |
(...skipping 10 matching lines...) Expand all Loading... |
498 found_match = match_combined | 510 found_match = match_combined |
499 break | 511 break |
500 | 512 |
501 # If current match is not already in, add it to the list of matches. | 513 # If current match is not already in, add it to the list of matches. |
502 if not found_match: | 514 if not found_match: |
503 combined_matches.append((stack_index, cl, match)) | 515 combined_matches.append((stack_index, cl, match)) |
504 continue | 516 continue |
505 | 517 |
506 # Combine the reason if the current match is already in there. | 518 # Combine the reason if the current match is already in there. |
507 found_match.reason += match.reason | 519 found_match.reason += match.reason |
| 520 if match.min_distance < found_match.min_distance: |
| 521 found_match.min_distance = match.min_distance |
| 522 found_match.min_distance_info = match.min_distance_info |
| 523 |
| 524 for stack_index, cl, match in combined_matches: |
| 525 if match.min_distance_info: |
| 526 file_name, min_crashed_line, min_changed_line = match.min_distance_info |
| 527 match.reason += \ |
| 528 ('\nMininimum distance from crashed line to changed line: %d. ' |
| 529 '(File: %s, Crashed on: %d, Changed: %d).' % |
| 530 (match.min_distance, file_name, min_crashed_line, min_changed_line)) |
508 | 531 |
509 return combined_matches | 532 return combined_matches |
510 | 533 |
511 | 534 |
512 def FilterAndGenerateReasonForMatches(result): | 535 def FilterAndGenerateReasonForMatches(result): |
513 """A wrapper function. | 536 """A wrapper function. |
514 | 537 |
515 It generates reasons for the matches and returns string representation | 538 It generates reasons for the matches and returns string representation |
516 of filtered results. | 539 of filtered results. |
517 | 540 |
(...skipping 10 matching lines...) Expand all Loading... |
528 | 551 |
529 | 552 |
530 def ParseCrashComponents(main_stack): | 553 def ParseCrashComponents(main_stack): |
531 """Parses the crashing component. | 554 """Parses the crashing component. |
532 | 555 |
533 Crashing components is a component that top_n_frames of the stacktrace is | 556 Crashing components is a component that top_n_frames of the stacktrace is |
534 from. | 557 from. |
535 | 558 |
536 Args: | 559 Args: |
537 main_stack: Main stack from the stacktrace. | 560 main_stack: Main stack from the stacktrace. |
538 top_n_frames: A number of frames to regard as crashing frame. | |
539 | 561 |
540 Returns: | 562 Returns: |
541 A set of components. | 563 A set of components. |
542 """ | 564 """ |
543 components = set() | 565 components = set() |
544 | 566 |
545 for frame in main_stack.frame_list: | 567 for frame in main_stack.frame_list: |
546 components.add(frame.component_path) | 568 components.add(frame.component_path) |
547 | 569 |
548 return components | 570 return components |
549 | 571 |
550 | 572 |
551 def GenerateAndFilterBlameList(callstack, component_to_crash_revision_dict, | 573 def GenerateAndFilterBlameList(callstack, component_to_crash_revision_dict, |
552 component_to_regression_dict): | 574 component_to_regression_dict): |
553 """A wrapper function. | 575 """A wrapper function. |
554 | 576 |
555 Finds blame information for stack and returns string representation. | 577 Finds blame information for stack and returns string representation. |
556 | 578 |
557 Args: | 579 Args: |
558 callstack: A callstack to find the blame information. | 580 callstack: A callstack to find the blame information. |
559 component_to_crash_revision_dict: A dictionary mapping component to its | 581 component_to_crash_revision_dict: A dictionary mapping component to its |
560 crash revision. | 582 crash revision. |
561 component_to_regression_dict: A dictionary mapping component to its | 583 component_to_regression_dict: A dictionary mapping component to its |
562 regression. | 584 regression. |
563 | 585 |
564 Returns: | 586 Returns: |
565 A list of blame results. | 587 A list of blame results. |
566 """ | 588 """ |
| 589 if component_to_regression_dict: |
| 590 parsed_deps = component_to_regression_dict |
| 591 else: |
| 592 parsed_deps = component_to_crash_revision_dict |
| 593 |
567 # Setup parser objects to use for parsing blame information. | 594 # Setup parser objects to use for parsing blame information. |
568 svn_parser = svn_repository_parser.SVNParser(CONFIG['svn']) | 595 svn_parser = svn_repository_parser.SVNParser(CONFIG['svn']) |
569 git_parser = git_repository_parser.GitParser(component_to_regression_dict, | 596 git_parser = git_repository_parser.GitParser(parsed_deps, CONFIG['git']) |
570 CONFIG['git']) | |
571 parsers = {} | 597 parsers = {} |
572 parsers['svn'] = svn_parser | 598 parsers['svn'] = svn_parser |
573 parsers['git'] = git_parser | 599 parsers['git'] = git_parser |
574 | 600 |
575 # Create and generate the blame objects from the callstack. | 601 # Create and generate the blame objects from the callstack. |
576 blame_list = blame.BlameList() | 602 blame_list = blame.BlameList() |
577 blame_list.FindBlame(callstack, component_to_crash_revision_dict, | 603 blame_list.FindBlame(callstack, component_to_crash_revision_dict, |
578 component_to_regression_dict, | 604 component_to_regression_dict, |
579 parsers) | 605 parsers) |
580 | 606 |
(...skipping 14 matching lines...) Expand all Loading... |
595 no results for all stacktraces in the stacktrace_list. | 621 no results for all stacktraces in the stacktrace_list. |
596 component_to_regression_dict: A parsed regression information as a | 622 component_to_regression_dict: A parsed regression information as a |
597 result of parsing DEPS file. | 623 result of parsing DEPS file. |
598 component_to_crash_revision_dict: A parsed crash revision information. | 624 component_to_crash_revision_dict: A parsed crash revision information. |
599 | 625 |
600 Returns: | 626 Returns: |
601 A list of result objects, with the message how the result is created. | 627 A list of result objects, with the message how the result is created. |
602 """ | 628 """ |
603 # If regression information is not available, return blame information. | 629 # If regression information is not available, return blame information. |
604 if not component_to_regression_dict: | 630 if not component_to_regression_dict: |
605 return_message = ( | |
606 'Regression information is not available. The result is ' | |
607 'the blame information.') | |
608 result = GenerateAndFilterBlameList(callstack, | 631 result = GenerateAndFilterBlameList(callstack, |
609 component_to_crash_revision_dict, | 632 component_to_crash_revision_dict, |
610 component_to_regression_dict) | 633 component_to_regression_dict) |
| 634 if result: |
| 635 return_message = ( |
| 636 'Regression information is not available. The result is ' |
| 637 'the blame information.') |
| 638 else: |
| 639 return_message = ('Findit could not find any suspected CLs.') |
| 640 |
611 return (return_message, result) | 641 return (return_message, result) |
612 | 642 |
613 for stacktrace in stacktrace_list: | 643 for stacktrace in stacktrace_list: |
614 # Check the next stacktrace if current one is empty. | 644 # Check the next stacktrace if current one is empty. |
615 if not stacktrace.stack_list: | 645 if not stacktrace.stack_list: |
616 continue | 646 continue |
617 | 647 |
618 # Get the crash stack for this stacktrace, and extract crashing components | 648 # Get the crash stack for this stacktrace, and extract crashing components |
619 # from it. | 649 # from it. |
620 main_stack = stacktrace.GetCrashStack() | 650 main_stack = stacktrace.GetCrashStack() |
621 components = ParseCrashComponents(main_stack) | 651 components = ParseCrashComponents(main_stack) |
622 | 652 |
623 result_for_stacktrace = FindMatchForStacktrace( | 653 result_for_stacktrace = FindMatchForStacktrace( |
624 stacktrace, components, component_to_regression_dict) | 654 stacktrace, components, component_to_regression_dict) |
| 655 filtered_result = FilterAndGenerateReasonForMatches(result_for_stacktrace) |
625 | 656 |
626 # If the result is empty, check the next stacktrace. Else, return the | 657 # If the result is empty, check the next stacktrace. Else, return the |
627 # filtered result. | 658 # filtered result. |
628 if not result_for_stacktrace: | 659 if not filtered_result: |
629 continue | 660 continue |
630 | 661 |
631 return_message = ( | 662 return_message = ( |
632 'The result is a list of CLs that change the crashed files.') | 663 'The result is a list of CLs that change the crashed files.') |
633 result = FilterAndGenerateReasonForMatches(result_for_stacktrace) | 664 return (return_message, filtered_result) |
634 return (return_message, result) | |
635 | 665 |
636 # If no match is found, return the blame information for the input | 666 # If no match is found, return the blame information for the input |
637 # callstack. | 667 # callstack. |
638 return_message = ( | |
639 'There are no CLs that change the crashed files. The result is the ' | |
640 'blame information.') | |
641 result = GenerateAndFilterBlameList( | 668 result = GenerateAndFilterBlameList( |
642 callstack, component_to_crash_revision_dict, | 669 callstack, component_to_crash_revision_dict, |
643 component_to_regression_dict) | 670 component_to_regression_dict) |
| 671 |
| 672 if result: |
| 673 return_message = ( |
| 674 'No CL in the regression changes the crashed files. The result is ' |
| 675 'the blame information.') |
| 676 |
| 677 # When findit could not find any CL that changes file in stacktrace or if |
| 678 # if cannot get any blame information, return a message saying that no |
| 679 # results are available. |
| 680 else: |
| 681 return_message = ('Findit could not find any suspected CLs.') |
| 682 |
644 return (return_message, result) | 683 return (return_message, result) |
645 | 684 |
OLD | NEW |