#!/usr/bin/env python

# u1trial: Test runner for Python unit tests needing DBus
#
# Author: Rodney Dawes <rodney.dawes@canonical.com>
#
# Copyright 2009-2010 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify it 
# under the terms of the GNU General Public License version 3, as published 
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but 
# WITHOUT ANY WARRANTY; without even the implied warranties of 
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR 
# PURPOSE.  See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along 
# with this program.  If not, see <http://www.gnu.org/licenses/>.
"""Test runner that uses a private dbus session and glib main loop."""

import os
import re
import sys
import unittest

sys.path.insert(0, os.path.abspath("."))

# pylint: disable=W0212

class TestRunner(object):
    """The test runner implementation."""

    def _load_unittest(self, relpath):
        """Load unit tests from a Python module with the given relative path."""
        assert relpath.endswith(".py"), (
            "%s does not appear to be a Python module" % relpath)
        modpath = relpath.replace(os.path.sep, ".")[:-3]
        module = __import__(modpath, None, None, [""])

        # If the module has a 'suite' or 'test_suite' function, use that
        # to load the tests.
        if hasattr(module, "suite"):
            return module.suite()
        elif hasattr(module, "test_suite"):
            return module.test_suite()
        else:
            return unittest.defaultTestLoader.loadTestsFromModule(module)

    def _collect_tests(self, path, test_pattern):
        """Return the set of unittests."""
        suite = unittest.TestSuite()
        if test_pattern:
            pattern = re.compile('.*%s.*' % test_pattern)
        else:
            pattern = None 

        if path:
            try:
                module_suite = self._load_unittest(path)
                if pattern:
                    for inner_suite in module_suite._tests:
                        for test in inner_suite._tests:
                            if pattern.match(test.id()):
                                suite.addTest(test)
                else:
                    suite.addTests(module_suite)
                return suite
            except AssertionError:
                pass
        else:
            parser.print_help()
            exit(1)

        # We don't use the dirs variable, so ignore the warning
        # pylint: disable=W0612
        for root, dirs, files in os.walk(path):
            for test in files:
                path = os.path.join(root, test)
                if test.endswith(".py") and test.startswith("test_"):
                    module_suite = self._load_unittest(path)
                    if pattern:
                        for inner_suite in module_suite._tests:
                            for test in inner_suite._tests:
                                if pattern.match(test.id()):
                                    suite.addTest(test)
                    else:
                        suite.addTests(module_suite)
        return suite

    def run(self, path, test_pattern=None, loops=None):
        """run the tests. """
        # install the glib2reactor before any import of the reactor to avoid
        # using the default SelectReactor and be able to run the dbus tests
        from twisted.internet import glib2reactor
        glib2reactor.install()
        from twisted.trial.reporter import TreeReporter
        from twisted.trial.runner import TrialRunner

        from ubuntuone.devtools.dbus_util import DBusRunner
        dbus_runner = DBusRunner()
        dbus_runner.startDBus()

        working_dir = os.path.join(os.getcwd(), "_trial_temp", "tmp")
        runner = TrialRunner(reporterFactory=TreeReporter, realTimeErrors=True,
                            workingDirectory=working_dir)

        # setup a custom XDG_CACHE_HOME and create the logs directory
        xdg_cache = os.path.join(os.getcwd(), "_trial_temp", "xdg_cache")
        os.environ["XDG_CACHE_HOME"] = xdg_cache
        # setup the ROOTDIR env var
        os.environ['ROOTDIR'] = os.getcwd()
        if not os.path.exists(xdg_cache):
            os.makedirs(xdg_cache)
        success = 0
        try:
            suite = self._collect_tests(path, test_pattern)
            if loops:
                old_suite = suite
                suite = unittest.TestSuite()
                for _ in xrange(loops):
                    suite.addTest(old_suite)
            result = runner.run(suite)
            success = result.wasSuccessful()
        finally:
            dbus_runner.stopDBus()
        if not success:
            sys.exit(1)
        else:
            sys.exit(0)


if __name__ == '__main__':
    from optparse import OptionParser
    usage = '%prog [options] path'
    parser = OptionParser(usage=usage)
    parser.add_option("-t", "--test", dest="test",
                  help = "run specific tests, e.g: className.methodName")
    parser.add_option("-l", "--loop", dest="loops", type="int", default=1,
                      help = "loop selected tests LOOPS number of times", 
                      metavar="LOOPS")

    (options, args) = parser.parse_args()
    if args:
        testpath = args[0]
        if not os.path.exists(testpath):
            print "the path to test does not exists!"
            sys.exit()
    else:
        testpath = None
    TestRunner().run(testpath, options.test, options.loops) 
