| OLD | NEW |
| 1 # -*- coding: utf-8 -*- | 1 # -*- coding: utf-8 -*- |
| 2 # | 2 # |
| 3 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. | 3 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| 4 # Use of this source code is governed by a BSD-style license that can be | 4 # Use of this source code is governed by a BSD-style license that can be |
| 5 # found in the LICENSE file. | 5 # found in the LICENSE file. |
| 6 | 6 |
| 7 AUTHOR = "Chrome OS Team" | 7 AUTHOR = "Chrome OS Team" |
| 8 NAME = "Factory" | 8 NAME = "Factory" |
| 9 TIME = "LONG" | 9 TIME = "LONG" |
| 10 TEST_CATEGORY = "Functional" | 10 TEST_CATEGORY = "Functional" |
| 11 TEST_CLASS = "suite" | 11 TEST_CLASS = "suite" |
| 12 TEST_TYPE = "client" | 12 TEST_TYPE = "client" |
| 13 | 13 |
| 14 DOC = """ | 14 DOC = """ |
| 15 This suite executed all of the factory tests, and incorporates an | 15 This suite executed all of the factory tests, and incorporates an |
| 16 X-windows GTK-based UI to highlight testing results and to allow | 16 X-windows GTK-based UI to highlight testing results and to allow |
| 17 factory operators to switch between tests, and to re-run tests, all | 17 factory operators to switch between tests, and to re-run tests, all |
| 18 on-demand via keyboard shortcuts. | 18 on-demand via keyboard shortcuts. |
| 19 | 19 |
| 20 The UI is implemented as a seperate process (see _FACTORY_UI_PATH), | 20 The UI is implemented as a seperate process (see _FACTORY_UI_PATH), |
| 21 which means that interprocess communication is needed between this | 21 which means that interprocess communication is needed between this |
| 22 control, the UI, and the tests (which are forked children of this | 22 control, the UI, and the tests (which are forked children of this |
| 23 control process). """ | 23 control process). """ |
| 24 | 24 |
| 25 | 25 |
| 26 import imp |
| 26 import subprocess | 27 import subprocess |
| 27 import sys | 28 import sys |
| 28 import time | 29 import time |
| 29 | 30 |
| 31 imp.load_source('common', job.autodir + '/bin/common.py') |
| 32 from autotest_lib.client.bin import factory |
| 30 | 33 |
| 31 _FACTORY_LOG_PATH = '/var/log/factory.log' | 34 |
| 32 _RESULT_FILE_PATH = '/var/run/factory_test_result' | 35 _STATUS_FILE_PATH = job.autodir + '/results/default/status' |
| 36 _FACTORY_UI_PATH = job.autodir + '/bin/factory_ui' |
| 37 |
| 33 | 38 |
| 34 _REBOOT_SEQ_ITERATIONS = 2 | 39 _REBOOT_SEQ_ITERATIONS = 2 |
| 35 | 40 |
| 36 | 41 |
| 37 def XXX_log(s): | |
| 38 print >> sys.stderr, 'FACTORY: ' + s | |
| 39 | |
| 40 | |
| 41 # Hack to work around autotest's obsession with GRUB. | 42 # Hack to work around autotest's obsession with GRUB. |
| 42 job.bootloader.set_default = lambda x: None | 43 job.bootloader.set_default = lambda x: None |
| 43 job.bootloader.boot_once = lambda x: None | 44 job.bootloader.boot_once = lambda x: None |
| 44 | 45 |
| 45 | 46 |
| 46 # This is the definition of the test_data class, which holds | |
| 47 # factory-specific information on the tests to be run. Specifically, | |
| 48 # the order of items in the list reflect the order they are to be run | |
| 49 # on the line. The label and trigger fields contain the description | |
| 50 # strings to be shown in the test control list of the UI. The trigger | |
| 51 # field specifies the keyboard shortcut to allow on-demain | |
| 52 # out-of-order test activation. The dargs field allows test specific | |
| 53 # extra arguments. Note: this datastructure is defined as a string | |
| 54 # and exec()ed to allow it to be defined once here, but to be also | |
| 55 # used by the factory_ui process. | |
| 56 | |
| 57 test_data_class_def = ''' | |
| 58 class test_data: | |
| 59 | |
| 60 def __init__(self, label_en='', label_zw='', formal_name=None, | |
| 61 tag_prefix=None, trigger=None, automated_seq=[], dargs={}): | |
| 62 self.__dict__.update(vars()) | |
| 63 | |
| 64 def __repr__(self): | |
| 65 d = ['%s=%s' % (l,repr(v)) | |
| 66 for l,v in self.__dict__.items() | |
| 67 if l != 'self'] | |
| 68 c = ('%s' % self.__class__).rpartition('.')[2] | |
| 69 return '%s(%s)' % (c, ','.join(d)) | |
| 70 ''' | |
| 71 exec(test_data_class_def) | |
| 72 | |
| 73 | |
| 74 test_list = [ | 47 test_list = [ |
| 75 test_data( | 48 factory.TestData( |
| 76 label_en='start', | 49 label_en='start', |
| 50 label_zw='開始', |
| 77 formal_name='factory_Dummy', | 51 formal_name='factory_Dummy', |
| 78 trigger='a', | 52 trigger='e', |
| 79 dargs={'quit_key':ord(' '), | 53 dargs={'quit_key':ord(' '), |
| 80 'msg':'Hit SPACE to start testing...\n按 "空白鍵" 開始測試...'}), | 54 'msg':'Hit SPACE to start testing...\n按 "空白鍵" 開始測試...'}), |
| 81 test_data( | 55 factory.TestData( |
| 82 label_en='sync', | 56 label_en='sync', |
| 57 label_zw='同步', |
| 83 formal_name='factory_ScriptWrapper', | 58 formal_name='factory_ScriptWrapper', |
| 84 trigger='s', | 59 trigger='s', |
| 85 dargs={'cmdline':'/usr/local/autotest/exscr'}), | 60 dargs={'cmdline': job.autodir + |
| 86 test_data( | 61 '/site_tests/factory_ScriptWrapper/dummy.sh'}), |
| 87 label_en='leds', | 62 factory.TestData( |
| 88 formal_name='factory_Dummy', | 63 label_en='run-in', |
| 89 trigger='l', | 64 label_zw='燒機測試', |
| 90 dargs={'msg':'LEDs test, one day...'}), | 65 formal_name='step_runin', |
| 91 test_data( | 66 automated_seq=[ |
| 92 label_en='usb', | 67 factory.TestData( |
| 93 formal_name='factory_ExternalStorage', | 68 label_en='component validation', |
| 94 trigger='u'), | 69 label_zw='元件驗證', |
| 95 test_data( | 70 formal_name='hardware_Components', |
| 96 label_en='display', | 71 dargs={'approved_db':'qualified_components'}), |
| 97 formal_name='factory_Display', | 72 factory.TestData( |
| 98 trigger='m'), | 73 label_en='gpio switch check', |
| 99 test_data( | 74 label_zw='檢查 gpio 開關', |
| 100 label_en='camera', | 75 formal_name='hardware_GPIOSwitches'), |
| 101 formal_name='factory_Camera', | 76 factory.TestData( |
| 102 trigger='c'), | 77 label_en='system stress', |
| 103 test_data( | 78 label_zw='壓力測試', |
| 79 formal_name='hardware_SAT'), |
| 80 factory.TestData( |
| 81 label_en='reboot (%s times)' % _REBOOT_SEQ_ITERATIONS, |
| 82 label_zw='重新開機 (%s 次)' % _REBOOT_SEQ_ITERATIONS, |
| 83 formal_name='factory_RebootStub')], |
| 84 trigger='r'), |
| 85 factory.TestData( |
| 104 label_en='keyboard', | 86 label_en='keyboard', |
| 105 label_zw='鍵盤', | 87 label_zw='鍵盤', |
| 106 formal_name='factory_Keyboard', | 88 formal_name='factory_Keyboard', |
| 107 trigger='k', | 89 trigger='k', |
| 108 dargs={'layout':'en_us'}), | 90 dargs={'layout':'en_us'}), |
| 109 test_data( | 91 factory.TestData( |
| 110 label_en='touchpad', | 92 label_en='touchpad', |
| 111 label_zw='觸控板', | 93 label_zw='觸控板', |
| 112 formal_name='factory_Touchpad', | 94 formal_name='factory_Touchpad', |
| 113 trigger='t'), | 95 trigger='t'), |
| 114 test_data( | 96 factory.TestData( |
| 97 label_en='leds', |
| 98 label_zw='機身側燈', |
| 99 formal_name='factory_Dummy', |
| 100 trigger='l', |
| 101 dargs={'msg':'LEDs test, one day...'}), |
| 102 factory.TestData( |
| 103 label_en='display', |
| 104 label_zw='顯示', |
| 105 formal_name='factory_Display', |
| 106 trigger='m'), |
| 107 factory.TestData( |
| 108 label_en='camera', |
| 109 label_zw='相機', |
| 110 formal_name='factory_Camera', |
| 111 trigger='c'), |
| 112 factory.TestData( |
| 113 label_en='audio', |
| 114 label_zw='聲音', |
| 115 formal_name='factory_Dummy', |
| 116 trigger='a', |
| 117 dargs={'msg':'audio test, one day...'}), |
| 118 factory.TestData( |
| 119 label_en='usb', |
| 120 formal_name='factory_ExternalStorage', |
| 121 trigger='u'), |
| 122 factory.TestData( |
| 123 label_en='sd', |
| 124 formal_name='factory_ExternalStorage', |
| 125 trigger='d'), |
| 126 factory.TestData( |
| 127 label_en='bluetooth', |
| 128 label_zw='藍牙', |
| 129 formal_name='factory_Dummy', |
| 130 trigger='o', |
| 131 dargs={'msg':'bluetooth test, one day...'}), |
| 132 factory.TestData( |
| 133 label_en='3g', |
| 134 label_zw='第三代', |
| 135 formal_name='factory_Dummy', |
| 136 trigger='g', |
| 137 dargs={'msg':'3g test, one day...'}), |
| 138 factory.TestData( |
| 139 label_en='wifi', |
| 140 label_zw='無線上網', |
| 141 formal_name='factory_Dummy', |
| 142 trigger='w', |
| 143 dargs={'msg':'wifi test, one day...'}), |
| 144 factory.TestData( |
| 115 label_en='devrec', | 145 label_en='devrec', |
| 146 label_zw='特殊模式', |
| 116 formal_name='factory_DeveloperRecovery', | 147 formal_name='factory_DeveloperRecovery', |
| 117 trigger='d', | 148 trigger='b', |
| 118 dargs={'layout':'devrec'}), | 149 dargs={'layout':'devrec'}), |
| 119 test_data( | 150 factory.TestData( |
| 120 label_en='run-in', | 151 label_en='final check', |
| 121 formal_name='step_runin', | 152 label_zw='最後檢查', |
| 122 automated_seq=[ | |
| 123 test_data( | |
| 124 label_en='component validation', | |
| 125 formal_name='hardware_Components', | |
| 126 dargs={'approved_db':'qualified_components'}), | |
| 127 test_data( | |
| 128 label_en='gpio switch check', | |
| 129 formal_name='hardware_GPIOSwitches'), | |
| 130 test_data( | |
| 131 label_en='system stress', | |
| 132 formal_name='hardware_SAT'), | |
| 133 test_data( | |
| 134 label_en='reboot (%s times)' % _REBOOT_SEQ_ITERATIONS, | |
| 135 formal_name='factory_RebootStub')], | |
| 136 trigger='r'), | |
| 137 test_data( | |
| 138 label_en='end', | |
| 139 formal_name='factory_Dummy', | 153 formal_name='factory_Dummy', |
| 140 trigger='e', | 154 trigger='f', |
| 141 dargs={'msg':'end of testing...\n(chinese)...'}), | 155 dargs={'msg':'google required checks...'}), |
| 156 factory.TestData( |
| 157 label_en='wipe', |
| 158 label_zw='擦拭', |
| 159 formal_name='factory_Dummy', |
| 160 trigger='x', |
| 161 dargs={'msg':('hit TAB+RETURN to finish testing and wipe test image!' + |
| 162 '...\n(chinese)...')}), |
| 142 ] | 163 ] |
| 143 | 164 |
| 144 for test in test_list: | 165 for test in test_list: |
| 145 test.tag_prefix = test.trigger | 166 test.tag_prefix = test.trigger |
| 146 for subtest in test.automated_seq: | 167 for subtest in test.automated_seq: |
| 147 subtest.tag_prefix = test.formal_name | 168 subtest.tag_prefix = test.formal_name |
| 148 | 169 |
| 149 def test_map_index(formal_name, tag_prefix): | 170 test_map = factory.make_test_map(test_list) |
| 150 return formal_name + '.' + tag_prefix | 171 trigger_set = factory.make_trigger_set(test_list) |
| 151 | |
| 152 test_map = dict((test_map_index(test.formal_name, test.tag_prefix), test) | |
| 153 for test in test_list) | |
| 154 | |
| 155 trigger_set = set(test.trigger for test in test_list) | |
| 156 | |
| 157 | |
| 158 class factory_ui: | |
| 159 '''Support communication with the factory_ui process. To simplify | |
| 160 surrounding code, this communication is an exchange of well formed | |
| 161 python expressions. Basically send wraps its arguments in a call | |
| 162 to repr() and recv calls eval() to re-generate the python data.''' | |
| 163 | |
| 164 def __init__(self, factory_ui_path): | |
| 165 self._proc = subprocess.Popen(factory_ui_path, | |
| 166 stdin=subprocess.PIPE, | |
| 167 stdout=subprocess.PIPE) | |
| 168 | |
| 169 def __del__(self): | |
| 170 XXX_log('control deleting factory_ui subprocess') | |
| 171 self._proc.terminate() | |
| 172 time.sleep(1) | |
| 173 if self._proc.poll() is None: | |
| 174 self._proc.kill() | |
| 175 | |
| 176 def send(self, x=None): | |
| 177 print >> self._proc.stdin, repr(x) | |
| 178 self._proc.stdin.flush() | |
| 179 | |
| 180 def send_cmd_next_test(self): | |
| 181 self.send(('next_test', None)) | |
| 182 | |
| 183 def send_cmd_switch_to(self, trigger): | |
| 184 self.send(('switch_to', trigger)) | |
| 185 | |
| 186 def recv(self): | |
| 187 return eval(self._proc.stdout.readline().rstrip()) | |
| 188 | |
| 189 def recv_target_test_update(self): | |
| 190 update = self.recv() | |
| 191 XXX_log('control recv target test %s' % repr(update)) | |
| 192 formal_name, tag_prefix, count = update | |
| 193 test = test_map.get(test_map_index(formal_name, tag_prefix), None) | |
| 194 return (test, count) | |
| 195 | 172 |
| 196 | 173 |
| 197 def step_reboot_seq(i, tag): | 174 def step_reboot_seq(i, tag): |
| 198 if i < _REBOOT_SEQ_ITERATIONS: | 175 if i < _REBOOT_SEQ_ITERATIONS: |
| 199 job.next_step_prepend([step_reboot_seq, i + 1, tag]) | 176 job.next_step_prepend([step_reboot_seq, i + 1, tag]) |
| 200 XXX_log('rebooting (iteration %d)' % i) | 177 factory.log('rebooting (iteration %d)' % i) |
| 201 time.sleep(5) | 178 time.sleep(5) |
| 202 job.reboot() | 179 job.reboot() |
| 203 else: | 180 else: |
| 204 job.run_test('factory_RebootStub', tag=tag) | 181 job.run_test('factory_RebootStub', tag=tag) |
| 205 step_init() | 182 step_init() |
| 206 | 183 |
| 207 | 184 |
| 208 def step_runin(ui, tag): | 185 def step_runin(ui, tag): |
| 209 job.run_test('hardware_Components', | 186 job.run_test('hardware_Components', |
| 210 approved_db='qualified_components', | 187 approved_db='qualified_components', |
| (...skipping 12 matching lines...) Expand all Loading... |
| 223 out-of-order test execution based on keyboard shortcuts. | 200 out-of-order test execution based on keyboard shortcuts. |
| 224 | 201 |
| 225 For each test, a trigger (possibly None) is communicated to the | 202 For each test, a trigger (possibly None) is communicated to the |
| 226 UI, which then replies with the test name and a count number that | 203 UI, which then replies with the test name and a count number that |
| 227 becomes the autotest tag to allow repeated test execution while | 204 becomes the autotest tag to allow repeated test execution while |
| 228 preserving logs. | 205 preserving logs. |
| 229 | 206 |
| 230 When the tests themselves run, they are expected to look for | 207 When the tests themselves run, they are expected to look for |
| 231 (using the factory_test library) keyboard events that match test | 208 (using the factory_test library) keyboard events that match test |
| 232 switching triggers. When a trigger happens, it should be written | 209 switching triggers. When a trigger happens, it should be written |
| 233 to the _RESULT_FILE_PATH, which will be read after the test | 210 to the factory.RESULT_FILE_PATH, which will be read after the test |
| 234 completed and the result comminicated onwards to the UI.''' | 211 completed and the result comminicated onwards to the UI.''' |
| 235 | 212 |
| 236 job.next_step([step_init]) | 213 job.next_step([step_init]) |
| 237 | 214 |
| 238 status_file_path = job.autodir + '/results/default/status' | 215 ui = factory.UiClient(_FACTORY_UI_PATH) |
| 239 factory_ui_path = job.autodir + '/deps/factory/ui' | |
| 240 | 216 |
| 241 ui = factory_ui(factory_ui_path) | |
| 242 | |
| 243 ui.send(test_data_class_def) | |
| 244 ui.send(test_list) | 217 ui.send(test_list) |
| 245 ui.send(status_file_path) | 218 ui.send(_STATUS_FILE_PATH) |
| 246 ui.send(_FACTORY_LOG_PATH) | |
| 247 | 219 |
| 248 test_widget_size = ui.recv() | 220 test_widget_size = ui.recv() |
| 249 XXX_log('received test_widget_size = %s' % repr(test_widget_size)) | 221 factory.log('received test_widget_size = %s' % repr(test_widget_size)) |
| 250 | 222 |
| 251 ui.send_cmd_next_test() | 223 ui.send_cmd_next_test() |
| 252 test, test_count = ui.recv_target_test_update() | 224 test, test_count = ui.recv_target_test_update(test_map) |
| 253 | 225 |
| 254 while test is not None: | 226 while test is not None: |
| 255 if test.automated_seq: | 227 if test.automated_seq: |
| 256 tag = '%s_%s' % (test.formal_name, test_count) | 228 tag = '%s_%s' % (test.formal_name, test_count) |
| 257 exec('%s(ui, "%s")' % (test.formal_name, tag)) | 229 exec('%s(ui, "%s")' % (test.formal_name, tag)) |
| 258 result = None | 230 result = None |
| 259 else: | 231 else: |
| 260 dargs = test.dargs | 232 dargs = test.dargs |
| 261 dargs.update({ | 233 dargs.update({ |
| 262 'tag': '%s_%s' % (test.tag_prefix, test_count), | 234 'tag': '%s_%s' % (test.tag_prefix, test_count), |
| 263 'test_tag_prefix': test.tag_prefix, | 235 'test_tag_prefix': test.tag_prefix, |
| 264 'test_count': test_count, | 236 'test_count': test_count, |
| 265 'test_widget_size': test_widget_size, | 237 'test_widget_size': test_widget_size, |
| 266 'trigger_set': trigger_set, | 238 'trigger_set': trigger_set, |
| 267 'result_file_path': _RESULT_FILE_PATH}) | 239 'result_file_path': factory.RESULT_FILE_PATH}) |
| 268 with open(_RESULT_FILE_PATH, 'w') as file: | 240 with open(factory.RESULT_FILE_PATH, 'w') as file: |
| 269 file.write('None\n') | 241 file.write('None\n') |
| 270 job.run_test(test.formal_name, **dargs) | 242 job.run_test(test.formal_name, **dargs) |
| 271 with open(_RESULT_FILE_PATH, 'r') as file: | 243 with open(factory.RESULT_FILE_PATH, 'r') as file: |
| 272 result = eval(file.readline()) | 244 result = eval(file.readline()) |
| 273 | 245 |
| 274 if result is not None: | 246 if result is not None: |
| 275 ui.send_cmd_switch_to(result) | 247 ui.send_cmd_switch_to(result) |
| 276 else: | 248 else: |
| 277 ui.send_cmd_next_test() | 249 ui.send_cmd_next_test() |
| 278 | 250 |
| 279 test, test_count = ui.recv_target_test_update() | 251 test, test_count = ui.recv_target_test_update(test_map) |
| 280 | 252 |
| 281 XXX_log('factory testing completed') | 253 factory.log('factory testing completed') |
| OLD | NEW |