This removes ambiguity as to which modules are being imported, making import slightly faster as Python doesn't need to test so many directories for file presence. All files should already be using absolute imports because mach command modules aren't imported to the package they belong to: they instead belong to the "mach" package. So relative imports shouldn't have been used.
121 lines
4.8 KiB
Python
121 lines
4.8 KiB
Python
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
from __future__ import absolute_import, print_function, unicode_literals
|
|
|
|
import argparse
|
|
import glob
|
|
import logging
|
|
import mozpack.path as mozpath
|
|
import os
|
|
|
|
from mozbuild.base import (
|
|
MachCommandBase,
|
|
)
|
|
|
|
from mach.decorators import (
|
|
CommandArgument,
|
|
CommandProvider,
|
|
Command,
|
|
)
|
|
|
|
|
|
@CommandProvider
|
|
class MachCommands(MachCommandBase):
|
|
@Command('python', category='devenv',
|
|
description='Run Python.')
|
|
@CommandArgument('args', nargs=argparse.REMAINDER)
|
|
def python(self, args):
|
|
# Avoid logging the command
|
|
self.log_manager.terminal_handler.setLevel(logging.CRITICAL)
|
|
|
|
self._activate_virtualenv()
|
|
|
|
return self.run_process([self.virtualenv_manager.python_path] + args,
|
|
pass_thru=True, # Allow user to run Python interactively.
|
|
ensure_exit_code=False, # Don't throw on non-zero exit code.
|
|
# Note: subprocess requires native strings in os.environ on Windows
|
|
append_env={b'PYTHONDONTWRITEBYTECODE': str('1')})
|
|
|
|
@Command('python-test', category='testing',
|
|
description='Run Python unit tests.')
|
|
@CommandArgument('--verbose',
|
|
default=False,
|
|
action='store_true',
|
|
help='Verbose output.')
|
|
@CommandArgument('--stop',
|
|
default=False,
|
|
action='store_true',
|
|
help='Stop running tests after the first error or failure.')
|
|
@CommandArgument('tests', nargs='+',
|
|
metavar='TEST',
|
|
help='Tests to run. Each test can be a single file or a directory.')
|
|
def python_test(self, tests, verbose=False, stop=False):
|
|
self._activate_virtualenv()
|
|
|
|
# Python's unittest, and in particular discover, has problems with
|
|
# clashing namespaces when importing multiple test modules. What follows
|
|
# is a simple way to keep environments separate, at the price of
|
|
# launching Python multiple times. This also runs tests via mozunit,
|
|
# which produces output in the format Mozilla infrastructure expects.
|
|
return_code = 0
|
|
files = []
|
|
# We search for files in both the current directory (for people running
|
|
# from topsrcdir or cd'd into their test directory) and topsrcdir (to
|
|
# support people running mach from the objdir). The |break|s in the
|
|
# loop below ensure that we don't run tests twice if we're running mach
|
|
# from topsrcdir
|
|
search_dirs = ['.', self.topsrcdir]
|
|
last_search_dir = search_dirs[-1]
|
|
for t in tests:
|
|
for d in search_dirs:
|
|
test = mozpath.join(d, t)
|
|
if test.endswith('.py') and os.path.isfile(test):
|
|
files.append(test)
|
|
break
|
|
elif os.path.isfile(test + '.py'):
|
|
files.append(test + '.py')
|
|
break
|
|
elif os.path.isdir(test):
|
|
files += glob.glob(mozpath.join(test, 'test*.py'))
|
|
files += glob.glob(mozpath.join(test, 'unit*.py'))
|
|
break
|
|
elif d == last_search_dir:
|
|
self.log(logging.WARN, 'python-test',
|
|
{'test': t},
|
|
'TEST-UNEXPECTED-FAIL | Invalid test: {test}')
|
|
if stop:
|
|
return 1
|
|
|
|
for f in files:
|
|
file_displayed_test = [] # Used as a boolean.
|
|
def _line_handler(line):
|
|
if not file_displayed_test and line.startswith('TEST-'):
|
|
file_displayed_test.append(True)
|
|
|
|
inner_return_code = self.run_process(
|
|
[self.virtualenv_manager.python_path, f],
|
|
ensure_exit_code=False, # Don't throw on non-zero exit code.
|
|
log_name='python-test',
|
|
# subprocess requires native strings in os.environ on Windows
|
|
append_env={b'PYTHONDONTWRITEBYTECODE': str('1')},
|
|
line_handler=_line_handler)
|
|
return_code += inner_return_code
|
|
|
|
if not file_displayed_test:
|
|
self.log(logging.WARN, 'python-test', {'file': f},
|
|
'TEST-UNEXPECTED-FAIL | No test output (missing mozunit.main() call?): {file}')
|
|
|
|
if verbose:
|
|
if inner_return_code != 0:
|
|
self.log(logging.INFO, 'python-test', {'file': f},
|
|
'Test failed: {file}')
|
|
else:
|
|
self.log(logging.INFO, 'python-test', {'file': f},
|
|
'Test passed: {file}')
|
|
if stop and return_code > 0:
|
|
return 1
|
|
|
|
return 0 if return_code == 0 else 1
|