• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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