Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(183)

Side by Side Diff: scripts/common/gtest_utils.py

Issue 373223003: Implemented parsing of the ignored failing tests spec and ignoring respective failures. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Fixed a typo Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | scripts/common/unittests/gtest_utils_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 from common import chromium_utils
6 import json 7 import json
7 import os 8 import os
8 import re 9 import re
9 import tempfile 10 import tempfile
10 11
11 12
12 def CompressList(lines, max_length, middle_replacement): 13 def CompressList(lines, max_length, middle_replacement):
13 """Ensures that |lines| is no longer than |max_length|. If |lines| need to 14 """Ensures that |lines| is no longer than |max_length|. If |lines| need to
14 be compressed then the middle items are replaced by |middle_replacement|. 15 be compressed then the middle items are replaced by |middle_replacement|.
15 """ 16 """
(...skipping 396 matching lines...) Expand 10 before | Expand all | Expand 10 after
412 413
413 def __init__(self): 414 def __init__(self):
414 self.json_file_path = None 415 self.json_file_path = None
415 self.delete_json_file = False 416 self.delete_json_file = False
416 417
417 self.disabled_tests = set() 418 self.disabled_tests = set()
418 self.passed_tests = set() 419 self.passed_tests = set()
419 self.failed_tests = set() 420 self.failed_tests = set()
420 self.flaky_tests = set() 421 self.flaky_tests = set()
421 self.test_logs = {} 422 self.test_logs = {}
423 self.ignored_failed_tests = set()
422 424
423 self.parsing_errors = [] 425 self.parsing_errors = []
424 426
425 self.master_name = None 427 self.master_name = None
426 428
427 def ProcessLine(self, line): 429 def ProcessLine(self, line):
428 # Deliberately do nothing - we parse out-of-band JSON summary 430 # Deliberately do nothing - we parse out-of-band JSON summary
429 # instead of in-band stdout. 431 # instead of in-band stdout.
430 pass 432 pass
431 433
432 def PassedTests(self): 434 def PassedTests(self):
433 return sorted(self.passed_tests) 435 return sorted(self.passed_tests)
434 436
435 def FailedTests(self, include_fails=False, include_flaky=False): 437 def FailedTests(self, include_fails=False, include_flaky=False):
436 return sorted(self.failed_tests) 438 return sorted(self.failed_tests - self.ignored_failed_tests)
437 439
438 def FailureDescription(self, test): 440 def FailureDescription(self, test):
439 return self.test_logs.get(test, []) 441 return self.test_logs.get(test, [])
440 442
443 def IgnoredFailedTests(self):
444 return sorted(self.ignored_failed_tests)
445
441 @staticmethod 446 @staticmethod
442 def SuppressionHashes(): 447 def SuppressionHashes():
443 return [] 448 return []
444 449
445 def ParsingErrors(self): 450 def ParsingErrors(self):
446 return self.parsing_errors 451 return self.parsing_errors
447 452
448 def ClearParsingErrors(self): 453 def ClearParsingErrors(self):
449 self.parsing_errors = ['Cleared.'] 454 self.parsing_errors = ['Cleared.']
450 455
(...skipping 12 matching lines...) Expand all
463 self.json_file_path = cmdline_path 468 self.json_file_path = cmdline_path
464 # If the caller requested JSON summary, do not delete it. 469 # If the caller requested JSON summary, do not delete it.
465 self.delete_json_file = False 470 self.delete_json_file = False
466 else: 471 else:
467 fd, self.json_file_path = tempfile.mkstemp() 472 fd, self.json_file_path = tempfile.mkstemp()
468 os.close(fd) 473 os.close(fd)
469 # When we create the file ourselves, delete it to avoid littering. 474 # When we create the file ourselves, delete it to avoid littering.
470 self.delete_json_file = True 475 self.delete_json_file = True
471 return self.json_file_path 476 return self.json_file_path
472 477
473 def ProcessJSONFile(self): 478 def ProcessJSONFile(self, build_dir):
474 if not self.json_file_path: 479 if not self.json_file_path:
475 return 480 return
476 481
477 with open(self.json_file_path) as json_file: 482 with open(self.json_file_path) as json_file:
478 try: 483 try:
479 json_output = json_file.read() 484 json_output = json_file.read()
480 json_data = json.loads(json_output) 485 json_data = json.loads(json_output)
481 except ValueError: 486 except ValueError:
482 # Only signal parsing error if the file is non-empty. Empty file 487 # Only signal parsing error if the file is non-empty. Empty file
483 # most likely means the binary doesn't support JSON output. 488 # most likely means the binary doesn't support JSON output.
484 if json_output: 489 if json_output:
485 self.parsing_errors = json_output.split('\n') 490 self.parsing_errors = json_output.split('\n')
486 else: 491 else:
487 self.ProcessJSONData(json_data) 492 self.ProcessJSONData(json_data, build_dir)
488 493
489 if self.delete_json_file: 494 if self.delete_json_file:
490 os.remove(self.json_file_path) 495 os.remove(self.json_file_path)
491 496
492 def ProcessJSONData(self, json_data): 497 @staticmethod
498 def ParseIgnoredFailedTestSpec(dir_in_chrome):
499 """Returns parsed ignored failed test spec.
500
501 Args:
502 dir_in_chrome: Any directory within chrome checkout to be used as a
503 reference to find ignored failed test spec file.
504
505 Returns:
506 A list of tuples (test_name, platforms), where platforms is a list of sets
507 of platform flags. For example:
508
509 [('MyTest.TestOne', [set('OS_WIN', 'CPU_32_BITS', 'MODE_RELEASE'),
510 set('OS_LINUX', 'CPU_64_BITS', 'MODE_DEBUG')]),
511 ('MyTest.TestTwo', [set('OS_MACOSX', 'CPU_64_BITS', 'MODE_RELEASE'),
512 set('CPU_32_BITS')]),
513 ('MyTest.TestThree', [set()]]
514 """
515
516 try:
517 ignored_failed_tests_path = chromium_utils.FindUpward(
518 os.path.abspath(dir_in_chrome), 'tools', 'ignorer_bot',
519 'ignored_failed_tests.txt')
520 except chromium_utils.PathNotFound:
521 return
522
523 with open(ignored_failed_tests_path) as ignored_failed_tests_file:
524 ignored_failed_tests_spec = ignored_failed_tests_file.readlines()
525
526 parsed_spec = []
527 for spec_line in ignored_failed_tests_spec:
528 spec_line = spec_line.strip()
529 if spec_line.startswith('#') or not spec_line:
530 continue
531
532 # Any number of platform flags identifiers separated by whitespace.
533 platform_spec_regexp = r'[A-Za-z0-9_\s]*'
534
535 match = re.match(
536 r'^crbug.com/\d+' # Issue URL.
537 r'\s+' # Some whitespace.
538 r'\[(' + # Opening square bracket '['.
539 platform_spec_regexp + # At least one platform, and...
540 r'(?:,' + # ...separated by commas...
541 platform_spec_regexp + # ...any number of additional...
542 r')*' # ...platforms.
543 r')\]' # Closing square bracket ']'.
544 r'\s+' # Some whitespace.
545 r'(\S+)$', spec_line) # Test name.
546
547 if not match:
548 continue
549
550 platform_specs = match.group(1).strip()
551 test_name = match.group(2).strip()
552
553 platforms = [set(platform.split())
554 for platform in platform_specs.split(',')]
555
556 parsed_spec.append((test_name, platforms))
557
558 return parsed_spec
559
560
561 def _RetrieveIgnoredFailuresForPlatform(self, build_dir, platform_flags):
562 """Parses the ignored failed tests spec into self.ignored_failed_tests."""
563 if not build_dir:
564 return
565
566 platform_flags = set(platform_flags)
567 parsed_spec = self.ParseIgnoredFailedTestSpec(build_dir)
568
569 if not parsed_spec:
570 return
571
572 for test_name, platforms in parsed_spec:
573 for required_platform_flags in platforms:
574 if required_platform_flags.issubset(platform_flags):
575 self.ignored_failed_tests.add(test_name)
576 break
577
578 def ProcessJSONData(self, json_data, build_dir=None):
493 # TODO(phajdan.jr): Require disabled_tests to be present (May 2014). 579 # TODO(phajdan.jr): Require disabled_tests to be present (May 2014).
494 self.disabled_tests.update(json_data.get('disabled_tests', [])) 580 self.disabled_tests.update(json_data.get('disabled_tests', []))
581 self._RetrieveIgnoredFailuresForPlatform(build_dir,
582 json_data.get('global_tags', []))
495 583
496 for iteration_data in json_data['per_iteration_data']: 584 for iteration_data in json_data['per_iteration_data']:
497 for test_name, test_runs in iteration_data.iteritems(): 585 for test_name, test_runs in iteration_data.iteritems():
498 if test_runs[-1]['status'] == 'SUCCESS': 586 if test_runs[-1]['status'] == 'SUCCESS':
499 self.passed_tests.add(test_name) 587 self.passed_tests.add(test_name)
500 else: 588 else:
501 self.failed_tests.add(test_name) 589 self.failed_tests.add(test_name)
502 590
503 if len(test_runs) > 1: 591 if len(test_runs) > 1:
504 self.flaky_tests.add(test_name) 592 self.flaky_tests.add(test_name)
505 593
506 self.test_logs.setdefault(test_name, []) 594 self.test_logs.setdefault(test_name, [])
507 for run_index, run_data in enumerate(test_runs, start=1): 595 for run_index, run_data in enumerate(test_runs, start=1):
508 run_lines = ['%s (run #%d):' % (test_name, run_index)] 596 run_lines = ['%s (run #%d):' % (test_name, run_index)]
509 # Make sure the annotations are ASCII to avoid character set related 597 # Make sure the annotations are ASCII to avoid character set related
510 # errors. They are mostly informational anyway, and more detailed 598 # errors. They are mostly informational anyway, and more detailed
511 # info can be obtained from the original JSON output. 599 # info can be obtained from the original JSON output.
512 ascii_lines = run_data['output_snippet'].encode('ascii', 600 ascii_lines = run_data['output_snippet'].encode('ascii',
513 errors='replace') 601 errors='replace')
514 decoded_lines = CompressList( 602 decoded_lines = CompressList(
515 ascii_lines.decode('string_escape').split('\n'), 603 ascii_lines.decode('string_escape').split('\n'),
516 self.OUTPUT_SNIPPET_LINES_LIMIT, 604 self.OUTPUT_SNIPPET_LINES_LIMIT,
517 '<truncated, full output is in gzipped JSON ' 605 '<truncated, full output is in gzipped JSON '
518 'output at end of step>') 606 'output at end of step>')
519 run_lines.extend(decoded_lines) 607 run_lines.extend(decoded_lines)
520 self.test_logs[test_name].extend(run_lines) 608 self.test_logs[test_name].extend(run_lines)
OLDNEW
« no previous file with comments | « no previous file | scripts/common/unittests/gtest_utils_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698