~roadmr/ubuntu/precise/checkbox/0.13.1

« back to all changes in this revision

Viewing changes to checkbox/test.py

  • Committer: Bazaar Package Importer
  • Author(s): Marc Tardif, Gabor Keleman
  • Date: 2009-08-19 15:36:05 UTC
  • Revision ID: james.westby@ubuntu.com-20090819153605-weo6htup3yi6zn0t
Tags: 0.8~alpha4
* New upstream version:
  * Changed icon.
  * Added timeout property to lock_prompt plugin.
  * Added concept of attachments to tests.
  * Added support for backslahes in templates to wrap lines.
  * Added support blacklisting and whitelisting both tests and suites.
  * Introduced the concept of jobs for suites, tests and attachments.
  * Removed upstart event which is no longer needed.
  * Replaced architecture and category with requires in test definitions.
* Fixed pygst dependency (LP: #334442)
* Fixed configuration file updates during install (LP: #330596)
* Fixed and expanded translations (LP: #347038)
* Fixed ignored system proxy settings (LP: #345548)
* Fixed parsing blank lines in templates (LP: #393907)
* Fixed escaping of lists (LP: #394001)
* Fixed timeout in manual tests (LP: #377986)
* Fixed CLI interface dialog.
* Fixed support for FreeDesktop XDG base directory specification (LP: #363549)
* Added general and package specific apport hooks

[ Gabor Keleman ]
* Fixed untranslated strings in tests (LP: #374666)
* Fixed untranslated last screen (LP: #374646)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#
2
 
# This file is part of Checkbox.
3
 
#
4
 
# Copyright 2008 Canonical Ltd.
5
 
#
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.
10
 
#
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.
15
 
#
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/>.
18
 
#
19
 
"""
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
24
 
of test.
25
 
"""
26
 
 
27
 
import re
28
 
import logging
29
 
 
30
 
from gettext import gettext as _
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.frontend import frontend
37
 
from checkbox.requires import Requires
38
 
 
39
 
 
40
 
DESKTOP = "desktop"
41
 
LAPTOP = "laptop"
42
 
SERVER = "server"
43
 
ALL_CATEGORIES = [DESKTOP, LAPTOP, SERVER]
44
 
 
45
 
I386 = "i386"
46
 
AMD64 = "amd64"
47
 
LPIA = "lpia"
48
 
SPARC = "sparc"
49
 
ALL_ARCHITECTURES = [I386, AMD64, LPIA, SPARC]
50
 
 
51
 
FAIL = "fail"
52
 
PASS = "pass"
53
 
SKIP = "skip"
54
 
ALL_STATUS = [PASS, FAIL, SKIP]
55
 
 
56
 
 
57
 
class TestResult(object):
58
 
 
59
 
    def __init__(self, test, status, data, duration=None):
60
 
        self.test = test
61
 
        self.status = status
62
 
        self.data = data
63
 
        self.duration = duration
64
 
 
65
 
    @property
66
 
    def attributes(self):
67
 
        return {
68
 
            "status": self.status,
69
 
            "data": self.data,
70
 
            "duration": self.duration}
71
 
 
72
 
    @property
73
 
    def devices(self):
74
 
        return self.test.requires.get_devices()
75
 
 
76
 
    @property
77
 
    def packages(self):
78
 
        return self.test.requires.get_packages()
79
 
 
80
 
    def _get_status(self):
81
 
        return self._status
82
 
 
83
 
    def _set_status(self, status):
84
 
        if status not in ALL_STATUS:
85
 
            raise Exception, "Invalid status: %s" % status
86
 
 
87
 
        self._status = status
88
 
 
89
 
    status = property(_get_status, _set_status)
90
 
 
91
 
 
92
 
class TestCommand(Command):
93
 
 
94
 
    def __init__(self, test, *args, **kwargs):
95
 
        super(TestCommand, self).__init__(test.command, test.timeout,
96
 
            *args, **kwargs)
97
 
        self.test = test
98
 
 
99
 
    @frontend("get_test_result")
100
 
    def execute(self, *args, **kwargs):
101
 
        return super(TestCommand, self).execute(*args, **kwargs)
102
 
 
103
 
    def post_execute(self, result):
104
 
        result = super(TestCommand, self).post_execute(result)
105
 
 
106
 
        if result.if_exited:
107
 
            exit_status = result.exit_status
108
 
            if exit_status == 0:
109
 
                status = PASS
110
 
                data = result.stdout
111
 
                if not data:
112
 
                    data = result.stderr
113
 
            elif exit_status == 127:
114
 
                status = SKIP
115
 
                data = _("Command failed, skipping.")
116
 
            else:
117
 
                status = FAIL
118
 
                data = result.stderr
119
 
        elif result.if_signaled:
120
 
            status = SKIP
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))
125
 
        else:
126
 
            raise Exception, "Command not terminated: %s" \
127
 
                % self.get_command()
128
 
 
129
 
        duration = result.duration
130
 
        return TestResult(self.test, status, data, duration)
131
 
 
132
 
 
133
 
class TestDescription(Command):
134
 
 
135
 
    def __init__(self, test):
136
 
        super(TestDescription, self).__init__(test.description, test.timeout)
137
 
        self.test = test
138
 
        self.output = None
139
 
 
140
 
    def get_command(self):
141
 
        command = super(TestDescription, self).get_command()
142
 
        return "cat <<EOF\n%s\nEOF\n" % command
143
 
 
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()
149
 
 
150
 
            if result:
151
 
                self.output = result.data.strip()
152
 
                result.data = ""
153
 
 
154
 
            add_variable("output", self.output)
155
 
 
156
 
    @frontend("get_test_description")
157
 
    def execute(self, *args, **kwargs):
158
 
        return super(TestDescription, self).execute(*args, **kwargs)
159
 
 
160
 
    def post_execute(self, result):
161
 
        result = super(TestDescription, self).post_execute(result)
162
 
        remove_variable("output")
163
 
 
164
 
        if not result.if_exited \
165
 
           or result.exit_status != 0:
166
 
            raise Exception, "Description failed: %s" \
167
 
                % self.get_command()
168
 
 
169
 
        return result.stdout
170
 
 
171
 
 
172
 
class Test(object):
173
 
    """
174
 
    Test base class which should be inherited by each test
175
 
    implementation. A test instance contains the following required
176
 
    fields:
177
 
 
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.
182
 
 
183
 
    An instance also contains the following optional fields:
184
 
 
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.
198
 
    """
199
 
 
200
 
    required_fields = ["name", "plugin", "description", "suite"]
201
 
    optional_fields = {
202
 
        "architectures": [],
203
 
        "categories": [],
204
 
        "command": None,
205
 
        "depends": [],
206
 
        "requires": None,
207
 
        "timeout": None,
208
 
        "user": None,
209
 
        "optional": False}
210
 
 
211
 
    def __init__(self, registry, **attributes):
212
 
        super(Test, self).__setattr__("attributes", attributes)
213
 
 
214
 
        # Typed fields
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])
221
 
 
222
 
        # Optional fields
223
 
        for field in self.optional_fields.keys():
224
 
            if not attributes.has_key(field):
225
 
                attributes[field] = self.optional_fields[field]
226
 
 
227
 
        # Requires field
228
 
        attributes["requires"] = Requires(registry, attributes["requires"])
229
 
 
230
 
        # Command field
231
 
        attributes["command"] = TestCommand(self)
232
 
 
233
 
        # Description field
234
 
        attributes["description"] = TestDescription(self)
235
 
 
236
 
        self._validate()
237
 
 
238
 
    def _validate(self):
239
 
        # Unknown fields
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",
243
 
                    field)
244
 
                del self.attributes[field]
245
 
 
246
 
        # Required fields
247
 
        for field in self.required_fields:
248
 
            if not self.attributes.has_key(field):
249
 
                raise Exception, \
250
 
                    "Test attributes does not contain '%s': %s" \
251
 
                    % (field, self.attributes)
252
 
 
253
 
    def __getattr__(self, name):
254
 
        if name not in self.attributes:
255
 
            raise AttributeError, name
256
 
 
257
 
        return self.attributes[name]
258
 
 
259
 
    def __setattr__(self, name, value):
260
 
        if name not in self.attributes:
261
 
            raise AttributeError, name
262
 
 
263
 
        self.attributes[name] = value
264
 
        self._validate()