lib/calabash/android/device.rb in calabash-2.0.0.pre11 vs lib/calabash/android/device.rb in calabash-2.0.0.prelegacy

- old
+ new

@@ -3,14 +3,10 @@ module Calabash module Android # A representation of a Calabash Android device. # @!visibility private class Device < ::Calabash::Device - require 'calabash/android/device/helper_application' - - include Calabash::Android::Device::HelperApplication - attr_reader :adb def initialize(identifier, server) super @adb = ADB.new(identifier) @@ -68,41 +64,41 @@ end end # @!visibility private def installed_packages - installed_apps.map{|e| e[:package]} + adb.shell('pm list packages').lines.map do |line| + line.sub('package:', '').chomp + end end # @!visibility private def installed_apps - @installed_apps_cache ||= lambda do - adb.shell('pm list packages -f').lines.map do |line| - # line will be package:<path>=<package> - # e.g. "package:/system/app/GoogleEars.apk=com.google.android.ears" - info = line.sub("package:", "") + adb.shell('pm list packages -f').lines.map do |line| + # line will be package:<path>=<package> + # e.g. "package:/system/app/GoogleEars.apk=com.google.android.ears" + info = line.sub("package:", "") - app_path, app_id = info.split('=').map(&:chomp) + app_path, app_id = info.split('=').map(&:chomp) - {package: app_id, path: app_path} - end - end.call + {package: app_id, path: app_path} + end end # @!visibility private def test_server_responding? begin - http_client.post(HTTP::Request.new('ping'), retries: 1).body == 'pong' + http_client.get(HTTP::Request.new('ping'), retries: 1).body == 'pong' rescue HTTP::Error => _ false end end # @!visibility private def test_server_ready? begin - http_client.post(HTTP::Request.new('ready')).body == 'true' + http_client.get(HTTP::Request.new('ready')).body == 'true' rescue HTTP::Error => _ false end end @@ -168,13 +164,13 @@ parameters = make_map_parameters(query, method_name, *method_args) request = HTTP::Request.new('map', params_for_request(parameters)) http_result = if method_name == :flash - http_client.post(request, timeout: 30) + http_client.get(request, timeout: 30) else - http_client.post(request) + http_client.get(request) end result = JSON.parse(http_result.body) if result['outcome'] != 'SUCCESS' @@ -187,13 +183,13 @@ # @!visibility private def perform_action(action, *arguments) @logger.log "Action: #{action} - Arguments: #{arguments.join(', ')}" parameters = {command: action, arguments: arguments} - request = HTTP::Request.new('', params_for_request(parameters)) + request = HTTP::Request.new('/', params_for_request(parameters)) - result = JSON.parse(http_client.post(request).body) + result = JSON.parse(http_client.get(request).body) unless result['success'] message = result['message'] || result['bonusInformation'] if message.is_a?(Array) @@ -214,21 +210,10 @@ def enter_text(text) perform_action('keyboard_enter_text', text) end # @!visibility private - def md5_checksum_for_app_package(package) - app = installed_apps.find{|app| app[:package] == package} - - unless app - raise "Application with package '#{app}' not installed" - end - - md5_checksum(app[:path]) - end - - # @!visibility private def md5_checksum(file_path) result = adb.shell("#{md5_binary} '#{file_path}'") captures = result.match(/(\w+)/).captures if captures.length != 1 @@ -242,11 +227,11 @@ def backdoor(method, *arguments) parameters = {method_name: method, arguments: arguments} json = parameters.to_json request = HTTP::Request.new('/backdoor', json: json) - body = http_client.post(request).body + body = http_client.get(request).body result = JSON.parse(body) if result['outcome'] != 'SUCCESS' details = if result['detail'].nil? || result['detail'].empty? '' @@ -379,11 +364,11 @@ } json = parameters.to_json request = HTTP::Request.new('/map', json: json) - body = http_client.post(request).body + body = http_client.get(request).body result = JSON.parse(body) if result['outcome'] != 'SUCCESS' if result['results'] parsed_result = result['results'].map {|r| "\"#{r}\","}.join("\n") @@ -422,79 +407,29 @@ def adb_file_exists?(file) cmd = "ls #{file}" adb.shell(cmd, no_exit_code_check: true).chomp == file end - def file_exists?(file) - ensure_helper_application_started - - request = HTTP::Request.new('file-exists', params_for_request(fileName: file)) - response = helper_application_http_client.post(request) - - if response.status != 200 - result = JSON.parse(response.body) - raise "Failed to find out if file exists #{result['reason']}" - end - - case response.body - when 'true' - return true - when 'false' - return false - else - raise "Invalid resposne '#{response.body}'" - end - end - - def read_file(file) - ensure_helper_application_started - - request = HTTP::Request.new('read-file', params_for_request(fileName: file)) - response = helper_application_http_client.post(request) - - if response.status != 200 - result = JSON.parse(response.body) - raise "Failed to read file. Reason: #{result['reason']}" - end - - response.body - end - def calabash_server_failure_exists?(application) - file_exists?(calabash_server_failure_file_path(application)) + adb_file_exists?(calabash_server_failure_file_path(application)) end def calabash_server_finished_exists?(application) - file_exists?(calabash_server_finished_file_path(application)) + adb_file_exists?(calabash_server_finished_file_path(application)) end def read_calabash_sever_failure(application) - read_file(calabash_server_failure_file_path(application)) + adb.shell("cat #{calabash_server_failure_file_path(application)}") end def read_calabash_sever_finished(application) - read_file(calabash_server_finished_file_path(application)) + adb.shell("cat #{calabash_server_finished_file_path(application)}") end def clear_calabash_server_report(application) if installed_packages.include?(application.test_server.identifier) - parameters = - { - intent: { - flags: 0x10000000, - component: { - packageName: application.test_server.identifier, - className: 'sh.calaba.instrumentationbackend.StatusReporterActivity', - }, - extras: { - method: 'clear' - } - } - } - - ensure_helper_application_started - start_activity(parameters) + adb.shell("am start -e method clear -n #{application.test_server.identifier}/sh.calaba.instrumentationbackend.StatusReporterActivity") end end def _start_app(application, options={}) env_options = {} @@ -502,17 +437,16 @@ options.fetch(:extras, {}).each do |k, v| env_options[k] = v end env_options[:test_server_port] = server.test_server_port + env_options[:target_package] = application.identifier env_options[:class] = options.fetch(:class, 'sh.calaba.instrumentationbackend.InstrumentationBackend') if options[:activity] env_options[:main_activity] = options[:activity] - else - env_options[:main_activity] = 'null' end if application.test_server.nil? raise 'Invalid application. No test-server set.' end @@ -523,24 +457,29 @@ unless app_installed?(application.test_server.identifier) raise "The test-server '#{application.test_server.identifier}' is not installed" end - installed_app_md5_checksum = md5_checksum_for_app_package(application.identifier) + installed_app = installed_apps.find{|app| app[:package] == application.identifier} + installed_app_md5_checksum = md5_checksum(installed_app[:path]) if application.md5_checksum != installed_app_md5_checksum raise "The specified app is not the same as the installed app (#{application.md5_checksum} != #{installed_app_md5_checksum})." end - installed_test_server_md5_checksum = md5_checksum_for_app_package(application.test_server.identifier) + installed_test_server = installed_apps.find{|app| app[:package] == application.test_server.identifier} + installed_test_server_md5_checksum = md5_checksum(installed_test_server[:path]) if application.test_server.md5_checksum != installed_test_server_md5_checksum raise "The specified test-server is not the same as the installed test-server (#{application.test_server.md5_checksum} != #{installed_test_server_md5_checksum})." end ensure_screen_on + # Clear any old error reports + clear_calabash_server_report(application) + # We have to forward the port ourselves, as an old test-server could be # running on the old port. If the retriable client was able to # determine if the port had been forwarded, we would not need this. port_forward(server.endpoint.port, server.test_server_port) @@ -556,29 +495,26 @@ rescue => _ raise 'Failed to stop old running test-server' end end - # Clear any old error reports - clear_calabash_server_report(application) - extras = '' env_options.each_pair do |key, val| - extras = "#{extras} -e #{key.to_s} #{val.to_s}" + extras = "#{extras} -e \"#{key.to_s}\" \"#{val.to_s}\"" end begin - adb_instrument(application, + instrument(application, 'sh.calaba.instrumentationbackend.CalabashInstrumentationTestRunner', extras) rescue ADB::ADBCallError => e raise "Failed to start the application: '#{e.stderr.lines.first.chomp}'" end begin - Calabash::Retry.retry(retries: 30, interval: 1, timeout: 30, on_errors: [RetryError]) do + Retriable.retriable(tries: 30, interval: 1, timeout: 30, on: RetryError) do unless test_server_responding? # Read any message the test-server might have if calabash_server_failure_exists?(application) failure_message = read_calabash_sever_failure(application) @@ -593,46 +529,30 @@ @logger.log('For information, see the adb logcat', :error) raise 'Could not contact test-server' end begin - Calabash::Retry.retry(retries: 10, interval: 1, timeout: 10, on_errors: [RetryError]) do + Retriable.retriable(tries: 10, interval: 1, timeout: 10) do unless test_server_ready? raise RetryError end end rescue RetryError => _ @logger.log('Test-server was never ready', :error) @logger.log('For information, see the adb logcat', :error) raise 'Test-server was never ready' end - start_application(options[:intent]) - # Return true to avoid cluttering the console true end # @!visibility private - def start_application(intent) - request = HTTP::Request.new('start-application', params_for_request(intent: intent)) - body = http_client.post(request).body - - result = JSON.parse(body) - - if result['outcome'] != 'SUCCESS' - raise "Failed to start application. Reason: #{result['reason']}" - end - - result['result'] - end - - # @!visibility private def _stop_app - Calabash::Retry.retry(retries: 5, interval: 1) do + Retriable.retriable(tries: 5, interval: 1) do begin - http_client.post(HTTP::Request.new('kill'), retries: 1, interval: 0) + http_client.get(HTTP::Request.new('kill'), retries: 1, interval: 0) rescue HTTP::Error => _ # It's fine that we can't contact the test-server, as it might already have been shut down if test_server_responding? raise 'Could not kill the test-server' end @@ -672,11 +592,11 @@ raise 'Could not turn screen on' end # @!visibility private - def adb_instrument(application, test_server_activity, extras = '') + def instrument(application, test_server_activity, extras = '') unless application.is_a?(Android::Application) raise ArgumentError, "Invalid application type '#{application.class}'" end if application.test_server.nil? @@ -700,15 +620,15 @@ # @!visibility private class EnsureInstrumentActionError < RuntimeError; end # @!visibility private - def ensure_adb_instrument_action(application, test_server_activity, extras = '') + def ensure_instrument_action(application, test_server_activity, extras = '') clear_calabash_server_report(application) begin - adb_instrument(application, test_server_activity, extras) + instrument(application, test_server_activity, extras) rescue ADB::ADBCallError => e raise EnsureInstrumentActionError, e end begin @@ -733,57 +653,13 @@ raise EnsureInstrumentActionError, 'Timed out waiting for status' end end # @!visibility private - def ensure_instrument_action(application, parameters) - ensure_helper_application_started - clear_calabash_server_report(application) - - begin - instrument(parameters) - rescue ADB::ADBCallError => e - raise EnsureInstrumentActionError, e - end - - begin - Timeout.timeout(10) do - loop do - if calabash_server_failure_exists?(application) - failure_message = read_calabash_sever_failure(application) - - raise EnsureInstrumentActionError, parse_failure_message(failure_message) - end - - if calabash_server_finished_exists?(application) - output = read_calabash_sever_finished(application) - - if output == 'SUCCESSFUL' - break - end - end - end - end - rescue Timeout::Error => _ - raise EnsureInstrumentActionError, 'Timed out waiting for status' - end - end - - # @!visibility private def ts_clear_app_data(application) - parameters = - { - intent: { - component: { - className: 'sh.calaba.instrumentationbackend.ClearAppData2', - packageName: application.test_server.identifier - } - } - } - begin - ensure_instrument_action(application, parameters) + ensure_instrument_action(application, 'sh.calaba.instrumentationbackend.ClearAppData2') rescue EnsureInstrumentActionError => e raise "Failed to clear app data: #{e.message}" end end @@ -864,14 +740,15 @@ @logger.log "Ensuring #{application.path} is installed" if installed_packages.include?(application.identifier) @logger.log 'Application is already installed. Ensuring right checksum' - installed_app_md5_checksum = md5_checksum_for_app_package(application.identifier) + installed_app = installed_apps.find{|app| app[:package] == application.identifier} + installed_app_md5_checksum = md5_checksum(installed_app[:path]) if application.md5_checksum != installed_app_md5_checksum - @logger.log("The md5 checksum has changed for '#{application.identifier}' (#{application.md5_checksum} != #{installed_app_md5_checksum}).", :info) + @logger.log("The md5 checksum has changed (#{application.md5_checksum} != #{installed_app_md5_checksum}).", :info) _install_app(application) end else adb_install_app(application) end @@ -1025,11 +902,10 @@ timeout: options[:timeout])) end # @!visibility private def adb_uninstall_app(package) - @installed_apps_cache = nil @logger.log "Uninstalling #{package}" result = adb.command('uninstall', package, timeout: 60).lines.last if result.downcase.chomp != 'success' raise "Could not uninstall app '#{package}': #{result.chomp}" @@ -1040,11 +916,10 @@ end end # @!visibility private def adb_install_app(application) - @installed_apps_cache = nil # Because of a bug in the latest version of ADB # https://github.com/android/platform_system_core/blob/0f91887868e51de67bdf9aedc97fbcb044dc1969/adb/commandline.cpp#L1466 # ADB now uses rm -f ... to remove the temporary application on the # device, but not all devices (below a certain OS) supports this flag. # The user will be unable to install the app and instead receive: @@ -1111,13 +986,13 @@ end end # @!visibility private def execute_gesture(multi_touch_gesture) - request = HTTP::Request.new('gesture', params_for_request(multi_touch_gesture)) + request = HTTP::Request.new('gesture', json: multi_touch_gesture.to_json) - body = http_client.post(request, timeout: multi_touch_gesture.timeout + 10).body + body = http_client.get(request, timeout: multi_touch_gesture.timeout + 10).body result = JSON.parse(body) if result['outcome'] != 'SUCCESS' raise "Failed to perform gesture. #{result['reason']}" end @@ -1137,40 +1012,10 @@ else results end end - class InstrumentationError < RuntimeError; end - - def instrument(parameters, http_client = helper_application_http_client) - request = HTTP::Request.new('instrument', params_for_request(parameters)) - - body = http_client.post(request).body - result = JSON.parse(body) - - if result['outcome'] != 'SUCCESS' - raise InstrumentationError, "Failed to instrument. Reason: #{result['reason']}" - end - - true - end - - class StartActivityError < RuntimeError; end - - def start_activity(parameters, http_client = helper_application_http_client) - request = HTTP::Request.new('start-activity', params_for_request(parameters)) - - body = http_client.post(request).body - result = JSON.parse(body) - - if result['outcome'] != 'SUCCESS' - raise StartActivityError, "Failed to start activity. Reason: #{result['reason']}" - end - - true - end - # @!visibility private def params_for_request(parameters) {json: parameters.to_json} end @@ -1179,12 +1024,9 @@ if @md5_binary @md5_binary else if adb.shell('md5', no_exit_code_check: true).chomp == 'md5 file ...' @md5_binary = 'md5' - elsif (r = adb.shell('md5sum _cal_no_such_file', no_exit_code_check: true)) && r.chomp.start_with?('md5sum:') && - !r.include?('permission denied') - @md5_binary = 'md5sum' else # The device does not have 'md5' calmd5 = Calabash::Android.binary_location('calmd5', info[:cpu_architecture], can_handle_pie_binaries?) adb.command('push', calmd5, '/data/local/tmp/calmd5') @md5_binary = '/data/local/tmp/calmd5'