2
# Copyright (c) 2008 Canonical
4
# Written by Marc Tardif <marc@interunion.ca>
6
# This file is part of Checkbox.
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.
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.
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/>.
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
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.requires import Requires
42
ALL_CATEGORIES = [DESKTOP, LAPTOP, SERVER]
48
ALL_ARCHITECTURES = [I386, AMD64, LPIA, SPARC]
53
ALL_STATUS = [PASS, FAIL, SKIP]
56
class TestResult(object):
58
def __init__(self, test, status, data, duration=None):
62
self.duration = duration
67
"status": self.status,
69
"duration": self.duration}
73
return self.test.requires.get_devices()
77
return self.test.requires.get_packages()
79
def _get_status(self):
82
def _set_status(self, status):
83
if status not in ALL_STATUS:
84
raise Exception, "Invalid status: %s" % status
88
status = property(_get_status, _set_status)
91
class TestCommand(Command):
93
def __init__(self, test, *args, **kwargs):
94
super(TestCommand, self).__init__(test.command, test.timeout,
98
def post_execute(self, result):
99
result = super(TestCommand, self).post_execute(result)
102
exit_status = result.exit_status
108
elif exit_status == 127:
110
data = "Command failed, skipping."
114
elif result.if_signaled:
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))
121
raise Exception, "Command not terminated: %s" % self.command
123
duration = result.duration
124
return TestResult(self.test, status=status, data=data, duration=duration)
127
class TestDescription(Command):
129
def __init__(self, test):
130
super(TestDescription, self).__init__(test.description, test.timeout)
134
def get_command(self):
135
command = super(TestDescription, self).get_command()
136
return "cat <<EOF\n%s\nEOF\n" % command
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()
145
self.output = result.data.strip()
148
add_variable("output", self.output)
150
def post_execute(self, result):
151
result = super(TestDescription, self).post_execute(result)
152
remove_variable("output")
154
if not result.if_exited \
155
or result.exit_status != 0:
156
raise Exception, "Description failed: %s" % self.command
163
Test base class which should be inherited by each test
164
implementation. A test instance contains the following required
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.
172
An instance also contains the following optional fields:
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.
188
required_fields = ["name", "plugin", "description", "suite"]
198
def __init__(self, registry, **attributes):
199
super(Test, self).__setattr__("attributes", attributes)
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])
210
for field in self.optional_fields.keys():
211
if not attributes.has_key(field):
212
attributes[field] = self.optional_fields[field]
215
attributes["requires"] = Requires(registry, attributes["requires"])
218
attributes["command"] = TestCommand(self)
221
attributes["description"] = TestDescription(self)
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",
231
del self.attributes[field]
234
for field in self.required_fields:
235
if not self.attributes.has_key(field):
237
"Test attributes does not contain '%s': %s" \
238
% (field, self.attributes)
240
def __getattr__(self, name):
241
if name not in self.attributes:
242
raise AttributeError, name
244
return self.attributes[name]
246
def __setattr__(self, name, value):
247
if name not in self.attributes:
248
raise AttributeError, name
250
self.attributes[name] = value