########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Lib/TestSuite/TestModule.py,v 1.9 2006-08-11 15:50:12 jkloth Exp $
"""
Provides the TestModule class for wrapping modules/packages.

Copyright 2006 Fourthought, Inc. (USA).
Detailed license and copyright information: http://4suite.org/COPYRIGHT
Project home, documentation, distributions: http://4suite.org/
"""

import os
from Ft.Lib import ImportUtil
import TestLoader, TestFunction, TestMode, TestCoverage

class TestModule(TestLoader.TestLoader):
    """Test object for a module or package."""

    def __init__(self, name, module, addModes, skipModes, allModes):
        TestLoader.TestLoader.__init__(self, name, module.__name__, addModes,
                                       skipModes, allModes)
        self.module = module
        self.modes = self.getModes(addModes, skipModes, allModes)
        loader = ImportUtil.FindLoader(self.path)
        self.isPackage = loader.is_package(self.path)
        return

    def getModes(self, addModes, skipModes, allModes):
        # Create the list of modes we will run
        modes = getattr(self.module, 'MODES', [TestMode.DefaultMode()])
        run_modes = []
        if allModes:
            # Use whatever modes are not skipped
            for mode in modes:
                if mode.name not in skipModes:
                    run_modes.append(mode)
        else:
            # Use the specified modes that are not also skipped
            for mode in modes:
                if mode.name in addModes and mode.name not in skipModes:
                    run_modes.append(mode)

            # If no specified modes found, use the default
            if not run_modes:
                for mode in modes:
                    if mode.default and mode.name not in skipModes:
                        run_modes.append(mode)
        return run_modes

    def getTests(self):
        """
        Get the test objects contained within this module.
        """

        # If there are no cached results, gather the sub-tests based on
        # the type of module.
        if not self.tests:

            # Get the test function(s) defined in this module
            for name in dir(self.module):
                if name == 'Test': #name.startswith('Test'):
                    obj = getattr(self.module, name)
                    if callable(obj):
                        self.tests.append(TestFunction.TestFunction(obj))

            # If this is a package, get the available modules
            if self.isPackage:
                files = []
                dirs = []
                path = ImportUtil.GetSearchPath(self.path)
                for importer, name, ispkg in ImportUtil.IterModules(path):
                    if ispkg:
                        dirs.append(name)
                    else:
                        files.append(name)

                # Default running order is alphabetical
                dirs.sort()
                files.sort()

                # Let the module manipulate the test lists
                if hasattr(self.module, 'PreprocessFiles'):
                    (dirs, files) = self.module.PreprocessFiles(dirs, files)

                # Add the test lists to our available tests
                for name in dirs + files:
                    self.addTest(name)

                # If this modules defines a CoverageModule, add the coverage
                # start and end functions.
                if hasattr(self.module, 'CoverageModule'):
                    ignored = None
                    if hasattr(self.module,'CoverageIgnored'):
                        ignored = self.module.CoverageIgnored
                    ct = TestCoverage.TestCoverage(self.module.CoverageModule,ignored)
                    self.tests.insert(0, TestFunction.TestFunction(ct._start))
                    self.tests.append(TestFunction.TestFunction(ct._end))

        return self.tests

    def showTests(self, indent):
        if self.isPackage:
            # A package
            print '%s%s%s' % (indent, self.name, os.sep)
            new_indent = indent + ' '*2
            for test in self.getTests():
                test.showTests(new_indent)
        else:
            # A simple module
            print '%s%s' % (indent, self.name)
        return

    def run(self, tester):
        # Determine the modes
        tester.startGroup(self.name)

        modes = []
        for mode in self.modes:
            if mode.initialize(tester):
                modes.append(mode)

        if not modes:
            tester.warning("All modes have been skipped")

        for mode in modes:
            mode.start(tester)
            try:
                have_run = 0
                for test in self.getTests():
                    self.runTest(tester, test)
                    have_run = 1
                if not have_run:
                    tester.warning('Module does define any tests')
            finally:
                mode.finish(tester)

        tester.groupDone()
        return

    def runTest(self, tester, testObject):
        # Saved to check for misbehaving tests
        depth = len(tester.groups)

        # Run the test
        try:
            testObject.run(tester)
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            tester.exception('Unhandled exception in test')
            # Clean up for the interrupted test
            if tester.test:
                tester.testDone()
            while len(tester.groups) > depth:
                tester.groupDone()
            return

        if tester.test:
            tester.warning('Failed to finish test (fixed)')
            tester.testDone()

        # Verify proper group count
        count = len(tester.groups) - depth
        if count < 0:
            tester.error('Closed too many groups')
        elif count > 0:
            tester.warning('Failed to close %d groups (fixed)' % count)
            while count:
                count -= 1
                tester.message('Closing group %s' % tester.groups[-1])
                tester.groupDone()
        return
