1 # Copyright 2014 The Android Open Source Project 2 # 3 # Licensed under the Apache License, Version 2.0 (the "License"); 4 # you may not use this file except in compliance with the License. 5 # You may obtain a copy of the License at 6 # 7 # http://www.apache.org/licenses/LICENSE-2.0 8 # 9 # Unless required by applicable law or agreed to in writing, software 10 # distributed under the License is distributed on an "AS IS" BASIS, 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 # See the License for the specific language governing permissions and 13 # limitations under the License. 14 15 import copy 16 import os 17 import os.path 18 import tempfile 19 import subprocess 20 import time 21 import sys 22 23 import its.caps 24 import its.device 25 from its.device import ItsSession 26 27 CHART_DELAY = 1 # seconds 28 FACING_EXTERNAL = 2 29 NUM_TRYS = 2 30 SKIP_RET_CODE = 101 # note this must be same as tests/scene*/test_* 31 32 33 def evaluate_socket_failure(err_file_path): 34 """Determine if test fails due to socket FAIL.""" 35 socket_fail = False 36 with open(err_file_path, 'r') as ferr: 37 for line in ferr: 38 if (line.find('socket.error') != -1 or 39 line.find('Problem with socket') != -1): 40 socket_fail = True 41 return socket_fail 42 43 44 def skip_sensor_fusion(): 45 """Determine if sensor fusion test is skipped for this camera.""" 46 47 skip_code = SKIP_RET_CODE 48 with ItsSession() as cam: 49 props = cam.get_camera_properties() 50 if (its.caps.sensor_fusion(props) and its.caps.manual_sensor(props) and 51 props['android.lens.facing'] is not FACING_EXTERNAL): 52 skip_code = None 53 return skip_code 54 55 56 def main(): 57 """Run all the automated tests, saving intermediate files, and producing 58 a summary/report of the results. 59 60 Script should be run from the top-level CameraITS directory. 61 62 Command line arguments: 63 camera: the camera(s) to be tested. Use comma to separate multiple 64 camera Ids. Ex: "camera=0,1" or "camera=1" 65 device: device id for adb 66 scenes: the test scene(s) to be executed. Use comma to separate 67 multiple scenes. Ex: "scenes=scene0,scene1" or 68 "scenes=0,1,sensor_fusion" (sceneX can be abbreviated by X 69 where X is a integer) 70 chart: [Experimental] another android device served as test chart 71 display. When this argument presents, change of test scene 72 will be handled automatically. Note that this argument 73 requires special physical/hardware setup to work and may not 74 work on all android devices. 75 result: Device ID to forward results to (in addition to the device 76 that the tests are running on). 77 rot_rig: [Experimental] ID of the rotation rig being used (formatted as 78 "<vendor ID>:<product ID>:<channel #>" or "default") 79 tmp_dir: location of temp directory for output files 80 skip_scene_validation: force skip scene validation. Used when test scene 81 is setup up front and don't require tester validation. 82 """ 83 84 # Not yet mandated tests 85 NOT_YET_MANDATED = { 86 "scene0": [ 87 "test_jitter", 88 "test_burst_capture" 89 ], 90 "scene1": [ 91 "test_ae_af", 92 "test_ae_precapture_trigger", 93 "test_crop_region_raw", 94 "test_ev_compensation_advanced", 95 "test_ev_compensation_basic", 96 "test_yuv_plus_jpeg" 97 ], 98 "scene2": [ 99 "test_num_faces", 100 ], 101 "scene3": [ 102 "test_3a_consistency", 103 "test_lens_movement_reporting", 104 "test_lens_position" 105 ], 106 "scene4": [], 107 "scene5": [], 108 "sensor_fusion": [] 109 } 110 111 all_scenes = ["scene0", "scene1", "scene2", "scene3", "scene4", "scene5", 112 "sensor_fusion"] 113 114 auto_scenes = ["scene0", "scene1", "scene2", "scene3", "scene4"] 115 116 scene_req = { 117 "scene0": None, 118 "scene1": "A grey card covering at least the middle 30% of the scene", 119 "scene2": "A picture containing human faces", 120 "scene3": "The ISO 12233 chart", 121 "scene4": "A specific test page of a circle covering at least the " 122 "middle 50% of the scene. See CameraITS.pdf section 2.3.4 " 123 "for more details", 124 "scene5": "Capture images with a diffuser attached to the camera. See " 125 "CameraITS.pdf section 2.3.4 for more details", 126 "sensor_fusion": "Rotating checkboard pattern. See " 127 "sensor_fusion/SensorFusion.pdf for detailed " 128 "instructions.\nNote that this test will be skipped " 129 "on devices not supporting REALTIME camera timestamp." 130 } 131 scene_extra_args = { 132 "scene5": ["doAF=False"] 133 } 134 135 camera_ids = [] 136 scenes = [] 137 chart_host_id = None 138 result_device_id = None 139 rot_rig_id = None 140 tmp_dir = None 141 skip_scene_validation = False 142 for s in sys.argv[1:]: 143 if s[:7] == "camera=" and len(s) > 7: 144 camera_ids = s[7:].split(',') 145 elif s[:7] == "scenes=" and len(s) > 7: 146 scenes = s[7:].split(',') 147 elif s[:6] == 'chart=' and len(s) > 6: 148 chart_host_id = s[6:] 149 elif s[:7] == 'result=' and len(s) > 7: 150 result_device_id = s[7:] 151 elif s[:8] == 'rot_rig=' and len(s) > 8: 152 rot_rig_id = s[8:] # valid values: 'default' or '$VID:$PID:$CH' 153 # The default '$VID:$PID:$CH' is '04d8:fc73:1' 154 elif s[:8] == 'tmp_dir=' and len(s) > 8: 155 tmp_dir = s[8:] 156 elif s == 'skip_scene_validation': 157 skip_scene_validation = True 158 159 auto_scene_switch = chart_host_id is not None 160 merge_result_switch = result_device_id is not None 161 162 # Run through all scenes if user does not supply one 163 possible_scenes = auto_scenes if auto_scene_switch else all_scenes 164 if not scenes: 165 scenes = possible_scenes 166 else: 167 # Validate user input scene names 168 valid_scenes = True 169 temp_scenes = [] 170 for s in scenes: 171 if s in possible_scenes: 172 temp_scenes.append(s) 173 else: 174 try: 175 # Try replace "X" to "sceneX" 176 scene_str = "scene" + s 177 if scene_str not in possible_scenes: 178 valid_scenes = False 179 break 180 temp_scenes.append(scene_str) 181 except ValueError: 182 valid_scenes = False 183 break 184 185 if not valid_scenes: 186 print "Unknown scene specifiied:", s 187 assert False 188 scenes = temp_scenes 189 190 # Initialize test results 191 results = {} 192 result_key = ItsSession.RESULT_KEY 193 for s in all_scenes: 194 results[s] = {result_key: ItsSession.RESULT_NOT_EXECUTED} 195 196 # Make output directories to hold the generated files. 197 topdir = tempfile.mkdtemp(dir=tmp_dir) 198 subprocess.call(['chmod', 'g+rx', topdir]) 199 print "Saving output files to:", topdir, "\n" 200 201 device_id = its.device.get_device_id() 202 device_id_arg = "device=" + device_id 203 print "Testing device " + device_id 204 205 # Sanity Check for devices 206 device_bfp = its.device.get_device_fingerprint(device_id) 207 assert device_bfp is not None 208 209 if auto_scene_switch: 210 chart_host_bfp = its.device.get_device_fingerprint(chart_host_id) 211 assert chart_host_bfp is not None 212 213 if merge_result_switch: 214 result_device_bfp = its.device.get_device_fingerprint(result_device_id) 215 assert_err_msg = ('Cannot merge result to a different build, from ' 216 '%s to %s' % (device_bfp, result_device_bfp)) 217 assert device_bfp == result_device_bfp, assert_err_msg 218 219 # user doesn't specify camera id, run through all cameras 220 if not camera_ids: 221 camera_ids_path = os.path.join(topdir, "camera_ids.txt") 222 out_arg = "out=" + camera_ids_path 223 cmd = ['python', 224 os.path.join(os.getcwd(), "tools/get_camera_ids.py"), out_arg, 225 device_id_arg] 226 cam_code = subprocess.call(cmd, cwd=topdir) 227 assert cam_code == 0 228 with open(camera_ids_path, "r") as f: 229 for line in f: 230 camera_ids.append(line.replace('\n', '')) 231 232 print "Running ITS on camera: %s, scene %s" % (camera_ids, scenes) 233 234 if auto_scene_switch: 235 # merge_result only supports run_parallel_tests 236 if merge_result_switch and camera_ids[0] == '1': 237 print 'Skip chart screen' 238 time.sleep(1) 239 else: 240 print 'Waking up chart screen: ', chart_host_id 241 screen_id_arg = ('screen=%s' % chart_host_id) 242 cmd = ['python', os.path.join(os.environ['CAMERA_ITS_TOP'], 'tools', 243 'wake_up_screen.py'), screen_id_arg] 244 wake_code = subprocess.call(cmd) 245 assert wake_code == 0 246 247 for camera_id in camera_ids: 248 # Loop capturing images until user confirm test scene is correct 249 camera_id_arg = "camera=" + camera_id 250 print "Preparing to run ITS on camera", camera_id 251 252 os.mkdir(os.path.join(topdir, camera_id)) 253 for d in scenes: 254 os.mkdir(os.path.join(topdir, camera_id, d)) 255 256 for scene in scenes: 257 skip_code = None 258 tests = [(s[:-3], os.path.join("tests", scene, s)) 259 for s in os.listdir(os.path.join("tests", scene)) 260 if s[-3:] == ".py" and s[:4] == "test"] 261 tests.sort() 262 263 summary = "Cam" + camera_id + " " + scene + "\n" 264 numpass = 0 265 numskip = 0 266 num_not_mandated_fail = 0 267 numfail = 0 268 validate_switch = True 269 if scene_req[scene] is not None: 270 out_path = os.path.join(topdir, camera_id, scene+".jpg") 271 out_arg = "out=" + out_path 272 if scene == 'sensor_fusion': 273 skip_code = skip_sensor_fusion() 274 if rot_rig_id or skip_code == SKIP_RET_CODE: 275 validate_switch = False 276 if skip_scene_validation: 277 validate_switch = False 278 cmd = None 279 if auto_scene_switch: 280 if (not merge_result_switch or 281 (merge_result_switch and camera_ids[0] == '0')): 282 scene_arg = 'scene=' + scene 283 cmd = ['python', 284 os.path.join(os.getcwd(), 'tools/load_scene.py'), 285 scene_arg, screen_id_arg] 286 else: 287 time.sleep(CHART_DELAY) 288 else: 289 # Skip scene validation under certain conditions 290 if validate_switch and not merge_result_switch: 291 scene_arg = 'scene=' + scene_req[scene] 292 extra_args = scene_extra_args.get(scene, []) 293 cmd = ['python', 294 os.path.join(os.getcwd(), 295 'tools/validate_scene.py'), 296 camera_id_arg, out_arg, 297 scene_arg, device_id_arg] + extra_args 298 if cmd is not None: 299 valid_scene_code = subprocess.call(cmd, cwd=topdir) 300 assert valid_scene_code == 0 301 print "Start running ITS on camera %s, %s" % (camera_id, scene) 302 # Run each test, capturing stdout and stderr. 303 for (testname, testpath) in tests: 304 if auto_scene_switch: 305 if merge_result_switch and camera_ids[0] == '0': 306 # Send an input event to keep the screen not dimmed. 307 # Since we are not using camera of chart screen, FOCUS event 308 # should do nothing but keep the screen from dimming. 309 # The "sleep after x minutes of inactivity" display setting 310 # determines how long this command can keep screen bright. 311 # Setting it to something like 30 minutes should be enough. 312 cmd = ('adb -s %s shell input keyevent FOCUS' 313 % chart_host_id) 314 subprocess.call(cmd.split()) 315 t0 = time.time() 316 for num_try in range(NUM_TRYS): 317 outdir = os.path.join(topdir, camera_id, scene) 318 outpath = os.path.join(outdir, testname+'_stdout.txt') 319 errpath = os.path.join(outdir, testname+'_stderr.txt') 320 if scene == 'sensor_fusion': 321 if skip_code is not SKIP_RET_CODE: 322 if rot_rig_id: 323 print 'Rotating phone w/ rig %s' % rot_rig_id 324 rig = ('python tools/rotation_rig.py rotator=%s' % 325 rot_rig_id) 326 subprocess.Popen(rig.split()) 327 else: 328 print 'Rotate phone 15s as shown in SensorFusion.pdf' 329 else: 330 test_code = skip_code 331 if skip_code is not SKIP_RET_CODE: 332 cmd = ['python', os.path.join(os.getcwd(), testpath)] 333 cmd += sys.argv[1:] + [camera_id_arg] 334 with open(outpath, 'w') as fout, open(errpath, 'w') as ferr: 335 test_code = subprocess.call( 336 cmd, stderr=ferr, stdout=fout, cwd=outdir) 337 if test_code == 0 or test_code == SKIP_RET_CODE: 338 break 339 else: 340 socket_fail = evaluate_socket_failure(errpath) 341 if socket_fail: 342 if num_try != NUM_TRYS-1: 343 print ' Retry %s/%s' % (scene, testname) 344 else: 345 break 346 else: 347 break 348 t1 = time.time() 349 350 test_failed = False 351 if test_code == 0: 352 retstr = "PASS " 353 numpass += 1 354 elif test_code == SKIP_RET_CODE: 355 retstr = "SKIP " 356 numskip += 1 357 elif test_code != 0 and testname in NOT_YET_MANDATED[scene]: 358 retstr = "FAIL*" 359 num_not_mandated_fail += 1 360 else: 361 retstr = "FAIL " 362 numfail += 1 363 test_failed = True 364 365 msg = "%s %s/%s [%.1fs]" % (retstr, scene, testname, t1-t0) 366 print msg 367 msg_short = "%s %s [%.1fs]" % (retstr, testname, t1-t0) 368 if test_failed: 369 summary += msg_short + "\n" 370 371 if numskip > 0: 372 skipstr = ", %d test%s skipped" % ( 373 numskip, "s" if numskip > 1 else "") 374 else: 375 skipstr = "" 376 377 test_result = "\n%d / %d tests passed (%.1f%%)%s" % ( 378 numpass + num_not_mandated_fail, len(tests) - numskip, 379 100.0 * float(numpass + num_not_mandated_fail) / 380 (len(tests) - numskip) 381 if len(tests) != numskip else 100.0, skipstr) 382 print test_result 383 384 if num_not_mandated_fail > 0: 385 msg = "(*) tests are not yet mandated" 386 print msg 387 388 summary_path = os.path.join(topdir, camera_id, scene, "summary.txt") 389 with open(summary_path, "w") as f: 390 f.write(summary) 391 392 passed = numfail == 0 393 results[scene][result_key] = (ItsSession.RESULT_PASS if passed 394 else ItsSession.RESULT_FAIL) 395 results[scene][ItsSession.SUMMARY_KEY] = summary_path 396 397 print "Reporting ITS result to CtsVerifier" 398 if merge_result_switch: 399 # results are modified by report_result 400 results_backup = copy.deepcopy(results) 401 its.device.report_result(result_device_id, camera_id, results_backup) 402 403 its.device.report_result(device_id, camera_id, results) 404 405 if auto_scene_switch: 406 if merge_result_switch: 407 print 'Skip shutting down chart screen' 408 else: 409 print 'Shutting down chart screen: ', chart_host_id 410 screen_id_arg = ('screen=%s' % chart_host_id) 411 cmd = ['python', os.path.join(os.environ['CAMERA_ITS_TOP'], 'tools', 412 'turn_off_screen.py'), screen_id_arg] 413 screen_off_code = subprocess.call(cmd) 414 assert screen_off_code == 0 415 416 print 'Shutting down DUT screen: ', device_id 417 screen_id_arg = ('screen=%s' % device_id) 418 cmd = ['python', os.path.join(os.environ['CAMERA_ITS_TOP'], 'tools', 419 'turn_off_screen.py'), screen_id_arg] 420 screen_off_code = subprocess.call(cmd) 421 assert screen_off_code == 0 422 423 print "ITS tests finished. Please go back to CtsVerifier and proceed" 424 425 if __name__ == '__main__': 426 main() 427