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

« back to all changes in this revision

Viewing changes to checkbox/traverser.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 traverser exposes a simple iterator interface for accessing the graph
 
23
of tests as expressed by the dependency tree. The constructor provides an
 
24
additional callbacks instance for processing tests which might be skipped.
 
25
"""
 
26
 
 
27
import logging
 
28
 
 
29
from checkbox.lib.iterator import (Iterator, IteratorContain,
 
30
    IteratorExclude, IteratorPostRepeat, IteratorPreRepeat)
 
31
 
 
32
from checkbox.resolver import Resolver
 
33
from checkbox.test import FAIL, SKIP
 
34
 
 
35
 
 
36
class TraverserCallbacks(object):
 
37
 
 
38
    def get_architecture(self):
 
39
        return None
 
40
 
 
41
    def get_category(self):
 
42
        return None
 
43
 
 
44
    def get_priorities(self):
 
45
        return []
 
46
 
 
47
    def skipped_dependent(self, test, result):
 
48
        logging.info("Test '%s' dependent on skipped test",
 
49
            test.name)
 
50
 
 
51
    def failed_dependent(self, test, result):
 
52
        logging.info("Test '%s' dependent on failed test",
 
53
            test.name)
 
54
 
 
55
    def unsupported_requires(self, test, result):
 
56
        logging.info("Test '%s' does not support requires field: %s",
 
57
            test.name, test.requires)
 
58
 
 
59
    def undefined_architecture(self, test, result):
 
60
        logging.info("No system architecture defined, skipping test: %s",
 
61
            test.name)
 
62
 
 
63
    def unsupported_architecture(self, test, result):
 
64
        logging.info("System architecture not supported, skipping test: %s",
 
65
            test.name)
 
66
 
 
67
    def undefined_category(self, test, result):
 
68
        logging.info("No system category defined, skipping test: %s",
 
69
            test.name)
 
70
 
 
71
    def unsupported_category(self, test, result):
 
72
        logging.info("System category not supported, skipping test: %s",
 
73
            test.name)
 
74
 
 
75
 
 
76
class Traverser(IteratorContain):
 
77
 
 
78
    def __init__(self, tests=[], callbacks=TraverserCallbacks, *args, **kwargs):
 
79
        self._callbacks = callbacks(*args, **kwargs)
 
80
 
 
81
        self._dependent_next = []
 
82
        self._dependent_prev = []
 
83
        self._dependent_status = None
 
84
 
 
85
        # Express dependents as objects rather than string names
 
86
        resolver = Resolver(self._compare_func)
 
87
        test_dict = dict(((t.suite, t.name), t) for t in tests)
 
88
        for test in tests:
 
89
            test_depends = [test_dict[(test.suite, d)] for d in test.depends]
 
90
            resolver.add(test, *test_depends)
 
91
 
 
92
        tests = resolver.get_dependents()
 
93
        iterator = Iterator(tests)
 
94
        iterator = IteratorExclude(iterator,
 
95
            self._requires_exclude_func,
 
96
            self._requires_exclude_func)
 
97
        iterator = IteratorExclude(iterator,
 
98
            self._architecture_exclude_func,
 
99
            self._architecture_exclude_func)
 
100
        iterator = IteratorExclude(iterator,
 
101
            self._category_exclude_func,
 
102
            self._category_exclude_func)
 
103
        iterator = IteratorExclude(iterator,
 
104
            self._dependent_exclude_next_func,
 
105
            self._dependent_exclude_prev_func)
 
106
        iterator = IteratorPreRepeat(iterator,
 
107
            lambda test, result, resolver=resolver: \
 
108
                   self._dependent_prerepeat_next_func(test, result, resolver))
 
109
        iterator = IteratorPostRepeat(iterator,
 
110
            prev_func=lambda test, result, resolver=resolver: \
 
111
                   self._dependent_prerepeat_prev_func(test, result, resolver))
 
112
 
 
113
        super(Traverser, self).__init__(iterator)
 
114
 
 
115
    def _compare_func(self, a, b):
 
116
        priorities = self._callbacks.get_priorities()
 
117
        if a.plugin in priorities:
 
118
            if b.plugin in priorities:
 
119
                ia = priorities.index(a.plugin)
 
120
                ib = priorities.index(b.plugin)
 
121
                if ia != ib:
 
122
                    return cmp(ia, ib)
 
123
            else:
 
124
                return -1
 
125
        elif b.plugin in priorities:
 
126
            return 1
 
127
 
 
128
        return cmp((a.suite, a.name), (b.suite, b.name))
 
129
 
 
130
    def _dependent_prerepeat_next_func(self, test, result, resolver):
 
131
        """IteratorPreRepeat function which completely skips dependents
 
132
           of tests which either have a status of FAIL or SKIP."""
 
133
        if result and result.test == test:
 
134
            if result.status == FAIL or result.status == SKIP:
 
135
                self._dependent_next = resolver.get_dependents(test)
 
136
                self._dependent_status = result.status
 
137
            else:
 
138
                self._dependent_next = []
 
139
 
 
140
        self._dependent_prev.append(test)
 
141
 
 
142
    def _dependent_prerepeat_prev_func(self, test, result, resolver):
 
143
        """IteratorPreRepeat function which supplements the above by
 
144
           keeping track of a stack of previously run tests."""
 
145
        self._dependent_prev.pop()
 
146
 
 
147
    def _dependent_exclude_next_func(self, test, result):
 
148
        if test in self._dependent_next:
 
149
            if self._dependent_status == FAIL:
 
150
                self._callbacks.failed_dependent(test, result)
 
151
            elif self._dependent_status == SKIP:
 
152
                self._callbacks.skipped_dependent(test, result)
 
153
            return True
 
154
 
 
155
        return False
 
156
 
 
157
    def _dependent_exclude_prev_func(self, test, result):
 
158
        if self._dependent_prev[-1] != test:
 
159
            return True
 
160
 
 
161
        return False
 
162
 
 
163
    def _requires_exclude_func(self, test, result):
 
164
        """IteratorExclude function which removes test when the
 
165
           requires field contains a False value."""
 
166
        if False in test.requires.get_mask():
 
167
            self._callbacks.unsupported_requires(test, result)
 
168
            return True
 
169
 
 
170
        return False
 
171
 
 
172
    def _architecture_exclude_func(self, test, result):
 
173
        """IteratorExclude function which removes test when the
 
174
           architectures field exists and doesn't meet the given
 
175
           requirements."""
 
176
        if test.architectures:
 
177
            architecture = self._callbacks.get_architecture()
 
178
            if architecture is None:
 
179
                self._callbacks.undefined_architecture(test, result)
 
180
                return True
 
181
            elif architecture not in test.architectures:
 
182
                self._callbacks.unsupported_architecture(test, result)
 
183
                return True
 
184
 
 
185
        return False
 
186
 
 
187
    def _category_exclude_func(self, test, result):
 
188
        """IteratorExclude function which removes test when
 
189
           the categories field exists and doesn't meet the given
 
190
           requirements."""
 
191
        if test.categories:
 
192
            category = self._callbacks.get_category()
 
193
            if category is None:
 
194
                self._callbacks.undefined_category(test, result)
 
195
                return True
 
196
            elif category not in test.categories:
 
197
                self._callbacks.unsupported_category(test, result)
 
198
                return True
 
199
 
 
200
        return False