# Copyright 2007 Google Inc. Released under the GPL v2
#
# Eric Li <ericli@google.com>

import logging, os, pickle, re, sys
import common

from autotest_lib.client.bin import job as client_job
from autotest_lib.client.common_lib import base_job
from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib import logging_manager
from autotest_lib.client.common_lib import packages


class setup_job(client_job.job):
    """
    setup_job is a job which runs client test setup() method at server side.

    This job is used to pre-setup client tests when development toolchain is not
    available at client.
    """

    def __init__(self, options):
        """
        Since setup_job is a client job but run on a server, it takes no control
        file as input. So client_job.__init__ is by-passed.

        @param options: an object passed in from command line OptionParser.
                        See all options defined on client/bin/autotest.
        """
        base_job.base_job.__init__(self, options=options)
        self._cleanup_debugdir_files()
        self._cleanup_results_dir()
        self.machine_dict_list = [{'hostname' : options.hostname}]
        # Client side tests should always run the same whether or not they are
        # running in the lab.
        self.in_lab = False
        self.pkgmgr = packages.PackageManager(
            self.autodir, run_function_dargs={'timeout':3600})


def init_test(options, testdir):
    """
    Instantiate a client test object from a given test directory.

    @param options Command line options passed in to instantiate a setup_job
                   which associates with this test.
    @param testdir The test directory.
    @returns A test object or None if failed to instantiate.
    """

    locals_dict = locals().copy()
    globals_dict = globals().copy()

    locals_dict['testdir'] = testdir

    job = setup_job(options=options)
    locals_dict['job'] = job

    test_name = os.path.split(testdir)[-1]
    outputdir = os.path.join(job.resultdir, test_name)
    try:
        os.makedirs(outputdir)
    except OSError:
        pass
    locals_dict['outputdir'] = outputdir

    sys.path.insert(0, testdir)
    client_test = None
    try:
        try:
            import_stmt = 'import %s' % test_name
            init_stmt = ('auto_test = %s.%s(job, testdir, outputdir)' %
                         (test_name, test_name))
            exec import_stmt + '\n' + init_stmt in locals_dict, globals_dict
            client_test = globals_dict['auto_test']
        except ImportError, e:
            # skips error if test is control file without python test
            if re.search(test_name, str(e)):
                pass
            # give the user a warning if there is an import error.
            else:
                logging.exception('%s import error: %s.  Skipping %s' %
                              (test_name, e, test_name))
        except Exception, e:
            # Log other errors (e.g., syntax errors) and collect the test.
            logging.exception("%s: %s", test_name, e)
    finally:
        sys.path.pop(0) # pop up testbindir
    return client_test


def load_all_client_tests(options):
    """
    Load and instantiate all client tests.

    This function is inspired from runtest() on client/common_lib/test.py.

    @param options: an object passed in from command line OptionParser.
                    See all options defined on client/bin/autotest.

    @return a tuple containing the list of all instantiated tests and
            a list of tests that failed to instantiate.
    """

    local_namespace = locals().copy()
    global_namespace = globals().copy()

    all_tests = []
    broken_tests = []
    for test_base_dir in ['tests', 'site_tests']:
        testdir = os.path.join(os.environ['AUTODIR'], test_base_dir)
        for test_name in sorted(os.listdir(testdir)):
            client_test = init_test(options, os.path.join(testdir, test_name))
            if client_test:
                all_tests.append(client_test)
            else:
                broken_tests.append(test_name)
    return all_tests, broken_tests


def setup_test(client_test):
    """
    Direct invoke test.setup() method.

    @returns A boolean to represent success or not.
    """

    # TODO: check if its already build. .version? hash?
    test_name = client_test.__class__.__name__
    cwd = os.getcwd()
    good_setup = False
    try:
        try:
            outputdir = os.path.join(client_test.job.resultdir, test_name)
            try:
                os.makedirs(outputdir)
                os.chdir(outputdir)
            except OSError:
                pass
            logging.info('setup %s.' % test_name)
            client_test.setup()

            # Touch .version file under src to prevent further setup on client
            # host. See client/common_lib/utils.py update_version()
            if os.path.exists(client_test.srcdir):
                versionfile = os.path.join(client_test.srcdir, '.version')
                pickle.dump(client_test.version, open(versionfile, 'w'))
            good_setup = True
        except Exception, err:
            logging.error(err)
            raise error.AutoservError('Failed to build client test %s on '
                                      'server.' % test_name)
    finally:
        # back to original working dir
        os.chdir(cwd)
    return good_setup


def setup_tests(options):
    """
    Load and instantiate all client tests.

    This function is inspired from runtest() on client/common_lib/test.py.

    @param options: an object passed in from command line OptionParser.
                    See all options defined on client/bin/autotest.
    """

    assert options.client_test_setup, 'Specify prebuild client tests on the ' \
                                      'command line.'

    requested_tests = options.client_test_setup.split(',')
    candidates, broken_tests = load_all_client_tests(options)

    failed_tests = []
    if 'all' in requested_tests:
        need_to_setup = candidates
        failed_tests += broken_tests
    else:
        need_to_setup = []
        for candidate in candidates:
            if candidate.__class__.__name__ in requested_tests:
                need_to_setup.append(candidate)
        for broken_test in broken_tests:
            if broken_test in requested_tests:
                failed_tests.append(broken_test)

    if need_to_setup:
        cwd = os.getcwd()
        os.chdir(need_to_setup[0].job.clientdir)
        os.system('tools/make_clean')
        os.chdir(cwd)
    elif not failed_tests:
        logging.error('### No test setup candidates ###')
        raise error.AutoservError('No test setup candidates.')

    for client_test in need_to_setup:
        good_setup = setup_test(client_test)
        if not good_setup:
            failed_tests.append(client_test.__class__.__name__)

    logging.info('############################# SUMMARY '
                 '#############################')

    # Print out tests that failed
    if failed_tests:
        logging.info('Finished setup -- The following tests failed')
        for failed_test in failed_tests:
            logging.info(failed_test)
    else:
        logging.info('Finished setup -- All tests built successfully')
    logging.info('######################### END SUMMARY '
                 '##############################')
    if failed_tests:
        raise error.AutoservError('Finished setup with errors.')