12a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#!/usr/bin/env python
22a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#
33551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)# Copyright 2013 The Chromium Authors. All rights reserved.
42a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
52a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# found in the LICENSE file.
62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
72a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)"""Runs semi-automated update testing on a non-rooted device."""
82a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import logging
92a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import optparse
102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import os
112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import shutil
122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import sys
132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import time
142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)from pylib import android_commands
16a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochfrom pylib.device import device_utils
172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
18a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochdef _SaveAppData(device, package_name, from_apk=None, data_dir=None):
192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def _BackupAppData(data_dir=None):
20a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    device.old_interface.Adb().SendCommand('backup %s' % package_name)
212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    backup_file = os.path.join(os.getcwd(), 'backup.ab')
222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    assert os.path.exists(backup_file), 'Backup failed.'
232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if data_dir:
242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      if not os.path.isdir(data_dir):
252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        os.makedirs(data_dir)
262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      shutil.move(backup_file, data_dir)
272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      backup_file = os.path.join(data_dir, 'backup.ab')
282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    print 'Application data saved to %s' % backup_file
292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if from_apk:
312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    logging.info('Installing %s...', from_apk)
32f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    # TODO(jbudorick) Switch to AdbWrapper.Install on the impl switch.
33a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    output = device.old_interface.Install(from_apk, reinstall=True)
342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if 'Success' not in output:
352a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      raise Exception('Unable to install %s. output: %s' % (from_apk, output))
362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  raw_input('Set the application state. Once ready, press enter and '
382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            'select "Backup my data" on the device.')
392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  _BackupAppData(data_dir)
402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
412a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
42a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochdef _VerifyAppUpdate(device, to_apk, app_data, from_apk=None):
432a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def _RestoreAppData():
442a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    assert os.path.exists(app_data), 'Backup file does not exist!'
45a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    device.old_interface.Adb().SendCommand('restore %s' % app_data)
462a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    # It seems restore command is not synchronous.
472a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    time.sleep(15)
482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if from_apk:
502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    logging.info('Installing %s...', from_apk)
51f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    # TODO(jbudorick) Switch to AdbWrapper.Install on the impl switch.
52a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    output = device.old_interface.Install(from_apk, reinstall=True)
532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if 'Success' not in output:
542a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      raise Exception('Unable to install %s. output: %s' % (from_apk, output))
552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  logging.info('Restoring the application data...')
572a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  raw_input('Press enter and select "Restore my data" on the device.')
582a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  _RestoreAppData()
592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  logging.info('Verifying that %s cannot be installed side-by-side...',
612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)               to_apk)
62f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  # TODO(jbudorick) Switch to AdbWrapper.Install on the impl switch.
63a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  output = device.old_interface.Install(to_apk)
642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if 'INSTALL_FAILED_ALREADY_EXISTS' not in output:
652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if 'Success' in output:
662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      raise Exception('Package name has changed! output: %s' % output)
672a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    else:
682a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      raise Exception(output)
692a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
702a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  logging.info('Verifying that %s can be overinstalled...', to_apk)
71f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)  # TODO(jbudorick) Switch to AdbWrapper.Install on the impl switch.
72a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  output = device.old_interface.Install(to_apk, reinstall=True)
732a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if 'Success' not in output:
742a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    raise Exception('Unable to install %s.\n output: %s' % (to_apk, output))
752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  logging.info('Successfully updated to the new apk. Please verify that the '
762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)               'the application data is preserved.')
772a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)def main():
802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  logger = logging.getLogger()
812a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  logger.setLevel(logging.DEBUG)
822a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  desc = (
832a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      'Performs semi-automated application update verification testing. '
842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      'When given --save, it takes a snapshot of the application data '
852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      'on the device. (A dialog on the device will prompt the user to grant '
862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      'permission to backup the data.) Otherwise, it performs the update '
872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      'testing as follows: '
882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      '1. Installs the |from-apk| (optional). '
892a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      '2. Restores the previously stored snapshot of application data '
902a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      'given by |app-data| '
912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      '(A dialog on the device will prompt the user to grant permission to '
922a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      'restore the data.) '
932a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      '3. Verifies that |to-apk| cannot be installed side-by-side. '
942a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      '4. Verifies that |to-apk| can replace |from-apk|.')
952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  parser = optparse.OptionParser(description=desc)
962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  parser.add_option('--package-name', help='Package name for the application.')
972a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  parser.add_option('--save', action='store_true',
982a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    help=('Save a snapshot of application data. '
992a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                          'This will be saved as backup.db in the '
1002a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                          'current directory if |app-data| directory '
1012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                          'is not specifid.'))
1022a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  parser.add_option('--from-apk',
1032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    help=('APK to update from. This is optional if you already '
1042a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                          'have the app installed.'))
1052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  parser.add_option('--to-apk', help='APK to update to.')
1062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  parser.add_option('--app-data',
1072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                    help=('Path to the application data to be restored or the '
1082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                          'directory where the data should be saved.'))
1092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  (options, args) = parser.parse_args()
1102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if args:
1122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    parser.print_help(sys.stderr)
1132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    parser.error('Unknown arguments: %s.' % args)
1142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
115a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  devices = android_commands.GetAttachedDevices()
116a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  if len(devices) != 1:
1172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    parser.error('Exactly 1 device must be attached.')
118a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch  device = device_utils.DeviceUtils(devices[0])
1192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if options.from_apk:
1212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    assert os.path.isfile(options.from_apk)
1222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  if options.save:
1242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if not options.package_name:
1252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      parser.print_help(sys.stderr)
1262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      parser.error('Missing --package-name.')
127a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    _SaveAppData(device, options.package_name, from_apk=options.from_apk,
1282a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                 data_dir=options.app_data)
1292a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  else:
1302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if not options.to_apk or not options.app_data:
1312a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      parser.print_help(sys.stderr)
1322a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      parser.error('Missing --to-apk or --app-data.')
1332a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    assert os.path.isfile(options.to_apk)
1342a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    assert os.path.isfile(options.app_data)
135a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch    _VerifyAppUpdate(device, options.to_apk, options.app_data,
1362a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                     from_apk=options.from_apk)
1372a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1382a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
1392a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)if __name__ == '__main__':
1402a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  main()
141