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/.
5 from __future__
import print_function
, unicode_literals
14 from mozbuild
.base
import (
18 from mach
.decorators
import (
26 class MachCommands(MachCommandBase
):
27 @Command('python', category
='devenv',
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
)
35 self
._activate
_virtualenv
()
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')})
43 @Command('python-test', category
='testing',
44 description
='Run Python unit tests.')
45 @CommandArgument('--verbose',
48 help='Verbose output.')
49 @CommandArgument('--stop',
52 help='Stop running tests after the first error or failure.')
53 @CommandArgument('tests', nargs
='+',
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
()
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.
66 for test
in [mozpack
.path
.join(self
.topsrcdir
, t
) for t
in tests
]:
67 if test
.endswith('.py') and os
.path
.isfile(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'))
75 self
.log(logging
.WARN
, 'python-test',
76 {'test': mozpack
.path
.relpath(test
, self
.topsrcdir
)},
77 'TEST-UNEXPECTED-FAIL | Invalid test: {test}')
82 file_displayed_test
= [] # Used as a boolean.
83 def _line_handler(line
):
84 if not file_displayed_test
and line
.startswith('TEST-'):
85 file_displayed_test
.append(True)
87 inner_return_code
= self
.run_process(
88 [self
.virtualenv_manager
.python_path
, f
],
89 ensure_exit_code
=False, # Don't throw on non-zero exit code.
90 log_name
='python-test',
91 # subprocess requires native strings in os.environ on Windows
92 append_env
={b
'PYTHONDONTWRITEBYTECODE': str('1')},
93 line_handler
=_line_handler
)
94 return_code
+= inner_return_code
96 if not file_displayed_test
:
97 self
.log(logging
.WARN
, 'python-test', {'file': f
},
98 'TEST-UNEXPECTED-FAIL | No test output (missing mozunit.main() call?): {file}')
101 if inner_return_code
!= 0:
102 self
.log(logging
.INFO
, 'python-test', {'file': f
},
103 'Test failed: {file}')
105 self
.log(logging
.INFO
, 'python-test', {'file': f
},
106 'Test passed: {file}')
107 if stop
and return_code
> 0:
110 return 0 if return_code
== 0 else 1