Bug 818744 - mach commands to run Python and Python unit tests. r=gps
DONTBUILD because NPOTB
--- a/build/mach_bootstrap.py
+++ b/build/mach_bootstrap.py
@@ -50,16 +50,17 @@ SEARCH_PATHS = [
'testing/mozbase/mozrunner',
'testing/mozbase/mozinfo',
]
# Individual files providing mach commands.
MACH_MODULES = [
'addon-sdk/mach_commands.py',
'layout/tools/reftest/mach_commands.py',
+ 'python/mach_commands.py',
'python/mach/mach/commands/commandinfo.py',
'python/mozboot/mozboot/mach_commands.py',
'python/mozbuild/mozbuild/config.py',
'python/mozbuild/mozbuild/mach_commands.py',
'python/mozbuild/mozbuild/frontend/mach_commands.py',
'testing/marionette/mach_commands.py',
'testing/mochitest/mach_commands.py',
'testing/xpcshell/mach_commands.py',
new file mode 100644
--- /dev/null
+++ b/python/mach_commands.py
@@ -0,0 +1,130 @@
+# 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 print_function, unicode_literals
+
+import argparse
+import glob
+import logging
+import mozpack.path
+import os
+import sys
+
+from mozbuild.base import (
+ MachCommandBase,
+)
+
+from mach.decorators import (
+ CommandArgument,
+ CommandProvider,
+ Command,
+)
+
+
+@CommandProvider
+class MachCommands(MachCommandBase):
+ '''
+ Easily run Python and Python unit tests.
+ '''
+ def __init__(self, context):
+ MachCommandBase.__init__(self, context)
+ self._python_executable = None
+
+ @property
+ def python_executable(self):
+ '''
+ Return path to Python executable, or print and sys.exit(1) if
+ executable does not exist.
+ '''
+ if self._python_executable:
+ return self._python_executable
+ if self._is_windows():
+ executable = '_virtualenv/Scripts/python.exe'
+ else:
+ executable = '_virtualenv/bin/python'
+ path = mozpack.path.join(self.topobjdir, executable)
+ if not os.path.exists(path):
+ print("Could not find Python executable at %s." % path,
+ "Run |mach configure| or |mach build| to install it.")
+ sys.exit(1)
+ self._python_executable = path
+ return path
+
+ @Command('python', category='devenv',
+ allow_all_args=True,
+ description='Run Python.')
+ @CommandArgument('args', nargs=argparse.REMAINDER)
+ def python(self, args):
+ return self.run_process([self.python_executable] + args,
+ pass_thru=True, # Allow user to run Python interactively.
+ ensure_exit_code=False, # Don't throw on non-zero exit code.
+ append_env={'PYTHONDONTWRITEBYTECODE': '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):
+ # Make sure we can find Python before doing anything else.
+ self.python_executable
+
+ # 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 = []
+ for test in tests:
+ if test.endswith('.py') and os.path.isfile(test):
+ files.append(test)
+ elif os.path.isfile(test + '.py'):
+ files.append(test + '.py')
+ elif os.path.isdir(test):
+ files += glob.glob(mozpack.path.join(test, 'test*.py'))
+ files += glob.glob(mozpack.path.join(test, 'unit*.py'))
+ else:
+ self.log(logging.WARN, 'python-test', {'test': test},
+ 'TEST-UNEXPECTED-FAIL | Invalid test: {test}')
+ if stop:
+ return 1
+
+ for file 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.python_executable, file],
+ ensure_exit_code=False, # Don't throw on non-zero exit code.
+ log_name='python-test',
+ append_env={'PYTHONDONTWRITEBYTECODE': '1'},
+ line_handler=_line_handler)
+ return_code += inner_return_code
+
+ if not file_displayed_test:
+ self.log(logging.WARN, 'python-test', {'file': file},
+ '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': file},
+ 'Test failed: {file}')
+ else:
+ self.log(logging.INFO, 'python-test', {'file': file},
+ 'Test passed: {file}')
+ if stop and return_code > 0:
+ return 1
+
+ return 0 if return_code == 0 else 1