#!/usr/bin/python
#
# This script checks python. Current tests:
#   - import python modules available on the systen
#
# (C) 2012 Canonical Ltd.
# Author: Jean-Baptiste Lallement <jean-baptiste.lallement@canonical.com>
#         Based on original version from Michael Vogt
# License: GPL v2 or higher

import unittest
import ConfigParser
import os
import logging
import subprocess

logging.basicConfig(
    filename='/tmp/%s.log' % os.path.basename(__file__)[:-3],
    filemode='w',
    level=logging.DEBUG)

# stuff that we know does not work when doing a simple "import"
BLACKLIST = ["speechd_config",
             "PAMmodule.so",
             "aomodule.so",
             "plannerui.so",
             "desktopcouch", # needs a KeyringDaemon
             "ropemacs", # just hangs
             "keyring", # needs X
             "invest",
             "Onboard",
             "goocanvasmodule.so",
             ]

# If you want to test modules that require a display you'll have to install
# xvfb in the base image
XVFB_BIN='/usr/bin/xvfb-run'
XVFB_OPT=[]

def py_module_filter(pymodule):
    return not (
        pymodule.endswith(".egg-info") or
        pymodule.endswith(".pth") or
        pymodule.startswith("_") or
        pymodule.endswith(".pyc") or
        pymodule.endswith("_d.so") or
        pymodule in BLACKLIST
    )

def get_module_from_path(path):
    f = os.path.basename(path)
    if path and os.path.exists(os.path.join(path, "__init__.py")):
        return f
    elif f.endswith(".py"):
        return f.split(".")[0]
    # swig uses this, calls it "foomodule.so" but the import is "foo"
    # (eg xdelta3module.so, pqueuemodule.so)
    elif f.endswith("module.so"):
        return f.split("modules.so")[0]
    elif f.endswith(".so"):
        return f.split(".")[0]

class TestPython(unittest.TestCase):
    # total imports
    total = 0
    failed = []

    def _try_import(self, path):
        '''Try to import a module from a path

        a simple __import__(module) does not work, the problem
        is that module import have funny side-effects (like
        "import uno; import pyatspi" will fail, but importing
        them individually is fine
        '''
        logging.info('Importing %s', path)
        module = get_module_from_path(path)
        rc = True
        if not module:
            logging.warn("could not get module for '%s'" % path)
            return rc

        try:
            cmd = ["python", "-c","import %s" % module]
            self.total += 1
            subprocess.check_call(cmd, stderr = self.stderr)
        except subprocess.CalledProcessError:
            try:
                pkg = subprocess.check_output(["dpkg", "-S", os.path.realpath(path)])
                self.failed.append((module, pkg.strip()))
                logging.error('Import failed. Package providing this module: %s', pkg)
            except subprocess.CalledProcessError:
                logging.error("Import of %s failed, and no package ships this module.", path)
            rc = False
        return rc

    def setUp(self):
        # Read default python version installed on the system
        config = ConfigParser.SafeConfigParser()
        config.read('/usr/share/python/debian_defaults')
        self.default_version = config.get('DEFAULT', 'default-version')
        self.distpackages = '/usr/lib/%s/dist-packages/' % self.default_version
        self.stderr = open('/tmp/%s.stderr' % os.path.basename(__file__)[:-3],'w')

    def tearDown(self):
        self.stderr.close()

    def test_python_import(self):
        '''Import python modules from /usr/lib/PYTHONVER/dist-packages/'''
        res = True

        for module in filter(py_module_filter, os.listdir(self.distpackages)):
            res &= self._try_import(os.path.join(self.distpackages, module))

        logging.info('Modules imported: %d', self.total)
        if res:
            logging.info('No failure')

        self.assertTrue(res, '%d module(s) failed to import' % len(self.failed))

if __name__ == '__main__':
    if 'DISPLAY' in os.environ:
        unittest.main()
    elif os.path.exists(XVFB_BIN):
        logging.info("'%s' found and DISPLAY not set. Re-executing myself with xvfb")
        cmd = [ XVFB_BIN ] + XVFB_OPT + [os.path.abspath(__file__)]
        logging.info(cmd)
        subprocess.call(cmd)
    else:
        logging.warning("'%s' not found and DISPLAY not set. Executing test without a display", XVFB_BIN)
        unittest.main()
