• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2015 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 glob
6import logging
7import os
8import pipes
9import re
10import shutil
11import subprocess
12import sys
13import tempfile
14
15from autotest_lib.client.bin import test, utils
16from autotest_lib.client.common_lib import error
17from autotest_lib.client.common_lib.cros import chrome, arc_common
18
19_ADB_KEYS_PATH = '/tmp/adb_keys'
20_ADB_VENDOR_KEYS = 'ADB_VENDOR_KEYS'
21_ANDROID_CONTAINER_PID_PATH = '/var/run/containers/android_*/container.pid'
22_SCREENSHOT_DIR_PATH = '/var/log/arc-screenshots'
23_SCREENSHOT_BASENAME = 'arc-screenshot'
24_MAX_SCREENSHOT_NUM = 10
25_SDCARD_PID_PATH = '/var/run/arc/sdcard.pid'
26_ANDROID_ADB_KEYS_PATH = '/data/misc/adb/adb_keys'
27_PROCESS_CHECK_INTERVAL_SECONDS = 1
28_WAIT_FOR_ADB_READY = 60
29_WAIT_FOR_ANDROID_PROCESS_SECONDS = 60
30_WAIT_FOR_DATA_MOUNTED_SECONDS = 60
31_VAR_LOGCAT_PATH = '/var/log/logcat'
32
33
34def setup_adb_host():
35    """Setup ADB host keys.
36
37    This sets up the files and environment variables that wait_for_adb_ready()
38    needs"""
39    if _ADB_VENDOR_KEYS in os.environ:
40        return
41    if not os.path.exists(_ADB_KEYS_PATH):
42        os.mkdir(_ADB_KEYS_PATH)
43    # adb expects $HOME to be writable.
44    os.environ['HOME'] = _ADB_KEYS_PATH
45
46    # Generate and save keys for adb if needed
47    key_path = os.path.join(_ADB_KEYS_PATH, 'test_key')
48    if not os.path.exists(key_path):
49        utils.system('adb keygen ' + pipes.quote(key_path))
50    os.environ[_ADB_VENDOR_KEYS] = key_path
51
52
53def adb_connect():
54    """Attempt to connect ADB to the Android container.
55
56    Returns true if successful. Do not call this function directly. Call
57    wait_for_adb_ready() instead."""
58    if not is_android_booted():
59        return False
60    if utils.system('adb connect localhost:22', ignore_status=True) != 0:
61        return False
62    return is_adb_connected()
63
64
65def is_adb_connected():
66    """Return true if adb is connected to the container."""
67    output = utils.system_output('adb get-state', ignore_status=True)
68    logging.debug('adb get-state: %s', output)
69    return output.strip() == 'device'
70
71
72def is_partial_boot_enabled():
73    """Return true if partial boot is enabled.
74
75    When partial boot is enabled, Android is started at login screen without
76    any persistent state (e.g. /data is not mounted).
77    """
78    return _android_shell('getprop ro.boot.partial_boot') == '1'
79
80
81def _is_android_data_mounted():
82    """Return true if Android's /data is mounted with partial boot enabled."""
83    return _android_shell('getprop ro.data_mounted') == '1'
84
85
86def _wait_for_data_mounted(timeout=_WAIT_FOR_DATA_MOUNTED_SECONDS):
87    utils.poll_for_condition(
88            condition=_is_android_data_mounted,
89            desc='Wait for /data mounted',
90            timeout=timeout,
91            sleep_interval=_PROCESS_CHECK_INTERVAL_SECONDS)
92
93
94def wait_for_adb_ready(timeout=_WAIT_FOR_ADB_READY):
95    """Wait for the ADB client to connect to the ARC container.
96
97    @param timeout: Timeout in seconds.
98    """
99    # When partial boot is enabled, although adbd is started at login screen,
100    # we still need /data to be mounted to set up key-based authentication.
101    # /data should be mounted once the user has logged in.
102    if is_partial_boot_enabled():
103        _wait_for_data_mounted()
104
105    setup_adb_host()
106    if is_adb_connected():
107      return
108
109    # Push keys for adb.
110    pubkey_path = os.environ[_ADB_VENDOR_KEYS] + '.pub'
111    with open(pubkey_path, 'r') as f:
112        _write_android_file(_ANDROID_ADB_KEYS_PATH, f.read())
113    _android_shell('restorecon ' + pipes.quote(_ANDROID_ADB_KEYS_PATH))
114
115    # This starts adbd.
116    _android_shell('setprop sys.usb.config mtp,adb')
117
118    # Kill existing adb server to ensure that a full reconnect is performed.
119    utils.system('adb kill-server', ignore_status=True)
120
121    exception = error.TestFail('adb is not ready in %d seconds.' % timeout)
122    utils.poll_for_condition(adb_connect,
123                             exception,
124                             timeout)
125
126
127def grant_permissions(package, permissions):
128    """Grants permissions to a package.
129
130    @param package: Package name.
131    @param permissions: A list of permissions.
132
133    """
134    for permission in permissions:
135        adb_shell('pm grant %s android.permission.%s' % (
136                  pipes.quote(package), pipes.quote(permission)))
137
138
139def adb_cmd(cmd, **kwargs):
140    """Executed cmd using adb. Must wait for adb ready.
141
142    @param cmd: Command to run.
143    """
144    wait_for_adb_ready()
145    return utils.system_output('adb %s' % cmd, **kwargs)
146
147
148def adb_shell(cmd, **kwargs):
149    """Executed shell command with adb.
150
151    @param cmd: Command to run.
152    """
153    output = adb_cmd('shell %s' % pipes.quote(cmd), **kwargs)
154    # Some android commands include a trailing CRLF in their output.
155    if kwargs.pop('strip_trailing_whitespace', True):
156      output = output.rstrip()
157    return output
158
159
160def adb_install(apk):
161    """Install an apk into container. You must connect first.
162
163    @param apk: Package to install.
164    """
165    return adb_cmd('install -r %s' % apk)
166
167
168def adb_uninstall(apk):
169    """Remove an apk from container. You must connect first.
170
171    @param apk: Package to uninstall.
172    """
173    return adb_cmd('uninstall %s' % apk)
174
175
176def adb_reboot():
177    """Reboots the container. You must connect first."""
178    adb_root()
179    return adb_cmd('reboot', ignore_status=True)
180
181
182def adb_root():
183    """Restart adbd with root permission."""
184    adb_cmd('root')
185
186
187def get_container_root():
188    """Returns path to Android container root directory.
189
190    Raises:
191      TestError if no container root directory is found, or
192      more than one container root directories are found.
193    """
194    # Find the PID file rather than the android_XXXXXX/ directory to ignore
195    # stale and empty android_XXXXXX/ directories when they exist.
196    # TODO(yusukes): Investigate why libcontainer sometimes fails to remove
197    # the directory. See b/63376749 for more details.
198    arc_container_pid_files = glob.glob(_ANDROID_CONTAINER_PID_PATH)
199
200    if len(arc_container_pid_files) == 0:
201        raise error.TestError('Android container not available')
202
203    if len(arc_container_pid_files) > 1:
204        raise error.TestError('Multiple Android containers found: %r. '
205                              'Reboot your DUT to recover.' % (
206                                  arc_container_pid_files))
207
208    return os.path.dirname(arc_container_pid_files[0])
209
210
211def get_job_pid(job_name):
212    """Returns the PID of an upstart job."""
213    status = utils.system_output('status %s' % job_name)
214    match = re.match(r'^%s start/running, process (\d+)$' % job_name,
215                     status)
216    if not match:
217        raise error.TestError('Unexpected status: "%s"' % status)
218    return match.group(1)
219
220
221def get_container_pid():
222    """Returns the PID of the container."""
223    container_root = get_container_root()
224    pid_path = os.path.join(container_root, 'container.pid')
225    return utils.read_one_line(pid_path)
226
227
228def get_sdcard_pid():
229    """Returns the PID of the sdcard container."""
230    return utils.read_one_line(_SDCARD_PID_PATH)
231
232
233def get_removable_media_pid():
234    """Returns the PID of the arc-removable-media FUSE daemon."""
235    job_pid = get_job_pid('arc-removable-media')
236    # |job_pid| is the minijail process, obtain the PID of the process running
237    # inside the mount namespace.
238    # FUSE process is the only process running as chronos in the process group.
239    return utils.system_output('pgrep -u chronos -g %s' % job_pid)
240
241
242def get_obb_mounter_pid():
243    """Returns the PID of the OBB mounter."""
244    return utils.system_output('pgrep -f -u root ^/usr/bin/arc-obb-mounter')
245
246
247def is_android_booted():
248    """Return whether Android has completed booting."""
249    # We used to check sys.boot_completed system property to detect Android has
250    # booted in Android M, but in Android N it is set long before BOOT_COMPLETED
251    # intent is broadcast. So we read event logs instead.
252    log = _android_shell(
253        'logcat -d -b events *:S arc_system_event', ignore_status=True)
254    return 'ArcAppLauncher:started' in log
255
256
257def is_android_process_running(process_name):
258    """Return whether Android has completed booting.
259
260    @param process_name: Process name.
261    """
262    output = adb_shell('ps | grep %s' % pipes.quote(' %s$' % process_name))
263    return bool(output)
264
265
266def check_android_file_exists(filename):
267    """Checks whether the given file exists in the Android filesystem
268
269    @param filename: File to check.
270    """
271    return adb_shell('test -e {} && echo FileExists'.format(
272            pipes.quote(filename))).find("FileExists") >= 0
273
274
275def read_android_file(filename):
276    """Reads a file in Android filesystem.
277
278    @param filename: File to read.
279    """
280    with tempfile.NamedTemporaryFile() as tmpfile:
281        adb_cmd('pull %s %s' % (pipes.quote(filename),
282                                pipes.quote(tmpfile.name)))
283        with open(tmpfile.name) as f:
284            return f.read()
285
286    return None
287
288
289def write_android_file(filename, data):
290    """Writes to a file in Android filesystem.
291
292    @param filename: File to write.
293    @param data: Data to write.
294    """
295    with tempfile.NamedTemporaryFile() as tmpfile:
296        tmpfile.write(data)
297        tmpfile.flush()
298
299        adb_cmd('push %s %s' % (pipes.quote(tmpfile.name),
300                                pipes.quote(filename)))
301
302
303def _write_android_file(filename, data):
304    """Writes to a file in Android filesystem.
305
306    This is an internal function used to bootstrap adb.
307    Tests should use write_android_file instead.
308    """
309    android_cmd = 'cat > %s' % pipes.quote(filename)
310    cros_cmd = 'android-sh -c %s' % pipes.quote(android_cmd)
311    utils.run(cros_cmd, stdin=data)
312
313
314def remove_android_file(filename):
315    """Removes a file in Android filesystem.
316
317    @param filename: File to remove.
318    """
319    adb_shell('rm -f %s' % pipes.quote(filename))
320
321
322def wait_for_android_boot(timeout=None):
323    """Sleep until Android has completed booting or timeout occurs.
324
325    @param timeout: Timeout in seconds.
326    """
327    arc_common.wait_for_android_boot(timeout)
328
329
330def wait_for_android_process(process_name,
331                             timeout=_WAIT_FOR_ANDROID_PROCESS_SECONDS):
332    """Sleep until an Android process is running or timeout occurs.
333
334    @param process_name: Process name.
335    @param timeout: Timeout in seconds.
336    """
337    condition = lambda: is_android_process_running(process_name)
338    utils.poll_for_condition(condition=condition,
339                             desc='%s is running' % process_name,
340                             timeout=timeout,
341                             sleep_interval=_PROCESS_CHECK_INTERVAL_SECONDS)
342
343
344def _android_shell(cmd, **kwargs):
345    """Execute cmd instead the Android container.
346
347    This function is strictly for internal use only, as commands do not run in a
348    fully consistent Android environment. Prefer adb_shell instead.
349    """
350    return utils.system_output('android-sh -c {}'.format(pipes.quote(cmd)),
351                               **kwargs)
352
353
354def is_android_container_alive():
355    """Check if android container is alive."""
356    try:
357        container_pid = get_container_pid()
358    except Exception, e:
359        logging.error('is_android_container_alive failed: %r', e)
360        return False
361    return utils.pid_is_alive(int(container_pid))
362
363
364def is_package_installed(package):
365    """Check if a package is installed. adb must be ready.
366
367    @param package: Package in request.
368    """
369    packages = adb_shell('pm list packages').splitlines()
370    package_entry = 'package:{}'.format(package)
371    return package_entry in packages
372
373
374def _before_iteration_hook(obj):
375    """Executed by parent class before every iteration.
376
377    This function resets the run_once_finished flag before every iteration
378    so we can detect failure on every single iteration.
379
380    Args:
381        obj: the test itself
382    """
383    obj.run_once_finished = False
384
385
386def _after_iteration_hook(obj):
387    """Executed by parent class after every iteration.
388
389    The parent class will handle exceptions and failures in the run and will
390    always call this hook afterwards. Take a screenshot if the run has not
391    been marked as finished (i.e. there was a failure/exception).
392
393    Args:
394        obj: the test itself
395    """
396    if not obj.run_once_finished:
397        if not os.path.exists(_SCREENSHOT_DIR_PATH):
398            os.mkdir(_SCREENSHOT_DIR_PATH, 0755)
399        obj.num_screenshots += 1
400        if obj.num_screenshots <= _MAX_SCREENSHOT_NUM:
401            logging.warning('Iteration %d failed, taking a screenshot.',
402                            obj.iteration)
403            from cros.graphics.gbm import crtcScreenshot
404            try:
405                image = crtcScreenshot()
406                image.save('{}/{}_iter{}.png'.format(_SCREENSHOT_DIR_PATH,
407                                                     _SCREENSHOT_BASENAME,
408                                                     obj.iteration))
409            except Exception:
410                e = sys.exc_info()[0]
411                logging.warning('Unable to capture screenshot. %s' % e)
412        else:
413            logging.warning('Too many failures, no screenshot taken')
414
415
416def send_keycode(keycode):
417    """Sends the given keycode to the container
418
419    @param keycode: keycode to send.
420    """
421    adb_shell('input keyevent {}'.format(keycode))
422
423
424def get_android_sdk_version():
425    """Returns the Android SDK version.
426
427    This function can be called before Android container boots.
428    """
429    with open('/etc/lsb-release') as f:
430        values = dict(line.split('=', 1) for line in f.read().splitlines())
431    try:
432        return int(values['CHROMEOS_ARC_ANDROID_SDK_VERSION'])
433    except (KeyError, ValueError):
434        raise error.TestError('Could not determine Android SDK version')
435
436
437class ArcTest(test.test):
438    """ Base class of ARC Test.
439
440    This class could be used as super class of an ARC test for saving
441    redundant codes for container bringup, autotest-dep package(s) including
442    uiautomator setup if required, and apks install/remove during
443    arc_setup/arc_teardown, respectively. By default arc_setup() is called in
444    initialize() after Android have been brought up. It could also be overridden
445    to perform non-default tasks. For example, a simple ArcHelloWorldTest can be
446    just implemented with print 'HelloWorld' in its run_once() and no other
447    functions are required. We could expect ArcHelloWorldTest would bring up
448    browser and  wait for container up, then print 'Hello World', and shutdown
449    browser after. As a precaution, if you overwrite initialize(), arc_setup(),
450    or cleanup() function(s) in ARC test, remember to call the corresponding
451    function(s) in this base class as well.
452
453    """
454    version = 1
455    _PKG_UIAUTOMATOR = 'uiautomator'
456    _FULL_PKG_NAME_UIAUTOMATOR = 'com.github.uiautomator'
457
458    def __init__(self, *args, **kwargs):
459        """Initialize flag setting."""
460        super(ArcTest, self).__init__(*args, **kwargs)
461        self.initialized = False
462        # Set the flag run_once_finished to detect if a test is executed
463        # successfully without any exception thrown. Otherwise, generate
464        # a screenshot in /var/log for debugging.
465        self.run_once_finished = False
466        self.logcat_proc = None
467        self.dep_package = None
468        self.apks = None
469        self.full_pkg_names = []
470        self.uiautomator = False
471        self.email_id = None
472        self.password = None
473        self._chrome = None
474        if os.path.exists(_SCREENSHOT_DIR_PATH):
475            shutil.rmtree(_SCREENSHOT_DIR_PATH)
476        self.register_before_iteration_hook(_before_iteration_hook)
477        self.register_after_iteration_hook(_after_iteration_hook)
478        # Keep track of the number of debug screenshots taken and keep the
479        # total number sane to avoid issues.
480        self.num_screenshots = 0
481
482    def initialize(self, extension_path=None,
483                   arc_mode=arc_common.ARC_MODE_ENABLED, **chrome_kargs):
484        """Log in to a test account."""
485        extension_paths = [extension_path] if extension_path else []
486        self._chrome = chrome.Chrome(extension_paths=extension_paths,
487                                     arc_mode=arc_mode,
488                                     **chrome_kargs)
489        if extension_path:
490            self._extension = self._chrome.get_extension(extension_path)
491        else:
492            self._extension = None
493        # With ARC enabled, Chrome will wait until container to boot up
494        # before returning here, see chrome.py.
495        self.initialized = True
496        try:
497            if is_android_container_alive():
498                self.arc_setup()
499            else:
500                logging.error('Container is alive?')
501        except Exception as err:
502            self.cleanup()
503            raise error.TestFail(err)
504
505    def after_run_once(self):
506        """Executed after run_once() only if there were no errors.
507
508        This function marks the run as finished with a flag. If there was a
509        failure the flag won't be set and the failure can then be detected by
510        testing the run_once_finished flag.
511        """
512        logging.info('After run_once')
513        self.run_once_finished = True
514
515    def cleanup(self):
516        """Log out of Chrome."""
517        if not self.initialized:
518            logging.info('Skipping ARC cleanup: not initialized')
519            return
520        logging.info('Starting ARC cleanup')
521        try:
522            if is_android_container_alive():
523                self.arc_teardown()
524        except Exception as err:
525            raise error.TestFail(err)
526        finally:
527            try:
528                self._stop_logcat()
529            finally:
530                if self._chrome is not None:
531                    self._chrome.close()
532
533    def arc_setup(self, dep_package=None, apks=None, full_pkg_names=None,
534                  uiautomator=False, email_id=None, password=None,
535                  block_outbound=False):
536        """ARC test setup: Setup dependencies and install apks.
537
538        This function disables package verification and enables non-market
539        APK installation. Then, it installs specified APK(s) and uiautomator
540        package and path if required in a test.
541
542        @param dep_package: Package name of autotest_deps APK package.
543        @param apks: Array of APK names to be installed in dep_package.
544        @param full_pkg_names: Array of full package names to be removed
545                               in teardown.
546        @param uiautomator: uiautomator python package is required or not.
547
548        @param email_id: email id to be attached to the android. Only used
549                         when  account_util is set to true.
550        @param password: password related to the email_id.
551        @param block_outbound: block outbound network traffic during a test.
552        """
553        if not self.initialized:
554            logging.info('Skipping ARC setup: not initialized')
555            return
556        logging.info('Starting ARC setup')
557        self.dep_package = dep_package
558        self.apks = apks
559        self.uiautomator = uiautomator
560        self.email_id = email_id
561        self.password = password
562        # Setup dependent packages if required
563        packages = []
564        if dep_package:
565            packages.append(dep_package)
566        if self.uiautomator:
567            packages.append(self._PKG_UIAUTOMATOR)
568        if packages:
569            logging.info('Setting up dependent package(s) %s', packages)
570            self.job.setup_dep(packages)
571
572        # TODO(b/29341443): Run logcat on non ArcTest test cases too.
573        with open(_VAR_LOGCAT_PATH, 'w') as f:
574            self.logcat_proc = subprocess.Popen(
575                ['android-sh', '-c', 'logcat -v threadtime'],
576                stdout=f,
577                stderr=subprocess.STDOUT,
578                close_fds=True)
579
580        wait_for_adb_ready()
581
582        # package_verifier_user_consent == -1 means to reject Google's
583        # verification on the server side through Play Store.  This suppress a
584        # consent dialog from the system.
585        adb_shell('settings put secure package_verifier_user_consent -1')
586        adb_shell('settings put global package_verifier_enable 0')
587        adb_shell('settings put secure install_non_market_apps 1')
588
589        if self.dep_package:
590            apk_path = os.path.join(self.autodir, 'deps', self.dep_package)
591            if self.apks:
592                for apk in self.apks:
593                    logging.info('Installing %s', apk)
594                    adb_install('%s/%s' % (apk_path, apk))
595                # Verify if package(s) are installed correctly
596                if not full_pkg_names:
597                    raise error.TestError('Package names of apks expected')
598                for pkg in full_pkg_names:
599                    logging.info('Check if %s is installed', pkg)
600                    if not is_package_installed(pkg):
601                        raise error.TestError('Package %s not found' % pkg)
602                    # Make sure full_pkg_names contains installed packages only
603                    # so arc_teardown() knows what packages to uninstall.
604                    self.full_pkg_names.append(pkg)
605
606        if self.uiautomator:
607            path = os.path.join(self.autodir, 'deps', self._PKG_UIAUTOMATOR)
608            sys.path.append(path)
609        if block_outbound:
610            self.block_outbound()
611
612    def _stop_logcat(self):
613        """Stop the adb logcat process gracefully."""
614        if not self.logcat_proc:
615            return
616        # Running `adb kill-server` should have killed `adb logcat`
617        # process, but just in case also send termination signal.
618        self.logcat_proc.terminate()
619
620        class TimeoutException(Exception):
621            """Termination timeout timed out."""
622
623        try:
624            utils.poll_for_condition(
625                condition=lambda: self.logcat_proc.poll() is not None,
626                exception=TimeoutException,
627                timeout=10,
628                sleep_interval=0.1,
629                desc='Waiting for adb logcat to terminate')
630        except TimeoutException:
631            logging.info('Killing adb logcat due to timeout')
632            self.logcat_proc.kill()
633            self.logcat_proc.wait()
634
635    def arc_teardown(self):
636        """ARC test teardown.
637
638        This function removes all installed packages in arc_setup stage
639        first. Then, it restores package verification and disables non-market
640        APK installation.
641
642        """
643        if self.full_pkg_names:
644            for pkg in self.full_pkg_names:
645                logging.info('Uninstalling %s', pkg)
646                if not is_package_installed(pkg):
647                    raise error.TestError('Package %s was not installed' % pkg)
648                adb_uninstall(pkg)
649        if self.uiautomator:
650            logging.info('Uninstalling %s', self._FULL_PKG_NAME_UIAUTOMATOR)
651            adb_uninstall(self._FULL_PKG_NAME_UIAUTOMATOR)
652        adb_shell('settings put secure install_non_market_apps 0')
653        adb_shell('settings put global package_verifier_enable 1')
654        adb_shell('settings put secure package_verifier_user_consent 0')
655
656        remove_android_file(_ANDROID_ADB_KEYS_PATH)
657        utils.system_output('adb kill-server')
658
659    def block_outbound(self):
660        """ Blocks the connection from the container to outer network.
661
662            The iptables settings accept only 100.115.92.2 port 5555 (adb) and
663            all local connections, e.g. uiautomator.
664        """
665        logging.info('Blocking outbound connection')
666        _android_shell('iptables -I OUTPUT -j REJECT')
667        _android_shell('iptables -I OUTPUT -p tcp -s 100.115.92.2 --sport 5555 '
668                       '-j ACCEPT')
669        _android_shell('iptables -I OUTPUT -p tcp -d localhost -j ACCEPT')
670
671    def unblock_outbound(self):
672        """ Unblocks the connection from the container to outer network.
673
674            The iptables settings are not permanent which means they reset on
675            each instance invocation. But we can still use this function to
676            unblock the outbound connections during the test if needed.
677        """
678        logging.info('Unblocking outbound connection')
679        _android_shell('iptables -D OUTPUT -p tcp -d localhost -j ACCEPT')
680        _android_shell('iptables -D OUTPUT -p tcp -s 100.115.92.2 --sport 5555 '
681                       '-j ACCEPT')
682        _android_shell('iptables -D OUTPUT -j REJECT')
683