Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
michael@0 | 1 | # This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 4 | |
michael@0 | 5 | from __future__ import print_function, unicode_literals |
michael@0 | 6 | |
michael@0 | 7 | import argparse |
michael@0 | 8 | import glob |
michael@0 | 9 | import logging |
michael@0 | 10 | import mozpack.path |
michael@0 | 11 | import os |
michael@0 | 12 | import sys |
michael@0 | 13 | |
michael@0 | 14 | from mozbuild.base import ( |
michael@0 | 15 | MachCommandBase, |
michael@0 | 16 | ) |
michael@0 | 17 | |
michael@0 | 18 | from mach.decorators import ( |
michael@0 | 19 | CommandArgument, |
michael@0 | 20 | CommandProvider, |
michael@0 | 21 | Command, |
michael@0 | 22 | ) |
michael@0 | 23 | |
michael@0 | 24 | |
michael@0 | 25 | @CommandProvider |
michael@0 | 26 | class MachCommands(MachCommandBase): |
michael@0 | 27 | @Command('python', category='devenv', |
michael@0 | 28 | allow_all_args=True, |
michael@0 | 29 | description='Run Python.') |
michael@0 | 30 | @CommandArgument('args', nargs=argparse.REMAINDER) |
michael@0 | 31 | def python(self, args): |
michael@0 | 32 | # Avoid logging the command |
michael@0 | 33 | self.log_manager.terminal_handler.setLevel(logging.CRITICAL) |
michael@0 | 34 | |
michael@0 | 35 | self._activate_virtualenv() |
michael@0 | 36 | |
michael@0 | 37 | return self.run_process([self.virtualenv_manager.python_path] + args, |
michael@0 | 38 | pass_thru=True, # Allow user to run Python interactively. |
michael@0 | 39 | ensure_exit_code=False, # Don't throw on non-zero exit code. |
michael@0 | 40 | # Note: subprocess requires native strings in os.environ on Windows |
michael@0 | 41 | append_env={b'PYTHONDONTWRITEBYTECODE': str('1')}) |
michael@0 | 42 | |
michael@0 | 43 | @Command('python-test', category='testing', |
michael@0 | 44 | description='Run Python unit tests.') |
michael@0 | 45 | @CommandArgument('--verbose', |
michael@0 | 46 | default=False, |
michael@0 | 47 | action='store_true', |
michael@0 | 48 | help='Verbose output.') |
michael@0 | 49 | @CommandArgument('--stop', |
michael@0 | 50 | default=False, |
michael@0 | 51 | action='store_true', |
michael@0 | 52 | help='Stop running tests after the first error or failure.') |
michael@0 | 53 | @CommandArgument('tests', nargs='+', |
michael@0 | 54 | metavar='TEST', |
michael@0 | 55 | help='Tests to run. Each test can be a single file or a directory.') |
michael@0 | 56 | def python_test(self, tests, verbose=False, stop=False): |
michael@0 | 57 | self._activate_virtualenv() |
michael@0 | 58 | |
michael@0 | 59 | # Python's unittest, and in particular discover, has problems with |
michael@0 | 60 | # clashing namespaces when importing multiple test modules. What follows |
michael@0 | 61 | # is a simple way to keep environments separate, at the price of |
michael@0 | 62 | # launching Python multiple times. This also runs tests via mozunit, |
michael@0 | 63 | # which produces output in the format Mozilla infrastructure expects. |
michael@0 | 64 | return_code = 0 |
michael@0 | 65 | files = [] |
michael@0 | 66 | for test in tests: |
michael@0 | 67 | if test.endswith('.py') and os.path.isfile(test): |
michael@0 | 68 | files.append(test) |
michael@0 | 69 | elif os.path.isfile(test + '.py'): |
michael@0 | 70 | files.append(test + '.py') |
michael@0 | 71 | elif os.path.isdir(test): |
michael@0 | 72 | files += glob.glob(mozpack.path.join(test, 'test*.py')) |
michael@0 | 73 | files += glob.glob(mozpack.path.join(test, 'unit*.py')) |
michael@0 | 74 | else: |
michael@0 | 75 | self.log(logging.WARN, 'python-test', {'test': test}, |
michael@0 | 76 | 'TEST-UNEXPECTED-FAIL | Invalid test: {test}') |
michael@0 | 77 | if stop: |
michael@0 | 78 | return 1 |
michael@0 | 79 | |
michael@0 | 80 | for f in files: |
michael@0 | 81 | file_displayed_test = [] # Used as a boolean. |
michael@0 | 82 | def _line_handler(line): |
michael@0 | 83 | if not file_displayed_test and line.startswith('TEST-'): |
michael@0 | 84 | file_displayed_test.append(True) |
michael@0 | 85 | |
michael@0 | 86 | inner_return_code = self.run_process( |
michael@0 | 87 | [self.virtualenv_manager.python_path, f], |
michael@0 | 88 | ensure_exit_code=False, # Don't throw on non-zero exit code. |
michael@0 | 89 | log_name='python-test', |
michael@0 | 90 | # subprocess requires native strings in os.environ on Windows |
michael@0 | 91 | append_env={b'PYTHONDONTWRITEBYTECODE': str('1')}, |
michael@0 | 92 | line_handler=_line_handler) |
michael@0 | 93 | return_code += inner_return_code |
michael@0 | 94 | |
michael@0 | 95 | if not file_displayed_test: |
michael@0 | 96 | self.log(logging.WARN, 'python-test', {'file': f}, |
michael@0 | 97 | 'TEST-UNEXPECTED-FAIL | No test output (missing mozunit.main() call?): {file}') |
michael@0 | 98 | |
michael@0 | 99 | if verbose: |
michael@0 | 100 | if inner_return_code != 0: |
michael@0 | 101 | self.log(logging.INFO, 'python-test', {'file': f}, |
michael@0 | 102 | 'Test failed: {file}') |
michael@0 | 103 | else: |
michael@0 | 104 | self.log(logging.INFO, 'python-test', {'file': f}, |
michael@0 | 105 | 'Test passed: {file}') |
michael@0 | 106 | if stop and return_code > 0: |
michael@0 | 107 | return 1 |
michael@0 | 108 | |
michael@0 | 109 | return 0 if return_code == 0 else 1 |