test.py 7.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; specifically version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See LICENSE for more details.
#
11 12 13
# This code was inspired in the autotest project,
# client/shared/test.py
# Authors: Martin J Bligh <mbligh@google.com>, Andy Whitcroft <apw@shadowen.org>
14

L
Lucas Meneghel Rodrigues 已提交
15 16 17 18 19
"""
Contains the base test implementation, used as a base for the actual
framework tests.
"""

L
Lucas Meneghel Rodrigues 已提交
20
import logging
21
import os
22 23 24 25 26
import sys
import time
import traceback
import unittest

27
from avocado.core import data_dir
L
Lucas Meneghel Rodrigues 已提交
28
from avocado.core import exceptions
29
from avocado.utils import process
30
from avocado import sysinfo
L
Lucas Meneghel Rodrigues 已提交
31

32

33
class Test(unittest.TestCase):
34 35

    """
L
Lucas Meneghel Rodrigues 已提交
36 37 38 39
    Base implementation for the test class.

    You'll inherit from this to write your own tests. Tipically you'll want
    to implement setup(), action() and cleanup() methods on your own tests.
40 41
    """

42
    def __init__(self, name, base_logdir=None, tag=None):
L
Lucas Meneghel Rodrigues 已提交
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
        """
        Initializes the test.

        :param name: Test Name. Example: 'sleeptest'.
        :param tag: Tag that differentiates 2 executions of the same test name.
                Example: 'long', 'short', so we can differentiate
                'sleeptest.long' and 'sleeptest.short'.

        Test Attributes:
        basedir: Where the test .py file is located (root dir).
        depsdir: If this is an existing test suite wrapper, it'll contain the
                test suite sources and other auxiliary files. Usually inside
                basedir, 'deps' subdirectory.
        workdir: Place where temporary copies of the source code, binaries,
                image files will be created and modified.
58
        base_logdir: Base log directory, where logs from all tests go to.
L
Lucas Meneghel Rodrigues 已提交
59
        """
60 61
        self.name = name
        self.tag = tag
L
Lucas Meneghel Rodrigues 已提交
62
        self.basedir = os.path.join(data_dir.get_test_dir(), name)
L
Lucas Meneghel Rodrigues 已提交
63
        self.depsdir = os.path.join(self.basedir, 'deps')
64 65 66 67 68 69
        self.workdir = os.path.join(data_dir.get_tmp_dir(), self.name)
        if not os.path.isdir(self.workdir):
            os.makedirs(self.workdir)
        self.srcdir = os.path.join(self.workdir, 'src')
        if not os.path.isdir(self.srcdir):
            os.makedirs(self.srcdir)
70 71
        if base_logdir is None:
            base_logdir = os.path.expanduser('~/avocado')
72 73 74 75 76 77 78
        self.tagged_name = self.get_tagged_name(base_logdir, self.name,
                                                self.tag)
        self.logdir = os.path.join(base_logdir, self.tagged_name)
        if not os.path.isdir(self.logdir):
            os.makedirs(self.logdir)
        self.logfile = os.path.join(self.logdir, 'debug.log')
        self.sysinfodir = os.path.join(self.logdir, 'sysinfo')
79 80 81

        self.log = logging.getLogger("avocado.test")

82 83
        self.debugdir = None
        self.resultsdir = None
84
        self.status = None
85
        self.fail_reason = None
86 87

        self.time_elapsed = None
88 89 90 91 92 93 94
        unittest.TestCase.__init__(self)

    def __str__(self):
        return str(self.name)

    def __repr__(self):
        return "Test(%r)" % self.tagged_name
95

96 97 98
    def get_deps_path(self, basename):
        return os.path.join(self.depsdir, basename)

99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
    def start_logging(self):
        """
        Simple helper for adding a file logger to the root logger.
        """
        self.file_handler = logging.FileHandler(filename=self.logfile)
        self.file_handler.setLevel(logging.DEBUG)

        fmt = '%(asctime)s %(levelname)-5.5s| %(message)s'
        formatter = logging.Formatter(fmt=fmt, datefmt='%H:%M:%S')

        self.file_handler.setFormatter(formatter)
        self.log.addHandler(self.file_handler)

    def stop_logging(self):
        self.log.removeHandler(self.file_handler)

115 116 117 118 119 120 121 122 123 124 125 126
    def get_tagged_name(self, logdir, name, tag):
        if tag is not None:
            return "%s.%s" % (self.name, self.tag)
        tag = 1
        tagged_name = "%s.%s" % (name, tag)
        test_logdir = os.path.join(logdir, tagged_name)
        while os.path.isdir(test_logdir):
            tag += 1
            tagged_name = "%s.%s" % (name, tag)
            test_logdir = os.path.join(logdir, tagged_name)
        return tagged_name

127
    def setup(self):
L
Lucas Meneghel Rodrigues 已提交
128 129 130 131 132 133 134
        """
        Setup stage that the test needs before passing to the actual action.

        Must be implemented by tests if they want such an stage. Commonly we'll
        download/compile test suites, create files needed for a test, among
        other possibilities.
        """
135 136 137
        pass

    def action(self):
L
Lucas Meneghel Rodrigues 已提交
138 139 140 141 142 143 144 145 146 147
        """
        Actual test payload. Must be implemented by tests.

        In case of an existing test suite wrapper, it'll execute the suite,
        or perform a series of operations, and based in the results of the
        operations decide if the test pass (let the test complete) or fail
        (raise a test related exception).
        """
        raise NotImplementedError('Test subclasses must implement an action '
                                  'method')
148

149
    def cleanup(self):
L
Lucas Meneghel Rodrigues 已提交
150 151 152 153 154 155 156
        """
        Cleanup stage after the action is done.

        Examples of cleanup actions are deleting temporary files, restoring
        firewall configurations or other system settings that were changed
        in setup.
        """
157
        pass
158

159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
    def runTest(self, result=None):
        """
        Run test method, for compatibility with unittest.TestCase.
        """
        start_time = time.time()
        try:
            sysinfo_logger = sysinfo.SysInfo(basedir=self.sysinfodir)
            self.start_logging()
            sysinfo_logger.start_job_hook()
            try:
                self.setup()
            except Exception, details:
                raise exceptions.TestSetupFail(details)
            self.action()
            self.cleanup()
            self.status = 'PASS'
        except exceptions.TestBaseException, detail:
            self.status = detail.status
            self.fail_reason = detail
        except AssertionError, detail:
            self.status = 'FAIL'
            self.fail_reason = detail
        except Exception, detail:
            exc_type, exc_value, exc_traceback = sys.exc_info()
            tb_info = traceback.format_exception(exc_type, exc_value,
                                                 exc_traceback.tb_next)
            tb_info = "".join(tb_info)
            for e_line in tb_info.splitlines():
                self.log.error(e_line)
            self.status = 'FAIL'
            self.fail_reason = detail
        finally:
            end_time = time.time()
            self.time_elapsed = end_time - start_time
            self.report()
            self.stop_logging()

196 197 198 199 200 201 202 203 204 205
    def report(self):
        if self.fail_reason is not None:
            self.log.error("%s %s -> %s: %s", self.status,
                           self.tagged_name,
                           self.fail_reason.__class__.__name__,
                           self.fail_reason)

        else:
            self.log.info("%s %s", self.status,
                          self.tagged_name)
206 207 208 209 210 211 212 213 214 215 216 217 218 219


class DropinTest(Test):

    """
    Run an arbitrary command that returns either 0 (PASS) or !=0 (FAIL).
    """

    def __init__(self, path, base_logdir, tag=None):
        basename = os.path.basename(path)
        name = basename.split(".")[0]
        self.path = os.path.abspath(path)
        super(DropinTest, self).__init__(name, base_logdir, tag)

220 221 222 223 224
    def _log_detailed_cmd_info(self, result):
        run_info = str(result)
        for line in run_info.splitlines():
            self.log.info(line)

225
    def action(self):
226 227 228 229 230 231
        try:
            result = process.run(self.path, verbose=True)
            self._log_detailed_cmd_info(result)
        except exceptions.CmdError, details:
            self._log_detailed_cmd_info(details.result)
            raise exceptions.TestFail(details)