| 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 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 44 if l != 'self'] | 44 if l != 'self'] |
| 45 c = ('%s' % self.__class__).rpartition('.')[2] | 45 c = ('%s' % self.__class__).rpartition('.')[2] |
| 46 return '%s(%s)' % (c, ','.join(d)) | 46 return '%s(%s)' % (c, ','.join(d)) |
| 47 | 47 |
| 48 class FactoryAutotestTest(FactoryTest): | 48 class FactoryAutotestTest(FactoryTest): |
| 49 # Placeholder parent for tests with autotest_name fields. | 49 # Placeholder parent for tests with autotest_name fields. |
| 50 pass | 50 pass |
| 51 | 51 |
| 52 class OperatorTest(FactoryAutotestTest): | 52 class OperatorTest(FactoryAutotestTest): |
| 53 def __init__(self, label_en='', label_zw='', autotest_name=None, | 53 def __init__(self, label_en='', label_zw='', autotest_name=None, |
| 54 kbd_shortcut=None, dargs={}, drop_caches=False): | 54 kbd_shortcut=None, dargs={}, drop_caches=False, |
| 55 unique_name=None): |
| 55 self.__dict__.update(vars()) | 56 self.__dict__.update(vars()) |
| 56 | 57 |
| 57 class InformationScreen(OperatorTest): | 58 class InformationScreen(OperatorTest): |
| 58 # These tests never pass or fail, just return to untested state. | 59 # These tests never pass or fail, just return to untested state. |
| 59 pass | 60 pass |
| 60 | 61 |
| 61 class AutomatedSequence(FactoryTest): | 62 class AutomatedSequence(FactoryTest): |
| 62 def __init__(self, label_en='', label_zw='', subtest_tag_prefix=None, | 63 def __init__(self, label_en='', label_zw='', subtest_tag_prefix=None, |
| 63 kbd_shortcut=None, subtest_list=[]): | 64 kbd_shortcut=None, subtest_list=[], unique_name=None): |
| 64 self.__dict__.update(vars()) | 65 self.__dict__.update(vars()) |
| 65 | 66 |
| 66 class AutomatedSubTest(FactoryAutotestTest): | 67 class AutomatedSubTest(FactoryAutotestTest): |
| 67 def __init__(self, label_en='', label_zw='', autotest_name=None, | 68 def __init__(self, label_en='', label_zw='', autotest_name=None, |
| 68 dargs={}, drop_caches=False): | 69 dargs={}, drop_caches=False, unique_name=None): |
| 69 self.__dict__.update(vars()) | 70 self.__dict__.update(vars()) |
| 70 | 71 |
| 71 class AutomatedRebootSubTest(FactoryAutotestTest): | 72 class AutomatedRebootSubTest(FactoryAutotestTest): |
| 72 def __init__(self, label_en='', label_zw='', iterations=None, | 73 def __init__(self, label_en='', label_zw='', iterations=None, |
| 73 autotest_name='factory_RebootStub', dargs={}, | 74 autotest_name='factory_RebootStub', dargs={}, |
| 74 drop_caches=False): | 75 drop_caches=False, unique_name=None): |
| 75 self.__dict__.update(vars()) | 76 self.__dict__.update(vars()) |
| 76 | 77 |
| 77 | 78 |
| 78 class TestDatabase: | 79 class TestDatabase: |
| 79 | 80 |
| 80 def __init__(self, test_list): | 81 def __init__(self, test_list): |
| 81 self.test_queue = [t for t in reversed(test_list)] | 82 self.test_queue = [t for t in reversed(test_list)] |
| 82 self._subtest_parent_map = {} | 83 self._subtest_parent_map = {} |
| 83 self._tag_prefix_map = {} | 84 self._tag_prefix_map = {} |
| 84 for test in test_list: | 85 for test in test_list: |
| 85 if not isinstance(test, AutomatedSequence): | 86 if not isinstance(test, AutomatedSequence): |
| 86 self._tag_prefix_map[test] = test.kbd_shortcut | 87 self._tag_prefix_map[test] = test.kbd_shortcut |
| 87 continue | 88 continue |
| 88 step_count = 1 | 89 step_count = 1 |
| 89 for subtest in test.subtest_list: | 90 for subtest in test.subtest_list: |
| 90 self._subtest_parent_map[subtest] = test | 91 self._subtest_parent_map[subtest] = test |
| 91 if not isinstance(subtest, FactoryAutotestTest): | 92 if not isinstance(subtest, FactoryAutotestTest): |
| 92 continue | 93 continue |
| 93 tag_prefix = ('%s_s%d' % (test.subtest_tag_prefix, step_count)) | 94 tag_prefix = ('%s_s%d' % (test.subtest_tag_prefix, step_count)) |
| 94 self._tag_prefix_map[subtest] = tag_prefix | 95 self._tag_prefix_map[subtest] = tag_prefix |
| 95 step_count += 1 | 96 step_count += 1 |
| 96 self.seq_test_set = set(test for test in test_list | 97 self.seq_test_set = set(test for test in test_list |
| 97 if isinstance(test, AutomatedSequence)) | 98 if isinstance(test, AutomatedSequence)) |
| 98 self.subtest_set = set(reduce(lambda x, y: x + y, | 99 self.subtest_set = set(reduce(lambda x, y: x + y, |
| 99 [test.subtest_list for test in | 100 [test.subtest_list for test in |
| 100 self.seq_test_set], [])) | 101 self.seq_test_set], [])) |
| 101 self._subtest_map = dict((self._tag_prefix_map[st], st) | 102 self._subtest_map = dict((self._tag_prefix_map[st], st) |
| 102 for st in self.subtest_set) | 103 for st in self.subtest_set) |
| 103 self._unique_name_map = dict((self.get_unique_name(t), t) | 104 self.all_tests = set(test_list) | self.subtest_set |
| 104 for t in self._tag_prefix_map) | 105 self._unique_name_map = dict((t.unique_name, t) for t in self.all_tests |
| 106 if isinstance(t, FactoryAutotestTest) |
| 107 and t.unique_name is not None) |
| 108 self._unique_details_map = dict((self.get_unique_details(t), t) |
| 109 for t in self.all_tests) |
| 105 self._kbd_shortcut_map = dict((test.kbd_shortcut, test) | 110 self._kbd_shortcut_map = dict((test.kbd_shortcut, test) |
| 106 for test in test_list) | 111 for test in test_list) |
| 107 self.kbd_shortcut_set = set(self._kbd_shortcut_map) | 112 self.kbd_shortcut_set = set(self._kbd_shortcut_map) |
| 108 | 113 |
| 109 # Validate keyboard shortcut uniqueness. | 114 # Validate keyboard shortcut uniqueness. |
| 110 assert(None not in self.kbd_shortcut_set) | 115 assert(None not in self.kbd_shortcut_set) |
| 111 delta = set(test_list) - set(self._kbd_shortcut_map.values()) | 116 delta = set(test_list) - set(self._kbd_shortcut_map.values()) |
| 112 for test in delta: | 117 for test in delta: |
| 113 collision = kbd_shortcut_map[test.kbd_shortcut] | 118 collision = kbd_shortcut_map[test.kbd_shortcut] |
| 114 log('ERROR: tests %s and %s both have kbd_shortcut %s' % | 119 log('ERROR: tests %s and %s both have kbd_shortcut %s' % |
| 115 (test.label_en, collision.label_en, test.kbd_shortcut)) | 120 (test.label_en, collision.label_en, test.kbd_shortcut)) |
| 116 assert not delta | 121 assert not delta |
| 117 | 122 |
| 118 def get_test_by_details(self, autotest_name, tag_prefix): | 123 def get_tag_prefix(self, test): |
| 119 unique_name = '%s.%s' % (autotest_name, tag_prefix) | 124 return self._tag_prefix_map[test] |
| 120 return self._unique_name_map.get(unique_name) | 125 |
| 126 def get_unique_details(self, test): |
| 127 if isinstance(test, AutomatedSequence): |
| 128 return test.subtest_tag_prefix |
| 129 return '%s.%s' % (test.autotest_name, self.get_tag_prefix(test)) |
| 130 |
| 131 def get_test_by_unique_details(self, autotest_name, tag_prefix): |
| 132 unique_details = '%s.%s' % (autotest_name, tag_prefix) |
| 133 return self._unique_details_map.get(unique_details) |
| 121 | 134 |
| 122 def get_test_by_kbd_shortcut(self, kbd_shortcut): | 135 def get_test_by_kbd_shortcut(self, kbd_shortcut): |
| 123 return self._kbd_shortcut_map.get(kbd_shortcut) | 136 return self._kbd_shortcut_map.get(kbd_shortcut) |
| 124 | 137 |
| 125 def get_unique_name(self, test): | 138 def get_test_by_unique_name(self, unique_name): |
| 126 if isinstance(test, AutomatedSequence): | 139 return self._unique_name_map.get(unique_name) |
| 127 return test.subtest_tag_prefix | |
| 128 return '%s.%s' % (test.autotest_name, self._tag_prefix_map[test]) | |
| 129 | |
| 130 def get_tag_prefix(self, test): | |
| 131 return self._tag_prefix_map[test] | |
| 132 | |
| 133 def get_all_tests(self): | |
| 134 return set(self.test_queue) | self.subtest_set | |
| 135 | 140 |
| 136 def get_subtest_parent(self, test): | 141 def get_subtest_parent(self, test): |
| 137 return self._subtest_parent_map.get(test) | 142 return self._subtest_parent_map.get(test) |
| 138 | 143 |
| 139 def get_subtest_by_tag_prefix(self, tag_prefix): | 144 def get_subtest_by_tag_prefix(self, tag_prefix): |
| 140 return self._subtest_map.get(tag_prefix) | 145 return self._subtest_map.get(tag_prefix) |
| 141 | 146 |
| 142 | 147 |
| 143 class StatusMap: | 148 class StatusMap: |
| 144 | 149 |
| 145 class Entry: | 150 class Entry: |
| 146 | 151 |
| 147 def __init__(self): | 152 def __init__(self): |
| 148 self.status = UNTESTED | 153 self.status = UNTESTED |
| 149 self.count = 0 | 154 self.count = 0 |
| 150 self.label_box = None | 155 self.label_box = None |
| 151 self.error_msg = None | 156 self.error_msg = None |
| 152 | 157 |
| 153 def __init__(self, test_list, status_file_path): | 158 def __init__(self, test_list, status_file_path): |
| 154 self.test_db = TestDatabase(test_list) | 159 self.test_db = TestDatabase(test_list) |
| 155 all_tests = self.test_db.get_all_tests() | 160 all_tests = self.test_db.all_tests |
| 156 self._status_map = dict((t, StatusMap.Entry()) for t in all_tests) | 161 self._status_map = dict((t, StatusMap.Entry()) for t in all_tests) |
| 157 self._status_file_path = status_file_path | 162 self._status_file_path = status_file_path |
| 158 self._status_file_pos = 0 | 163 self._status_file_pos = 0 |
| 159 self.read_new_data() | 164 self.read_new_data() |
| 160 | 165 |
| 161 def lookup_status(self, test): | 166 def lookup_status(self, test): |
| 162 return self._status_map[test].status | 167 return self._status_map[test].status |
| 163 | 168 |
| 164 def lookup_count(self, test): | 169 def lookup_count(self, test): |
| 165 return self._status_map[test].count | 170 return self._status_map[test].count |
| (...skipping 14 matching lines...) Expand all Loading... |
| 180 | 185 |
| 181 def filter(self, target_status): | 186 def filter(self, target_status): |
| 182 comp = (isinstance(target_status, list) and | 187 comp = (isinstance(target_status, list) and |
| 183 (lambda s: s in target_status) or | 188 (lambda s: s in target_status) or |
| 184 (lambda s: s == target_status)) | 189 (lambda s: s == target_status)) |
| 185 return [t for t in self.test_db.test_queue | 190 return [t for t in self.test_db.test_queue |
| 186 if comp(self.lookup_status(t))] | 191 if comp(self.lookup_status(t))] |
| 187 | 192 |
| 188 def next_untested(self): | 193 def next_untested(self): |
| 189 remaining = self.filter(UNTESTED) | 194 remaining = self.filter(UNTESTED) |
| 190 unique_names = [self.test_db.get_unique_name(t) for t in remaining] | 195 unique_details = [self.test_db.get_unique_details(t) for t in remaining] |
| 191 log('remaining untested = [%s]' % ', '.join(unique_names)) | 196 log('remaining untested = [%s]' % ', '.join(unique_details)) |
| 192 return remaining is not [] and remaining.pop() or None | 197 return remaining is not [] and remaining.pop() or None |
| 193 | 198 |
| 194 def read_new_data(self): | 199 def read_new_data(self): |
| 195 with open(self._status_file_path) as file: | 200 with open(self._status_file_path) as file: |
| 196 file.seek(self._status_file_pos) | 201 file.seek(self._status_file_pos) |
| 197 for line in file: | 202 for line in file: |
| 198 cols = line.strip().split('\t') + [''] | 203 cols = line.strip().split('\t') + [''] |
| 199 code = cols[0] | 204 code = cols[0] |
| 200 test_id = cols[1] | 205 test_id = cols[1] |
| 201 if code not in STATUS_CODE_MAP or test_id == '----': | 206 if code not in STATUS_CODE_MAP or test_id == '----': |
| 202 continue | 207 continue |
| 203 status = STATUS_CODE_MAP[code] | 208 status = STATUS_CODE_MAP[code] |
| 204 error_msg = status == FAILED and cols[len(cols) - 2] or None | 209 error_msg = status == FAILED and cols[len(cols) - 2] or None |
| 205 log('reading code = %s, test_id = %s, error_msg = "%s"' | 210 log('reading code = %s, test_id = %s, error_msg = "%s"' |
| 206 % (code, test_id, error_msg)) | 211 % (code, test_id, error_msg)) |
| 207 autotest_name, _, tag = test_id.rpartition('.') | 212 autotest_name, _, tag = test_id.rpartition('.') |
| 208 tag_prefix, _, count = tag.rpartition('_') | 213 tag_prefix, _, count = tag.rpartition('_') |
| 209 test = self.test_db.get_test_by_details( | 214 test = self.test_db.get_test_by_unique_details( |
| 210 autotest_name, tag_prefix) | 215 autotest_name, tag_prefix) |
| 211 if test is None: | 216 if test is None: |
| 212 log('ignoring update (%s) for test "%s" "%s"' % | 217 log('ignoring update (%s) for test "%s" "%s"' % |
| 213 (status, autotest_name, tag_prefix)) | 218 (status, autotest_name, tag_prefix)) |
| 214 continue | 219 continue |
| 215 self.update(test, status, int(count), error_msg) | 220 self.update(test, status, int(count), error_msg) |
| 216 map(self.update_seq_test, self.test_db.seq_test_set) | 221 map(self.update_seq_test, self.test_db.seq_test_set) |
| 217 self._status_file_pos = file.tell() | 222 self._status_file_pos = file.tell() |
| 218 | 223 |
| 219 def get_active_top_level_test(self): | 224 def get_active_top_level_test(self): |
| 220 active_tests = set(self.filter(ACTIVE)) - self.test_db.subtest_set | 225 active_tests = set(self.filter(ACTIVE)) - self.test_db.subtest_set |
| 221 return active_tests and active_tests.pop() or None | 226 return active_tests and active_tests.pop() or None |
| 222 | 227 |
| 223 def get_active_subtest(self): | 228 def get_active_subtest(self): |
| 224 active_subtests = set(self.filter(ACTIVE)) & self.test_db.subtest_set | 229 active_subtests = set(self.filter(ACTIVE)) & self.test_db.subtest_set |
| 225 return active_subtests and active_subtests.pop() or None | 230 return active_subtests and active_subtests.pop() or None |
| 226 | 231 |
| 227 def register_active(self, test): | 232 def register_active(self, test): |
| 228 active_tests = set(self.filter(ACTIVE)) | 233 active_tests = set(self.filter(ACTIVE)) |
| 229 assert(test not in active_tests) | 234 assert(test not in active_tests) |
| 230 if test in self.test_db.subtest_set: | 235 if test in self.test_db.subtest_set: |
| 231 parent_seq_test = self.test_db.get_subtest_parent(test) | 236 parent_seq_test = self.test_db.get_subtest_parent(test) |
| 232 active_tests -= set([parent_seq_test]) | 237 active_tests -= set([parent_seq_test]) |
| 233 for bad_test in active_tests: | 238 for bad_test in active_tests: |
| 234 unique_name = self.test_db.get_unique_name(bad_test) | 239 unique_details = self.test_db.get_unique_details(bad_test) |
| 235 log('WARNING: assuming test %s FAILED (status log has no data)' % | 240 log('WARNING: assuming test %s FAILED (status log has no data)' % |
| 236 unique_name) | 241 unique_details) |
| 237 self.update(bad_test, FAILED, self.lookup_count(bad_test), | 242 self.update(bad_test, FAILED, self.lookup_count(bad_test), |
| 238 'assumed FAILED (status log has no data)') | 243 'assumed FAILED (status log has no data)') |
| 239 | 244 |
| 240 def update(self, test, status, count, error_msg): | 245 def update(self, test, status, count, error_msg): |
| 241 entry = self._status_map[test] | 246 entry = self._status_map[test] |
| 242 unique_name = self.test_db.get_unique_name(test) | 247 unique_details = self.test_db.get_unique_details(test) |
| 243 if count < entry.count: | 248 if count < entry.count: |
| 244 log('ERROR: count regression for %s (%d -> %d)' % | 249 log('ERROR: count regression for %s (%d -> %d)' % |
| 245 (unique_name, entry.count, count)) | 250 (unique_details, entry.count, count)) |
| 246 if isinstance(test, InformationScreen) and status in [PASSED, FAILED]: | 251 if isinstance(test, InformationScreen) and status in [PASSED, FAILED]: |
| 247 status = UNTESTED | 252 status = UNTESTED |
| 248 if status != entry.status: | 253 if status != entry.status: |
| 249 log('status change for %s : %s/%s -> %s/%s' % | 254 log('status change for %s : %s/%s -> %s/%s' % |
| 250 (unique_name, entry.status, entry.count, status, count)) | 255 (unique_details, entry.status, entry.count, status, count)) |
| 251 if entry.label_box is not None: | 256 if entry.label_box is not None: |
| 252 entry.label_box.update(status) | 257 entry.label_box.update(status) |
| 253 if status == ACTIVE: | 258 if status == ACTIVE: |
| 254 self.register_active(test) | 259 self.register_active(test) |
| 255 entry.status = status | 260 entry.status = status |
| 256 entry.count = count | 261 entry.count = count |
| 257 entry.error_msg = error_msg | 262 entry.error_msg = error_msg |
| 258 log('%s new status = %s' % (unique_name, self._status_map[test].status)) | 263 log('%s new status = %s' % |
| 264 (unique_details, self._status_map[test].status)) |
| 259 | 265 |
| 260 def update_seq_test(self, test): | 266 def update_seq_test(self, test): |
| 261 subtest_status_set = set(map(self.lookup_status, test.subtest_list)) | 267 subtest_status_set = set(map(self.lookup_status, test.subtest_list)) |
| 262 max_count = max(map(self.lookup_count, test.subtest_list)) | 268 max_count = max(map(self.lookup_count, test.subtest_list)) |
| 263 if len(subtest_status_set) == 1: | 269 if len(subtest_status_set) == 1: |
| 264 status = subtest_status_set.pop() | 270 status = subtest_status_set.pop() |
| 271 elif subtest_status_set == set([PASSED, FAILED]): |
| 272 status = FAILED |
| 265 else: | 273 else: |
| 266 status = ACTIVE in subtest_status_set and ACTIVE or FAILED | 274 status = ACTIVE |
| 267 self.update(test, status, max_count, None) | 275 self.update(test, status, max_count, None) |
| 268 | 276 |
| 269 def set_label_box(self, test, label_box): | 277 def set_label_box(self, test, label_box): |
| 270 entry = self._status_map[test] | 278 entry = self._status_map[test] |
| 271 entry.label_box = label_box | 279 entry.label_box = label_box |
| 272 label_box.update(entry.status) | 280 label_box.update(entry.status) |
| 273 | 281 |
| 274 | 282 |
| 275 class LogData: | 283 class LogData: |
| 276 | 284 |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 346 if test.drop_caches: | 354 if test.drop_caches: |
| 347 self._job.drop_caches_between_iterations = True | 355 self._job.drop_caches_between_iterations = True |
| 348 self._job.run_test(test.autotest_name, **dargs) | 356 self._job.run_test(test.autotest_name, **dargs) |
| 349 self._job.drop_caches_between_iterations = False | 357 self._job.drop_caches_between_iterations = False |
| 350 self._log_data.read_new_data() | 358 self._log_data.read_new_data() |
| 351 activated_ks = self._log_data.shared_dict.pop( | 359 activated_ks = self._log_data.shared_dict.pop( |
| 352 'activated_kbd_shortcut', None) | 360 'activated_kbd_shortcut', None) |
| 353 lookup = self._status_map.test_db.get_test_by_kbd_shortcut | 361 lookup = self._status_map.test_db.get_test_by_kbd_shortcut |
| 354 self.activated_kbd_shortcut_test = ( | 362 self.activated_kbd_shortcut_test = ( |
| 355 activated_ks and lookup(activated_ks) or None) | 363 activated_ks and lookup(activated_ks) or None) |
| OLD | NEW |