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

Side by Side Diff: presubmit_canned_checks.py

Issue 14247012: Add support for parallel presubmit unit testing. (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: fix nits Created 7 years, 8 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
« no previous file with comments | « PRESUBMIT.py ('k') | presubmit_support.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 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 # Copyright (c) 2012 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 """Generic presubmit checks that can be reused by other presubmit checks.""" 5 """Generic presubmit checks that can be reused by other presubmit checks."""
6 6
7 import os as _os 7 import os as _os
8 _HERE = _os.path.dirname(_os.path.abspath(__file__)) 8 _HERE = _os.path.dirname(_os.path.abspath(__file__))
9 9
10 10
(...skipping 465 matching lines...) Expand 10 before | Expand all | Expand 10 after
476 connection.close() 476 connection.close()
477 if input_api.re.match(closed, status): 477 if input_api.re.match(closed, status):
478 long_text = status + '\n' + url 478 long_text = status + '\n' + url
479 return [output_api.PresubmitError('The tree is closed.', 479 return [output_api.PresubmitError('The tree is closed.',
480 long_text=long_text)] 480 long_text=long_text)]
481 except IOError as e: 481 except IOError as e:
482 return [output_api.PresubmitError('Error fetching tree status.', 482 return [output_api.PresubmitError('Error fetching tree status.',
483 long_text=str(e))] 483 long_text=str(e))]
484 return [] 484 return []
485 485
486 486 def GetUnitTestsInDirectory(
487 def RunUnitTestsInDirectory(
488 input_api, output_api, directory, whitelist=None, blacklist=None): 487 input_api, output_api, directory, whitelist=None, blacklist=None):
489 """Lists all files in a directory and runs them. Doesn't recurse. 488 """Lists all files in a directory and runs them. Doesn't recurse.
490 489
491 It's mainly a wrapper for RunUnitTests. USe whitelist and blacklist to filter 490 It's mainly a wrapper for RunUnitTests. USe whitelist and blacklist to filter
492 tests accordingly. 491 tests accordingly.
493 """ 492 """
494 unit_tests = [] 493 unit_tests = []
495 test_path = input_api.os_path.abspath( 494 test_path = input_api.os_path.abspath(
496 input_api.os_path.join(input_api.PresubmitLocalPath(), directory)) 495 input_api.os_path.join(input_api.PresubmitLocalPath(), directory))
497 496
(...skipping 12 matching lines...) Expand all
510 continue 509 continue
511 unit_tests.append(input_api.os_path.join(directory, filename)) 510 unit_tests.append(input_api.os_path.join(directory, filename))
512 to_run += 1 511 to_run += 1
513 input_api.logging.debug('Found %d files, running %d' % (found, to_run)) 512 input_api.logging.debug('Found %d files, running %d' % (found, to_run))
514 if not to_run: 513 if not to_run:
515 return [ 514 return [
516 output_api.PresubmitPromptWarning( 515 output_api.PresubmitPromptWarning(
517 'Out of %d files, found none that matched w=%r, b=%r in directory %s' 516 'Out of %d files, found none that matched w=%r, b=%r in directory %s'
518 % (found, whitelist, blacklist, directory)) 517 % (found, whitelist, blacklist, directory))
519 ] 518 ]
520 return RunUnitTests(input_api, output_api, unit_tests) 519 return GetUnitTests(input_api, output_api, unit_tests)
521 520
522 521
523 def RunUnitTests(input_api, output_api, unit_tests): 522 def GetUnitTests(input_api, output_api, unit_tests):
524 """Runs all unit tests in a directory. 523 """Runs all unit tests in a directory.
525 524
526 On Windows, sys.executable is used for unit tests ending with ".py". 525 On Windows, sys.executable is used for unit tests ending with ".py".
527 """ 526 """
528 # We don't want to hinder users from uploading incomplete patches. 527 # We don't want to hinder users from uploading incomplete patches.
529 if input_api.is_committing: 528 if input_api.is_committing:
530 message_type = output_api.PresubmitError 529 message_type = output_api.PresubmitError
531 else: 530 else:
532 message_type = output_api.PresubmitPromptWarning 531 message_type = output_api.PresubmitPromptWarning
533 532
534 results = [] 533 results = []
535 for unit_test in unit_tests: 534 for unit_test in unit_tests:
536 cmd = [] 535 cmd = []
537 if input_api.platform == 'win32' and unit_test.endswith('.py'): 536 if input_api.platform == 'win32' and unit_test.endswith('.py'):
538 # Windows needs some help. 537 # Windows needs some help.
539 cmd = [input_api.python_executable] 538 cmd = [input_api.python_executable]
540 cmd.append(unit_test) 539 cmd.append(unit_test)
541 if input_api.verbose: 540 if input_api.verbose:
542 print('Running %s' % unit_test) 541 print('Running %s' % unit_test)
543 cmd.append('--verbose') 542 cmd.append('--verbose')
544 try: 543 results.append(input_api.Command(
545 if input_api.verbose: 544 name=unit_test,
546 input_api.subprocess.check_call(cmd, cwd=input_api.PresubmitLocalPath()) 545 cmd=cmd,
547 else: 546 kwargs={'cwd': input_api.PresubmitLocalPath()},
548 input_api.subprocess.check_output( 547 message=message_type))
549 cmd,
550 stderr=input_api.subprocess.STDOUT,
551 cwd=input_api.PresubmitLocalPath())
552 except (OSError, input_api.subprocess.CalledProcessError), e:
553 results.append(message_type('%s failed!\n%s' % (unit_test, e)))
554 return results 548 return results
555 549
556 550 def GetPythonUnitTests(input_api, output_api, unit_tests):
557 def RunPythonUnitTests(input_api, output_api, unit_tests):
558 """Run the unit tests out of process, capture the output and use the result 551 """Run the unit tests out of process, capture the output and use the result
559 code to determine success. 552 code to determine success.
560 553
561 DEPRECATED. 554 DEPRECATED.
562 """ 555 """
563 # We don't want to hinder users from uploading incomplete patches. 556 # We don't want to hinder users from uploading incomplete patches.
564 if input_api.is_committing: 557 if input_api.is_committing:
565 message_type = output_api.PresubmitError 558 message_type = output_api.PresubmitError
566 else: 559 else:
567 message_type = output_api.PresubmitNotifyResult 560 message_type = output_api.PresubmitNotifyResult
(...skipping 15 matching lines...) Expand all
583 unit_test = input_api.os_path.basename(unit_test) 576 unit_test = input_api.os_path.basename(unit_test)
584 env = input_api.environ.copy() 577 env = input_api.environ.copy()
585 # At least on Windows, it seems '.' must explicitly be in PYTHONPATH 578 # At least on Windows, it seems '.' must explicitly be in PYTHONPATH
586 backpath = [ 579 backpath = [
587 '.', input_api.os_path.pathsep.join(['..'] * (cwd.count('/') + 1)) 580 '.', input_api.os_path.pathsep.join(['..'] * (cwd.count('/') + 1))
588 ] 581 ]
589 if env.get('PYTHONPATH'): 582 if env.get('PYTHONPATH'):
590 backpath.append(env.get('PYTHONPATH')) 583 backpath.append(env.get('PYTHONPATH'))
591 env['PYTHONPATH'] = input_api.os_path.pathsep.join((backpath)) 584 env['PYTHONPATH'] = input_api.os_path.pathsep.join((backpath))
592 cmd = [input_api.python_executable, '-m', '%s' % unit_test] 585 cmd = [input_api.python_executable, '-m', '%s' % unit_test]
593 try: 586 results.append(input_api.Command(
594 input_api.subprocess.check_output( 587 name=unit_test_name,
595 cmd, stderr=input_api.subprocess.STDOUT, cwd=cwd, env=env) 588 cmd=cmd,
596 except (OSError, input_api.subprocess.CalledProcessError), e: 589 kwargs={'env': env, 'cwd': cwd},
597 results.append(message_type('%s failed!\n%s' % (unit_test_name, e))) 590 message=message_type))
598 return results 591 return results
599 592
600 593
594 def RunUnitTestsInDirectory(input_api, *args, **kwargs):
595 """Run tests in a directory serially.
596
597 For better performance, use GetUnitTestsInDirectory and then
598 pass to input_api.RunTests.
599 """
600 return input_api.RunTests(
601 GetUnitTestsInDirectory(input_api, *args, **kwargs), False)
602
603
604 def RunUnitTests(input_api, *args, **kwargs):
605 """Run tests serially.
606
607 For better performance, use GetUnitTests and then pass to
608 input_api.RunTests.
609 """
610 return input_api.RunTests(GetUnitTests(input_api, *args, **kwargs), False)
611
612
613 def RunPythonUnitTests(input_api, *args, **kwargs):
614 """Run python tests in a directory serially.
615
616 DEPRECATED
617 """
618 return input_api.RunTests(
619 GetPythonUnitTests(input_api, *args, **kwargs), False)
620
621
601 def _FetchAllFiles(input_api, white_list, black_list): 622 def _FetchAllFiles(input_api, white_list, black_list):
602 """Hack to fetch all files.""" 623 """Hack to fetch all files."""
603 # We cannot use AffectedFiles here because we want to test every python 624 # We cannot use AffectedFiles here because we want to test every python
604 # file on each single python change. It's because a change in a python file 625 # file on each single python change. It's because a change in a python file
605 # can break another unmodified file. 626 # can break another unmodified file.
606 # Use code similar to InputApi.FilterSourceFile() 627 # Use code similar to InputApi.FilterSourceFile()
607 def Find(filepath, filters): 628 def Find(filepath, filters):
608 for item in filters: 629 for item in filters:
609 if input_api.re.match(item, filepath): 630 if input_api.re.match(item, filepath):
610 return True 631 return True
611 return False 632 return False
612 633
613 files = [] 634 files = []
614 path_len = len(input_api.PresubmitLocalPath()) 635 path_len = len(input_api.PresubmitLocalPath())
615 for dirpath, dirnames, filenames in input_api.os_walk( 636 for dirpath, dirnames, filenames in input_api.os_walk(
616 input_api.PresubmitLocalPath()): 637 input_api.PresubmitLocalPath()):
617 # Passes dirnames in black list to speed up search. 638 # Passes dirnames in black list to speed up search.
618 for item in dirnames[:]: 639 for item in dirnames[:]:
619 filepath = input_api.os_path.join(dirpath, item)[path_len + 1:] 640 filepath = input_api.os_path.join(dirpath, item)[path_len + 1:]
620 if Find(filepath, black_list): 641 if Find(filepath, black_list):
621 dirnames.remove(item) 642 dirnames.remove(item)
622 for item in filenames: 643 for item in filenames:
623 filepath = input_api.os_path.join(dirpath, item)[path_len + 1:] 644 filepath = input_api.os_path.join(dirpath, item)[path_len + 1:]
624 if Find(filepath, white_list) and not Find(filepath, black_list): 645 if Find(filepath, white_list) and not Find(filepath, black_list):
625 files.append(filepath) 646 files.append(filepath)
626 return files 647 return files
627 648
628 649
629 def RunPylint(input_api, output_api, white_list=None, black_list=None, 650 def GetPylint(input_api, output_api, white_list=None, black_list=None,
630 disabled_warnings=None, extra_paths_list=None): 651 disabled_warnings=None, extra_paths_list=None):
631 """Run pylint on python files. 652 """Run pylint on python files.
632 653
633 The default white_list enforces looking only at *.py files. 654 The default white_list enforces looking only at *.py files.
634 """ 655 """
635 white_list = tuple(white_list or ('.*\.py$',)) 656 white_list = tuple(white_list or ('.*\.py$',))
636 black_list = tuple(black_list or input_api.DEFAULT_BLACK_LIST) 657 black_list = tuple(black_list or input_api.DEFAULT_BLACK_LIST)
637 extra_paths_list = extra_paths_list or [] 658 extra_paths_list = extra_paths_list or []
638 659
639 if input_api.is_committing: 660 if input_api.is_committing:
(...skipping 22 matching lines...) Expand all
662 input_api.logging.info('Skipping pylint: no matching changes.') 683 input_api.logging.info('Skipping pylint: no matching changes.')
663 return [] 684 return []
664 685
665 extra_args = ['--rcfile=%s' % input_api.os_path.join(_HERE, 'pylintrc')] 686 extra_args = ['--rcfile=%s' % input_api.os_path.join(_HERE, 'pylintrc')]
666 if disabled_warnings: 687 if disabled_warnings:
667 extra_args.extend(['-d', ','.join(disabled_warnings)]) 688 extra_args.extend(['-d', ','.join(disabled_warnings)])
668 689
669 files = _FetchAllFiles(input_api, white_list, black_list) 690 files = _FetchAllFiles(input_api, white_list, black_list)
670 if not files: 691 if not files:
671 return [] 692 return []
693 files.sort()
672 694
673 input_api.logging.info('Running pylint on %d files', len(files)) 695 input_api.logging.info('Running pylint on %d files', len(files))
674 input_api.logging.debug('Running pylint on: %s', files) 696 input_api.logging.debug('Running pylint on: %s', files)
675 # Copy the system path to the environment so pylint can find the right 697 # Copy the system path to the environment so pylint can find the right
676 # imports. 698 # imports.
677 env = input_api.environ.copy() 699 env = input_api.environ.copy()
678 import sys 700 import sys
679 env['PYTHONPATH'] = input_api.os_path.pathsep.join( 701 env['PYTHONPATH'] = input_api.os_path.pathsep.join(
680 extra_paths_list + sys.path).encode('utf8') 702 extra_paths_list + sys.path).encode('utf8')
681 703
682 def run_lint(files): 704 def GetPylintCmd(files):
683 # We can't import pylint directly due to licensing issues, so we run 705 # Windows needs help running python files so we explicitly specify
684 # it in another process. Windows needs help running python files so we 706 # the interpreter to use. It also has limitations on the size of
685 # explicitly specify the interpreter to use. It also has limitations on 707 # the command-line, so we pass arguments via a pipe.
686 # the size of the command-line, so we pass arguments via a pipe. 708 if len(files) == 1:
687 command = [input_api.python_executable, 709 description = files[0]
688 input_api.os_path.join(_HERE, 'third_party', 'pylint.py'), 710 else:
689 '--args-on-stdin'] 711 description = '%s files' % len(files)
690 try:
691 child = input_api.subprocess.Popen(command, env=env,
692 stdin=input_api.subprocess.PIPE)
693 712
694 # Dump the arguments to the child process via a pipe. 713 return input_api.Command(
695 for filename in files: 714 name='Pylint (%s)' % description,
696 child.stdin.write(filename + '\n') 715 cmd=[input_api.python_executable,
697 for arg in extra_args: 716 input_api.os_path.join(_HERE, 'third_party', 'pylint.py'),
698 child.stdin.write(arg + '\n') 717 '--args-on-stdin'],
699 child.stdin.close() 718 kwargs={'env': env, 'stdin': '\n'.join(files + extra_args)},
719 message=error_type)
700 720
701 child.communicate()
702 return child.returncode
703 except OSError:
704 return 'Pylint failed!'
705
706 result = None
707 # Always run pylint and pass it all the py files at once. 721 # Always run pylint and pass it all the py files at once.
708 # Passing py files one at time is slower and can produce 722 # Passing py files one at time is slower and can produce
709 # different results. input_api.verbose used to be used 723 # different results. input_api.verbose used to be used
710 # to enable this behaviour but differing behaviour in 724 # to enable this behaviour but differing behaviour in
711 # verbose mode is not desirable. 725 # verbose mode is not desirable.
712 # Leave this unreachable code in here so users can make 726 # Leave this unreachable code in here so users can make
713 # a quick local edit to diagnose pylint issues more 727 # a quick local edit to diagnose pylint issues more
714 # easily. 728 # easily.
715 if True: 729 if True:
716 print('Running pylint on %d files.' % len(files)) 730 return [GetPylintCmd(files)]
717 result = run_lint(sorted(files))
718 else: 731 else:
719 for filename in sorted(files): 732 return map(GetPylintCmd, files)
720 print('Running pylint on %s' % filename) 733
721 result = run_lint([filename]) or result 734
722 if isinstance(result, basestring): 735 def RunPylint(input_api, *args, **kwargs):
723 return [error_type(result)] 736 """Legacy presubmit function.
724 elif result: 737
725 return [error_type('Fix pylint errors first.')] 738 For better performance, get all tests and then pass to
726 return [] 739 input_api.RunTests.
740 """
741 return input_api.RunTests(GetPylint(input_api, *args, **kwargs), False)
727 742
728 743
729 # TODO(dpranke): Get the host_url from the input_api instead 744 # TODO(dpranke): Get the host_url from the input_api instead
730 def CheckRietveldTryJobExecution(dummy_input_api, dummy_output_api, 745 def CheckRietveldTryJobExecution(dummy_input_api, dummy_output_api,
731 dummy_host_url, dummy_platforms, 746 dummy_host_url, dummy_platforms,
732 dummy_owner): 747 dummy_owner):
733 # Temporarily 'fix' the check while the Rietveld API is being upgraded to 748 # Temporarily 'fix' the check while the Rietveld API is being upgraded to
734 # something sensible. 749 # something sensible.
735 return [] 750 return []
736 751
(...skipping 269 matching lines...) Expand 10 before | Expand all | Expand 10 after
1006 snapshot("checking description") 1021 snapshot("checking description")
1007 results.extend(input_api.canned_checks.CheckChangeHasDescription( 1022 results.extend(input_api.canned_checks.CheckChangeHasDescription(
1008 input_api, output_api)) 1023 input_api, output_api))
1009 results.extend(input_api.canned_checks.CheckDoNotSubmitInDescription( 1024 results.extend(input_api.canned_checks.CheckDoNotSubmitInDescription(
1010 input_api, output_api)) 1025 input_api, output_api))
1011 snapshot("checking do not submit in files") 1026 snapshot("checking do not submit in files")
1012 results.extend(input_api.canned_checks.CheckDoNotSubmitInFiles( 1027 results.extend(input_api.canned_checks.CheckDoNotSubmitInFiles(
1013 input_api, output_api)) 1028 input_api, output_api))
1014 snapshot("done") 1029 snapshot("done")
1015 return results 1030 return results
OLDNEW
« no previous file with comments | « PRESUBMIT.py ('k') | presubmit_support.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698