1# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import logging
6import os
7import pprint
8import shutil
9import subprocess
10import sys
11import time
12from autotest_lib.client.bin import test, utils
13from autotest_lib.client.common_lib import error
14from autotest_lib.client.common_lib.cros import chrome
15from autotest_lib.client.cros import constants, cros_logging
16
17# The name of the Chrome OS Pepper Flash binary.
18_BINARY = 'libpepflashplayer.so'
19# The path to the system provided (read only) Flash binary.
20_SYSTEM_STORE = '/opt/google/chrome/pepper'
21# The name of the file containing metainformation for the system binary.
22_FLASH_INFO = 'pepper-flash.info'
23# The name of the component updated manifest describing version, OS,
24# architecture and required ppapi interfaces.
25_MANIFEST = 'manifest.json'
26# The tmp location Chrome downloads the bits from Omaha to.
27_DOWNLOAD_STORE = '/home/chronos/PepperFlash'
28# The location the CrOS component updater stores new images in.
29_COMPONENT_STORE = '/var/lib/imageloader/PepperFlashPlayer'
30# latest-version gets updated after the library in the store. We use it to
31# check for completion of download.
32_COMPONENT_STORE_LATEST = _COMPONENT_STORE + '/latest-version'
33# The location at which the latest component updated Flash binary is mounted
34# for execution.
35_COMPONENT_MOUNT = '/run/imageloader/PepperFlashPlayer'
36# Set of all possible paths at which Flash binary could be found.
37_FLASH_PATHS = {
38    _SYSTEM_STORE, _DOWNLOAD_STORE, _COMPONENT_STORE, _COMPONENT_MOUNT}
39
40# Run the traditional Flash sanity check (just check that any Flash works).
41_CU_ACTION_SANITY = 'sanity'
42# Clean out all component update state (in preparation to next update).
43_CU_ACTION_DELETE = 'delete-component'
44# TODO(ihf): Implement this action to simulated component on component update.
45_CU_ACTION_INSTALL_OLD = 'install-old-component'
46# Download the latest available component from Omaha.
47_CU_ACTION_DOWNLOAD = 'download-omaha-component'
48# Using current state of DUT verify the Flash in _COMPONENT_MOUNT.
49_CU_ACTION_VERIFY_COMPONENT = 'verify-component-flash'
50# Using current state of DUT verify the Flash shipping with the system image.
51_CU_ACTION_VERIFY_SYSTEM = 'verify-system-flash'
52
53
54class desktopui_FlashSanityCheck(test.test):
55    """
56    Sanity test that ensures flash instance is launched when a swf is played.
57    """
58    version = 4
59
60    _messages_log_reader = None
61    _ui_log_reader = None
62    _test_url = None
63    _testServer = None
64    _time_to_wait_secs = 5
65    _swf_runtime = 5
66    _retries = 10
67    _component_download_timeout_secs = 300
68
69    def verify_file(self, name):
70        """
71        Does sanity checks on a file on disk.
72
73        @param name: filename to verify.
74        """
75        if not os.path.exists(name):
76            raise error.TestFail('Failed: File does not exist %s' % name)
77        if not os.path.isfile(name):
78            raise error.TestFail('Failed: Not a file %s' % name)
79        if os.path.getsize(name) <= 0:
80            raise error.TestFail('Failed: File is too short %s' % name)
81        if name.endswith('libpepflashplayer.so'):
82            output = subprocess.check_output(['file %s' % name], shell=True)
83            if not 'stripped' in output:
84                logging.error(output)
85                raise error.TestFail('Failed: Flash binary not stripped.')
86            if not 'dynamically linked' in output:
87                logging.error(output)
88                raise error.TestFail('Failed: Flash not dynamically linked.')
89            arch = utils.get_arch_userspace()
90            logging.info('get_arch_userspace = %s', arch)
91            if arch == 'arm' and not 'ARM' in output:
92                logging.error(output)
93                raise error.TestFail('Failed: Flash binary not for ARM.')
94            if arch == 'x86_64' and not 'x86-64' in output:
95                logging.error(output)
96                raise error.TestFail('Failed: Flash binary not for x86_64.')
97            if arch == 'i386' and not '80386' in output:
98                logging.error(output)
99                raise error.TestFail('Failed: Flash binary not for i386.')
100        logging.info('Verified file %s', name)
101
102    def serve_swf_to_browser(self, browser):
103        """
104        Tries to serve a sample swf to browser.
105
106        A failure of this function does not imply a problem with Flash.
107        @param browser: The Browser object to run the test with.
108        @return: True if we managed to send swf to browser, False otherwise.
109        """
110        # Prepare index.html/Trivial.swf to be served.
111        browser.platform.SetHTTPServerDirectories(self.bindir)
112        test_url = browser.platform.http_server.UrlOf(os.path.join(self.bindir,
113                                                      'index.html'))
114        tab = None
115        # BUG(485108): Work around a telemetry timing out after login.
116        try:
117            logging.info('Getting tab from telemetry...')
118            tab = browser.tabs[0]
119        except:
120            logging.warning('Unexpected exception getting tab: %s',
121                            pprint.pformat(sys.exc_info()[0]))
122        if tab is None:
123            return False
124
125        logging.info('Initialize reading system logs.')
126        self._messages_log_reader = cros_logging.LogReader()
127        self._messages_log_reader.set_start_by_current()
128        self._ui_log_reader = cros_logging.LogReader('/var/log/ui/ui.LATEST')
129        self._ui_log_reader.set_start_by_current()
130        logging.info('Done initializing system logs.')
131
132        # Verify that the swf got pulled.
133        try:
134            tab.Navigate(test_url)
135            tab.WaitForDocumentReadyStateToBeComplete()
136            return True
137        except:
138            logging.warning('Unexpected exception waiting for document: %s',
139                            pprint.pformat(sys.exc_info()[0]))
140            return False
141
142
143    def verify_flash_process(self, load_path=None):
144        """Verifies the Flash process runs and doesn't crash.
145
146        @param load_path: The expected path of the Flash binary. If set
147                          function and Flash was loaded from a different path,
148                          function will fail the test.
149        """
150        logging.info('Waiting for Pepper process.')
151        # Verify that we see a ppapi process and assume it is Flash.
152        ppapi = utils.wait_for_value_changed(
153            lambda: (utils.get_process_list('chrome', '--type=ppapi')),
154            old_value=[],
155            timeout_sec=self._time_to_wait_secs)
156        logging.info('ppapi process list at start: %s', ', '.join(ppapi))
157        if not ppapi:
158            msg = 'flash/platform/pepper/pep_'
159            if not self._ui_log_reader.can_find(msg):
160                raise error.TestFail(
161                    'Failed: Flash did not start (logs) and no ppapi process '
162                    'found.'
163                )
164            # There is a chrome bug where the command line of the ppapi and
165            # other processes is shown as "type=zygote". Bail out if we see more
166            # than 2. Notice, we already did the waiting, so there is no need to
167            # do more of it.
168            zygote = utils.get_process_list('chrome', '--type=zygote')
169            if len(zygote) > 2:
170                logging.warning('Flash probably launched by Chrome as zygote: '
171                                '<%s>.', ', '.join(zygote))
172
173        # We have a ppapi process. Let it run for a little and see if it is
174        # still alive.
175        logging.info('Running Flash content for a little while.')
176        time.sleep(self._swf_runtime)
177        logging.info('Verifying the Pepper process is still around.')
178        ppapi = utils.wait_for_value_changed(
179            lambda: (utils.get_process_list('chrome', '--type=ppapi')),
180            old_value=[],
181            timeout_sec=self._time_to_wait_secs)
182        # Notice that we are not checking for equality of ppapi on purpose.
183        logging.info('PPapi process list found: <%s>', ', '.join(ppapi))
184
185        # Any better pattern matching?
186        msg = ' Received crash notification for ' + constants.BROWSER
187        if self._messages_log_reader.can_find(msg):
188            raise error.TestFail('Failed: Browser crashed during test.')
189        if not ppapi:
190            raise error.TestFail(
191                'Failed: Pepper process disappeared during test.')
192
193        # At a minimum Flash identifies itself during process start.
194        msg = 'flash/platform/pepper/pep_'
195        if not self._ui_log_reader.can_find(msg):
196            raise error.TestFail(
197                'Failed: Saw ppapi process but no Flash output.')
198
199        # Check that libpepflashplayer.so was loaded from the expected path.
200        if load_path:
201            # Check all current process for Flash library.
202            output = subprocess.check_output(
203                ['grep libpepflashplayer.so /proc/*/maps'], shell=True)
204            # Verify there was no other than the expected location.
205            for dont_load_path in _FLASH_PATHS - {load_path}:
206                if dont_load_path in output:
207                    logging.error('Flash incorrectly loaded from %s',
208                                  dont_load_path)
209                    logging.info(output)
210                    raise error.TestFail('Failed: Flash incorrectly loaded '
211                                         'from %s' % dont_load_path)
212                logging.info('Verified Flash was indeed not loaded from %s',
213                             dont_load_path)
214            # Verify at least one of the libraries came from where we expected.
215            if not load_path in output:
216                # Mystery. We saw a Flash loaded from who knows where.
217                logging.error('Flash not loaded from %s', load_path)
218                logging.info(output)
219                raise error.TestFail('Failed: Flash not loaded from %s' %
220                                     load_path)
221            logging.info('Saw a flash library loaded from %s.', load_path)
222
223
224    def action_delete_component(self):
225        """
226        Deletes all components on the DUT. Notice _COMPONENT_MOUNT cannot be
227        deleted. It will remain until after reboot of the DUT.
228        """
229        if os.path.exists(_COMPONENT_STORE):
230            shutil.rmtree(_COMPONENT_STORE)
231            if os.path.exists(_COMPONENT_STORE):
232                raise error.TestFail('Error: could not delete %s',
233                                     _COMPONENT_STORE)
234        if os.path.exists(_DOWNLOAD_STORE):
235            shutil.rmtree(_DOWNLOAD_STORE)
236            if os.path.exists(_DOWNLOAD_STORE):
237                raise error.TestFail('Error: could not delete %s',
238                                     _DOWNLOAD_STORE)
239
240    def action_download_omaha_component(self):
241        """
242        Pretend we have no system Flash binary and tell browser to
243        accelerate the component update process.
244        TODO(ihf): Is this better than pretending the system binary is old?
245        """
246        # TODO(ihf): Find ways to test component updates on top of component
247        # updates maybe by checking hashlib.md5(open(_COMPONENT_STORE_LATEST)).
248        if os.path.exists(_COMPONENT_STORE):
249            raise error.TestFail('Error: currently unable to test component '
250                                 'update as component store not clean before '
251                                 'download.')
252        # TODO(ihf): Remove --component-updater=test-request once Finch is set
253        # up to behave more like a user in the field.
254        browser_args = ['--ppapi-flash-path=',
255                        '--ppapi-flash-version=0.0.0.0',
256                        '--component-updater=fast-update,test-request']
257        logging.info(browser_args)
258        # Browser will download component, but it will require a subsequent
259        # reboot by the caller to use it. (Browser restart is not enough.)
260        with chrome.Chrome(extra_browser_args=browser_args,
261                           init_network_controller=True) as cr:
262            self.serve_swf_to_browser(cr.browser)
263            # Wait for the last file to be written by component updater.
264            utils.wait_for_value_changed(
265                lambda: (os.path.exists(_COMPONENT_STORE_LATEST)),
266                False,
267                timeout_sec=self._component_download_timeout_secs)
268            if not os.path.exists(_COMPONENT_STORE):
269                raise error.TestFail('Failed: after download no component at '
270                                     '%s' % _COMPONENT_STORE)
271            # This may look silly but we prefer giving the system a bit more
272            # time to write files to disk before subsequent reboot.
273            os.system('sync')
274            time.sleep(10)
275
276    def action_install_old_component(self):
277        """
278        Puts an old/mock manifest and Flash binary into _COMPONENT_STORE.
279        """
280        # TODO(ihf): Implement. Problem is, mock component binaries need to be
281        # signed by Omaha. But if we had this we could test component updating
282        # a component update.
283        pass
284
285    def action_verify_component_flash(self):
286        """
287        Verifies that the next use of Flash is from _COMPONENT_MOUNT.
288        """
289        # Verify there is already a binary in the component store.
290        self.verify_file(_COMPONENT_STORE_LATEST)
291        # Verify that binary was mounted during boot.
292        self.verify_file(os.path.join(_COMPONENT_MOUNT, 'libpepflashplayer.so'))
293        self.verify_file(os.path.join(_COMPONENT_MOUNT, 'manifest.json'))
294        # Pretend we have a really old Flash revision on system to force using
295        # the downloaded component.
296        browser_args = ['--ppapi-flash-version=1.0.0.0']
297        # Verify that Flash runs from _COMPONENT_MOUNT.
298        self.run_flash_test(
299            browser_args=browser_args, load_path=_COMPONENT_MOUNT)
300
301    def action_verify_system_flash(self):
302        """
303        Verifies that next use of Flash is from the _SYSTEM_STORE.
304        """
305        # Verify there is a binary in the system store.
306        self.verify_file(os.path.join(_SYSTEM_STORE, _BINARY))
307        # Enable component updates and pretend we have a really new Flash
308        # version on the system image.
309        browser_args = ['--ppapi-flash-version=9999.0.0.0']
310        # Verify that Flash runs from _SYSTEM_STORE.
311        self.run_flash_test(browser_args=browser_args, load_path=_SYSTEM_STORE)
312
313    def run_flash_test(self, browser_args=None, load_path=None):
314        """
315        Verifies that directing the browser to an swf file results in a running
316        Pepper Flash process which does not immediately crash.
317
318        @param browser_args: additional browser args.
319        @param load_path: flash load path.
320        """
321        if not browser_args:
322            browser_args = []
323        # This is Flash. Disable html5 by default feature.
324        browser_args += ['--disable-features=PreferHtmlOverPlugins']
325        # As this is an end to end test with nontrivial setup we can expect a
326        # certain amount of flakes which are *unrelated* to running Flash. We
327        # try to hide these unrelated flakes by selective retry.
328        for _ in range(0, self._retries):
329            logging.info(browser_args)
330            with chrome.Chrome(extra_browser_args=browser_args,
331                               init_network_controller=True) as cr:
332                if self.serve_swf_to_browser(cr.browser):
333                    self.verify_flash_process(load_path)
334                    return
335        raise error.TestFail(
336            'Error: Unable to test Flash due to setup problems.')
337
338    def run_once(self, CU_action=_CU_ACTION_SANITY):
339        """
340        Main entry point for desktopui_FlashSanityCheck.
341
342        Performs an action as specified by control file or
343        by the component_UpdateFlash server test. (The current need to reboot
344        after switching to/from component binary makes this test a server test.)
345
346        @param CU_action: component updater action to verify (typically called
347                          from server test).
348        """
349        logging.info('+++++ desktopui_FlashSanityCheck +++++')
350        logging.info('Performing %s', CU_action)
351        if CU_action == _CU_ACTION_DELETE:
352            self.action_delete_component()
353        elif CU_action == _CU_ACTION_DOWNLOAD:
354            self.action_download_omaha_component()
355        elif CU_action == _CU_ACTION_INSTALL_OLD:
356            self.action_install_old_component()
357        elif CU_action == _CU_ACTION_SANITY:
358            self.run_flash_test()
359        elif CU_action == _CU_ACTION_VERIFY_COMPONENT:
360            self.action_verify_component_flash()
361        elif CU_action == _CU_ACTION_VERIFY_SYSTEM:
362            self.action_verify_system_flash()
363        else:
364            raise error.TestError('Error: unknown action %s', CU_action)
365