run_loop 2.1.1 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|