OLD | NEW |
1 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. | 1 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 | 5 |
6 # DESCRIPTION : | 6 # DESCRIPTION : |
7 # | 7 # |
8 # This library provides common types and routines for the factory ui | 8 # This library provides common types and routines for the factory ui |
9 # infrastructure. This library explicitly does not import gtk, to | 9 # infrastructure. This library explicitly does not import gtk, to |
10 # allow its use by the autotest control process. | 10 # allow its use by the autotest control process. |
(...skipping 20 matching lines...) Expand all Loading... |
31 LOG_PATH = '/var/log/factory.log' | 31 LOG_PATH = '/var/log/factory.log' |
32 DATA_PREFIX = 'FACTORY_DATA:' | 32 DATA_PREFIX = 'FACTORY_DATA:' |
33 FINAL_VERIFICATION_TEST_UNIQUE_NAME = 'factory_Verify' | 33 FINAL_VERIFICATION_TEST_UNIQUE_NAME = 'factory_Verify' |
34 | 34 |
35 def log(s): | 35 def log(s): |
36 print >> sys.stderr, 'FACTORY: ' + s | 36 print >> sys.stderr, 'FACTORY: ' + s |
37 | 37 |
38 def log_shared_data(key, value): | 38 def log_shared_data(key, value): |
39 print >> sys.stderr, '%s %s=%s' % (DATA_PREFIX, key, repr(value)) | 39 print >> sys.stderr, '%s %s=%s' % (DATA_PREFIX, key, repr(value)) |
40 | 40 |
41 def lookup_status_by_unique_name(unique_name, test_list, status_file_path): | |
42 """ quick way to determine the status of given test """ | |
43 status_map = StatusMap(test_list, status_file_path) | |
44 testdb = status_map.test_db | |
45 xtest = testdb.get_test_by_unique_name(unique_name) | |
46 return status_map.lookup_status(xtest) | |
47 | 41 |
48 class FactoryTest: | 42 class FactoryTest: |
49 def __repr__(self): | 43 def __repr__(self): |
50 d = ['%s=%s' % (l, repr(v)) | 44 d = ['%s=%s' % (l, repr(v)) |
51 for l, v in self.__dict__.items() | 45 for l, v in self.__dict__.items() |
52 if l != 'self'] | 46 if l != 'self'] |
53 c = ('%s' % self.__class__).rpartition('.')[2] | 47 c = ('%s' % self.__class__).rpartition('.')[2] |
54 return '%s(%s)' % (c, ','.join(d)) | 48 return '%s(%s)' % (c, ','.join(d)) |
55 | 49 |
56 class FactoryAutotestTest(FactoryTest): | 50 class FactoryAutotestTest(FactoryTest): |
(...skipping 13 matching lines...) Expand all Loading... |
70 class AutomatedSequence(FactoryTest): | 64 class AutomatedSequence(FactoryTest): |
71 def __init__(self, label_en='', label_zw='', subtest_tag_prefix=None, | 65 def __init__(self, label_en='', label_zw='', subtest_tag_prefix=None, |
72 kbd_shortcut=None, subtest_list=[], unique_name=None): | 66 kbd_shortcut=None, subtest_list=[], unique_name=None): |
73 self.__dict__.update(vars()) | 67 self.__dict__.update(vars()) |
74 | 68 |
75 class AutomatedSubTest(FactoryAutotestTest): | 69 class AutomatedSubTest(FactoryAutotestTest): |
76 def __init__(self, label_en='', label_zw='', autotest_name=None, | 70 def __init__(self, label_en='', label_zw='', autotest_name=None, |
77 dargs={}, drop_caches=False, unique_name=None): | 71 dargs={}, drop_caches=False, unique_name=None): |
78 self.__dict__.update(vars()) | 72 self.__dict__.update(vars()) |
79 | 73 |
80 class AutomatedRebootSubTest(FactoryAutotestTest): | 74 class AutomatedRebootSubTest(AutomatedSubTest): |
81 def __init__(self, label_en='', label_zw='', iterations=None, | 75 def __init__(self, label_en='', label_zw='', iterations=None, |
82 autotest_name='factory_RebootStub', dargs={}, | 76 autotest_name='factory_RebootStub', dargs={}, |
83 drop_caches=False, unique_name=None): | 77 drop_caches=False, unique_name=None): |
84 self.__dict__.update(vars()) | 78 self.__dict__.update(vars()) |
85 | 79 |
86 | 80 |
| 81 class KbdShortcutDatabase: |
| 82 '''Track the bindings between keyboard shortcuts and tests.''' |
| 83 |
| 84 def __init__(self, test_list, test_db): |
| 85 self._kbd_shortcut_map = dict( |
| 86 (test.kbd_shortcut, test) for test in test_list) |
| 87 |
| 88 # Validate keyboard shortcut uniqueness. |
| 89 assert(None not in self._kbd_shortcut_map) |
| 90 delta = set(test_list) - set(self._kbd_shortcut_map.values()) |
| 91 for test in delta: |
| 92 collision = kbd_shortcut_map[test.kbd_shortcut] |
| 93 log('ERROR: tests %s and %s both have kbd_shortcut %s' % |
| 94 (test_db.get_unique_id_str(test), |
| 95 test_db.get_unique_id_str(collision), |
| 96 test.kbd_shortcut)) |
| 97 assert not delta |
| 98 |
| 99 def get_shortcut_keys(self): |
| 100 return set(self._kbd_shortcut_map) |
| 101 |
| 102 def lookup_test(self, kbd_shortcut): |
| 103 return self._kbd_shortcut_map.get(kbd_shortcut) |
| 104 |
| 105 |
87 class TestDatabase: | 106 class TestDatabase: |
| 107 '''This class parses a test_list and allows searching for tests or |
| 108 their attributes. It also generates tag_prefix values for each |
| 109 runnable test. Autotest allows tests with the same name to be |
| 110 uniquely identified by the "tag" parameter to the job.run_test() |
| 111 function. The factory system generates this value as the |
| 112 combination of a tag_prefix value and a counter that tracks how |
| 113 many times a test has run. Tests are identified uniquely in the |
| 114 status log by both the tag_prefix and their autotest name. These |
| 115 values are referred to here as the unique_details for the test.''' |
88 | 116 |
89 def __init__(self, test_list): | 117 def __init__(self, test_list): |
90 self.test_queue = [t for t in reversed(test_list)] | 118 self._test_list = test_list |
| 119 self._subtest_set = set() |
91 self._subtest_parent_map = {} | 120 self._subtest_parent_map = {} |
92 self._tag_prefix_map = {} | 121 self._tag_prefix_map = {} |
93 for test in test_list: | 122 for test in test_list: |
94 if not isinstance(test, AutomatedSequence): | 123 if not isinstance(test, AutomatedSequence): |
95 self._tag_prefix_map[test] = test.kbd_shortcut | 124 self._tag_prefix_map[test] = test.kbd_shortcut |
96 continue | 125 continue |
97 step_count = 1 | 126 step_count = 1 |
98 for subtest in test.subtest_list: | 127 for subtest in test.subtest_list: |
99 self._subtest_parent_map[subtest] = test | 128 self._subtest_parent_map[subtest] = test |
| 129 self._subtest_set.add(subtest) |
100 if not isinstance(subtest, FactoryAutotestTest): | 130 if not isinstance(subtest, FactoryAutotestTest): |
101 continue | 131 continue |
102 tag_prefix = ('%s_s%d' % (test.subtest_tag_prefix, step_count)) | 132 tag_prefix = ('%s_s%d' % (test.subtest_tag_prefix, step_count)) |
103 self._tag_prefix_map[subtest] = tag_prefix | 133 self._tag_prefix_map[subtest] = tag_prefix |
104 step_count += 1 | 134 step_count += 1 |
105 self.seq_test_set = set(test for test in test_list | 135 self._tag_prefix_to_subtest_map = dict( |
106 if isinstance(test, AutomatedSequence)) | 136 (self._tag_prefix_map[st], st) for st in self._subtest_set) |
107 self.subtest_set = set(reduce(lambda x, y: x + y, | 137 self._unique_details_map = dict( |
108 [test.subtest_list for test in | 138 (self.get_unique_details(t), t) for t in self.get_all_tests() |
109 self.seq_test_set], [])) | 139 if isinstance(t, FactoryAutotestTest)) |
110 self._subtest_map = dict((self._tag_prefix_map[st], st) | 140 self._unique_name_map = dict( |
111 for st in self.subtest_set) | 141 (t.unique_name, t) for t in self.get_all_tests() |
112 self.all_tests = set(test_list) | self.subtest_set | 142 if getattr(t, 'unique_name', None) is not None) |
113 self._unique_name_map = dict((t.unique_name, t) for t in self.all_tests | |
114 if isinstance(t, FactoryAutotestTest) | |
115 and t.unique_name is not None) | |
116 self._unique_details_map = dict((self.get_unique_details(t), t) | |
117 for t in self.all_tests) | |
118 self._kbd_shortcut_map = dict((test.kbd_shortcut, test) | |
119 for test in test_list) | |
120 self.kbd_shortcut_set = set(self._kbd_shortcut_map) | |
121 | 143 |
122 # Validate keyboard shortcut uniqueness. | 144 def get_test_by_unique_details(self, autotest_name, tag_prefix): |
123 assert(None not in self.kbd_shortcut_set) | 145 return self._unique_details_map.get((autotest_name, tag_prefix)) |
124 delta = set(test_list) - set(self._kbd_shortcut_map.values()) | |
125 for test in delta: | |
126 collision = kbd_shortcut_map[test.kbd_shortcut] | |
127 log('ERROR: tests %s and %s both have kbd_shortcut %s' % | |
128 (test.label_en, collision.label_en, test.kbd_shortcut)) | |
129 assert not delta | |
130 | 146 |
131 def get_tag_prefix(self, test): | 147 def get_tag_prefix(self, test): |
132 return self._tag_prefix_map[test] | 148 return self._tag_prefix_map[test] |
133 | 149 |
134 def get_unique_details(self, test): | 150 def get_unique_details(self, test): |
135 if isinstance(test, AutomatedSequence): | 151 return (test.autotest_name, self.get_tag_prefix(test)) |
136 return test.subtest_tag_prefix | |
137 return '%s.%s' % (test.autotest_name, self.get_tag_prefix(test)) | |
138 | |
139 def get_test_by_unique_details(self, autotest_name, tag_prefix): | |
140 unique_details = '%s.%s' % (autotest_name, tag_prefix) | |
141 return self._unique_details_map.get(unique_details) | |
142 | |
143 def get_test_by_kbd_shortcut(self, kbd_shortcut): | |
144 return self._kbd_shortcut_map.get(kbd_shortcut) | |
145 | 152 |
146 def get_test_by_unique_name(self, unique_name): | 153 def get_test_by_unique_name(self, unique_name): |
147 return self._unique_name_map.get(unique_name) | 154 return self._unique_name_map.get(unique_name) |
148 | 155 |
149 def get_subtest_parent(self, test): | 156 def get_subtest_parent(self, test): |
150 return self._subtest_parent_map.get(test) | 157 return self._subtest_parent_map.get(test) |
151 | 158 |
152 def get_subtest_by_tag_prefix(self, tag_prefix): | 159 def get_subtest_by_tag_prefix(self, tag_prefix): |
153 return self._subtest_map.get(tag_prefix) | 160 return self._tag_prefix_to_subtest_map.get(tag_prefix) |
| 161 |
| 162 def get_automated_sequences(self): |
| 163 return [test for test in self._test_list |
| 164 if isinstance(test, AutomatedSequence)] |
| 165 |
| 166 def get_all_tests(self): |
| 167 return set(self._test_list) | self._subtest_set |
| 168 |
| 169 def get_unique_id_str(self, test): |
| 170 '''Intended primarily to identify tests for debugging.''' |
| 171 if test is None: |
| 172 return None |
| 173 if isinstance(test, AutomatedSequence): |
| 174 return test.subtest_tag_prefix |
| 175 return '%s.%s' % self.get_unique_details(test) |
154 | 176 |
155 | 177 |
156 class StatusMap: | 178 class StatusMap: |
| 179 '''Parse the contents of an autotest status file for factory tests |
| 180 into a database containing status, count, and error message |
| 181 information. On __init__ the status file is parsed once. Changes |
| 182 to the file are dealt with by running read_new_data(). Complexity |
| 183 is introduced here because AutomatedSequences are not directly |
| 184 represented in the status file and their status must be derived |
| 185 from subtest results.''' |
157 | 186 |
158 class Entry: | 187 class Entry: |
159 | 188 |
160 def __init__(self): | 189 def __init__(self): |
161 self.status = UNTESTED | 190 self.status = UNTESTED |
162 self.count = 0 | 191 self.count = 0 |
163 self.label_box = None | |
164 self.error_msg = None | 192 self.error_msg = None |
165 | 193 |
166 def __init__(self, test_list, status_file_path): | 194 def __init__(self, test_list, status_file_path, test_db=None, |
167 self.test_db = TestDatabase(test_list) | 195 status_change_callback=None): |
168 all_tests = self.test_db.all_tests | 196 self._test_list = [test for test in test_list] |
169 self._status_map = dict((t, StatusMap.Entry()) for t in all_tests) | 197 self._test_db = test_db if test_db else TestDatabase(test_list) |
| 198 self._status_map = dict( |
| 199 (test, StatusMap.Entry()) for test in self._test_db.get_all_tests()) |
170 self._status_file_path = status_file_path | 200 self._status_file_path = status_file_path |
171 self._status_file_pos = 0 | 201 self._status_file_pos = 0 |
| 202 self._active_automated_seq = None |
| 203 self._status_change_callback = status_change_callback |
172 self.read_new_data() | 204 self.read_new_data() |
173 | 205 |
174 def lookup_status(self, test): | 206 def lookup_status(self, test, min_count=0): |
175 return self._status_map[test].status | 207 entry = self._status_map[test] |
| 208 return entry.status if entry.count >= min_count else UNTESTED |
176 | 209 |
177 def lookup_count(self, test): | 210 def lookup_count(self, test): |
178 return self._status_map[test].count | 211 if isinstance(test, AutomatedSubTest): |
179 | 212 parent = self._test_db.get_subtest_parent(test) |
180 def lookup_label_box(self, test): | 213 return self._status_map[parent].count |
181 return self._status_map[test].label_box | 214 else: |
| 215 return self._status_map[test].count |
182 | 216 |
183 def lookup_error_msg(self, test): | 217 def lookup_error_msg(self, test): |
184 return self._status_map[test].error_msg | 218 return self._status_map[test].error_msg |
185 | 219 |
186 def lookup_tag(self, test): | 220 def filter_by_status(self, target_status): |
187 tag_prefix = self.test_db.get_tag_prefix(test) | |
188 count = self._status_map[test].count | |
189 return '%s_%s' % (tag_prefix, count) | |
190 | |
191 def incr_count(self, test): | |
192 self._status_map[test].count += 1 | |
193 | |
194 def filter(self, target_status): | |
195 comp = (isinstance(target_status, list) and | 221 comp = (isinstance(target_status, list) and |
196 (lambda s: s in target_status) or | 222 (lambda s: s in target_status) or |
197 (lambda s: s == target_status)) | 223 (lambda s: s == target_status)) |
198 return [t for t in self.test_db.test_queue | 224 return [test for test in self._test_db.all_tests |
199 if comp(self.lookup_status(t))] | 225 if comp(self.lookup_status(test))] |
200 | 226 |
201 def next_untested(self): | 227 def next_untested(self): |
202 remaining = self.filter(UNTESTED) | 228 for test in self._test_list: |
203 unique_details = [self.test_db.get_unique_details(t) for t in remaining] | 229 if self.lookup_status(test) == UNTESTED: |
204 log('remaining untested = [%s]' % ', '.join(unique_details)) | 230 return test |
205 return remaining is not [] and remaining.pop() or None | 231 return None |
| 232 |
| 233 def get_active_top_level_test(self): |
| 234 if self._active_automated_seq: |
| 235 return self._active_automated_seq |
| 236 active_tests = [test for test in self._test_list |
| 237 if self.lookup_status(test) == ACTIVE] |
| 238 if len(active_tests) > 1: |
| 239 log('ERROR -- multiple active top level tests %s' % |
| 240 repr([self._test_db.get_unique_id_str(test) |
| 241 for test in active_tests])) |
| 242 return active_tests.pop() if active_tests != [] else None |
206 | 243 |
207 def read_new_data(self): | 244 def read_new_data(self): |
208 with open(self._status_file_path) as file: | 245 with open(self._status_file_path) as file: |
209 file.seek(self._status_file_pos) | 246 file.seek(self._status_file_pos) |
210 for line in file: | 247 for line in file: |
211 cols = line.strip().split('\t') + [''] | 248 cols = line.strip().split('\t') + [''] |
212 code = cols[0] | 249 code = cols[0] |
213 test_id = cols[1] | 250 test_id = cols[1] |
214 if code not in STATUS_CODE_MAP or test_id == '----': | 251 if code not in STATUS_CODE_MAP or test_id == '----': |
215 continue | 252 continue |
216 status = STATUS_CODE_MAP[code] | 253 status = STATUS_CODE_MAP[code] |
217 error_msg = status == FAILED and cols[len(cols) - 2] or None | 254 error_msg = status == FAILED and cols[len(cols) - 2] or None |
218 log('reading code = %s, test_id = %s, error_msg = "%s"' | |
219 % (code, test_id, error_msg)) | |
220 autotest_name, _, tag = test_id.rpartition('.') | 255 autotest_name, _, tag = test_id.rpartition('.') |
221 tag_prefix, _, count = tag.rpartition('_') | 256 tag_prefix, _, count = tag.rpartition('_') |
222 test = self.test_db.get_test_by_unique_details( | 257 test = self._test_db.get_test_by_unique_details( |
223 autotest_name, tag_prefix) | 258 autotest_name, tag_prefix) |
224 if test is None: | 259 if test is None: |
225 log('ignoring update (%s) for test "%s" "%s"' % | 260 log('status map ignoring update (%s) for test %s %s' % |
226 (status, autotest_name, tag_prefix)) | 261 (status, repr(autotest_name), repr(tag_prefix))) |
227 continue | 262 continue |
228 self.update(test, status, int(count), error_msg) | 263 self.update(test, status, int(count), error_msg) |
229 map(self.update_seq_test, self.test_db.seq_test_set) | |
230 self._status_file_pos = file.tell() | 264 self._status_file_pos = file.tell() |
231 | 265 |
232 def get_active_top_level_test(self): | |
233 active_tests = set(self.filter(ACTIVE)) - self.test_db.subtest_set | |
234 return active_tests and active_tests.pop() or None | |
235 | |
236 def get_active_subtest(self): | |
237 active_subtests = set(self.filter(ACTIVE)) & self.test_db.subtest_set | |
238 return active_subtests and active_subtests.pop() or None | |
239 | |
240 def register_active(self, test): | |
241 active_tests = set(self.filter(ACTIVE)) | |
242 assert(test not in active_tests) | |
243 if test in self.test_db.subtest_set: | |
244 parent_seq_test = self.test_db.get_subtest_parent(test) | |
245 active_tests -= set([parent_seq_test]) | |
246 for bad_test in active_tests: | |
247 unique_details = self.test_db.get_unique_details(bad_test) | |
248 log('WARNING: assuming test %s FAILED (status log has no data)' % | |
249 unique_details) | |
250 self.update(bad_test, FAILED, self.lookup_count(bad_test), | |
251 'assumed FAILED (status log has no data)') | |
252 | |
253 def update(self, test, status, count, error_msg): | 266 def update(self, test, status, count, error_msg): |
254 entry = self._status_map[test] | 267 entry = self._status_map[test] |
255 unique_details = self.test_db.get_unique_details(test) | 268 unique_id_str = self._test_db.get_unique_id_str(test) |
256 if count < entry.count: | 269 if count < entry.count: |
257 log('ERROR: count regression for %s (%d -> %d)' % | 270 log('ignoring older data for %s (data count %d < state count %d)' % |
258 (unique_details, entry.count, count)) | 271 (unique_id_str, count, entry.count)) |
| 272 return |
259 if isinstance(test, InformationScreen) and status in [PASSED, FAILED]: | 273 if isinstance(test, InformationScreen) and status in [PASSED, FAILED]: |
260 status = UNTESTED | 274 status = UNTESTED |
| 275 parent_as = self._test_db.get_subtest_parent(test) |
261 if status != entry.status: | 276 if status != entry.status: |
262 log('status change for %s : %s/%s -> %s/%s' % | 277 log('status change for %s : %s/%s -> %s/%s (as = %s)' % |
263 (unique_details, entry.status, entry.count, status, count)) | 278 (unique_id_str, entry.status, entry.count, status, |
264 if entry.label_box is not None: | 279 count, self._test_db.get_unique_id_str(parent_as))) |
265 entry.label_box.update(status) | 280 if self._status_change_callback is not None: |
266 if status == ACTIVE: | 281 self._status_change_callback(test, status) |
267 self.register_active(test) | |
268 entry.status = status | 282 entry.status = status |
269 entry.count = count | 283 entry.count = count |
270 entry.error_msg = error_msg | 284 entry.error_msg = error_msg |
271 log('%s new status = %s' % | 285 if (status == ACTIVE and not isinstance(test, AutomatedSequence) and |
272 (unique_details, self._status_map[test].status)) | 286 self._active_automated_seq != parent_as): |
| 287 if self._active_automated_seq is not None: |
| 288 self.update_automated_sequence(self._active_automated_seq) |
| 289 self._active_automated_seq = parent_as |
| 290 if parent_as is not None: |
| 291 self.update_automated_sequence(parent_as) |
273 | 292 |
274 def update_seq_test(self, test): | 293 def update_automated_sequence(self, test): |
275 subtest_status_set = set(map(self.lookup_status, test.subtest_list)) | 294 max_count = max([self._status_map[st].count |
276 max_count = max(map(self.lookup_count, test.subtest_list)) | 295 for st in test.subtest_list]) |
| 296 lookup_fn = lambda x: self.lookup_status(x, min_count=max_count) |
| 297 subtest_status_set = set( |
| 298 self.lookup_status(subtest) for subtest in test.subtest_list) |
| 299 log('automated sequence %s status set = %s' % ( |
| 300 self._test_db.get_unique_id_str(test), |
| 301 repr(subtest_status_set))) |
277 if len(subtest_status_set) == 1: | 302 if len(subtest_status_set) == 1: |
278 status = subtest_status_set.pop() | 303 status = subtest_status_set.pop() |
279 elif subtest_status_set == set([PASSED, FAILED]): | 304 elif (test == self._active_automated_seq and |
| 305 FAILED not in subtest_status_set): |
| 306 status = ACTIVE |
| 307 else: |
280 status = FAILED | 308 status = FAILED |
281 else: | |
282 status = ACTIVE | |
283 self.update(test, status, max_count, None) | 309 self.update(test, status, max_count, None) |
284 | 310 |
285 def set_label_box(self, test, label_box): | |
286 entry = self._status_map[test] | |
287 entry.label_box = label_box | |
288 label_box.update(entry.status) | |
289 | |
290 | 311 |
291 class LogData: | 312 class LogData: |
| 313 '''Parse the factory log looking for specially formatted |
| 314 name-value declarations and recording the last of any such |
| 315 bindings in a dict. Data in the right format is written to the |
| 316 log using log_shared_data().''' |
292 | 317 |
293 def __init__(self): | 318 def __init__(self): |
294 self._log_file_pos = 0 | 319 self._log_file_pos = 0 |
295 self.shared_dict = {} | 320 self.shared_dict = {} |
296 self.read_new_data() | 321 self.read_new_data() |
297 | 322 |
298 def read_new_data(self): | 323 def read_new_data(self): |
299 with open(LOG_PATH) as file: | 324 with open(LOG_PATH) as file: |
300 file.seek(self._log_file_pos) | 325 file.seek(self._log_file_pos) |
301 for line in file: | 326 for line in file: |
302 parts = line.rsplit(DATA_PREFIX, 1) | 327 parts = line.rsplit(DATA_PREFIX, 1) |
303 if not len(parts) == 2: | 328 if not len(parts) == 2: |
304 continue | 329 continue |
305 key, raw_value = parts.pop().strip().split('=', 1) | 330 key, raw_value = parts.pop().strip().split('=', 1) |
306 log('updating shared_dict[%s]=%s' % (key, raw_value)) | 331 log('updating shared_dict[%s]=%s' % (key, raw_value)) |
307 self.shared_dict[key] = eval(raw_value) | 332 self.shared_dict[key] = eval(raw_value) |
308 self._log_file_pos = file.tell() | 333 self._log_file_pos = file.tell() |
309 | 334 |
310 def get(self, key): | 335 def get(self, key): |
311 return self.shared_dict.get(key) | 336 return self.shared_dict.get(key) |
312 | 337 |
313 | 338 |
314 class ControlState: | 339 class ControlState: |
| 340 '''Track the state needed to run and terminate factory tests. The |
| 341 shared data written to the factory log records the pid information |
| 342 for each test as it gets run. If the factory UI sees a keyboard |
| 343 shortcut event, it sends a SIGUSR1 event to the control process, |
| 344 which then uses the log information to terminate the running |
| 345 test.''' |
315 | 346 |
316 def __init__(self, job, test_list, status_map, status_file_path, nuke_fn): | 347 def __init__(self, job, test_list, test_db, status_map, |
| 348 status_file_path, nuke_fn): |
317 self._job = job | 349 self._job = job |
318 self._status_map = status_map | 350 self._status_map = status_map |
| 351 self._kbd_shortcut_db = KbdShortcutDatabase(test_list, test_db) |
| 352 self._test_db = test_db |
319 self._log_data = LogData() | 353 self._log_data = LogData() |
320 self._std_dargs = { | 354 self._std_dargs = { |
321 'status_file_path' : status_file_path, | 355 'status_file_path' : status_file_path, |
322 'test_list': test_list} | 356 'test_list': test_list} |
323 self._nuke_fn = nuke_fn | 357 self._nuke_fn = nuke_fn |
324 self.activated_kbd_shortcut_test = None | |
325 signal.signal(signal.SIGUSR1, self.kill_current_test_callback) | 358 signal.signal(signal.SIGUSR1, self.kill_current_test_callback) |
326 | 359 |
327 def kill_current_test_callback(self, signum, frame): | 360 def kill_current_test_callback(self, signum, frame): |
328 self._log_data.read_new_data() | 361 self._log_data.read_new_data() |
329 active_test_data = self._log_data.get('active_test_data') | 362 active_test_data = self._log_data.get('active_test_data') |
330 log('KILLING active_test_data %s' % repr(active_test_data)) | |
331 if active_test_data is not None: | 363 if active_test_data is not None: |
| 364 log('SIGUSR1 ... KILLING active test %s' % repr(active_test_data)) |
332 self._nuke_fn(*active_test_data) | 365 self._nuke_fn(*active_test_data) |
| 366 else: |
| 367 log('SIGUSR1 ... KILLING NOTHING, no active test') |
333 | 368 |
334 def run_test(self, test): | 369 def process_kbd_shortcut_activation(self): |
| 370 kbd_shortcut = self._log_data.get('activated_kbd_shortcut') |
| 371 if kbd_shortcut is None: |
| 372 return None |
| 373 target_test = self._kbd_shortcut_db.lookup_test(kbd_shortcut) |
| 374 log('activated kbd_shortcut %s -> %s' % ( |
| 375 kbd_shortcut, self._test_db.get_unique_id_str(target_test))) |
335 log_shared_data('activated_kbd_shortcut', None) | 376 log_shared_data('activated_kbd_shortcut', None) |
| 377 return target_test |
336 | 378 |
337 self._status_map.incr_count(test) | 379 def run_test(self, test, count): |
338 self._log_data.read_new_data() | 380 self._log_data.read_new_data() |
339 test_tag = self._status_map.lookup_tag(test) | 381 test_tag = '%s_%s' % (self._test_db.get_tag_prefix(test), count) |
340 dargs = {} | 382 dargs = {} |
341 dargs.update(test.dargs) | 383 dargs.update(test.dargs) |
342 dargs.update(self._std_dargs) | 384 dargs.update(self._std_dargs) |
343 dargs.update({'tag': test_tag, | 385 dargs.update({'tag': test_tag, |
344 'subtest_tag': test_tag, | 386 'subtest_tag': test_tag, |
345 'shared_dict': self._log_data.shared_dict}) | 387 'shared_dict': self._log_data.shared_dict}) |
346 | |
347 self._job.factory_shared_dict = self._log_data.shared_dict | 388 self._job.factory_shared_dict = self._log_data.shared_dict |
348 | 389 self._job.drop_caches_between_iterations = test.drop_caches |
349 log('control shared dict = %s' % repr(self._log_data.shared_dict)) | |
350 | |
351 if test.drop_caches: | |
352 self._job.drop_caches_between_iterations = True | |
353 self.activated_kbd_shortcut_test = None | |
354 | |
355 self._job.run_test(test.autotest_name, **dargs) | 390 self._job.run_test(test.autotest_name, **dargs) |
356 | |
357 self._job.drop_caches_between_iterations = False | 391 self._job.drop_caches_between_iterations = False |
358 self._log_data.read_new_data() | 392 self._log_data.read_new_data() |
359 kbd_shortcut = self._log_data.shared_dict.pop( | 393 return self.process_kbd_shortcut_activation() |
360 'activated_kbd_shortcut', None) | 394 |
361 if kbd_shortcut is not None: | 395 |
362 test_db = self._status_map.test_db | 396 def lookup_status_by_unique_name(unique_name, test_list, status_file_path): |
363 target_test = test_db.get_test_by_kbd_shortcut(kbd_shortcut) | 397 """Determine the status of given test. Somewhat heavyweight, |
364 self.activated_kbd_shortcut_test = target_test | 398 since it parses the status file.""" |
365 log('kbd_shortcut %s -> %s)' % ( | 399 test = TestDatabase(test_list).get_test_by_unique_name(unique_name) |
366 kbd_shortcut, test_db.get_unique_details(target_test))) | 400 return StatusMap(test_list, status_file_path, test_db).lookup_status(test) |
OLD | NEW |