1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/python/mach_commands.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,109 @@ 1.4 +# This Source Code Form is subject to the terms of the Mozilla Public 1.5 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.7 + 1.8 +from __future__ import print_function, unicode_literals 1.9 + 1.10 +import argparse 1.11 +import glob 1.12 +import logging 1.13 +import mozpack.path 1.14 +import os 1.15 +import sys 1.16 + 1.17 +from mozbuild.base import ( 1.18 + MachCommandBase, 1.19 +) 1.20 + 1.21 +from mach.decorators import ( 1.22 + CommandArgument, 1.23 + CommandProvider, 1.24 + Command, 1.25 +) 1.26 + 1.27 + 1.28 +@CommandProvider 1.29 +class MachCommands(MachCommandBase): 1.30 + @Command('python', category='devenv', 1.31 + allow_all_args=True, 1.32 + description='Run Python.') 1.33 + @CommandArgument('args', nargs=argparse.REMAINDER) 1.34 + def python(self, args): 1.35 + # Avoid logging the command 1.36 + self.log_manager.terminal_handler.setLevel(logging.CRITICAL) 1.37 + 1.38 + self._activate_virtualenv() 1.39 + 1.40 + return self.run_process([self.virtualenv_manager.python_path] + args, 1.41 + pass_thru=True, # Allow user to run Python interactively. 1.42 + ensure_exit_code=False, # Don't throw on non-zero exit code. 1.43 + # Note: subprocess requires native strings in os.environ on Windows 1.44 + append_env={b'PYTHONDONTWRITEBYTECODE': str('1')}) 1.45 + 1.46 + @Command('python-test', category='testing', 1.47 + description='Run Python unit tests.') 1.48 + @CommandArgument('--verbose', 1.49 + default=False, 1.50 + action='store_true', 1.51 + help='Verbose output.') 1.52 + @CommandArgument('--stop', 1.53 + default=False, 1.54 + action='store_true', 1.55 + help='Stop running tests after the first error or failure.') 1.56 + @CommandArgument('tests', nargs='+', 1.57 + metavar='TEST', 1.58 + help='Tests to run. Each test can be a single file or a directory.') 1.59 + def python_test(self, tests, verbose=False, stop=False): 1.60 + self._activate_virtualenv() 1.61 + 1.62 + # Python's unittest, and in particular discover, has problems with 1.63 + # clashing namespaces when importing multiple test modules. What follows 1.64 + # is a simple way to keep environments separate, at the price of 1.65 + # launching Python multiple times. This also runs tests via mozunit, 1.66 + # which produces output in the format Mozilla infrastructure expects. 1.67 + return_code = 0 1.68 + files = [] 1.69 + for test in tests: 1.70 + if test.endswith('.py') and os.path.isfile(test): 1.71 + files.append(test) 1.72 + elif os.path.isfile(test + '.py'): 1.73 + files.append(test + '.py') 1.74 + elif os.path.isdir(test): 1.75 + files += glob.glob(mozpack.path.join(test, 'test*.py')) 1.76 + files += glob.glob(mozpack.path.join(test, 'unit*.py')) 1.77 + else: 1.78 + self.log(logging.WARN, 'python-test', {'test': test}, 1.79 + 'TEST-UNEXPECTED-FAIL | Invalid test: {test}') 1.80 + if stop: 1.81 + return 1 1.82 + 1.83 + for f in files: 1.84 + file_displayed_test = [] # Used as a boolean. 1.85 + def _line_handler(line): 1.86 + if not file_displayed_test and line.startswith('TEST-'): 1.87 + file_displayed_test.append(True) 1.88 + 1.89 + inner_return_code = self.run_process( 1.90 + [self.virtualenv_manager.python_path, f], 1.91 + ensure_exit_code=False, # Don't throw on non-zero exit code. 1.92 + log_name='python-test', 1.93 + # subprocess requires native strings in os.environ on Windows 1.94 + append_env={b'PYTHONDONTWRITEBYTECODE': str('1')}, 1.95 + line_handler=_line_handler) 1.96 + return_code += inner_return_code 1.97 + 1.98 + if not file_displayed_test: 1.99 + self.log(logging.WARN, 'python-test', {'file': f}, 1.100 + 'TEST-UNEXPECTED-FAIL | No test output (missing mozunit.main() call?): {file}') 1.101 + 1.102 + if verbose: 1.103 + if inner_return_code != 0: 1.104 + self.log(logging.INFO, 'python-test', {'file': f}, 1.105 + 'Test failed: {file}') 1.106 + else: 1.107 + self.log(logging.INFO, 'python-test', {'file': f}, 1.108 + 'Test passed: {file}') 1.109 + if stop and return_code > 0: 1.110 + return 1 1.111 + 1.112 + return 0 if return_code == 0 else 1