run_loop 1.2.6 → 1.2.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ab5bc95a6743db8faaae000d728b4af4c36401c2
4
- data.tar.gz: b00267da35da54a4bafdff8f18587bf187aa78f5
3
+ metadata.gz: 2654e1672654ee6ae0af23cb582fec43864992df
4
+ data.tar.gz: d481bf34a098f76d83d94826575b021ace8232ca
5
5
  SHA512:
6
- metadata.gz: 73d64d08b501e73dfa80e49c9446e164626d8bb39d7ccdc3fa1939d2db15163b14b7bbda41bd7bc53ea90c42b8cf97f17ab79c7e616d02ef6734015d6691c553
7
- data.tar.gz: d0a3b4661b426fe09a64252dea9214de136b496af4ab46d900c284c1f9ec59f96c620aedde16719f7b63ae4b16532a962aff064eca89b7b64196589284b95fd9
6
+ metadata.gz: db03ba41012e7bf8f5d4cefa4833a36c478d16f9359339e415eae29dfa4faf62e6fb3491ad301d872941bcb30a1ebb71c1aabd55f95ceb01210119f2f57f00ff
7
+ data.tar.gz: 30e5ec4733c0ef6da15aa590c9390c46e3a9a6fa5ddb08f4e609602ace3a05579ea76d538a132f4667708ea1d4cf83307aeb243573b90635cbd6609c8b0e21d3
data/lib/run_loop.rb CHANGED
@@ -1,10 +1,17 @@
1
+ require 'run_loop/environment'
2
+ require 'run_loop/process_terminator'
3
+ require 'run_loop/process_waiter'
4
+ require 'run_loop/lldb'
5
+ require 'run_loop/dylib_injector'
1
6
  require 'run_loop/core'
2
7
  require 'run_loop/version'
3
8
  require 'run_loop/xctools'
4
9
  require 'run_loop/plist_buddy'
10
+ require 'run_loop/app'
5
11
  require 'run_loop/sim_control'
6
12
  require 'run_loop/device'
7
13
  require 'run_loop/instruments'
8
14
  require 'run_loop/lipo'
9
15
  require 'run_loop/host_cache'
10
16
  require 'run_loop/monkey_patch'
17
+ require 'run_loop/simctl/bridge'
@@ -0,0 +1,67 @@
1
+ module RunLoop
2
+ # A class for interacting with .app bundles.
3
+ class App
4
+
5
+ # @!attribute [r] path
6
+ # @return [String] The path to the app bundle .app
7
+ attr_reader :path
8
+
9
+ # Creates a new App instance.
10
+ #
11
+ # @note The `app_bundle_path` is expanded during initialization.
12
+ #
13
+ # @param [String] app_bundle_path A path to a .app
14
+ # @return [RunLoop::App] A instance of App with a path.
15
+ def initialize(app_bundle_path)
16
+ @path = File.expand_path(app_bundle_path)
17
+ end
18
+
19
+ # Is this a valid app?
20
+ def valid?
21
+ [File.exist?(path),
22
+ File.directory?(path),
23
+ File.extname(path) == '.app'].all?
24
+ end
25
+
26
+ # Returns the Info.plist path.
27
+ # @raise [RuntimeError] If there is no Info.plist.
28
+ def info_plist_path
29
+ info_plist = File.join(path, 'Info.plist')
30
+ unless File.exist?(info_plist)
31
+ raise "Expected an Info.plist at '#{path}'"
32
+ end
33
+ info_plist
34
+ end
35
+
36
+ # Inspects the app's Info.plist for the bundle identifier.
37
+ # @return [String] The value of CFBundleIdentifier.
38
+ # @raise [RuntimeError] If the plist cannot be read or the
39
+ # CFBundleIdentifier is empty or does not exist.
40
+ def bundle_identifier
41
+ identifier = plist_buddy.plist_read('CFBundleIdentifier', info_plist_path)
42
+ unless identifier
43
+ raise "Expected key 'CFBundleIdentifier' in '#{info_plist_path}'"
44
+ end
45
+ identifier
46
+ end
47
+
48
+ # Inspects the app's Info.plist for the executable name.
49
+ # @return [String] The value of CFBundleIdentifier.
50
+ # @raise [RuntimeError] If the plist cannot be read or the
51
+ # CFBundleExecutable is empty or does not exist.
52
+ def executable_name
53
+ identifier = plist_buddy.plist_read('CFBundleExecutable', info_plist_path)
54
+ unless identifier
55
+ raise "Expected key 'CFBundleExecutable' in '#{info_plist_path}'"
56
+ end
57
+ identifier
58
+ end
59
+
60
+ private
61
+
62
+ def plist_buddy
63
+ @plist_buddy ||= RunLoop::PlistBuddy.new
64
+ end
65
+
66
+ end
67
+ end
data/lib/run_loop/core.rb CHANGED
@@ -135,7 +135,8 @@ module RunLoop
135
135
  sim_control ||= options[:sim_control] || RunLoop::SimControl.new
136
136
  xctools ||= options[:xctools] || sim_control.xctools
137
137
 
138
- RunLoop::Instruments.new.kill_instruments(xctools)
138
+ instruments = RunLoop::Instruments.new
139
+ instruments.kill_instruments
139
140
 
140
141
  device_target = options[:udid] || options[:device_target] || detect_connected_device || 'simulator'
141
142
  if device_target && device_target.to_s.downcase == 'device'
@@ -157,7 +158,6 @@ module RunLoop
157
158
 
158
159
  script = File.join(results_dir, '_run_loop.js')
159
160
 
160
-
161
161
  code = File.read(options[:script])
162
162
  code = code.gsub(/\$PATH/, results_dir)
163
163
  code = code.gsub(/\$READ_SCRIPT_PATH/, READ_SCRIPT_PATH)
@@ -186,10 +186,6 @@ module RunLoop
186
186
 
187
187
  args = options.fetch(:args, [])
188
188
 
189
- inject_dylib = self.dylib_path_from_options options
190
- # WIP This is brute-force call against all lldb processes.
191
- self.ensure_lldb_not_running if inject_dylib
192
-
193
189
  log_file ||= File.join(results_dir, 'run_loop.out')
194
190
 
195
191
  after = Time.now
@@ -217,22 +213,11 @@ module RunLoop
217
213
 
218
214
  self.log_run_loop_options(merged_options, xctools)
219
215
 
220
- cmd = instruments_command(merged_options, xctools)
216
+ automation_template = automation_template(xctools)
221
217
 
222
218
  log_header("Starting on #{device_target} App: #{bundle_dir_or_bundle_id}")
223
- cmd_str = cmd.join(' ')
224
219
 
225
- log(cmd_str) if ENV['DEBUG'] == '1'
226
-
227
- if !jruby? && RUBY_VERSION && RUBY_VERSION.start_with?('1.8')
228
- pid = fork do
229
- exec(cmd_str)
230
- end
231
- else
232
- pid = spawn(cmd_str)
233
- end
234
-
235
- Process.detach(pid)
220
+ pid = instruments.spawn(automation_template, merged_options, log_file)
236
221
 
237
222
  File.open(File.join(results_dir, 'run_loop.pid'), 'w') do |f|
238
223
  f.write pid
@@ -249,7 +234,6 @@ module RunLoop
249
234
 
250
235
  uia_timeout = options[:uia_timeout] || (ENV['UIA_TIMEOUT'] && ENV['UIA_TIMEOUT'].to_f) || 10
251
236
 
252
- raw_lldb_output = nil
253
237
  before = Time.now
254
238
  begin
255
239
 
@@ -262,63 +246,25 @@ module RunLoop
262
246
  read_response(run_loop, 0, uia_timeout)
263
247
  end
264
248
  end
265
-
266
- # inject_dylib will be nil or a path to a dylib
267
- if inject_dylib
268
- lldb_template_file = File.join(scripts_path, 'calabash.lldb.erb')
269
- lldb_template = ::ERB.new(File.read(lldb_template_file))
270
- lldb_template.filename = lldb_template_file
271
-
272
- # Special!
273
- # These are required by the ERB in calabash.lldb.erb
274
- # noinspection RubyUnusedLocalVariable
275
- cf_bundle_executable = find_cf_bundle_executable(bundle_dir_or_bundle_id)
276
- # noinspection RubyUnusedLocalVariable
277
- dylib_path_for_target = inject_dylib
278
-
279
- lldb_cmd = lldb_template.result(binding)
280
-
281
- tmpdir = Dir.mktmpdir('lldb_cmd')
282
- lldb_script = File.join(tmpdir, 'lldb')
283
-
284
- File.open(lldb_script, 'w') { |f| f.puts(lldb_cmd) }
285
-
286
- if ENV['DEBUG'] == '1'
287
- puts "lldb script #{lldb_script}"
288
- puts "=== lldb script ==="
289
- counter = 0
290
- File.open(lldb_script, 'r').readlines.each { |line|
291
- puts "#{counter} #{line}"
292
- counter = counter + 1
293
- }
294
- puts "=== lldb script ==="
295
- end
296
-
297
- # Forcing a timeout. Do not retry here. If lldb is hanging,
298
- # RunLoop::Core.run* needs to be called again. Put another way,
299
- # instruments and lldb must be terminated.
300
- Retriable.retriable({:tries => 1, :timeout => 12, :interval => 1}) do
301
- raw_lldb_output = `xcrun lldb -s #{lldb_script}`
302
- if ENV['DEBUG'] == '1'
303
- puts raw_lldb_output
304
- end
305
- end
306
- end
307
249
  rescue TimeoutError => e
308
250
  if ENV['DEBUG'] == '1'
309
251
  puts "Failed to launch."
310
252
  puts "#{e}: #{e && e.message}"
311
- if raw_lldb_output
312
- puts "LLDB OUTPUT: #{raw_lldb_output}"
313
- end
314
253
  end
315
254
  raise TimeoutError, "Time out waiting for UIAutomation run-loop to Start. \n Logfile #{log_file} \n\n #{File.read(log_file)}\n"
316
255
  end
317
256
 
318
- after = Time.now()
319
-
320
257
  if ENV['DEBUG']=='1'
321
- puts "Launching took #{after-before} seconds"
258
+ puts "Launching took #{Time.now-before} seconds"
259
+ end
260
+
261
+ dylib_path = self.dylib_path_from_options(merged_options)
262
+
263
+ if dylib_path
264
+ RunLoop::LLDB.kill_lldb_processes
265
+ app = RunLoop::App.new(options[:app])
266
+ lldb = RunLoop::DylibInjector.new(app.executable_name, dylib_path)
267
+ lldb.retriable_inject_dylib
322
268
  end
323
269
 
324
270
  run_loop
@@ -392,15 +338,6 @@ module RunLoop
392
338
  dylib_path
393
339
  end
394
340
 
395
- def self.find_cf_bundle_executable(bundle_dir_or_bundle_id)
396
- unless File.directory?(bundle_dir_or_bundle_id)
397
- raise "Injecting dylibs currently only works with simulator and app bundles"
398
- end
399
- info_plist = Dir[File.join(bundle_dir_or_bundle_id, 'Info.plist')].first
400
- raise "Unable to find Info.plist in #{bundle_dir_or_bundle_id}" if info_plist.nil?
401
- `/usr/libexec/PlistBuddy -c "Print :CFBundleExecutable" "#{info_plist}"`.strip
402
- end
403
-
404
341
  # Returns the a default simulator to target. This default needs to be one
405
342
  # that installed by default in the current Xcode version.
406
343
  #
@@ -409,7 +346,9 @@ module RunLoop
409
346
  # @param [RunLoop::XCTools] xcode_tools Used to detect the current xcode
410
347
  # version.
411
348
  def self.default_simulator(xcode_tools=RunLoop::XCTools.new)
412
- if xcode_tools.xcode_version_gte_62?
349
+ if xcode_tools.xcode_version_gte_63?
350
+ 'iPhone 5s (8.3 Simulator)'
351
+ elsif xcode_tools.xcode_version_gte_62?
413
352
  'iPhone 5s (8.2 Simulator)'
414
353
  elsif xcode_tools.xcode_version_gte_61?
415
354
  'iPhone 5s (8.1 Simulator)'
@@ -615,40 +554,6 @@ module RunLoop
615
554
  RunLoop::Instruments.new.instruments_pids(&block)
616
555
  end
617
556
 
618
- def self.instruments_command_prefix(udid, results_dir_trace)
619
- instruments_path = 'xcrun instruments'
620
- if udid
621
- instruments_path = "#{instruments_path} -w \"#{udid}\""
622
- end
623
- instruments_path << " -D \"#{results_dir_trace}\"" if results_dir_trace
624
- instruments_path
625
- end
626
-
627
-
628
- def self.instruments_command(options, xctools=RunLoop::XCTools.new)
629
- udid = options[:udid]
630
- results_dir_trace = options[:results_dir_trace]
631
- bundle_dir_or_bundle_id = options[:bundle_dir_or_bundle_id]
632
- results_dir = options[:results_dir]
633
- script = options[:script]
634
- log_file = options[:log_file]
635
- args= options[:args] || []
636
-
637
- instruments_prefix = instruments_command_prefix(udid, results_dir_trace)
638
- cmd = [
639
- instruments_prefix,
640
- '-t', "\"#{automation_template(xctools)}\"",
641
- "\"#{bundle_dir_or_bundle_id}\"",
642
- '-e', 'UIARESULTSPATH', results_dir,
643
- '-e', 'UIASCRIPT', script,
644
- args.join(' ')
645
- ]
646
- if log_file
647
- cmd << "&> #{log_file}"
648
- end
649
- cmd
650
- end
651
-
652
557
  def self.automation_template(xctools, candidate = ENV['TRACE_TEMPLATE'])
653
558
  unless candidate && File.exist?(candidate)
654
559
  candidate = default_tracetemplate xctools
@@ -710,22 +615,6 @@ module RunLoop
710
615
  def self.instruments_pids
711
616
  RunLoop::Instruments.new.instruments_pids
712
617
  end
713
-
714
- # @todo This is a WIP
715
- # @todo Needs rspec test
716
- def self.ensure_lldb_not_running
717
- descripts = `xcrun ps x -o pid,command | grep "lldb" | grep -v grep`.strip.split("\n")
718
- descripts.each do |process_desc|
719
- pid = process_desc.split(' ').first
720
- Open3.popen3("xcrun kill -9 #{pid} && xcrun wait #{pid}") do |_, stdout, stderr, _|
721
- out = stdout.read.strip
722
- err = stderr.read.strip
723
- next if out.to_s.empty? and err.to_s.empty?
724
- # there lots of 'ownership' problems trying to kill the lldb process
725
- #puts "kill process '#{pid}' => stdout: '#{out}' | stderr: '#{err}'"
726
- end
727
- end
728
- end
729
618
  end
730
619
 
731
620
  def self.default_script_for_uia_strategy(uia_strategy)
@@ -4,17 +4,29 @@ module RunLoop
4
4
  attr_reader :name
5
5
  attr_reader :version
6
6
  attr_reader :udid
7
+ attr_reader :state
7
8
 
8
- def initialize(name, version, udid)
9
+ # Create a new device.
10
+ #
11
+ # @param [String] name The name of the device. For sims this should be
12
+ # 'iPhone 5s' and for physical devices it will be the name the user gave
13
+ # to the device.
14
+ # @param [String, RunLoop::Version] version The iOS version that is running
15
+ # on the device. Can be a string or a Version instance.
16
+ # @param [String] udid The device identifier.
17
+ # @param [String] state (nil) This a simulator only value. It refers to
18
+ # the Booted/Shutdown/Creating state of the simulator. For pre-Xcode 6
19
+ # simulators, this value should be nil.
20
+ def initialize(name, version, udid, state=nil)
9
21
  @name = name
22
+ @udid = udid
23
+ @state = state
10
24
 
11
25
  if version.is_a? String
12
26
  @version = RunLoop::Version.new version
13
27
  else
14
28
  @version = version
15
29
  end
16
-
17
- @udid = udid
18
30
  end
19
31
 
20
32
  # Returns and instruments-ready device identifier that is a suitable value
@@ -0,0 +1,125 @@
1
+ module RunLoop
2
+
3
+ # @!visibility private
4
+ #
5
+ # This is experimental.
6
+ #
7
+ # Injects dylibs into running executables using lldb.
8
+ class DylibInjector
9
+
10
+ # @!attribute [r] process_name
11
+ # The name of the process to inject the dylib into. This should be obtained
12
+ # by inspecting the Info.plist in the app bundle.
13
+ # @return [String] The process_name
14
+ attr_reader :process_name
15
+
16
+ # @!attribute [r] dylib_path
17
+ # The path to the dylib that is to be injected.
18
+ # @return [String] The dylib_path
19
+ attr_reader :dylib_path
20
+
21
+ # Create a new dylib injector.
22
+ # @param [String] process_name The name of the process to inject the dylib
23
+ # into. This should be obtained by inspecting the Info.plist in the app
24
+ # bundle.
25
+ # @param [String] dylib_path The path the dylib to inject.
26
+ def initialize(process_name, dylib_path)
27
+ @process_name = process_name
28
+ @dylib_path = dylib_path
29
+ end
30
+
31
+ # Injects a dylib into a a currently running process.
32
+ def inject_dylib
33
+ debug_logging = RunLoop::Environment.debug?
34
+ puts "Starting lldb." if debug_logging
35
+
36
+ stderr_output = nil
37
+ lldb_status = nil
38
+ lldb_start_time = Time.now
39
+ Open3.popen3('sh') do |stdin, stdout, stderr, process_status|
40
+ stdin.puts 'xcrun lldb --no-lldbinit<<EOF'
41
+ stdin.puts "process attach -n '#{@process_name}'"
42
+ stdin.puts "expr (void*)dlopen(\"#{@dylib_path}\", 0x2)"
43
+ stdin.puts 'detach'
44
+ stdin.puts 'exit'
45
+ stdin.puts 'EOF'
46
+ stdin.close
47
+
48
+ puts "#{stdout.read}" if debug_logging
49
+
50
+ lldb_status = process_status
51
+ stderr_output = stderr.read.strip
52
+ end
53
+
54
+ pid = lldb_status.pid
55
+ exit_status = lldb_status.value.exitstatus
56
+
57
+ if stderr_output == ''
58
+ if debug_logging
59
+ puts "lldb '#{pid}' exited with value '#{exit_status}'."
60
+ puts "Took #{Time.now-lldb_start_time} for lldb to inject calabash dylib."
61
+ end
62
+ else
63
+ puts "#{stderr_output}"
64
+ if debug_logging
65
+ puts "lldb '#{pid}' exited with value '#{exit_status}'."
66
+ puts "lldb tried for #{Time.now-lldb_start_time} to inject calabash dylib before giving up."
67
+ end
68
+ end
69
+
70
+ stderr_output == ''
71
+ end
72
+
73
+ def inject_dylib_with_timeout(timeout)
74
+ success = false
75
+ Timeout.timeout(timeout) do
76
+ success = inject_dylib
77
+ end
78
+ success
79
+ end
80
+
81
+ def retriable_inject_dylib(options={})
82
+ merged_options = RETRIABLE_OPTIONS.merge(options)
83
+
84
+ debug_logging = RunLoop::Environment.debug?
85
+
86
+ on_retry = Proc.new do |_, try, elapsed_time, next_interval|
87
+ if debug_logging
88
+ # Retriable 2.0
89
+ if elapsed_time && next_interval
90
+ puts "LLDB: attempt #{try} failed in '#{elapsed_time}'; will retry in '#{next_interval}'"
91
+ else
92
+ puts "LLDB: attempt #{try} failed; will retry in '#{merged_options[:interval]}'"
93
+ end
94
+ end
95
+ RunLoop::LLDB.kill_lldb_processes
96
+ RunLoop::ProcessWaiter.new('lldb').wait_for_none
97
+ end
98
+
99
+ # For some reason, :timeout does not work here - the lldb process can
100
+ # hang indefinitely. Seems to work when
101
+ Retriable.retriable({:tries => merged_options[:retries],
102
+ # Retriable 2.0
103
+ :interval => merged_options[:interval],
104
+ :base_interval => merged_options[:interval],
105
+ :on_retry => on_retry}) do
106
+ #unless inject_dylib_with_timeout merged_options[:timeout]
107
+ unless inject_dylib_with_timeout merged_options[:timeout]
108
+ raise RuntimeError, "Could not inject dylib"
109
+ end
110
+ end
111
+ true
112
+ end
113
+
114
+ private
115
+
116
+ RETRIABLE_OPTIONS =
117
+ {
118
+ :retries => 3,
119
+ :timeout => 10,
120
+ # Retriable 2.0 replaces :interval with :base_interval
121
+ :interval => 2,
122
+ :base_interval => 2
123
+ }
124
+ end
125
+ end