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',
28 description
='Run Python.')
29 @CommandArgument('args', nargs
=argparse
.REMAINDER
)
30 def python(self
, args
):
31 # Avoid logging the command
32 self
.log_manager
.terminal_handler
.setLevel(logging
.CRITICAL
)
34 self
._activate
_virtualenv
()
36 return self
.run_process([self
.virtualenv_manager
.python_path
] + args
,
37 pass_thru
=True, # Allow user to run Python interactively.
38 ensure_exit_code
=False, # Don't throw on non-zero exit code.
39 # Note: subprocess requires native strings in os.environ on Windows
40 append_env
={b
'PYTHONDONTWRITEBYTECODE': str('1')})
42 @Command('python-test', category
='testing',
43 description
='Run Python unit tests.')
44 @CommandArgument('--verbose',
47 help='Verbose output.')
48 @CommandArgument('--stop',
51 help='Stop running tests after the first error or failure.')
52 @CommandArgument('tests', nargs
='+',
54 help='Tests to run. Each test can be a single file or a directory.')
55 def python_test(self
, tests
, verbose
=False, stop
=False):
56 self
._activate
_virtualenv
()
58 # Python's unittest, and in particular discover, has problems with
59 # clashing namespaces when importing multiple test modules. What follows
60 # is a simple way to keep environments separate, at the price of
61 # launching Python multiple times. This also runs tests via mozunit,
62 # which produces output in the format Mozilla infrastructure expects.
65 # We search for files in both the current directory (for people running
66 # from topsrcdir or cd'd into their test directory) and topsrcdir (to
67 # support people running mach from the objdir). The |break|s in the
68 # loop below ensure that we don't run tests twice if we're running mach
70 search_dirs
= ['.', self
.topsrcdir
]
71 last_search_dir
= search_dirs
[-1]
74 test
= mozpack
.path
.join(d
, t
)
75 if test
.endswith('.py') and os
.path
.isfile(test
):
78 elif os
.path
.isfile(test
+ '.py'):
79 files
.append(test
+ '.py')
81 elif os
.path
.isdir(test
):
82 files
+= glob
.glob(mozpack
.path
.join(test
, 'test*.py'))
83 files
+= glob
.glob(mozpack
.path
.join(test
, 'unit*.py'))
85 elif d
== last_search_dir
:
86 self
.log(logging
.WARN
, 'python-test',
88 'TEST-UNEXPECTED-FAIL | Invalid test: {test}')
93 file_displayed_test
= [] # Used as a boolean.
94 def _line_handler(line
):
95 if not file_displayed_test
and line
.startswith('TEST-'):
96 file_displayed_test
.append(True)
98 inner_return_code
= self
.run_process(
99 [self
.virtualenv_manager
.python_path
, f
],
100 ensure_exit_code
=False, # Don't throw on non-zero exit code.
101 log_name
='python-test',
102 # subprocess requires native strings in os.environ on Windows
103 append_env
={b
'PYTHONDONTWRITEBYTECODE': str('1')},
104 line_handler
=_line_handler
)
105 return_code
+= inner_return_code
107 if not file_displayed_test
:
108 self
.log(logging
.WARN
, 'python-test', {'file': f
},
109 'TEST-UNEXPECTED-FAIL | No test output (missing mozunit.main() call?): {file}')
112 if inner_return_code
!= 0:
113 self
.log(logging
.INFO
, 'python-test', {'file': f
},
114 'Test failed: {file}')
116 self
.log(logging
.INFO
, 'python-test', {'file': f
},
117 'Test passed: {file}')
118 if stop
and return_code
> 0:
121 return 0 if return_code
== 0 else 1