~ubuntu-branches/ubuntu/precise/checkbox/precise

« back to all changes in this revision

Viewing changes to checkbox/test.py

  • Committer: Bazaar Package Importer
  • Author(s): Marc Tardif
  • Date: 2009-01-20 16:46:15 UTC
  • Revision ID: james.westby@ubuntu.com-20090120164615-7iz6nmlef41h4vx2
Tags: 0.4
* Setup bzr-builddeb in native mode.
* Removed LGPL notice from the copyright file.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# Copyright (c) 2008 Canonical
 
3
#
 
4
# Written by Marc Tardif <marc@interunion.ca>
 
5
#
 
6
# This file is part of Checkbox.
 
7
#
 
8
# Checkbox is free software: you can redistribute it and/or modify
 
9
# it under the terms of the GNU General Public License as published by
 
10
# the Free Software Foundation, either version 3 of the License, or
 
11
# (at your option) any later version.
 
12
#
 
13
# Checkbox is distributed in the hope that it will be useful,
 
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
16
# GNU General Public License for more details.
 
17
#
 
18
# You should have received a copy of the GNU General Public License
 
19
# along with Checkbox.  If not, see <http://www.gnu.org/licenses/>.
 
20
#
 
21
"""
 
22
The purpose of this module is to encapsulate the concept of a test
 
23
which might be presented in several ways. For example, a test might
 
24
require manual intervention whereas another test might be completely
 
25
automatic. Either way, this module provides the base common to each type
 
26
of test.
 
27
"""
 
28
 
 
29
import re
 
30
import logging
 
31
 
 
32
from checkbox.lib.environ import add_variable, remove_variable
 
33
from checkbox.lib.signal import signal_to_name, signal_to_description
 
34
 
 
35
from checkbox.command import Command
 
36
from checkbox.requires import Requires
 
37
 
 
38
 
 
39
DESKTOP = "desktop"
 
40
LAPTOP = "laptop"
 
41
SERVER = "server"
 
42
ALL_CATEGORIES = [DESKTOP, LAPTOP, SERVER]
 
43
 
 
44
I386 = "i386"
 
45
AMD64 = "amd64"
 
46
LPIA = "lpia"
 
47
SPARC = "sparc"
 
48
ALL_ARCHITECTURES = [I386, AMD64, LPIA, SPARC]
 
49
 
 
50
FAIL = "fail"
 
51
PASS = "pass"
 
52
SKIP = "skip"
 
53
ALL_STATUS = [PASS, FAIL, SKIP]
 
54
 
 
55
 
 
56
class TestResult(object):
 
57
 
 
58
    def __init__(self, test, status, data, duration=None):
 
59
        self.test = test
 
60
        self.status = status
 
61
        self.data = data
 
62
        self.duration = duration
 
63
 
 
64
    @property
 
65
    def attributes(self):
 
66
        return {
 
67
            "status": self.status,
 
68
            "data": self.data,
 
69
            "duration": self.duration}
 
70
 
 
71
    @property
 
72
    def devices(self):
 
73
        return self.test.requires.get_devices()
 
74
 
 
75
    @property
 
76
    def packages(self):
 
77
        return self.test.requires.get_packages()
 
78
 
 
79
    def _get_status(self):
 
80
        return self._status
 
81
 
 
82
    def _set_status(self, status):
 
83
        if status not in ALL_STATUS:
 
84
            raise Exception, "Invalid status: %s" % status
 
85
 
 
86
        self._status = status
 
87
 
 
88
    status = property(_get_status, _set_status)
 
89
 
 
90
 
 
91
class TestCommand(Command):
 
92
 
 
93
    def __init__(self, test, *args, **kwargs):
 
94
        super(TestCommand, self).__init__(test.command, test.timeout,
 
95
            *args, **kwargs)
 
96
        self.test = test
 
97
 
 
98
    def post_execute(self, result):
 
99
        result = super(TestCommand, self).post_execute(result)
 
100
 
 
101
        if result.if_exited:
 
102
            exit_status = result.exit_status
 
103
            if exit_status == 0:
 
104
                status = PASS
 
105
                data = result.stdout
 
106
                if not data:
 
107
                    data = result.stderr
 
108
            elif exit_status == 127:
 
109
                status = SKIP
 
110
                data = "Command failed, skipping."
 
111
            else:
 
112
                status = FAIL
 
113
                data = result.stderr
 
114
        elif result.if_signaled:
 
115
            status = SKIP
 
116
            term_signal = result.term_signal
 
117
            data = "Received terminate signal %s: %s" % \
 
118
                (signal_to_name(term_signal),
 
119
                 signal_to_description(term_signal))
 
120
        else:
 
121
            raise Exception, "Command not terminated: %s" % self.command
 
122
 
 
123
        duration = result.duration
 
124
        return TestResult(self.test, status=status, data=data, duration=duration)
 
125
 
 
126
 
 
127
class TestDescription(Command):
 
128
 
 
129
    def __init__(self, test):
 
130
        super(TestDescription, self).__init__(test.description, test.timeout)
 
131
        self.test = test
 
132
        self.output = None
 
133
 
 
134
    def get_command(self):
 
135
        command = super(TestDescription, self).get_command()
 
136
        return "cat <<EOF\n%s\nEOF\n" % command
 
137
 
 
138
    def pre_execute(self, result=None):
 
139
        super(TestDescription, self).pre_execute()
 
140
        if re.search(r"\$output", self.get_command()):
 
141
            if not self.output and not result:
 
142
                result = self.test.command()
 
143
 
 
144
            if result:
 
145
                self.output = result.data.strip()
 
146
                result.data = ""
 
147
 
 
148
            add_variable("output", self.output)
 
149
 
 
150
    def post_execute(self, result):
 
151
        result = super(TestDescription, self).post_execute(result)
 
152
        remove_variable("output")
 
153
 
 
154
        if not result.if_exited \
 
155
           or result.exit_status != 0:
 
156
            raise Exception, "Description failed: %s" % self.command
 
157
 
 
158
        return result.stdout
 
159
 
 
160
 
 
161
class Test(object):
 
162
    """
 
163
    Test base class which should be inherited by each test
 
164
    implementation. A test instance contains the following required
 
165
    fields:
 
166
 
 
167
    name:          Unique name for a test.
 
168
    plugin:        Plugin name to handle this test.
 
169
    description:   Long description of what the test does.
 
170
    suite:         Name of the suite containing this test.
 
171
 
 
172
    An instance also contains the following optional fields:
 
173
 
 
174
    architectures: List of architectures for which this test is relevant:
 
175
                   amd64, i386, powerpc and/or sparc
 
176
    categories:    List of categories for which this test is relevant:
 
177
                   desktop, laptop and/or server
 
178
    command:       Command to run for the test.
 
179
    depends:       List of names on which this test depends. So, if
 
180
                   the other test fails, this test will be skipped.
 
181
    requires:      Registry expressions which are requirements for
 
182
                   this test: 'input.mouse' in info.capabilities
 
183
    timeout:       Timeout for running the command.
 
184
    optional:      Boolean expression set to True if this test is optional
 
185
                   or False if this test is required.
 
186
    """
 
187
 
 
188
    required_fields = ["name", "plugin", "description", "suite"]
 
189
    optional_fields = {
 
190
        "architectures": [],
 
191
        "categories": [],
 
192
        "command": None,
 
193
        "depends": [],
 
194
        "requires": None,
 
195
        "timeout": None,
 
196
        "optional": False}
 
197
 
 
198
    def __init__(self, registry, **attributes):
 
199
        super(Test, self).__setattr__("attributes", attributes)
 
200
 
 
201
        # Typed fields
 
202
        for field in ["architectures", "categories", "depends"]:
 
203
            if attributes.has_key(field):
 
204
                attributes[field] = re.split(r"\s*,\s*", attributes[field])
 
205
        for field in ["timeout"]:
 
206
            if attributes.has_key(field):
 
207
                attributes[field] = int(attributes[field])
 
208
 
 
209
        # Optional fields
 
210
        for field in self.optional_fields.keys():
 
211
            if not attributes.has_key(field):
 
212
                attributes[field] = self.optional_fields[field]
 
213
 
 
214
        # Requires field
 
215
        attributes["requires"] = Requires(registry, attributes["requires"])
 
216
 
 
217
        # Command field
 
218
        attributes["command"] = TestCommand(self)
 
219
 
 
220
        # Description field
 
221
        attributes["description"] = TestDescription(self)
 
222
 
 
223
        self._validate()
 
224
 
 
225
    def _validate(self):
 
226
        # Unknown fields
 
227
        for field in self.attributes.keys():
 
228
            if field not in self.required_fields + self.optional_fields.keys():
 
229
                logging.info("Test attributes contains unknown field: %s",
 
230
                    field)
 
231
                del self.attributes[field]
 
232
 
 
233
        # Required fields
 
234
        for field in self.required_fields:
 
235
            if not self.attributes.has_key(field):
 
236
                raise Exception, \
 
237
                    "Test attributes does not contain '%s': %s" \
 
238
                    % (field, self.attributes)
 
239
 
 
240
    def __getattr__(self, name):
 
241
        if name not in self.attributes:
 
242
            raise AttributeError, name
 
243
 
 
244
        return self.attributes[name]
 
245
 
 
246
    def __setattr__(self, name, value):
 
247
        if name not in self.attributes:
 
248
            raise AttributeError, name
 
249
 
 
250
        self.attributes[name] = value
 
251
        self._validate()