2
# This file is part of Checkbox.
4
# Copyright 2008 Canonical Ltd.
6
# Checkbox is free software: you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation, either version 3 of the License, or
9
# (at your option) any later version.
11
# Checkbox is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU General Public License for more details.
16
# You should have received a copy of the GNU General Public License
17
# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
20
The purpose of this module is to encapsulate the concept of a test
21
which might be presented in several ways. For example, a test might
22
require manual intervention whereas another test might be completely
23
automatic. Either way, this module provides the base common to each type
30
from gettext import gettext as _
32
from checkbox.lib.environ import add_variable, remove_variable
33
from checkbox.lib.signal import signal_to_name, signal_to_description
35
from checkbox.command import Command
36
from checkbox.frontend import frontend
37
from checkbox.requires import Requires
43
ALL_CATEGORIES = [DESKTOP, LAPTOP, SERVER]
49
ALL_ARCHITECTURES = [I386, AMD64, LPIA, SPARC]
54
ALL_STATUS = [PASS, FAIL, SKIP]
57
class TestResult(object):
59
def __init__(self, test, status, data, duration=None):
63
self.duration = duration
68
"status": self.status,
70
"duration": self.duration}
74
return self.test.requires.get_devices()
78
return self.test.requires.get_packages()
80
def _get_status(self):
83
def _set_status(self, status):
84
if status not in ALL_STATUS:
85
raise Exception, "Invalid status: %s" % status
89
status = property(_get_status, _set_status)
92
class TestCommand(Command):
94
def __init__(self, test, *args, **kwargs):
95
super(TestCommand, self).__init__(test.command, test.timeout,
99
@frontend("get_test_result")
100
def execute(self, *args, **kwargs):
101
return super(TestCommand, self).execute(*args, **kwargs)
103
def post_execute(self, result):
104
result = super(TestCommand, self).post_execute(result)
107
exit_status = result.exit_status
113
elif exit_status == 127:
115
data = _("Command failed, skipping.")
119
elif result.if_signaled:
121
term_signal = result.term_signal
122
data = _("Received terminate signal %s: %s") % \
123
(signal_to_name(term_signal),
124
signal_to_description(term_signal))
126
raise Exception, "Command not terminated: %s" \
129
duration = result.duration
130
return TestResult(self.test, status, data, duration)
133
class TestDescription(Command):
135
def __init__(self, test):
136
super(TestDescription, self).__init__(test.description, test.timeout)
140
def get_command(self):
141
command = super(TestDescription, self).get_command()
142
return "cat <<EOF\n%s\nEOF\n" % command
144
def pre_execute(self, result=None):
145
super(TestDescription, self).pre_execute()
146
if re.search(r"\$output", self.get_command()):
147
if not self.output and not result:
148
result = self.test.command()
151
self.output = result.data.strip()
154
add_variable("output", self.output)
156
@frontend("get_test_description")
157
def execute(self, *args, **kwargs):
158
return super(TestDescription, self).execute(*args, **kwargs)
160
def post_execute(self, result):
161
result = super(TestDescription, self).post_execute(result)
162
remove_variable("output")
164
if not result.if_exited \
165
or result.exit_status != 0:
166
raise Exception, "Description failed: %s" \
174
Test base class which should be inherited by each test
175
implementation. A test instance contains the following required
178
name: Unique name for a test.
179
plugin: Plugin name to handle this test.
180
description: Long description of what the test does.
181
suite: Name of the suite containing this test.
183
An instance also contains the following optional fields:
185
architectures: List of architectures for which this test is relevant:
186
amd64, i386, powerpc and/or sparc
187
categories: List of categories for which this test is relevant:
188
desktop, laptop and/or server
189
command: Command to run for the test.
190
depends: List of names on which this test depends. So, if
191
the other test fails, this test will be skipped.
192
requires: Registry expressions which are requirements for
193
this test: 'input.mouse' in info.capabilities
194
timeout: Timeout for running the command.
195
user: User to run the command.
196
optional: Boolean expression set to True if this test is optional
197
or False if this test is required.
200
required_fields = ["name", "plugin", "description", "suite"]
211
def __init__(self, registry, **attributes):
212
super(Test, self).__setattr__("attributes", attributes)
215
for field in ["architectures", "categories", "depends"]:
216
if attributes.has_key(field):
217
attributes[field] = re.split(r"\s*,\s*", attributes[field])
218
for field in ["timeout"]:
219
if attributes.has_key(field):
220
attributes[field] = int(attributes[field])
223
for field in self.optional_fields.keys():
224
if not attributes.has_key(field):
225
attributes[field] = self.optional_fields[field]
228
attributes["requires"] = Requires(registry, attributes["requires"])
231
attributes["command"] = TestCommand(self)
234
attributes["description"] = TestDescription(self)
240
for field in self.attributes.keys():
241
if field not in self.required_fields + self.optional_fields.keys():
242
logging.info("Test attributes contains unknown field: %s",
244
del self.attributes[field]
247
for field in self.required_fields:
248
if not self.attributes.has_key(field):
250
"Test attributes does not contain '%s': %s" \
251
% (field, self.attributes)
253
def __getattr__(self, name):
254
if name not in self.attributes:
255
raise AttributeError, name
257
return self.attributes[name]
259
def __setattr__(self, name, value):
260
if name not in self.attributes:
261
raise AttributeError, name
263
self.attributes[name] = value