DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Mercurial (31ec81b5d7bb)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
# 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):
        # Avoid logging the command
        self.log_manager.terminal_handler.setLevel(logging.CRITICAL)
        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.
            # 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):
        # 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',
                # 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': 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