Beispiel #1
0
def load_bytecode_processors():
    """
    Loading BytecodeProcessor from modipyd.BYTECODE_PROCESSORS
    settings. Return ChainedBytecodeProcessor instance holds
    all loaded processors.
    """
    if (BYTECODE_PROCESSORS and 
            (len(BYTECODE_PROCESSORS_CACHE) != len(BYTECODE_PROCESSORS))):
        del BYTECODE_PROCESSORS_CACHE[:]
        for i, name in enumerate(BYTECODE_PROCESSORS[:]):
            LOGGER.info("Loading BytecodeProcesser '%s'" % name)
            try:
                klass = utils.import_component(name)
            except (ImportError, AttributeError):
                LOGGER.warn(
                    "Loading BytecodeProcesser '%s' failed. "
                    "This setting is removed" % name,
                    exc_info=True)
                del BYTECODE_PROCESSORS[i]
            else:
                BYTECODE_PROCESSORS_CACHE.append(klass)

    processors = []
    for klass in BYTECODE_PROCESSORS_CACHE:
        processors.append(klass())
    return bc.ChainedBytecodeProcessor(processors)
Beispiel #2
0
    def spawn_unittest_runner(self, testables, extra_arguments=None):
        """Spawn test runner process"""
        args = [sys.executable, '-m', 'modipyd.tools.unittest_runner']
        if extra_arguments:
            args.extend(extra_arguments)
        if self.test_runner:
            args.extend(['-r', self.test_runner])
        for t in testables:
            args.append(t.filename)

        args = [str(arg) for arg in args]
        if sys.platform == "win32":
            # Avoid argument parsing problem in
            # windows, DOS platform
            args = ['"%s"' % arg for arg in args]

        # Manipulate PYTHONPATH environment variable so that
        # the unittest runner can find an appropriate modipyd package.
        environ = os.environ.copy()
        path = os.path.join(os.path.dirname(modipyd.__file__), '..')

        try:
            environ['PYTHONPATH'] += (':' + path)
        except KeyError:
            environ['PYTHONPATH'] = path

        LOGGER.debug(
            "Spawn test runner process: PYTHONPATH=%s %s" %
            (path, ' '.join(args)))
        return os.spawnve(os.P_WAIT, sys.executable, args, environ)
Beispiel #3
0
def run():
    parser = make_option_parser()
    options, args = parser.parse_args()

    try:
        make_application(options, args or '.').run()
    except KeyboardInterrupt:
        LOGGER.debug('Keyboard Interrupt', exc_info=True)
Beispiel #4
0
def collect_module_code(filepath_or_list, search_path=None):
    resolver = ModuleNameResolver(search_path)
    for filename, typebits in collect_python_module_file(filepath_or_list):
        try:
            yield read_module_code(filename,
                search_path=search_path, typebits=typebits,
                resolver=resolver, allow_compilation_failure=True)
        except ImportError:
            LOGGER.debug("Couldn't import file", exc_info=True)
Beispiel #5
0
def run():
    """Standalone program interface"""
    parser = make_option_parser()
    (options, args) = parser.parse_args()

    application = make_application(options, args or '.')
    try:
        application.run()
    except KeyboardInterrupt:
        LOGGER.debug('Keyboard Interrupt', exc_info=True)
Beispiel #6
0
 def descriptors(self):
     """
     All the monitoring modules. This is dictionary,
     maps name and module descriptors.
     """
     if self.__descriptors is None:
         self.__descriptors = {}
         entries = list(self.refresh())
         LOGGER.debug("%d descriptoes" % len(entries))
     return self.__descriptors
Beispiel #7
0
def collect_module_code(filepath_or_list, search_path=None):
    resolver = ModuleNameResolver(search_path)
    for filename, typebits in collect_python_module_file(filepath_or_list):
        # Since changing .py file is not reflected by .pyc, .pyo quickly,
        # the plain .py file takes first prioriry.
        try:
            yield read_module_code(filename,
                search_path=search_path, typebits=typebits,
                resolver=resolver, allow_compilation_failure=True)
        except ImportError:
            LOGGER.debug("Couldn't import file", exc_info=True)
Beispiel #8
0
 def invoke_plugins(self, event, monitor):
     context = dict(self.variables)
     for plugin in self.plugins:
         try:
             ret = plugin(event, monitor, context)
             # the plugin object can return (but not required)
             # a callable object. It is called with no arguments
             if callable(ret):
                 ret()
         except StandardError:
             LOGGER.warn(
                 "Exception occurred while invoking plugin",
                 exc_info=True)
Beispiel #9
0
    def reload(self, descriptors, co=None):
        """
        Reload module code, update dependency graph
        """
        LOGGER.info("Reload module descriptor '%s' at %s" % (self.name, relativepath(self.filename)))

        try:
            self.module_code.reload(co)
        except SyntaxError:
            # SyntaxError is OK
            LOGGER.warn("SyntaxError found in %s" % self.filename, exc_info=True)
        else:
            self.update_dependencies(descriptors)
Beispiel #10
0
    def refresh(self):
        assert isinstance(self.paths, (tuple, list))
        assert isinstance(self.__descriptors, dict)
        assert isinstance(self.__filenames, dict)
        assert isinstance(self.__failures, set)

        # localize variable access to minimize overhead
        # and to reduce the visual noise.
        descriptors = self.__descriptors
        filenames = self.__filenames
        failures = self.__failures

        # ``monitor()`` updates all entries and
        # removes deleted entries.
        for modified in self.monitor():
            yield modified

        # For now, only need to check new entries.
        resolver = ModuleNameResolver(self.search_path)
        newcomers = []
        for filename, typebits in collect_python_module_file(self.paths):
            if filename in filenames or filename in failures:
                continue
            try:
                mc = read_module_code(filename, typebits=typebits,
                        search_path=self.search_path,
                        resolver=resolver,
                        allow_compilation_failure=True,
                        allow_standalone=True)
            except ImportError:
                LOGGER.debug("Couldn't import file", exc_info=True)
                failures.add(filename)
                continue
            else:
                desc = ModuleDescriptor(mc)
                self.add(desc)
                # modifieds += new entries
                newcomers.append(desc)
                LOGGER.debug("Added: %s" % desc.describe())

        if newcomers:
            # Since there are some entries already refer new entry,
            # we need to update dependencies of all entries
            for desc in descriptors.itervalues():
                desc.update_dependencies(descriptors)

            # Notify caller what entries are appended
            for desc in newcomers:
                yield Event(Event.MODULE_CREATED, desc)
Beispiel #11
0
def read_module_code(filename, typebits=None, search_path=None,
        resolver=None,
        allow_compilation_failure=False,
        allow_standalone=False):
    """
    Read python module file, and return ``ModuleCode`` instance.
    If *typebits* argument is not ``None``, *filename* must be
    filepath without file extention.
    If *typebits* argument is ``None``, it is detected by filename.
    """

    if typebits is None:
        filename, _, typebits = module_file_typebits(filename)
    if resolver is None:
        resolver = ModuleNameResolver(search_path)

    code = None
    try:
        # Since editing .py files will not affect .pyc and .pyo files soon,
        # give priority to .py files.
        if typebits & PYTHON_SOURCE_MASK:
            # .py
            sourcepath = filename + '.py'
            code = compile_source(sourcepath)
        elif typebits & (PYTHON_OPTIMIZED_MASK | PYTHON_COMPILED_MASK):
            # .pyc, .pyo
            if typebits & PYTHON_OPTIMIZED_MASK:
                sourcepath = filename + '.pyo'
            else:
                sourcepath = filename + '.pyc'
                code = load_compiled(sourcepath)
        else:
            assert False, "illegal typebits: %d" % typebits
    except (SyntaxError, ImportError):
        LOGGER.warn(
            "Exception occurred while loading compiled bytecode",
            exc_info=True)
        if not allow_compilation_failure:
            raise

    try:
        module_name, package_name = resolver.resolve(sourcepath)
    except ImportError:
        if not allow_standalone:
            raise
        module_name = filepath_to_identifier(sourcepath)
        package_name = None

    return ModuleCode(module_name, package_name, sourcepath, code)
Beispiel #12
0
    def remove(self, descriptor):
        """Remove *descriptor*, and clear dependencies"""
        descriptors, filenames = self.descriptors, self.__filenames
        filename = splitext(descriptor.filename)[0]

        if (descriptor.name not in descriptors or 
                filename not in filenames):
            raise KeyError(
                "No monitoring descriptor '%s'" % \
                descriptor.name)

        LOGGER.debug("Removed: %s" % descriptor.describe())
        descriptor.clear_dependencies()
        del descriptors[descriptor.name]
        del filenames[filename]
Beispiel #13
0
    def test_autotest_plugin(self):
        monitor = Monitor(__file__)
        descriptor = ModuleDescriptor(read_module_code(__file__))
        event = Event(Event.MODULE_MODIFIED, descriptor)
        plugin = FakeAutotest(event, monitor, {});

        self.assertEqual(descriptor, plugin.descriptor)
        self.assertTrue(callable(plugin))

        plugin()
        self.assertEqual([descriptor], plugin.testables)
        self.assertEqual(['--loglevel', LOGGER.getEffectiveLevel()], plugin.extra_arguments)
Beispiel #14
0
    def start(self, interval=1.0, refresh_factor=5):
        if refresh_factor < 1:
            raise RuntimeError("refresh_factor must be greater or eqaul to 1")
        if interval <= 0:
            raise RuntimeError("interval must not be negative or 0")

        descriptors = self.descriptors

        if LOGGER.isEnabledFor(logging.INFO):
            desc = "\n".join([
                desc.describe(indent=4)
                for desc in descriptors.itervalues()])
            LOGGER.info("Monitoring:\n%s" % desc)

        # Prior to Python 2.5, the ``yield`` statement is not
        # allowed in the ``try`` clause of a ``try ... finally``
        # construct.
        try:
            self.monitoring = True

            times = 0
            while descriptors and self.monitoring:

                time.sleep(interval)
                times += 1

                if times % refresh_factor == 0:
                    monitor = self.refresh()
                else:
                    monitor = self.monitor()

                for modified in monitor:
                    if not self.monitoring:
                        break
                    yield modified
            else:
                LOGGER.info("Terminating monitor %s" % str(self))
        except:
            self.monitoring = False
            raise
Beispiel #15
0
    def install_plugin(self, plugin):
        """
        Install a plugin specified by *plugin*. The *plugin* argument
        must be callable object (e.g. function, class) or a qualified
        name of the plugin itself.

        Read the ``modipyd.application.plugins`` module documentation
        for the plugin architecture details.
        """

        if isinstance(plugin, basestring):
            try:
                plugin = import_component(plugin)
            except (ImportError, AttributeError):
                LOGGER.error("Loading plugin '%s' failed" % plugin)
                raise

        if not callable(plugin):
            raise TypeError("The plugin must be callable object")

        if hasattr(plugin, 'func_code'):
            LOGGER.info("Loading plugin: %s" % plugin.func_code.co_name)
        else:
            LOGGER.info("Loading plugin: %s" % plugin)

        self.plugins.append(plugin)
Beispiel #16
0
def collect_unittest(paths):
    suite = unittest.TestSuite()
    loader = unittest.defaultTestLoader
    resolver = resolve.ModuleNameResolver()
    paths = utils.sequence(paths)

    for filepath in paths:
        try:
            name, package = resolver.resolve(filepath)
        except ImportError:
            # .py file not in the search path
            name = filepath_to_identifier(filepath)
            package = None

        try:
            if package:
                module = utils.import_module(name)
            else:
                module = imp.load_source(name, filepath)
        except ImportError:
            LOGGER.warn("ImportError occurred while loading module", exc_info=True)
        else:
            tests = loader.loadTestsFromModule(module)
            if tests.countTestCases():
                suite.addTest(tests)
                LOGGER.info("Found %d test(s) in module '%s'" % (tests.countTestCases(), module.__name__))
            else:
                LOGGER.warn("No tests found in module '%s'" % module.__name__)
    return suite
Beispiel #17
0
def make_application(options, filepath):
    # options handling
    if options.verbosity > 0:
        LOGGER.setLevel(logging.INFO)
    if options.verbosity > 1:
        LOGGER.setLevel(logging.DEBUG)

    # So many projects contain its modules and packages at
    # the top level directory, modipyd inserts current directory
    # in ``sys.path`` module search path variable for convenience.
    sys.path.insert(0, os.getcwd())

    # Create Application instance, Install plugins
    application = Application(filepath)
    for plugin in options.plugins:
        application.install_plugin(plugin)

    # Predefine variables
    variables = {}
    for var in options.defines:
        i = var.find('=')
        if i == -1:
            variables[var] = ''
        else:
            variables[var[:i]] = var[i+1:]

    if variables:
        import pprint
        application.update_variables(variables)
        LOGGER.info(
            "Predefined variables: %s" % pprint.pformat(variables))

    # Load configuration (startup) file
    for rcfile in find_startup_files(os.environ, options.rcfile):
        LOGGER.info("Loading startup file from %s" % rcfile)
        execfile(rcfile, globals(), {'application': application})

    return application
Beispiel #18
0
    def __call__(self):

        # Walking dependency graph in imported module to
        # module imports order.
        testables = []
        for desc in self.descriptor.walk_dependency_graph(reverse=True):
            LOGGER.info("-> Affected: %s" % desc.name)
            if has_subclass(desc, unittest.TestCase):
                LOGGER.debug("-> unittest.TestCase detected: %s" % desc.name)
                testables.append(desc)

        # Runntine tests
        if testables:
            # We can reload affected modules manually and run
            # all TestCase in same process. Running another process,
            # however, is simple and perfect solution.
            if LOGGER.isEnabledFor(logging.INFO):
                desc = ', '.join([x.name for x in testables])
                LOGGER.info("Running UnitTests: %s" % desc)
            # Propagates the level of modipyd.LOGGER to
            # the unittest runner subprocess.
            extra = ['--loglevel', LOGGER.getEffectiveLevel()]
            self.spawn_unittest_runner(testables, extra)
Beispiel #19
0
def has_subclass(module_descriptor, baseclass):
    """
    Return ``True`` if the module has a class
    derived from *baseclass*
    """
    # We can't use ``unittest.TestLoader`` to loading tests,
    # bacause ``TestLoader`` imports (execute) module code.
    # If imported/executed module have a statement such as
    # ``sys.exit()``, ...program exit!

    if not isinstance(baseclass, (type, types.ClassType)):
        raise TypeError(
            "The baseclass argument must be instance of type or class, "
            "but was instance of %s" % type(baseclass))

    modcode = module_descriptor.module_code
    assert modcode

    # How to check unittest.TestCase
    # ============================================
    # 1. For all class definition in module code
    # 2. Check class is derived from base class(s)
    # 3. Check base class(s) is imported from another module
    # 4. Load base class(s) from that module
    #    Notes: Assume the module contains base class does not have
    #           a dangerous code such as ``sys.exit``.
    # 5. Check loaded class is *baseclass* or its subclass

    # Construct imported symbols.
    # This is used in phase 3.
    symbols = dict([(imp[0], imp) for imp in modcode.context['imports']])

    # 1. For all class definition in module code
    for klass in modcode.context['classdefs']:

        # 2. Check class is derived from base class(s)
        bases = klass[1]
        if not bases:
            continue

        # 3. Check base class(s) is imported from another module
        for base in bases:
            # Search imported symbol that is class name or module name
            if '.' in base:
                names = list(split_module_name(base))
            else:
                names = [base]

            import_ = symbols.get(names[0])
            if import_ is None:
                # Not an imported base class
                continue

            # Convert a name to a qualified module name
            #
            #   1. Resolve import alias if exists
            #   2. Qualify name as full module name
            #   3. Resolve relative module name
            #
            level = import_[2]
            names[0] = import_[1]

            fqn = '.'.join(names)
            fqn = resolve_relative_modulename(fqn, modcode.package_name, level)

            assert '.' in fqn, "fqn must be a qualified module fqn"
            LOGGER.debug("'%s' is derived from '%s'" % (module_descriptor.name, fqn))

            try:
                try:
                    klass = utils.import_component(fqn)
                except ImportError:
                    if level == -1 and modcode.package_name:
                        # The qualified name may be relative to current package.
                        fqn = '.'.join((modcode.package_name, fqn))
                        klass = utils.import_component(fqn)
                    else:
                        raise
            except (ImportError, AttributeError):
                LOGGER.warn("Exception occurred "
                    "while importing component '%s'" % fqn,
                    exc_info=True)
            else:
                # 5. Check loaded class is specified class or its subclass
                if isinstance(klass, (type, types.ClassType)) and \
                        issubclass(klass, baseclass):
                    return True

    return False
Beispiel #20
0
 def update_dependencies(self, descriptors):
     LOGGER.debug("Update dependencies of '%s'" % self.name)
     _update_module_dependencies(self, descriptors)
Beispiel #21
0
def has_subclass(module_descriptor, baseclass):
    """
    Return ``True`` if the module has a class
    derived from *baseclass*
    """
    # We can't use ``unittest.TestLoader`` to loading tests,
    # bacause ``TestLoader`` imports (execute) module code.
    # If imported/executed module have a statement such as
    # ``sys.exit()``, ...program exit!

    if not isinstance(baseclass, (type, types.ClassType)):
        raise TypeError(
            "The baseclass argument must be instance of type or class, "
            "but was instance of %s" % type(baseclass))

    modcode = module_descriptor.module_code
    assert modcode

    # How to check unittest.TestCase
    # ============================================
    # 1. For all class definition in module code
    # 2. Check class is derived from base class(s)
    # 3. Check base class(s) is imported from another module
    # 4. Load base class(s) from that module
    #    Notes: Assume the module contains base class does not have
    #           a dangerous code such as ``sys.exit``.
    # 5. Check loaded class is *baseclass* or its subclass

    # Construct imported symbols.
    # This is used in phase 3.
    symbols = dict([(imp[0], imp) for imp in modcode.context['imports']])

    # 1. For all class definition in module code
    for klass in modcode.context['classdefs']:

        # 2. Check class is derived from base class(s)
        bases = klass[1]
        if not bases:
            continue

        # 3. Check base class(s) is imported from another module
        for base in bases:
            # Search imported symbol that is class name or module name
            symbol = base
            if '.' in symbol:
                symbol = split_module_name(symbol)[0]

            import_ = symbols.get(symbol)
            if import_ is None:
                continue

            # Convert name to a qualified module name
            name, level = base, import_[2]
            parent = split_module_name(import_[1])[0]
            if parent:
                name = '.'.join((parent, name))
            name = resolve_relative_modulename(
                name, modcode.package_name, level)

            assert '.' in name, "name must be a qualified module name"
            LOGGER.debug("'%s' is derived from '%s'" % (base, name))

            try:
                klass = utils.import_component(name)
            except ImportError:
                klass = None
                exc = sys.exc_info()[:]

                if level == -1 and modcode.package_name:
                    # Try to resolve a name as relative module name.
                    try:
                        name2 = '.'.join((modcode.package_name, name))
                        klass = utils.import_component(name2)
                    except:
                        LOGGER.warn(
                            "Exception occurred while importing module '%s'" % name2,
                            exc_info=True)

                if not klass:
                    LOGGER.warn(
                        "Exception occurred while importing module '%s'" % name,
                        exc_info=exc)

                # Make sure to delete the traceback to avoid creating cycles.
                del exc

            except AttributeError:
                LOGGER.warn(
                    "Exception occurred while importing module '%s'" % name,
                    exc_info=True)
            else:
                # 5. Check loaded class is specified class or its subclass
                if isinstance(klass, (type, types.ClassType)) and \
                        issubclass(klass, baseclass):
                    return True

    return False
Beispiel #22
0
 def run(self):
     monitor = Monitor(self.paths)
     for event in monitor.start():
         LOGGER.info("%s: %s" % (TYPE_STRINGS[event.type],
             event.descriptor.describe(indent=4)))
         self.invoke_plugins(event, monitor)
Beispiel #23
0

if __name__ == "__main__":
    parser = OptionParser(usage="usage: %prog [options] file1, file2, ...")
    parser.add_option(
        "-r",
        "--runner",
        default="unittest.TextTestRunner",
        action="store",
        dest="runner",
        metavar="CLASS_NAME",
        help="qualified name of the unittest.TestRunner subclass " "(default: unittest.TextTestRunner)",
    )
    parser.add_option(
        "--loglevel",
        action="store",
        type="int",
        dest="loglevel",
        metavar="LOG_LEVEL",
        help="Specifies the lowest-severity log message a logger will handle",
    )

    options, args = parser.parse_args()

    if options.loglevel is not None:
        LOGGER.setLevel(options.loglevel)

    LOGGER.debug("Execute modipyd.tools.unittest_runner: %s" % " ".join(args))
    sys.path.insert(0, os.getcwd())
    main(args, options.runner)