run_loop 2.1.1 → 2.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/run_loop.rb +27 -54
- data/lib/run_loop/core.rb +101 -12
- data/lib/run_loop/core_simulator.rb +1 -1
- data/lib/run_loop/detect_aut/xcode.rb +3 -1
- data/lib/run_loop/device.rb +113 -114
- data/lib/run_loop/encoding.rb +39 -0
- data/lib/run_loop/environment.rb +26 -1
- data/lib/run_loop/physical_device/ideviceinstaller.rb +221 -0
- data/lib/run_loop/shell.rb +103 -0
- data/lib/run_loop/version.rb +1 -1
- data/lib/run_loop/xcrun.rb +5 -35
- data/lib/run_loop/xcuitest.rb +69 -12
- metadata +20 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 153c09febc934625b8890c29fbead9efd47ccec9
|
4
|
+
data.tar.gz: def40d52e20c4e6eff000a13b14ebedaf989b11c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8e4c05e8d8ffc9287c9f8b9b8c7db070eab11580d6219156f6c25d59938448ef6a617136a54184b6459f7c66f3741352f6234c5d27cdafb3fe4300e2f8403a6
|
7
|
+
data.tar.gz: f2896bcd29d50e34bdc418bd3f255bddb8e0adb172263e3a5a8859662ec4aa7437485754a9e35def02b682e069dd768acb97ed5074859ebf82083c3d3d772694
|
data/lib/run_loop.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'run_loop/regex'
|
2
2
|
require 'run_loop/directory'
|
3
|
+
require "run_loop/encoding"
|
4
|
+
require "run_loop/shell"
|
3
5
|
require 'run_loop/environment'
|
4
6
|
require 'run_loop/logging'
|
5
7
|
require 'run_loop/dot_dir'
|
@@ -66,10 +68,25 @@ module RunLoop
|
|
66
68
|
|
67
69
|
def self.run(options={})
|
68
70
|
|
71
|
+
cloned_options = options.clone
|
72
|
+
|
73
|
+
# We want to use the _exact_ objects that were passed.
|
74
|
+
if options[:xcode]
|
75
|
+
cloned_options[:xcode] = options[:xcode]
|
76
|
+
end
|
77
|
+
|
78
|
+
if options[:simctl]
|
79
|
+
cloned_options[:simctl] = options[:simctl]
|
80
|
+
end
|
81
|
+
|
82
|
+
# Soon to be unsupported.
|
83
|
+
if options[:sim_control]
|
84
|
+
cloned_options[:sim_control] = options[:sim_control]
|
85
|
+
end
|
86
|
+
|
69
87
|
if options[:xcuitest]
|
70
|
-
RunLoop::XCUITest.run(
|
88
|
+
RunLoop::XCUITest.run(cloned_options)
|
71
89
|
else
|
72
|
-
|
73
90
|
if RunLoop::Instruments.new.instruments_app_running?
|
74
91
|
raise %q(The Instruments.app is open.
|
75
92
|
|
@@ -79,58 +96,6 @@ control of your application.
|
|
79
96
|
Please quit the Instruments.app and try again.)
|
80
97
|
|
81
98
|
end
|
82
|
-
|
83
|
-
uia_strategy = options[:uia_strategy]
|
84
|
-
if options[:script]
|
85
|
-
script = validate_script(options[:script])
|
86
|
-
else
|
87
|
-
if uia_strategy
|
88
|
-
script = default_script_for_uia_strategy(uia_strategy)
|
89
|
-
else
|
90
|
-
if options[:calabash_lite]
|
91
|
-
uia_strategy = :host
|
92
|
-
script = Core.script_for_key(:run_loop_host)
|
93
|
-
else
|
94
|
-
uia_strategy = :preferences
|
95
|
-
script = default_script_for_uia_strategy(uia_strategy)
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
# At this point, 'script' has been chosen, but uia_strategy might not
|
100
|
-
unless uia_strategy
|
101
|
-
desired_script = options[:script]
|
102
|
-
if desired_script.is_a?(String) #custom path to script
|
103
|
-
uia_strategy = :host
|
104
|
-
elsif desired_script == :run_loop_host
|
105
|
-
uia_strategy = :host
|
106
|
-
elsif desired_script == :run_loop_fast_uia
|
107
|
-
uia_strategy = :preferences
|
108
|
-
elsif desired_script == :run_loop_shared_element
|
109
|
-
uia_strategy = :shared_element
|
110
|
-
else
|
111
|
-
raise "Inconsistent state: desired script #{desired_script} has not uia_strategy"
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
# At this point script and uia_strategy selected
|
116
|
-
cloned_options = options.clone
|
117
|
-
cloned_options[:script] = script
|
118
|
-
cloned_options[:uia_strategy] = uia_strategy
|
119
|
-
|
120
|
-
# Xcode and SimControl will not be properly cloned and we don't want
|
121
|
-
# them to be; we want to use the exact objects that were passed.
|
122
|
-
if options[:xcode]
|
123
|
-
cloned_options[:xcode] = options[:xcode]
|
124
|
-
end
|
125
|
-
|
126
|
-
if options[:sim_control]
|
127
|
-
cloned_options[:sim_control] = options[:sim_control]
|
128
|
-
end
|
129
|
-
|
130
|
-
if options[:simctl]
|
131
|
-
cloned_options[:simctl] = options[:simctl]
|
132
|
-
end
|
133
|
-
|
134
99
|
Core.run_with_options(cloned_options)
|
135
100
|
end
|
136
101
|
end
|
@@ -212,7 +177,11 @@ Please quit the Instruments.app and try again.)
|
|
212
177
|
FileUtils.cp(pngs, dest) if pngs and pngs.length > 0
|
213
178
|
end
|
214
179
|
|
180
|
+
# @!visibility private
|
181
|
+
#
|
182
|
+
# @deprecated since 2.1.2
|
215
183
|
def self.default_script_for_uia_strategy(uia_strategy)
|
184
|
+
self.deprecated("2.1.2", "Replaced by methods in RunLoop::Core")
|
216
185
|
case uia_strategy
|
217
186
|
when :preferences
|
218
187
|
Core.script_for_key(:run_loop_fast_uia)
|
@@ -225,7 +194,11 @@ Please quit the Instruments.app and try again.)
|
|
225
194
|
end
|
226
195
|
end
|
227
196
|
|
197
|
+
# @!visibility private
|
198
|
+
#
|
199
|
+
# @deprecated since 2.1.2
|
228
200
|
def self.validate_script(script)
|
201
|
+
self.deprecated("2.1.2", "Replaced by methods in RunLoop::Core")
|
229
202
|
if script.is_a?(String)
|
230
203
|
unless File.exist?(script)
|
231
204
|
raise "Unable to find file: #{script}"
|
data/lib/run_loop/core.rb
CHANGED
@@ -27,10 +27,6 @@ module RunLoop
|
|
27
27
|
READ_SCRIPT_PATH = File.join(SCRIPTS_PATH, 'read-cmd.sh')
|
28
28
|
TIMEOUT_SCRIPT_PATH = File.join(SCRIPTS_PATH, 'timeout3')
|
29
29
|
|
30
|
-
def self.scripts_path
|
31
|
-
SCRIPTS_PATH
|
32
|
-
end
|
33
|
-
|
34
30
|
def self.log_run_loop_options(options, xcode)
|
35
31
|
return unless RunLoop::Environment.debug?
|
36
32
|
# Ignore :sim_control b/c it is a ruby object; printing is not useful.
|
@@ -67,10 +63,21 @@ module RunLoop
|
|
67
63
|
xcode = options[:xcode] || RunLoop::Xcode.new
|
68
64
|
instruments = options[:instruments] || RunLoop::Instruments.new
|
69
65
|
|
70
|
-
#
|
66
|
+
# Device under test: DUT
|
71
67
|
device = RunLoop::Device.detect_device(options, xcode, simctl, instruments)
|
68
|
+
|
69
|
+
# App under test: AUT
|
72
70
|
app_details = RunLoop::DetectAUT.detect_app_under_test(options)
|
73
|
-
|
71
|
+
|
72
|
+
# Find the script to pass to instruments and the strategy to communicate
|
73
|
+
# with UIAutomation.
|
74
|
+
script_n_strategy = self.detect_instruments_script_and_strategy(options,
|
75
|
+
device,
|
76
|
+
xcode)
|
77
|
+
instruments_script = script_n_strategy[:script]
|
78
|
+
uia_strategy = script_n_strategy[:strategy]
|
79
|
+
|
80
|
+
# The app life cycle reset options.
|
74
81
|
reset_options = self.detect_reset_options(options)
|
75
82
|
|
76
83
|
instruments.kill_instruments(xcode)
|
@@ -82,14 +89,14 @@ module RunLoop
|
|
82
89
|
FileUtils.mkdir_p(results_dir_trace)
|
83
90
|
|
84
91
|
dependencies = options[:dependencies] || []
|
85
|
-
dependencies << File.join(
|
92
|
+
dependencies << File.join(SCRIPTS_PATH, 'calabash_script_uia.js')
|
86
93
|
dependencies.each do |dep|
|
87
94
|
FileUtils.cp(dep, results_dir)
|
88
95
|
end
|
89
96
|
|
90
97
|
script = File.join(results_dir, '_run_loop.js')
|
91
98
|
|
92
|
-
javascript = UIAScriptTemplate.new(SCRIPTS_PATH,
|
99
|
+
javascript = UIAScriptTemplate.new(SCRIPTS_PATH, instruments_script).result
|
93
100
|
UIAScriptTemplate.sub_path_var!(javascript, results_dir)
|
94
101
|
UIAScriptTemplate.sub_read_script_path_var!(javascript, READ_SCRIPT_PATH)
|
95
102
|
UIAScriptTemplate.sub_timeout_script_path_var!(javascript, TIMEOUT_SCRIPT_PATH)
|
@@ -137,7 +144,8 @@ module RunLoop
|
|
137
144
|
:results_dir => results_dir,
|
138
145
|
:script => script,
|
139
146
|
:log_file => log_file,
|
140
|
-
:args => args
|
147
|
+
:args => args,
|
148
|
+
:uia_strategy => uia_strategy
|
141
149
|
}
|
142
150
|
merged_options = options.merge(discovered_options)
|
143
151
|
|
@@ -314,8 +322,7 @@ Logfile: #{log_file}
|
|
314
322
|
begin
|
315
323
|
FileUtils.rm_f(repl_path)
|
316
324
|
return repl_path if system(%Q[mkfifo "#{repl_path}"])
|
317
|
-
rescue Errno::EINTR =>
|
318
|
-
#retry
|
325
|
+
rescue Errno::EINTR => _
|
319
326
|
sleep(0.1)
|
320
327
|
end
|
321
328
|
end
|
@@ -516,7 +523,7 @@ Logfile: #{log_file}
|
|
516
523
|
def self.detect_connected_device
|
517
524
|
begin
|
518
525
|
Timeout::timeout(1, RunLoop::TimeoutError) do
|
519
|
-
return `#{File.join(
|
526
|
+
return `#{File.join(SCRIPTS_PATH, 'udidetect')}`.chomp
|
520
527
|
end
|
521
528
|
rescue RunLoop::TimeoutError => _
|
522
529
|
`killall udidetect &> /dev/null`
|
@@ -670,6 +677,45 @@ Logfile: #{log_file}
|
|
670
677
|
strategy
|
671
678
|
end
|
672
679
|
|
680
|
+
# @!visibility private
|
681
|
+
#
|
682
|
+
# There is an unnatural relationship between the :script and the
|
683
|
+
# :uia_strategy keys.
|
684
|
+
#
|
685
|
+
# @param [Hash] options The launch options passed to .run_with_options
|
686
|
+
# @param [RunLoop::Device] device The device under test.
|
687
|
+
# @param [RunLoop::Xcode] xcode The active Xcode.
|
688
|
+
#
|
689
|
+
# @return [Hash] with two keys: :script and :uia_strategy
|
690
|
+
def self.detect_instruments_script_and_strategy(options, device, xcode)
|
691
|
+
strategy = options[:uia_strategy]
|
692
|
+
script = options[:script]
|
693
|
+
|
694
|
+
if script
|
695
|
+
script = self.expect_instruments_script(script)
|
696
|
+
if !strategy
|
697
|
+
strategy = :host
|
698
|
+
end
|
699
|
+
else
|
700
|
+
if strategy
|
701
|
+
script = self.instruments_script_for_uia_strategy(strategy)
|
702
|
+
else
|
703
|
+
if options[:calabash_lite]
|
704
|
+
strategy = :host
|
705
|
+
script = self.instruments_script_for_uia_strategy(strategy)
|
706
|
+
else
|
707
|
+
strategy = self.detect_uia_strategy(options, device, xcode)
|
708
|
+
script = self.instruments_script_for_uia_strategy(strategy)
|
709
|
+
end
|
710
|
+
end
|
711
|
+
end
|
712
|
+
|
713
|
+
{
|
714
|
+
:script => script,
|
715
|
+
:strategy => strategy
|
716
|
+
}
|
717
|
+
end
|
718
|
+
|
673
719
|
# @!visibility private
|
674
720
|
#
|
675
721
|
# UIAutomation buffers log output in some very strange ways. RunLoop
|
@@ -760,5 +806,48 @@ Logfile: #{log_file}
|
|
760
806
|
|
761
807
|
RunLoop.log_debug("Simulator instruction set '#{device.instruction_set}' is compatible with '#{lipo.info}'")
|
762
808
|
end
|
809
|
+
|
810
|
+
# @!visibility private
|
811
|
+
def self.expect_instruments_script(script)
|
812
|
+
if script.is_a?(String)
|
813
|
+
unless File.exist?(script)
|
814
|
+
raise %Q[Expected instruments JavaScript file at path:
|
815
|
+
|
816
|
+
#{script}
|
817
|
+
|
818
|
+
Check the :script key in your launch options.]
|
819
|
+
end
|
820
|
+
script
|
821
|
+
elsif script.is_a?(Symbol)
|
822
|
+
path = self.script_for_key(script)
|
823
|
+
if !path
|
824
|
+
raise %Q[Expected :#{script} to be one of:
|
825
|
+
|
826
|
+
#{Core::SCRIPTS.keys.map { |key| ":#{key}" }.join("\n")}
|
827
|
+
|
828
|
+
Check the :script key in your launch options.]
|
829
|
+
end
|
830
|
+
path
|
831
|
+
else
|
832
|
+
raise %Q[Expected '#{script}' to be a Symbol or a String.
|
833
|
+
|
834
|
+
Check the :script key in your launch options.]
|
835
|
+
end
|
836
|
+
end
|
837
|
+
|
838
|
+
# @!visibility private
|
839
|
+
def self.instruments_script_for_uia_strategy(uia_strategy)
|
840
|
+
case uia_strategy
|
841
|
+
when :preferences
|
842
|
+
self.script_for_key(:run_loop_fast_uia)
|
843
|
+
when :host
|
844
|
+
self.script_for_key(:run_loop_host)
|
845
|
+
when :shared_element
|
846
|
+
self.script_for_key(:run_loop_shared_element)
|
847
|
+
else
|
848
|
+
self.script_for_key(:run_loop_basic)
|
849
|
+
end
|
850
|
+
end
|
763
851
|
end
|
764
852
|
end
|
853
|
+
|
@@ -847,7 +847,7 @@ Command had no output
|
|
847
847
|
return true
|
848
848
|
end
|
849
849
|
|
850
|
-
RunLoop.log_debug("The app you are
|
850
|
+
RunLoop.log_debug("The app you are testing is not the same as the app that is installed.")
|
851
851
|
RunLoop.log_debug(" Installed app SHA: #{installed_sha}")
|
852
852
|
RunLoop.log_debug(" App to launch SHA: #{app_sha}")
|
853
853
|
RunLoop.log_debug("Will install #{app}")
|
data/lib/run_loop/device.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
module RunLoop
|
2
2
|
class Device
|
3
3
|
|
4
|
+
require 'securerandom'
|
4
5
|
include RunLoop::Regex
|
5
6
|
|
6
7
|
# Starting in Xcode 7, iOS 9 simulators have a new "booting" state.
|
@@ -133,7 +134,7 @@ removed (1.5.0). It has been replaced by an options hash with two keys:
|
|
133
134
|
#
|
134
135
|
# @param [Hash] options The launch options passed to RunLoop::Core
|
135
136
|
# @param [RunLoop::Xcode] xcode An Xcode instance
|
136
|
-
# @param [RunLoop::Simctl] simctl A
|
137
|
+
# @param [RunLoop::Simctl] simctl A Simctl instance
|
137
138
|
# @param [RunLoop::Instruments] instruments An Instruments instance
|
138
139
|
#
|
139
140
|
# @raise [ArgumentError] If "device" is detected as the device target and
|
@@ -318,130 +319,111 @@ version: #{version}
|
|
318
319
|
end.call
|
319
320
|
end
|
320
321
|
|
321
|
-
# @!visibility private
|
322
|
-
# Is this the first launch of this Simulator?
|
323
|
-
#
|
324
|
-
# TODO Needs unit and integration tests.
|
325
|
-
def simulator_first_launch?
|
326
|
-
megabytes = simulator_data_dir_size
|
327
|
-
|
328
|
-
if version >= RunLoop::Version.new('9.0')
|
329
|
-
megabytes < 20
|
330
|
-
elsif version >= RunLoop::Version.new('8.0')
|
331
|
-
megabytes < 12
|
332
|
-
else
|
333
|
-
megabytes < 8
|
334
|
-
end
|
335
|
-
end
|
336
|
-
|
337
|
-
# @!visibility private
|
338
|
-
# The size of the simulator data/ directory.
|
339
|
-
#
|
340
|
-
# TODO needs unit tests.
|
341
|
-
def simulator_data_dir_size
|
342
|
-
path = File.join(simulator_root_dir, 'data')
|
343
|
-
RunLoop::Directory.size(path, :mb)
|
344
|
-
end
|
345
|
-
|
346
322
|
# @!visibility private
|
347
323
|
#
|
348
324
|
# Waits for three conditions:
|
349
325
|
#
|
350
326
|
# 1. The SHA sum of the simulator data/ directory to be stable.
|
351
|
-
# 2. No more log messages are begin generated
|
352
|
-
# 3. 1 and 2 must hold for 1 seconds.
|
327
|
+
# 2. No more log messages are begin generated.
|
328
|
+
# 3. 1 and 2 must hold for 1.5 seconds.
|
353
329
|
#
|
354
|
-
# When the simulator version is >= iOS 9
|
355
|
-
#
|
356
|
-
# is added:
|
330
|
+
# When the simulator version is >= iOS 9, two more conditions are added to
|
331
|
+
# get past the iOS 9+ boot screen.
|
357
332
|
#
|
358
|
-
# 4.
|
333
|
+
# 4. Wait for com.apple.audio.SystemSoundServer-iOS-Simulator process to
|
334
|
+
# start.
|
335
|
+
# 5. 1 and 2 must hold for 1.5 seconds.
|
359
336
|
#
|
360
|
-
#
|
337
|
+
# When the simulator version is >= iOS 9 and the device is an iPad another
|
338
|
+
# condition is added because simctl fails to correctly install applications;
|
339
|
+
# the app and data container exists, but Springboard does not detect them.
|
340
|
+
#
|
341
|
+
# 6. 1 and 2 must hold for 1.5 seconds.
|
361
342
|
def simulator_wait_for_stable_state
|
362
|
-
require 'securerandom'
|
363
343
|
|
364
344
|
# How long to wait between stability checks.
|
345
|
+
# Shorter than this gives false positives.
|
365
346
|
delay = 0.5
|
366
347
|
|
367
|
-
|
348
|
+
# How many times to wait for stable state.
|
349
|
+
max_stable_count = 3
|
368
350
|
|
369
|
-
#
|
370
|
-
|
371
|
-
|
351
|
+
# How long to wait for iOS 9 boot screen.
|
352
|
+
boot_screen_wait_options = {
|
353
|
+
:max_boot_screen_wait => 10,
|
354
|
+
:raise_on_timeout => false
|
355
|
+
}
|
356
|
+
|
357
|
+
# How much additional time to wait for iOS 9+ iPads.
|
372
358
|
#
|
373
|
-
#
|
374
|
-
#
|
375
|
-
#
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
359
|
+
# Installing and launching on iPads is problematic.
|
360
|
+
# Sometimes the app is installed, but SpringBoard does
|
361
|
+
# not recognize that the app is installed even though
|
362
|
+
# simctl says that it is.
|
363
|
+
additional_ipad_delay = delay * 2
|
364
|
+
|
365
|
+
# Adjust for CI environments
|
366
|
+
if RunLoop::Environment.ci?
|
367
|
+
max_stable_count = 5
|
368
|
+
boot_screen_wait_options[:max_boot_screen_wait] = 20
|
369
|
+
additional_ipad_delay = delay * 4
|
381
370
|
end
|
382
371
|
|
383
|
-
|
384
|
-
|
385
|
-
poll_until = now + timeout
|
386
|
-
quiet = now + quiet_time
|
372
|
+
# iOS 9 simulators have an additional boot screen.
|
373
|
+
is_gte_ios9 = version >= RunLoop::Version.new('9.0')
|
387
374
|
|
388
|
-
|
375
|
+
# iOS 9 iPad simulators need additional time to stabilize.
|
376
|
+
is_ipad = simulator_is_ipad?
|
389
377
|
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
begin
|
394
|
-
# Typically, this returns in < 0.3 seconds.
|
395
|
-
Timeout.timeout(10, TimeoutError) do
|
396
|
-
# Errors are ignorable and users are confused by the messages.
|
397
|
-
options = { :handle_errors_by => :ignoring }
|
398
|
-
RunLoop::Directory.directory_digest(data_dir, options)
|
399
|
-
end
|
400
|
-
rescue => _
|
401
|
-
SecureRandom.uuid
|
402
|
-
end
|
403
|
-
end
|
378
|
+
timeout = SIM_STABLE_STATE_OPTIONS[:timeout]
|
379
|
+
now = Time.now
|
380
|
+
poll_until = now + timeout
|
404
381
|
|
405
|
-
RunLoop.log_debug("Waiting for simulator to stabilize with timeout: #{timeout}")
|
406
|
-
if first_launch
|
407
|
-
RunLoop.log_debug("Detected the first launch of an iOS >= 9.0 Simulator")
|
408
|
-
end
|
382
|
+
RunLoop.log_debug("Waiting for simulator to stabilize with timeout: #{timeout} seconds")
|
409
383
|
|
410
|
-
|
384
|
+
current_dir_sha = simulator_data_directory_sha
|
385
|
+
current_log_sha = simulator_log_file_sha
|
386
|
+
is_stable = false
|
387
|
+
waited_for_boot = false
|
388
|
+
waited_for_ipad = false
|
389
|
+
stable_count = 0
|
411
390
|
|
412
391
|
while Time.now < poll_until do
|
413
|
-
|
414
|
-
|
392
|
+
latest_dir_sha = simulator_data_directory_sha
|
393
|
+
latest_log_sha = simulator_log_file_sha
|
415
394
|
|
416
|
-
is_stable =
|
395
|
+
is_stable = [current_dir_sha == latest_dir_sha,
|
396
|
+
current_log_sha == latest_log_sha].all?
|
417
397
|
|
418
398
|
if is_stable
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
399
|
+
stable_count = stable_count + 1
|
400
|
+
if stable_count == max_stable_count
|
401
|
+
if is_gte_ios9 && !waited_for_boot
|
402
|
+
process_name = "com.apple.audio.SystemSoundServer-iOS-Simulator"
|
403
|
+
RunLoop::ProcessWaiter.new(process_name, boot_screen_wait_options).wait_for_any
|
404
|
+
waited_for_boot = true
|
405
|
+
stable_count = 0
|
406
|
+
elsif is_gte_ios9 && is_ipad && !waited_for_ipad
|
407
|
+
RunLoop.log_debug("Waiting additional time for iOS 9 iPad to stabilize")
|
408
|
+
sleep(additional_ipad_delay)
|
409
|
+
waited_for_ipad = true
|
410
|
+
stable_count = 0
|
425
411
|
else
|
426
412
|
break
|
427
413
|
end
|
428
|
-
else
|
429
|
-
quiet = Time.now + quiet_time
|
430
414
|
end
|
431
415
|
end
|
432
416
|
|
433
|
-
|
434
|
-
|
435
|
-
sleep
|
417
|
+
current_dir_sha = latest_dir_sha
|
418
|
+
current_log_sha = latest_log_sha
|
419
|
+
sleep(delay)
|
436
420
|
end
|
437
421
|
|
438
422
|
if is_stable
|
439
423
|
elapsed = Time.now - now
|
440
|
-
stabilized = elapsed - quiet_time
|
441
|
-
RunLoop.log_debug("Simulator stable after #{stabilized} seconds")
|
442
424
|
RunLoop.log_debug("Waited a total of #{elapsed} seconds for simulator to stabilize")
|
443
425
|
else
|
444
|
-
RunLoop.log_debug("Timed out
|
426
|
+
RunLoop.log_debug("Timed out after #{timeout} seconds waiting for simulator to stabilize")
|
445
427
|
end
|
446
428
|
end
|
447
429
|
|
@@ -530,33 +512,6 @@ version: #{version}
|
|
530
512
|
|
531
513
|
private
|
532
514
|
|
533
|
-
# @!visibility private
|
534
|
-
# TODO write a unit test.
|
535
|
-
def last_line_from_simulator_log_file
|
536
|
-
file = simulator_log_file_path
|
537
|
-
|
538
|
-
return nil if !File.exist?(file)
|
539
|
-
|
540
|
-
debug = RunLoop::Environment.debug?
|
541
|
-
|
542
|
-
begin
|
543
|
-
io = File.open(file, 'r')
|
544
|
-
io.seek(-100, IO::SEEK_END)
|
545
|
-
|
546
|
-
line = io.readline
|
547
|
-
rescue StandardError => e
|
548
|
-
RunLoop.log_error("Caught #{e} while reading simulator log file") if debug
|
549
|
-
ensure
|
550
|
-
io.close if io && !io.closed?
|
551
|
-
end
|
552
|
-
|
553
|
-
if line
|
554
|
-
line.chomp
|
555
|
-
else
|
556
|
-
line
|
557
|
-
end
|
558
|
-
end
|
559
|
-
|
560
515
|
# @!visibility private
|
561
516
|
def xcrun
|
562
517
|
RunLoop::Xcrun.new
|
@@ -650,6 +605,50 @@ version: #{version}
|
|
650
605
|
udid
|
651
606
|
end
|
652
607
|
|
608
|
+
# @!visibility private
|
609
|
+
def simulator_data_directory_sha
|
610
|
+
path = File.join(simulator_root_dir, 'data')
|
611
|
+
begin
|
612
|
+
# Typically, this returns in < 0.3 seconds.
|
613
|
+
Timeout.timeout(10, TimeoutError) do
|
614
|
+
# Errors are ignorable and users are confused by the messages.
|
615
|
+
options = { :handle_errors_by => :ignoring }
|
616
|
+
RunLoop::Directory.directory_digest(path, options)
|
617
|
+
end
|
618
|
+
rescue => _
|
619
|
+
SecureRandom.uuid
|
620
|
+
end
|
621
|
+
end
|
622
|
+
|
623
|
+
# @!visibility private
|
624
|
+
def simulator_log_file_sha
|
625
|
+
file = simulator_log_file_path
|
626
|
+
|
627
|
+
return nil if !File.exist?(file)
|
628
|
+
|
629
|
+
sha = OpenSSL::Digest::SHA256.new
|
630
|
+
|
631
|
+
begin
|
632
|
+
sha << File.read(file)
|
633
|
+
rescue => _
|
634
|
+
sha = SecureRandom.uuid
|
635
|
+
end
|
636
|
+
|
637
|
+
sha
|
638
|
+
end
|
639
|
+
|
640
|
+
# @!visibility private
|
641
|
+
# Value of <UDID>/.device.plist 'deviceType' key.
|
642
|
+
def simulator_device_type
|
643
|
+
plist = File.join(simulator_device_plist)
|
644
|
+
pbuddy.plist_read("deviceType", plist)
|
645
|
+
end
|
646
|
+
|
647
|
+
# @!visibility private
|
648
|
+
def simulator_is_ipad?
|
649
|
+
simulator_device_type[/iPad/, 0]
|
650
|
+
end
|
651
|
+
|
653
652
|
# @!visibility private
|
654
653
|
def self.ensure_physical_device_connected(identifier, options)
|
655
654
|
if identifier.nil?
|
@@ -0,0 +1,39 @@
|
|
1
|
+
|
2
|
+
module RunLoop
|
3
|
+
module Encoding
|
4
|
+
|
5
|
+
# Raised when a string cannot be coerced to UTF8
|
6
|
+
class UTF8Error < RuntimeError; end
|
7
|
+
|
8
|
+
# @!visibility private
|
9
|
+
def ensure_command_output_utf8(string, command)
|
10
|
+
return '' if !string
|
11
|
+
|
12
|
+
utf8 = string.force_encoding("UTF-8").chomp
|
13
|
+
|
14
|
+
return utf8 if utf8.valid_encoding?
|
15
|
+
|
16
|
+
encoded = utf8.encode("UTF-8", "UTF-8",
|
17
|
+
invalid: :replace,
|
18
|
+
undef: :replace,
|
19
|
+
replace: "")
|
20
|
+
|
21
|
+
return encoded if encoded.valid_encoding?
|
22
|
+
|
23
|
+
raise UTF8Error, %Q{
|
24
|
+
Could not force UTF-8 encoding on this string:
|
25
|
+
|
26
|
+
#{string}
|
27
|
+
|
28
|
+
which is the output of this command:
|
29
|
+
|
30
|
+
#{command}
|
31
|
+
|
32
|
+
Please file an issue with a stacktrace and the text of this error.
|
33
|
+
|
34
|
+
https://github.com/calabash/run_loop/issues
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
data/lib/run_loop/environment.rb
CHANGED
@@ -15,7 +15,11 @@ module RunLoop
|
|
15
15
|
|
16
16
|
# Returns true if Windows environment
|
17
17
|
def self.windows_env?
|
18
|
-
|
18
|
+
if @@windows_env.nil?
|
19
|
+
@@windows_env = Environment.host_os_is_win?
|
20
|
+
end
|
21
|
+
|
22
|
+
@@windows_env
|
19
23
|
end
|
20
24
|
|
21
25
|
# Returns true if debugging is enabled.
|
@@ -256,5 +260,26 @@ Check your environment.]
|
|
256
260
|
path
|
257
261
|
end
|
258
262
|
end
|
263
|
+
|
264
|
+
private
|
265
|
+
|
266
|
+
# @visibility private
|
267
|
+
WIN_PATTERNS = [
|
268
|
+
/bccwin/i,
|
269
|
+
/cygwin/i,
|
270
|
+
/djgpp/i,
|
271
|
+
/mingw/i,
|
272
|
+
/mswin/i,
|
273
|
+
/wince/i,
|
274
|
+
]
|
275
|
+
|
276
|
+
# @!visibility private
|
277
|
+
@@windows_env = nil
|
278
|
+
|
279
|
+
# @!visibility private
|
280
|
+
def self.host_os_is_win?
|
281
|
+
ruby_platform = RbConfig::CONFIG["host_os"]
|
282
|
+
!!WIN_PATTERNS.find { |r| ruby_platform =~ r }
|
283
|
+
end
|
259
284
|
end
|
260
285
|
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
module RunLoop
|
2
|
+
# @!visibility private
|
3
|
+
module PhysicalDevice
|
4
|
+
|
5
|
+
# @!visibility private
|
6
|
+
class IDeviceInstaller < LifeCycle
|
7
|
+
|
8
|
+
# Is the tool installed?
|
9
|
+
def self.tool_is_installed?
|
10
|
+
raise RunLoop::Abstract::AbstractMethodError,
|
11
|
+
"Subclass must implement '.tool_is_installed?'"
|
12
|
+
end
|
13
|
+
|
14
|
+
# Path to tool.
|
15
|
+
def self.executable_path
|
16
|
+
raise RunLoop::Abstract::AbstractMethodError,
|
17
|
+
"Subclass must implement '.executable_path'"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Is the app installed?
|
21
|
+
#
|
22
|
+
# @return [Boolean] true or false
|
23
|
+
def app_installed?(bundle_id)
|
24
|
+
abstract_method!
|
25
|
+
end
|
26
|
+
|
27
|
+
# Install the app or ipa.
|
28
|
+
#
|
29
|
+
# If the app is already installed, it will be reinstalled from disk;
|
30
|
+
# no version check is performed.
|
31
|
+
#
|
32
|
+
# App data is never preserved. If you want to preserve the app data,
|
33
|
+
# call `ensure_app_installed`.
|
34
|
+
#
|
35
|
+
# Possible return values:
|
36
|
+
#
|
37
|
+
# * :reinstalled => app was installed, but app data was not preserved.
|
38
|
+
# * :installed => app was not installed.
|
39
|
+
#
|
40
|
+
# @raise [InstallError] If app was not installed.
|
41
|
+
# @return [Symbol] A keyword describing the action that was performed.
|
42
|
+
def install_app(app_or_ipa)
|
43
|
+
abstract_method!
|
44
|
+
end
|
45
|
+
|
46
|
+
# Uninstall the app with bundle_id.
|
47
|
+
#
|
48
|
+
# App data is never preserved. If you want to install a new version of
|
49
|
+
# an app and preserve app data (upgrade testing), call
|
50
|
+
# `ensure_app_installed`.
|
51
|
+
#
|
52
|
+
# Possible return values:
|
53
|
+
#
|
54
|
+
# * :nothing => app was not installed
|
55
|
+
# * :uninstall => app was uninstalled
|
56
|
+
#
|
57
|
+
# @raise [UninstallError] If the app cannot be uninstalled, usually
|
58
|
+
# because it is a system app.
|
59
|
+
# @return [Symbol] A keyword that describes what action was performed.
|
60
|
+
def uninstall_app(bundle_id)
|
61
|
+
abstract_method!
|
62
|
+
end
|
63
|
+
|
64
|
+
# Ensures the app is installed and ensures that app is not stale by
|
65
|
+
# asking if the version of installed app is different than the version
|
66
|
+
# of the app or ipa on disk.
|
67
|
+
#
|
68
|
+
# The concrete implementation needs to check the CFBundleVersion and
|
69
|
+
# the CFBundleShortVersionString. If either are different, then the
|
70
|
+
# app should be reinstalled.
|
71
|
+
#
|
72
|
+
# If possible, the app data should be preserved across reinstallation.
|
73
|
+
#
|
74
|
+
# Possible return values:
|
75
|
+
#
|
76
|
+
# * :nothing => app was already installed and versions matched.
|
77
|
+
# * :upgraded => app was stale; newer version from disk was installed and
|
78
|
+
# app data was preserved.
|
79
|
+
# * :reinstalled => app was stale; newer version from disk was installed,
|
80
|
+
# but app data was not preserved.
|
81
|
+
# * :installed => app was not installed.
|
82
|
+
#
|
83
|
+
# @raise [InstallError] If the app could not be installed.
|
84
|
+
# @raise [UninstallError] If the app could not be uninstalled.
|
85
|
+
#
|
86
|
+
# @return [Symbol] A keyword that describes the action that was taken.
|
87
|
+
def ensure_app_installed(app_or_ipa)
|
88
|
+
abstract_method!
|
89
|
+
end
|
90
|
+
|
91
|
+
# Is the app on disk the same as the installed app?
|
92
|
+
#
|
93
|
+
# The concrete implementation needs to check the CFBundleVersion and
|
94
|
+
# the CFBundleShortVersionString. If either are different, then this
|
95
|
+
# method returns false.
|
96
|
+
#
|
97
|
+
# @raise [RuntimeError] If app is not already installed.
|
98
|
+
def installed_app_same_as?(app_or_ipa)
|
99
|
+
abstract_method!
|
100
|
+
end
|
101
|
+
|
102
|
+
# Clear the app sandbox.
|
103
|
+
#
|
104
|
+
# This method will never uninstall the app. If the concrete
|
105
|
+
# implementation cannot reset the app data, this method should raise
|
106
|
+
# an exception.
|
107
|
+
#
|
108
|
+
# Does not clear Keychain. Use the Calabash iOS Keychain API.
|
109
|
+
def reset_app_sandbox(bundle_id)
|
110
|
+
abstract_method!
|
111
|
+
end
|
112
|
+
|
113
|
+
# Return the architecture of the device.
|
114
|
+
def architecture
|
115
|
+
abstract_method!
|
116
|
+
end
|
117
|
+
|
118
|
+
# Is the app or ipa compatible with the architecture of the device?
|
119
|
+
def app_has_compatible_architecture?(app_or_ipa)
|
120
|
+
abstract_method!
|
121
|
+
end
|
122
|
+
|
123
|
+
# Return true if the device is an iPhone.
|
124
|
+
def iphone?
|
125
|
+
abstract_method!
|
126
|
+
end
|
127
|
+
|
128
|
+
# Return false if the device is an iPad.
|
129
|
+
def ipad?
|
130
|
+
abstract_method!
|
131
|
+
end
|
132
|
+
|
133
|
+
# Return the model of the device.
|
134
|
+
def model
|
135
|
+
abstract_method!
|
136
|
+
end
|
137
|
+
|
138
|
+
# Sideload data into the app's sandbox.
|
139
|
+
#
|
140
|
+
# These directories exist in the application sandbox.
|
141
|
+
#
|
142
|
+
# * sandbox/Documents
|
143
|
+
# * sandbox/Library
|
144
|
+
# * sandbox/Preferences
|
145
|
+
# * sandbox/tmp
|
146
|
+
#
|
147
|
+
# The data is a hash of arrays that define source/target file
|
148
|
+
# path pairs.
|
149
|
+
#
|
150
|
+
# {
|
151
|
+
# :documents => [
|
152
|
+
# {
|
153
|
+
# :source => "path/to/file/on/disk",
|
154
|
+
# :target => "sub/dir/under/Documents"
|
155
|
+
# },
|
156
|
+
# {
|
157
|
+
# :source => "path/to/other/file",
|
158
|
+
# :target => "./"
|
159
|
+
# }
|
160
|
+
# ],
|
161
|
+
#
|
162
|
+
# :library => [ < ditto >],
|
163
|
+
# :preferences => [ < ditto > ],
|
164
|
+
# :tmp => [ < ditto >]
|
165
|
+
# }
|
166
|
+
#
|
167
|
+
# * If a file exists at a target path, it will be replaced.
|
168
|
+
# * Subdirectories will be created as necessary.
|
169
|
+
# * :source files must exist.
|
170
|
+
def sideload(data)
|
171
|
+
abstract_method!
|
172
|
+
end
|
173
|
+
|
174
|
+
# Removes a file or directory from the app sandbox.
|
175
|
+
#
|
176
|
+
# If the path does not exist, no error will be raised.
|
177
|
+
#
|
178
|
+
# Documents, Library, Preferences, and tmp directories will be
|
179
|
+
# deleted, but then recreated. For example:
|
180
|
+
#
|
181
|
+
# remove_file_from_sandbox("Preferences")
|
182
|
+
#
|
183
|
+
# The Preferences directory will be deleted and then recreated.
|
184
|
+
def remove_from_sandbox(path)
|
185
|
+
abstract_method!
|
186
|
+
end
|
187
|
+
|
188
|
+
# @!visibility private
|
189
|
+
def expect_app_or_ipa(app_or_ipa)
|
190
|
+
if ![is_app?(app_or_ipa), is_ipa?(app_or_ipa)].any?
|
191
|
+
if app_or_ipa.nil?
|
192
|
+
object = "nil"
|
193
|
+
elsif app_or_ipa == ""
|
194
|
+
object = "<empty string>"
|
195
|
+
else
|
196
|
+
object = app_or_ipa
|
197
|
+
end
|
198
|
+
|
199
|
+
raise ArgumentError, %Q[Expected:
|
200
|
+
|
201
|
+
#{object}
|
202
|
+
|
203
|
+
to be a RunLoop::App or a RunLoop::Ipa.]
|
204
|
+
end
|
205
|
+
|
206
|
+
true
|
207
|
+
end
|
208
|
+
|
209
|
+
# @!visibility private
|
210
|
+
def is_app?(app_or_ipa)
|
211
|
+
app_or_ipa.is_a?(RunLoop::App)
|
212
|
+
end
|
213
|
+
|
214
|
+
# @!visibility private
|
215
|
+
def is_ipa?(app_or_ipa)
|
216
|
+
app_or_ipa.is_a?(RunLoop::Ipa)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module RunLoop
|
2
|
+
module Shell
|
3
|
+
|
4
|
+
require "command_runner"
|
5
|
+
require "run_loop/encoding"
|
6
|
+
include RunLoop::Encoding
|
7
|
+
|
8
|
+
# Controls the behavior of Shell#exec.
|
9
|
+
#
|
10
|
+
# You can override these values if they do not work in your environment.
|
11
|
+
#
|
12
|
+
# For cucumber users, the best place to override would be in your
|
13
|
+
# features/support/env.rb.
|
14
|
+
#
|
15
|
+
# For example:
|
16
|
+
#
|
17
|
+
# RunLoop::Shell::DEFAULT_OPTIONS[:timeout] = 60
|
18
|
+
DEFAULT_OPTIONS = {
|
19
|
+
:timeout => 30,
|
20
|
+
:log_cmd => false
|
21
|
+
}
|
22
|
+
|
23
|
+
# Raised when shell command fails.
|
24
|
+
class Error < RuntimeError; end
|
25
|
+
|
26
|
+
# Raised when shell command times out.
|
27
|
+
class TimeoutError < RuntimeError; end
|
28
|
+
|
29
|
+
def exec(args, options={})
|
30
|
+
|
31
|
+
merged_options = DEFAULT_OPTIONS.merge(options)
|
32
|
+
|
33
|
+
timeout = merged_options[:timeout]
|
34
|
+
|
35
|
+
unless args.is_a?(Array)
|
36
|
+
raise ArgumentError,
|
37
|
+
"Expected args '#{args}' to be an Array, but found '#{args.class}'"
|
38
|
+
end
|
39
|
+
|
40
|
+
args.each do |arg|
|
41
|
+
unless arg.is_a?(String)
|
42
|
+
raise ArgumentError,
|
43
|
+
%Q{Expected arg '#{arg}' to be a String, but found '#{arg.class}'
|
44
|
+
IO.popen requires all arguments to be Strings.
|
45
|
+
}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
cmd = "#{args.join(' ')}"
|
50
|
+
|
51
|
+
# Don't see your log?
|
52
|
+
# Commands are only logged when debugging.
|
53
|
+
RunLoop.log_unix_cmd(cmd) if merged_options[:log_cmd]
|
54
|
+
|
55
|
+
hash = {}
|
56
|
+
|
57
|
+
begin
|
58
|
+
|
59
|
+
start_time = Time.now
|
60
|
+
command_output = CommandRunner.run(args, timeout: timeout)
|
61
|
+
|
62
|
+
out = ensure_command_output_utf8(command_output[:out], cmd)
|
63
|
+
process_status = command_output[:status]
|
64
|
+
|
65
|
+
hash =
|
66
|
+
{
|
67
|
+
:out => out,
|
68
|
+
:pid => process_status.pid,
|
69
|
+
# nil if process was killed before completion
|
70
|
+
:exit_status => process_status.exitstatus
|
71
|
+
}
|
72
|
+
|
73
|
+
rescue RunLoop::Encoding::UTF8Error => e
|
74
|
+
raise e
|
75
|
+
rescue => e
|
76
|
+
elapsed = "%0.2f" % (Time.now - start_time)
|
77
|
+
raise Error,
|
78
|
+
%Q{Encountered an error after #{elapsed} seconds:
|
79
|
+
|
80
|
+
#{e.message}
|
81
|
+
|
82
|
+
executing this command:
|
83
|
+
|
84
|
+
#{cmd}
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
if hash[:exit_status].nil?
|
89
|
+
elapsed = "%0.2f" % (Time.now - start_time)
|
90
|
+
raise TimeoutError,
|
91
|
+
%Q{Timed out after #{elapsed} seconds executing
|
92
|
+
|
93
|
+
#{cmd}
|
94
|
+
|
95
|
+
with a timeout of #{timeout}
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
hash
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
data/lib/run_loop/version.rb
CHANGED
data/lib/run_loop/xcrun.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
module RunLoop
|
2
2
|
class Xcrun
|
3
3
|
|
4
|
-
require
|
4
|
+
require "command_runner"
|
5
|
+
require "run_loop/encoding"
|
6
|
+
include RunLoop::Encoding
|
5
7
|
|
6
8
|
# Controls the behavior of Xcrun#exec.
|
7
9
|
#
|
@@ -21,9 +23,6 @@ module RunLoop
|
|
21
23
|
# Raised when Xcrun fails.
|
22
24
|
class Error < RuntimeError; end
|
23
25
|
|
24
|
-
# Raised when the output of the command cannot be coerced to UTF8
|
25
|
-
class UTF8Error < RuntimeError; end
|
26
|
-
|
27
26
|
# Raised when Xcrun times out.
|
28
27
|
class TimeoutError < RuntimeError; end
|
29
28
|
|
@@ -60,7 +59,7 @@ IO.popen requires all arguments to be Strings.
|
|
60
59
|
start_time = Time.now
|
61
60
|
command_output = CommandRunner.run(['xcrun'] + args, timeout: timeout)
|
62
61
|
|
63
|
-
out =
|
62
|
+
out = ensure_command_output_utf8(command_output[:out], cmd)
|
64
63
|
process_status = command_output[:status]
|
65
64
|
|
66
65
|
hash =
|
@@ -71,7 +70,7 @@ IO.popen requires all arguments to be Strings.
|
|
71
70
|
:exit_status => process_status.exitstatus
|
72
71
|
}
|
73
72
|
|
74
|
-
rescue UTF8Error => e
|
73
|
+
rescue RunLoop::Encoding::UTF8Error => e
|
75
74
|
raise e
|
76
75
|
rescue => e
|
77
76
|
elapsed = "%0.2f" % (Time.now - start_time)
|
@@ -99,35 +98,6 @@ with a timeout of #{timeout}
|
|
99
98
|
|
100
99
|
hash
|
101
100
|
end
|
102
|
-
|
103
|
-
private
|
104
|
-
|
105
|
-
# @!visibility private
|
106
|
-
def encode_utf8_or_raise(string, command)
|
107
|
-
return '' if !string
|
108
|
-
|
109
|
-
utf8 = string.force_encoding("UTF-8").chomp
|
110
|
-
|
111
|
-
return utf8 if utf8.valid_encoding?
|
112
|
-
|
113
|
-
encoded = utf8.encode('UTF-8', 'UTF-8', invalid: :replace, undef: :replace, replace: '')
|
114
|
-
|
115
|
-
return encoded if encoded.valid_encoding?
|
116
|
-
|
117
|
-
raise UTF8Error, %Q{
|
118
|
-
Could not force UTF-8 encoding on this string:
|
119
|
-
|
120
|
-
#{string}
|
121
|
-
|
122
|
-
which is the output of this command:
|
123
|
-
|
124
|
-
#{command}
|
125
|
-
|
126
|
-
Please file an issue with a stacktrace and the text of this error.
|
127
|
-
|
128
|
-
https://github.com/calabash/run_loop/issues
|
129
|
-
}
|
130
|
-
end
|
131
101
|
end
|
132
102
|
end
|
133
103
|
|
data/lib/run_loop/xcuitest.rb
CHANGED
@@ -126,7 +126,7 @@ module RunLoop
|
|
126
126
|
# @!visibility private
|
127
127
|
def query(mark)
|
128
128
|
options = http_options
|
129
|
-
parameters = { :
|
129
|
+
parameters = { :id => mark }
|
130
130
|
request = request("query", parameters)
|
131
131
|
client = client(options)
|
132
132
|
response = client.post(request)
|
@@ -135,10 +135,19 @@ module RunLoop
|
|
135
135
|
|
136
136
|
# @!visibility private
|
137
137
|
def tap_mark(mark)
|
138
|
+
body = query(mark)
|
139
|
+
tap_query_result(body)
|
140
|
+
end
|
141
|
+
|
142
|
+
# @!visibility private
|
143
|
+
def tap_coordinate(x, y)
|
138
144
|
options = http_options
|
139
145
|
parameters = {
|
140
|
-
:gesture => "
|
141
|
-
:
|
146
|
+
:gesture => "touch",
|
147
|
+
:specifiers => {
|
148
|
+
:coordinate => {x: x, y: y}
|
149
|
+
},
|
150
|
+
:options => {}
|
142
151
|
}
|
143
152
|
request = request("gesture", parameters)
|
144
153
|
client(options)
|
@@ -147,14 +156,31 @@ module RunLoop
|
|
147
156
|
end
|
148
157
|
|
149
158
|
# @!visibility private
|
150
|
-
def
|
151
|
-
|
159
|
+
def rotate_home_button_to(position, sleep_for=1.0)
|
160
|
+
orientation = orientation_for_position(position)
|
161
|
+
parameters = {
|
162
|
+
:orientation => orientation
|
163
|
+
}
|
164
|
+
request = request("rotate_home_button_to", parameters)
|
165
|
+
client(http_options)
|
166
|
+
response = client.post(request)
|
167
|
+
json = expect_200_response(response)
|
168
|
+
sleep(sleep_for)
|
169
|
+
json
|
170
|
+
end
|
171
|
+
|
172
|
+
# @!visibility private
|
173
|
+
def perform_coordinate_gesture(gesture, x, y, options={})
|
152
174
|
parameters = {
|
153
|
-
:gesture =>
|
154
|
-
:
|
175
|
+
:gesture => gesture,
|
176
|
+
:specifiers => {
|
177
|
+
:coordinate => {x: x, y: y}
|
178
|
+
},
|
179
|
+
:options => options
|
155
180
|
}
|
181
|
+
|
156
182
|
request = request("gesture", parameters)
|
157
|
-
client(
|
183
|
+
client(http_options)
|
158
184
|
response = client.post(request)
|
159
185
|
expect_200_response(response)
|
160
186
|
end
|
@@ -273,7 +299,7 @@ module RunLoop
|
|
273
299
|
5.times do
|
274
300
|
begin
|
275
301
|
health
|
276
|
-
sleep(0
|
302
|
+
sleep(1.0)
|
277
303
|
rescue => _
|
278
304
|
break
|
279
305
|
end
|
@@ -299,6 +325,10 @@ module RunLoop
|
|
299
325
|
|
300
326
|
# @!visibility private
|
301
327
|
def xcodebuild
|
328
|
+
env = {
|
329
|
+
"COMMAND_LINE_BUILD" => "1"
|
330
|
+
}
|
331
|
+
|
302
332
|
args = [
|
303
333
|
"xcrun",
|
304
334
|
"xcodebuild",
|
@@ -318,10 +348,10 @@ module RunLoop
|
|
318
348
|
:err => log_file
|
319
349
|
}
|
320
350
|
|
321
|
-
command = args.join(" ")
|
351
|
+
command = "#{env.map.each { |k, v| "#{k}=#{v}" }.join(" ")} #{args.join(" ")}"
|
322
352
|
RunLoop.log_unix_cmd("#{command} >& #{log_file}")
|
323
353
|
|
324
|
-
pid = Process.spawn(*args, options)
|
354
|
+
pid = Process.spawn(env, *args, options)
|
325
355
|
Process.detach(pid)
|
326
356
|
pid
|
327
357
|
end
|
@@ -334,6 +364,9 @@ module RunLoop
|
|
334
364
|
|
335
365
|
shutdown
|
336
366
|
|
367
|
+
# Temp measure; we need to manage the xcodebuild pids.
|
368
|
+
system("pkill xcodebuild")
|
369
|
+
|
337
370
|
if device.simulator?
|
338
371
|
# quits the simulator
|
339
372
|
sim = CoreSimulator.new(device, "")
|
@@ -361,7 +394,9 @@ module RunLoop
|
|
361
394
|
RunLoop.log_debug("Launched #{bundle_id} on #{device}")
|
362
395
|
RunLoop.log_debug("#{response.body}")
|
363
396
|
if device.simulator?
|
364
|
-
|
397
|
+
# It is not clear yet whether we should do this. There is a problem
|
398
|
+
# in the simulator_wait_for_stable_state; it waits too long.
|
399
|
+
# device.simulator_wait_for_stable_state
|
365
400
|
end
|
366
401
|
expect_200_response(response)
|
367
402
|
rescue => e
|
@@ -409,6 +444,28 @@ Server replied with:
|
|
409
444
|
|
410
445
|
path
|
411
446
|
end
|
447
|
+
|
448
|
+
# @!visibility private
|
449
|
+
def orientation_for_position(position)
|
450
|
+
symbol = position.to_sym
|
451
|
+
|
452
|
+
case symbol
|
453
|
+
when :down, :bottom
|
454
|
+
return 1
|
455
|
+
when :up, :top
|
456
|
+
return 2
|
457
|
+
when :right
|
458
|
+
return 3
|
459
|
+
when :left
|
460
|
+
return 4
|
461
|
+
else
|
462
|
+
raise ArgumentError, %Q[
|
463
|
+
Could not coerce '#{position}' into a valid orientation.
|
464
|
+
|
465
|
+
Valid values are: :down, :up, :right, :left, :bottom, :top
|
466
|
+
]
|
467
|
+
end
|
468
|
+
end
|
412
469
|
end
|
413
470
|
end
|
414
471
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: run_loop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.1.
|
4
|
+
version: 2.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Karl Krukow
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-05-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -212,6 +212,20 @@ dependencies:
|
|
212
212
|
- - "~>"
|
213
213
|
- !ruby/object:Gem::Version
|
214
214
|
version: '2.0'
|
215
|
+
- !ruby/object:Gem::Dependency
|
216
|
+
name: listen
|
217
|
+
requirement: !ruby/object:Gem::Requirement
|
218
|
+
requirements:
|
219
|
+
- - '='
|
220
|
+
- !ruby/object:Gem::Version
|
221
|
+
version: 3.0.6
|
222
|
+
type: :development
|
223
|
+
prerelease: false
|
224
|
+
version_requirements: !ruby/object:Gem::Requirement
|
225
|
+
requirements:
|
226
|
+
- - '='
|
227
|
+
- !ruby/object:Gem::Version
|
228
|
+
version: 3.0.6
|
215
229
|
- !ruby/object:Gem::Dependency
|
216
230
|
name: growl
|
217
231
|
requirement: !ruby/object:Gem::Requirement
|
@@ -304,6 +318,7 @@ files:
|
|
304
318
|
- lib/run_loop/directory.rb
|
305
319
|
- lib/run_loop/dot_dir.rb
|
306
320
|
- lib/run_loop/dylib_injector.rb
|
321
|
+
- lib/run_loop/encoding.rb
|
307
322
|
- lib/run_loop/environment.rb
|
308
323
|
- lib/run_loop/fifo.rb
|
309
324
|
- lib/run_loop/host_cache.rb
|
@@ -321,10 +336,12 @@ files:
|
|
321
336
|
- lib/run_loop/logging.rb
|
322
337
|
- lib/run_loop/otool.rb
|
323
338
|
- lib/run_loop/patches/awesome_print.rb
|
339
|
+
- lib/run_loop/physical_device/ideviceinstaller.rb
|
324
340
|
- lib/run_loop/plist_buddy.rb
|
325
341
|
- lib/run_loop/process_terminator.rb
|
326
342
|
- lib/run_loop/process_waiter.rb
|
327
343
|
- lib/run_loop/regex.rb
|
344
|
+
- lib/run_loop/shell.rb
|
328
345
|
- lib/run_loop/sim_control.rb
|
329
346
|
- lib/run_loop/simctl.rb
|
330
347
|
- lib/run_loop/strings.rb
|
@@ -367,7 +384,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
367
384
|
version: '0'
|
368
385
|
requirements: []
|
369
386
|
rubyforge_project:
|
370
|
-
rubygems_version: 2.5.
|
387
|
+
rubygems_version: 2.5.1
|
371
388
|
signing_key:
|
372
389
|
specification_version: 4
|
373
390
|
summary: The bridge between Calabash iOS and Xcode command-line tools like instruments
|