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