lib/luffa/ios/ideviceinstaller.rb in luffa-1.0.4 vs lib/luffa/ios/ideviceinstaller.rb in luffa-1.0.5

- old
+ new

@@ -1,6 +1,7 @@ require 'retriable' +require 'open3' module Luffa class IDeviceInstaller attr_reader :ipa @@ -9,108 +10,150 @@ def initialize(ipa, bundle_id) @ipa = ipa @bundle_id = bundle_id end - def bin_path - @bin_path ||= `which ideviceinstaller`.chomp! - end - def ideviceinstaller_available? path = bin_path path and File.exist? bin_path end - def install(udid, cmd) - case cmd - when :install_internal - ipa - Retriable.retriable do - uninstall udid + def idevice_id_available? + path = idevice_id_bin_path + path and File.exist? path + end + + def install(udid, options={}) + unless options.is_a? Hash + Luffa.log_warn 'API CHANGE: install now takes an options hash as 2nd arg' + Luffa.log_warn "API CHANGE: ignoring '#{options}'; will use defaults" + merged_options = DEFAULT_OPTIONS + else + merged_options = DEFAULT_OPTIONS.merge(options) + end + + uninstall(udid, merged_options) + install_internal(udid, merged_options) + end + + # Can only be called when RunLoop is available. + def physical_devices_for_testing(xcode_tools) + # Xcode 6 + iOS 8 - devices on the same network, whether development or + # not, appear when calling $ xcrun instruments -s devices. For the + # purposes of testing, we will only try to connect to devices that are + # connected via USB. + # + # Also idevice_id, which ideviceinstaller relies on, will sometimes report + # devices 2x which will cause ideviceinstaller to fail. + @physical_devices_for_testing ||= lambda { + devices = xcode_tools.instruments(:devices) + if idevice_id_available? + white_list = `#{idevice_id_bin_path} -l`.strip.split("\n") + devices.select do | device | + white_list.include?(device.udid) && white_list.count(device.udid) == 1 end - Retriable.retriable do - install_internal udid - end - when :uninstall - Retriable.retriable do - uninstall udid - end else - cmds = [:install_internal, :uninstall] - raise ArgumentError, "expected '#{cmd}' to be one of '#{cmds}'" - end + devices + end + }.call end - def bundle_installed?(udid) - cmd = "#{bin_path} --udid #{udid} --list-apps" - Luffa.log_unix_cmd(cmd) if Luffa::Environment.debug? + private - Open3.popen3(cmd) do |_, stdout, stderr, _| - err = stderr.read.strip - Luffa.log_fail(err) if err && err != '' + DEFAULT_OPTIONS = { :timeout => 10.0, :tries => 2 } - out = stdout.read.strip - out.strip.split(/\s/).include? bundle_id - end + def bin_path + @bin_path ||= `which ideviceinstaller`.chomp! end - def install_internal(udid) - return true if bundle_installed? udid + def run_command_with_args(args, options={}) + merged_options = DEFAULT_OPTIONS.merge(options) - cmd = "#{bin_path} --udid #{udid} --install #{ipa}" + cmd = "#{bin_path} #{args.join(' ')}" Luffa.log_unix_cmd(cmd) if Luffa::Environment.debug? - Open3.popen3(cmd) do |_, _, stderr, _| - err = stderr.read.strip - Luffa.log_fail(err) if err && err != '' + exit_status = nil + out = nil + pid = nil + err = nil + + tries = merged_options[:tries] + timeout = merged_options[:timeout] + + on = [Timeout::Error, RuntimeError] + on_retry = Proc.new do |_, try, elapsed_time, next_interval| + # Retriable 2.0 + if elapsed_time && next_interval + Luffa.log_info "LLDB: attempt #{try} failed in '#{elapsed_time}'; will retry in '#{next_interval}'" + else + Luffa.log_info "LLDB: attempt #{try} failed; will retry" + end end - unless bundle_installed? udid - raise "could not install '#{ipa}' on '#{udid}' with '#{bundle_id}'" + Retriable.retriable({tries: tries, on: on, on_retry: on_retry} ) do + Timeout.timeout(timeout, TimeoutError) do + Open3.popen3(bin_path, *args) do |_, stdout, stderr, process_status| + err = stderr.read.strip + if err && err != '' + unless err[/iTunesMetadata.plist/,0] || err[/SC_Info/,0] + Luffa.log_fail(err) + end + end + out = stdout.read.strip + pid = process_status.pid + exit_status = process_status.value.exitstatus + end + end + + if exit_status != 0 + raise RuntimeError, "Could not execute #{args.join(' ')}" + end end - true + { + :out => out, + :err => err, + :pid => pid, + :exit_status => exit_status + } end - def uninstall(udid) - return true unless bundle_installed? udid + def bundle_installed?(udid, options={}) + merged_options = DEFAULT_OPTIONS.merge(options) - cmd = "#{bin_path} -udid #{udid} --uninstall #{bundle_id}" - Luffa.log_unix_cmd(cmd) if Luffa::Environment.debug? + args = ['--udid', udid, '--list-apps'] - Open3.popen3(cmd) do |_, _, stderr, _| - err = stderr.read.strip - Luffa.log_fail(err) if err && err != '' - end + hash = run_command_with_args(args, merged_options) + out = hash[:out] + out.split(/\s/).include? bundle_id + end - if bundle_installed? udid - raise "could not uninstall '#{bundle_id}' on '#{udid}'" + def install_internal(udid, options={}) + merged_options = DEFAULT_OPTIONS.merge(options) + + return true if bundle_installed?(udid, merged_options) + args = ['--udid', udid, '--install', ipa] + run_command_with_args(args, merged_options) + + unless bundle_installed?(udid, merged_options) + raise "could not install '#{ipa}' on '#{udid}' with '#{bundle_id}'" end true end - def idevice_id_bin_path - @idevice_id_bin_path ||= `which idevice_id`.chomp! - end + def uninstall(udid, options={}) + merged_options = DEFAULT_OPTIONS.merge(options) - def idevice_id_available? - path = idevice_id_bin_path - path and File.exist? path - end + return true unless bundle_installed?(udid, merged_options) + args = ['--udid', udid, '--uninstall', bundle_id] + run_command_with_args(args) - def physical_devices_for_testing(xcode_tools) - # Xcode 6 + iOS 8 - devices on the same network, whether development or - # not, appear when calling $ xcrun instruments -s devices. For the - # purposes of testing, we will only try to connect to devices that are - # connected via USB. - @physical_devices_for_testing ||= lambda { - devices = xcode_tools.instruments(:devices) - if idevice_id_available? - white_list = `#{idevice_id_bin_path} -l`.strip.split("\n") - devices.select { | device | white_list.include?(device.udid) } - else - devices - end - }.call + if bundle_installed?(udid, merged_options) + raise "Could not uninstall '#{bundle_id}' on '#{udid}'" + end + true end + def idevice_id_bin_path + @idevice_id_bin_path ||= `which idevice_id`.chomp! + end end end