1# Lint as: python2, python3 2# Copyright 2018 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6import contextlib 7import json 8import logging 9from lxml import etree 10import os 11import six 12import time 13 14from autotest_lib.client.common_lib import error, utils 15from autotest_lib.server.cros.tradefed import tradefed_chromelogin as login 16 17 18class ChartFixture: 19 """Sets up chart tablet to display dummy scene image.""" 20 DISPLAY_SCRIPT = '/usr/local/autotest/bin/display_chart.py' 21 OUTPUT_LOG = '/tmp/chart_service.log' 22 23 def __init__(self, chart_host, scene_uri): 24 self.host = chart_host 25 self.scene_uri = scene_uri 26 self.display_pid = None 27 self.host.run(['rm', '-f', self.OUTPUT_LOG]) 28 29 def initialize(self): 30 """Prepare scene file and display it on chart host.""" 31 logging.info('Prepare scene file') 32 tmpdir = self.host.get_tmp_dir() 33 scene_path = os.path.join( 34 tmpdir, self.scene_uri[self.scene_uri.rfind('/') + 1:]) 35 self.host.run('wget', args=('-O', scene_path, self.scene_uri)) 36 37 logging.info('Display scene file') 38 self.display_pid = self.host.run_background( 39 'python2 {script} {scene} >{log} 2>&1'.format( 40 script=self.DISPLAY_SCRIPT, 41 scene=scene_path, 42 log=self.OUTPUT_LOG)) 43 44 logging.info( 45 'Poll for "is ready" message for ensuring chart is ready.') 46 timeout = 60 47 poll_time_step = 0.1 48 while timeout > 0: 49 if self.host.run( 50 'grep', 51 args=('-q', 'Chart is ready.', self.OUTPUT_LOG), 52 ignore_status=True).exit_status == 0: 53 break 54 time.sleep(poll_time_step) 55 timeout -= poll_time_step 56 else: 57 raise error.TestError('Timeout waiting for chart ready') 58 59 def cleanup(self): 60 """Cleanup display script.""" 61 if self.display_pid is not None: 62 self.host.run( 63 'kill', 64 args=('-2', str(self.display_pid)), 65 ignore_status=True) 66 self.host.get_file(self.OUTPUT_LOG, '.') 67 68 69def get_chart_address(host_address, args): 70 """Get address of chart tablet from commandline args or mapping logic in 71 test lab. 72 73 @param host_address: a list of hostname strings. 74 @param args: a dict parse from commandline args. 75 @return: 76 A list of strings for chart tablet addresses. 77 """ 78 address = utils.args_to_dict(args).get('chart') 79 if address is not None: 80 return address.split(',') 81 elif utils.is_in_container(): 82 return [utils.get_lab_chart_address(host) for host in host_address] 83 else: 84 return None 85 86 87class DUTFixture: 88 """Sets up camera filter for target camera facing on DUT.""" 89 TEST_CONFIG_PATH = '/var/cache/camera/test_config.json' 90 CAMERA_PROFILE_PATH = ('/mnt/stateful_partition/encrypted/var/cache/camera' 91 '/media_profiles.xml') 92 CAMERA_SCENE_LOG = '/tmp/scene.jpg' 93 94 def __init__(self, test, host, facing): 95 self.test = test 96 self.host = host 97 self.facing = facing 98 99 @contextlib.contextmanager 100 def _set_selinux_permissive(self): 101 selinux_mode = self.host.run_output('getenforce') 102 self.host.run('setenforce 0') 103 yield 104 self.host.run('setenforce', args=(selinux_mode, )) 105 106 def _filter_camera_profile(self, content, facing): 107 """Filter camera profile of target facing from content of camera 108 profile. 109 110 @return: 111 New camera profile with only target facing, camera ids are 112 renumbered from 0. 113 """ 114 tree = etree.parse( 115 six.StringIO(content), 116 parser=etree.XMLParser(compact=False)) 117 root = tree.getroot() 118 profiles = root.findall('CamcorderProfiles') 119 logging.debug('%d number of camera(s) found in camera profile', 120 len(profiles)) 121 assert 1 <= len(profiles) <= 2 122 if len(profiles) == 2: 123 cam_id = 0 if facing == 'back' else 1 124 for p in profiles: 125 if cam_id == int(p.attrib['cameraId']): 126 p.attrib['cameraId'] = '0' 127 else: 128 root.remove(p) 129 else: 130 with login.login_chrome( 131 hosts=[self.host], 132 board=self.test._get_board_name(), 133 ), self._set_selinux_permissive(): 134 has_front_camera = ( 135 'feature:android.hardware.camera.front' in self.host. 136 run_output('android-sh -c "pm list features"')) 137 logging.debug('has_front_camera=%s', has_front_camera) 138 if (facing == 'front') != has_front_camera: 139 root.remove(profiles[0]) 140 return etree.tostring( 141 tree, xml_declaration=True, encoding=tree.docinfo.encoding) 142 143 def _read_file(self, filepath): 144 """Read content of filepath from host.""" 145 tmp_path = os.path.join(self.test.tmpdir, os.path.basename(filepath)) 146 self.host.get_file(filepath, tmp_path, delete_dest=True) 147 with open(tmp_path) as f: 148 return f.read() 149 150 def _write_file(self, filepath, content, permission=None, owner=None): 151 """Write content to filepath on remote host. 152 @param permission: set permission to 0xxx octal number of remote file. 153 @param owner: set owner of remote file. 154 """ 155 tmp_path = os.path.join(self.test.tmpdir, os.path.basename(filepath)) 156 with open(tmp_path, 'w') as f: 157 f.write(content) 158 if permission is not None: 159 os.chmod(tmp_path, permission) 160 self.host.send_file(tmp_path, filepath, delete_dest=True) 161 if owner is not None: 162 self.host.run('chown', args=(owner, filepath)) 163 164 def initialize(self): 165 """Filter out camera other than target facing on DUT.""" 166 logging.info('Restart camera service with filter option') 167 self._write_file( 168 self.TEST_CONFIG_PATH, 169 json.dumps({ 170 'enable_back_camera': self.facing == 'back', 171 'enable_front_camera': self.facing == 'front', 172 'enable_external_camera': False 173 }), 174 owner='arc-camera') 175 self.host.upstart_restart('cros-camera') 176 177 logging.info('Replace camera profile in ARC++ container') 178 profile = self._read_file(self.CAMERA_PROFILE_PATH) 179 new_profile = self._filter_camera_profile(profile, self.facing) 180 self._write_file(self.CAMERA_PROFILE_PATH, new_profile) 181 self.host.run('restart ui') 182 183 @contextlib.contextmanager 184 def _stop_camera_service(self): 185 self.host.upstart_stop('cros-camera') 186 yield 187 self.host.upstart_restart('cros-camera') 188 189 def log_camera_scene(self): 190 """Capture an image from camera as the log for debugging scene related 191 problem.""" 192 193 gtest_filter = ( 194 'Camera3StillCaptureTest/' 195 'Camera3DumpSimpleStillCaptureTest.DumpCaptureResult/0') 196 with self._stop_camera_service(): 197 self.host.run( 198 'sudo', 199 args=('--user=arc-camera', 'cros_camera_test', 200 '--gtest_filter=' + gtest_filter, 201 '--camera_facing=' + self.facing, 202 '--dump_still_capture_path=' + 203 self.CAMERA_SCENE_LOG)) 204 205 self.host.get_file(self.CAMERA_SCENE_LOG, '.') 206 207 def cleanup(self): 208 """Cleanup camera filter.""" 209 logging.info('Remove filter option and restore camera service') 210 self.host.run('rm', args=('-f', self.TEST_CONFIG_PATH)) 211 self.host.upstart_restart('cros-camera') 212 213 logging.info('Restore camera profile in ARC++ container') 214 self.host.run('restart ui') 215