run_loop 1.2.7 → 1.2.8

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: 2654e1672654ee6ae0af23cb582fec43864992df
4
- data.tar.gz: d481bf34a098f76d83d94826575b021ace8232ca
3
+ metadata.gz: 253c24d7121ffc84824a1d3e7a5e3c79d14dfcd8
4
+ data.tar.gz: d22bc4efbace3cbcd82e3ab023515984beff9618
5
5
  SHA512:
6
- metadata.gz: db03ba41012e7bf8f5d4cefa4833a36c478d16f9359339e415eae29dfa4faf62e6fb3491ad301d872941bcb30a1ebb71c1aabd55f95ceb01210119f2f57f00ff
7
- data.tar.gz: 30e5ec4733c0ef6da15aa590c9390c46e3a9a6fa5ddb08f4e609602ace3a05579ea76d538a132f4667708ea1d4cf83307aeb243573b90635cbd6609c8b0e21d3
6
+ metadata.gz: 716a2225ff8d2ff978b28852d803c84ba97ac05a75a0ce74a714f910ef35317f9322f4818c8812f88b72bafcda6542e3df1a82c87dfe73137e8737a687aeb61f
7
+ data.tar.gz: 2750db0951ae73dd859fac716b138ffd02972e42f2e1aec600f9faf4bb10addf7e19d334488fed3f59f3476aacbbb87f080bf8eca81a4263166fa2f3c58262e6
data/lib/run_loop.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  require 'run_loop/environment'
2
+ require 'run_loop/logging'
2
3
  require 'run_loop/process_terminator'
3
4
  require 'run_loop/process_waiter'
4
5
  require 'run_loop/lldb'
5
6
  require 'run_loop/dylib_injector'
7
+ require 'run_loop/fifo'
6
8
  require 'run_loop/core'
7
9
  require 'run_loop/version'
8
10
  require 'run_loop/xctools'
@@ -15,3 +17,174 @@ require 'run_loop/lipo'
15
17
  require 'run_loop/host_cache'
16
18
  require 'run_loop/monkey_patch'
17
19
  require 'run_loop/simctl/bridge'
20
+
21
+ module RunLoop
22
+
23
+ class TimeoutError < RuntimeError
24
+ end
25
+
26
+ class WriteFailedError < RuntimeError
27
+ end
28
+
29
+ def self.run(options={})
30
+
31
+ if RunLoop::Instruments.new.instruments_app_running?
32
+ msg =
33
+ [
34
+ "Please quit the Instruments.app.",
35
+ "If Instruments.app is open, the instruments command line",
36
+ "tool cannot take control of your application."
37
+ ]
38
+ raise msg.join("\n")
39
+ end
40
+
41
+ uia_strategy = options[:uia_strategy]
42
+ if options[:script]
43
+ script = validate_script(options[:script])
44
+ else
45
+ if uia_strategy
46
+ script = default_script_for_uia_strategy(uia_strategy)
47
+ else
48
+ if options[:calabash_lite]
49
+ uia_strategy = :host
50
+ script = Core.script_for_key(:run_loop_host)
51
+ else
52
+ uia_strategy = :preferences
53
+ script = default_script_for_uia_strategy(uia_strategy)
54
+ end
55
+ end
56
+ end
57
+ # At this point, 'script' has been chosen, but uia_strategy might not
58
+ unless uia_strategy
59
+ desired_script = options[:script]
60
+ if desired_script.is_a?(String) #custom path to script
61
+ uia_strategy = :host
62
+ elsif desired_script == :run_loop_host
63
+ uia_strategy = :host
64
+ elsif desired_script == :run_loop_fast_uia
65
+ uia_strategy = :preferences
66
+ elsif desired_script == :run_loop_shared_element
67
+ uia_strategy = :shared_element
68
+ else
69
+ raise "Inconsistent state: desired script #{desired_script} has not uia_strategy"
70
+ end
71
+ end
72
+ # At this point script and uia_strategy selected
73
+
74
+ options[:script] = script
75
+ options[:uia_strategy] = uia_strategy
76
+
77
+ Core.run_with_options(options)
78
+ end
79
+
80
+ def self.send_command(run_loop, cmd, options={timeout: 60}, num_retries=0, last_error=nil)
81
+ if num_retries > 3
82
+ if last_error
83
+ raise last_error
84
+ else
85
+ raise "Max retries exceeded #{num_retries} > 3. No error recorded."
86
+ end
87
+ end
88
+
89
+ if options.is_a?(Numeric)
90
+ options = {timeout: options}
91
+ end
92
+
93
+ if not cmd.is_a?(String)
94
+ raise "Illegal command #{cmd} (must be a string)"
95
+ end
96
+
97
+ if not options.is_a?(Hash)
98
+ raise "Illegal options #{options} (must be a Hash (or number for compatibility))"
99
+ end
100
+
101
+ timeout = options[:timeout] || 60
102
+ logger = options[:logger]
103
+ interrupt_retry_timeout = options[:interrupt_retry_timeout] || 25
104
+
105
+ expected_index = run_loop[:index]
106
+ result = nil
107
+ begin
108
+ expected_index = Core.write_request(run_loop, cmd, logger)
109
+ rescue RunLoop::WriteFailedError, Errno::EINTR => write_error
110
+ # Attempt recover from interrupt by attempting to read result (assuming write went OK)
111
+ # or retry if attempted read result fails
112
+ run_loop[:index] = expected_index # restore expected index in case it changed
113
+ log_info(logger, "Core.write_request failed: #{write_error}. Attempting recovery...")
114
+ log_info(logger, "Attempting read in case the request was received... Please wait (#{interrupt_retry_timeout})...")
115
+ begin
116
+ Timeout::timeout(interrupt_retry_timeout, TimeoutError) do
117
+ result = Core.read_response(run_loop, expected_index)
118
+ end
119
+ # Update run_loop expected index since we succeeded in reading the index
120
+ run_loop[:index] = expected_index + 1
121
+ log_info(logger, "Did read response for interrupted request of index #{expected_index}... Proceeding.")
122
+ return result
123
+ rescue TimeoutError => _
124
+ log_info(logger, "Read did not result in a response for index #{expected_index}... Retrying send_command...")
125
+ return send_command(run_loop, cmd, options, num_retries+1, write_error)
126
+ end
127
+ end
128
+
129
+
130
+ begin
131
+ Timeout::timeout(timeout, TimeoutError) do
132
+ result = Core.read_response(run_loop, expected_index)
133
+ end
134
+ rescue TimeoutError => _
135
+ raise TimeoutError, "Time out waiting for UIAutomation run-loop for command #{cmd}. Waiting for index:#{expected_index}"
136
+ end
137
+
138
+ result
139
+ end
140
+
141
+ def self.stop(run_loop, out=Dir.pwd)
142
+ return if run_loop.nil?
143
+ results_dir = run_loop[:results_dir]
144
+ dest = out
145
+
146
+ RunLoop::Instruments.new.kill_instruments
147
+
148
+ FileUtils.mkdir_p(dest)
149
+ if results_dir
150
+ pngs = Dir.glob(File.join(results_dir, 'Run 1', '*.png'))
151
+ else
152
+ pngs = []
153
+ end
154
+ FileUtils.cp(pngs, dest) if pngs and pngs.length > 0
155
+ end
156
+
157
+ def self.default_script_for_uia_strategy(uia_strategy)
158
+ case uia_strategy
159
+ when :preferences
160
+ Core.script_for_key(:run_loop_fast_uia)
161
+ when :host
162
+ Core.script_for_key(:run_loop_host)
163
+ when :shared_element
164
+ Core.script_for_key(:run_loop_shared_element)
165
+ else
166
+ Core.script_for_key(:run_loop_basic)
167
+ end
168
+ end
169
+
170
+ def self.validate_script(script)
171
+ if script.is_a?(String)
172
+ unless File.exist?(script)
173
+ raise "Unable to find file: #{script}"
174
+ end
175
+ elsif script.is_a?(Symbol)
176
+ script = Core.script_for_key(script)
177
+ unless script
178
+ raise "Unknown script for symbol: #{script}. Options: #{Core::SCRIPTS.keys.join(', ')}"
179
+ end
180
+ else
181
+ raise "Script must be a symbol or path: #{script}"
182
+ end
183
+ script
184
+ end
185
+
186
+ def self.log_info(*args)
187
+ RunLoop::Logging.log_info(*args)
188
+ end
189
+
190
+ end
data/lib/run_loop/core.rb CHANGED
@@ -8,12 +8,6 @@ require 'ap'
8
8
 
9
9
  module RunLoop
10
10
 
11
- class TimeoutError < RuntimeError
12
- end
13
-
14
- class WriteFailedError < RuntimeError
15
- end
16
-
17
11
  module Core
18
12
 
19
13
  START_DELIMITER = "OUTPUT_JSON:\n"
@@ -36,7 +30,7 @@ module RunLoop
36
30
  end
37
31
 
38
32
  def self.log_run_loop_options(options, xctools)
39
- return unless ENV['DEBUG'] == '1'
33
+ return unless RunLoop::Environment.debug?
40
34
  # Ignore :sim_control b/c it is a ruby object; printing is not useful.
41
35
  ignored_keys = [:sim_control]
42
36
  options_to_log = {}
@@ -49,7 +43,9 @@ module RunLoop
49
43
  # RunLoop::Version overrides '=='
50
44
  options_to_log[:xcode] = xctools.xcode_version.to_s
51
45
  options_to_log[:xcode_path] = xctools.xcode_developer_dir
52
- ap(options_to_log, {:sort_keys => true})
46
+ message = options_to_log.ai({:sort_keys => true})
47
+ logger = options[:logger]
48
+ RunLoop::Logging.log_debug(logger, "\n" + message)
53
49
  end
54
50
 
55
51
  # @deprecated since 1.0.0
@@ -78,10 +74,10 @@ module RunLoop
78
74
 
79
75
  def self.detect_connected_device
80
76
  begin
81
- Timeout::timeout(1, TimeoutError) do
77
+ Timeout::timeout(1, RunLoop::TimeoutError) do
82
78
  return `#{File.join(scripts_path, 'udidetect')}`.chomp
83
79
  end
84
- rescue TimeoutError => _
80
+ rescue RunLoop::TimeoutError => _
85
81
  `killall udidetect &> /dev/null`
86
82
  end
87
83
  nil
@@ -104,6 +100,7 @@ module RunLoop
104
100
  # @raise [RunLoop::IncompatibleArchitecture] Raises an error if the
105
101
  # application binary is not compatible with the target simulator.
106
102
  def self.expect_compatible_simulator_architecture(launch_options, sim_control)
103
+ logger = launch_options[:logger]
107
104
  if sim_control.xcode_version_gte_6?
108
105
  sim_identifier = launch_options[:udid]
109
106
  simulator = sim_control.simulators.find do |simulator|
@@ -117,14 +114,10 @@ module RunLoop
117
114
 
118
115
  lipo = RunLoop::Lipo.new(launch_options[:bundle_dir_or_bundle_id])
119
116
  lipo.expect_compatible_arch(simulator)
120
- if ENV['DEBUG'] == '1'
121
- puts "Simulator instruction set '#{simulator.instruction_set}' is compatible with #{lipo.info}"
122
- end
117
+ RunLoop::Logging.log_debug(logger, "Simulator instruction set '#{simulator.instruction_set}' is compatible with #{lipo.info}")
123
118
  true
124
119
  else
125
- if ENV['DEBUG'] == '1'
126
- puts "Xcode #{sim_control.xctools.xcode_version} detected; skipping simulator architecture check."
127
- end
120
+ RunLoop::Logging.log_debug(logger, "Xcode #{sim_control.xctools.xcode_version} detected; skipping simulator architecture check.")
128
121
  false
129
122
  end
130
123
  end
@@ -132,6 +125,7 @@ module RunLoop
132
125
  def self.run_with_options(options)
133
126
  before = Time.now
134
127
 
128
+ logger = options[:logger]
135
129
  sim_control ||= options[:sim_control] || RunLoop::SimControl.new
136
130
  xctools ||= options[:xctools] || sim_control.xctools
137
131
 
@@ -170,7 +164,9 @@ module RunLoop
170
164
  uia_strategy = options[:uia_strategy]
171
165
  if uia_strategy == :host
172
166
  create_uia_pipe(repl_path)
173
- RunLoop::HostCache.default.clear
167
+ RunLoop::HostCache.default.clear unless RunLoop::Environment.xtc?
168
+ else
169
+ FileUtils.touch repl_path
174
170
  end
175
171
 
176
172
  cal_script = File.join(SCRIPTS_PATH, 'calabash_script_uia.js')
@@ -189,9 +185,7 @@ module RunLoop
189
185
  log_file ||= File.join(results_dir, 'run_loop.out')
190
186
 
191
187
  after = Time.now
192
- if ENV['DEBUG'] == '1'
193
- puts "Preparation took #{after-before} seconds"
194
- end
188
+ RunLoop::Logging.log_debug(logger, "Preparation took #{after-before} seconds")
195
189
 
196
190
  discovered_options =
197
191
  {
@@ -215,7 +209,7 @@ module RunLoop
215
209
 
216
210
  automation_template = automation_template(xctools)
217
211
 
218
- log_header("Starting on #{device_target} App: #{bundle_dir_or_bundle_id}")
212
+ RunLoop::Logging.log_header(logger, "Starting on #{device_target} App: #{bundle_dir_or_bundle_id}")
219
213
 
220
214
  pid = instruments.spawn(automation_template, merged_options, log_file)
221
215
 
@@ -241,22 +235,24 @@ module RunLoop
241
235
  options[:validate_channel].call(run_loop, 0, uia_timeout)
242
236
  else
243
237
  cmd = "UIALogger.logMessage('Listening for run loop commands')"
244
- File.open(repl_path, 'w') { |file| file.puts "0:#{cmd}" }
245
- Timeout::timeout(timeout, TimeoutError) do
238
+ begin
239
+ fifo_timeout = options[:fifo_timeout] || 30
240
+ RunLoop::Fifo.write(repl_path, "0:#{cmd}", timeout: fifo_timeout)
241
+ rescue RunLoop::Fifo::NoReaderConfiguredError,
242
+ RunLoop::Fifo::WriteTimedOut => e
243
+ RunLoop::Logging.log_debug(logger, "Error while writing to fifo. #{e}")
244
+ raise RunLoop::TimeoutError.new("Error while writing to fifo. #{e}")
245
+ end
246
+ Timeout::timeout(timeout, RunLoop::TimeoutError) do
246
247
  read_response(run_loop, 0, uia_timeout)
247
248
  end
248
249
  end
249
- rescue TimeoutError => e
250
- if ENV['DEBUG'] == '1'
251
- puts "Failed to launch."
252
- puts "#{e}: #{e && e.message}"
253
- end
254
- raise TimeoutError, "Time out waiting for UIAutomation run-loop to Start. \n Logfile #{log_file} \n\n #{File.read(log_file)}\n"
250
+ rescue RunLoop::TimeoutError => e
251
+ RunLoop::Logging.log_debug(logger, "Failed to launch. #{e}: #{e && e.message}")
252
+ raise RunLoop::TimeoutError, "Time out waiting for UIAutomation run-loop #{e}. \n Logfile #{log_file} \n\n #{File.read(log_file)}\n"
255
253
  end
256
254
 
257
- if ENV['DEBUG']=='1'
258
- puts "Launching took #{Time.now-before} seconds"
259
- end
255
+ RunLoop::Logging.log_debug(logger, "Launching took #{Time.now-before} seconds")
260
256
 
261
257
  dylib_path = self.dylib_path_from_options(merged_options)
262
258
 
@@ -413,7 +409,7 @@ module RunLoop
413
409
 
414
410
  def self.create_uia_pipe(repl_path)
415
411
  begin
416
- Timeout::timeout(5, TimeoutError) do
412
+ Timeout::timeout(5, RunLoop::TimeoutError) do
417
413
  loop do
418
414
  begin
419
415
  FileUtils.rm_f(repl_path)
@@ -424,8 +420,8 @@ module RunLoop
424
420
  end
425
421
  end
426
422
  end
427
- rescue TimeoutError => _
428
- raise TimeoutError, 'Unable to create pipe (mkfifo failed)'
423
+ rescue RunLoop::TimeoutError => _
424
+ raise RunLoop::TimeoutError, 'Unable to create pipe (mkfifo failed)'
429
425
  end
430
426
  end
431
427
 
@@ -437,33 +433,37 @@ module RunLoop
437
433
  repl_path = run_loop[:repl_path]
438
434
  index = run_loop[:index]
439
435
  cmd_str = "#{index}:#{escape_host_command(cmd)}"
440
- should_log = (ENV['DEBUG'] == '1')
441
- RunLoop.log_info(logger, cmd_str) if should_log
436
+ RunLoop::Logging.log_debug(logger, cmd_str)
442
437
  write_succeeded = false
443
438
  2.times do |i|
444
- RunLoop.log_info(logger, "Trying write of command #{cmd_str} at index #{index}") if should_log
445
- File.open(repl_path, 'w') { |f| f.puts(cmd_str) }
446
- write_succeeded = validate_index_written(run_loop, index, logger)
439
+ RunLoop::Logging.log_debug(logger, "Trying write of command #{cmd_str} at index #{index}")
440
+ begin
441
+ RunLoop::Fifo.write(repl_path, cmd_str)
442
+ write_succeeded = validate_index_written(run_loop, index, logger)
443
+ rescue RunLoop::Fifo::NoReaderConfiguredError,
444
+ RunLoop::Fifo::WriteTimedOut => e
445
+ RunLoop::Logging.log_debug(logger, "Error while writing command (retry count #{i}). #{e}")
446
+ end
447
447
  break if write_succeeded
448
448
  end
449
449
  unless write_succeeded
450
- RunLoop.log_info(logger, 'Failing...Raising RunLoop::WriteFailedError') if should_log
450
+ RunLoop::Logging.log_debug(logger, 'Failing...Raising RunLoop::WriteFailedError')
451
451
  raise RunLoop::WriteFailedError.new("Trying write of command #{cmd_str} at index #{index}")
452
452
  end
453
453
  run_loop[:index] = index + 1
454
- RunLoop::HostCache.default.write(run_loop)
454
+ RunLoop::HostCache.default.write(run_loop) unless RunLoop::Environment.xtc?
455
455
  index
456
456
  end
457
457
 
458
458
  def self.validate_index_written(run_loop, index, logger)
459
459
  begin
460
- Timeout::timeout(10, TimeoutError) do
460
+ Timeout::timeout(10, RunLoop::TimeoutError) do
461
461
  Core.read_response(run_loop, index, 10, 'last_index')
462
462
  end
463
- RunLoop.log_info(logger, "validate index written for index #{index} ok")
463
+ RunLoop::Logging.log_debug(logger, "validate index written for index #{index} ok")
464
464
  return true
465
- rescue TimeoutError => _
466
- RunLoop.log_info(logger, "validate index written for index #{index} failed. Retrying.")
465
+ rescue RunLoop::TimeoutError => _
466
+ RunLoop::Logging.log_debug(logger, "validate index written for index #{index} failed. Retrying.")
467
467
  return false
468
468
  end
469
469
  end
@@ -485,9 +485,7 @@ module RunLoop
485
485
  next
486
486
  end
487
487
 
488
-
489
488
  size = File.size(log_file)
490
-
491
489
  output = File.read(log_file, size-offset, offset)
492
490
 
493
491
  if /AXError: Could not auto-register for pid status change/.match(output)
@@ -495,10 +493,10 @@ module RunLoop
495
493
  $stderr.puts "\n\n****** Accessibility is not enabled on device/simulator, please enable it *** \n\n"
496
494
  $stderr.flush
497
495
  end
498
- raise TimeoutError.new('AXError: Could not auto-register for pid status change')
496
+ raise RunLoop::TimeoutError.new('AXError: Could not auto-register for pid status change')
499
497
  end
500
498
  if /Automation Instrument ran into an exception/.match(output)
501
- raise TimeoutError.new('Exception while running script')
499
+ raise RunLoop::TimeoutError.new('Exception while running script')
502
500
  end
503
501
  index_if_found = output.index(START_DELIMITER)
504
502
  if ENV['DEBUG_READ']=='1'
@@ -545,7 +543,7 @@ module RunLoop
545
543
  end
546
544
 
547
545
  run_loop[:initial_offset] = offset
548
- RunLoop::HostCache.default.write(run_loop)
546
+ RunLoop::HostCache.default.write(run_loop) unless RunLoop::Environment.xtc?
549
547
  result
550
548
  end
551
549
 
@@ -588,20 +586,6 @@ module RunLoop
588
586
  raise msgs.join("\n")
589
587
  end
590
588
 
591
- def self.log(message)
592
- if ENV['DEBUG']=='1'
593
- puts "#{Time.now } #{message}"
594
- $stdout.flush
595
- end
596
- end
597
-
598
- def self.log_header(message)
599
- if ENV['DEBUG']=='1'
600
- puts "\n\e[#{35}m### #{message} ###\e[0m"
601
- $stdout.flush
602
- end
603
- end
604
-
605
589
  # @deprecated 1.0.5
606
590
  def self.ensure_instruments_not_running!
607
591
  RunLoop::Instruments.new.kill_instruments
@@ -617,170 +601,4 @@ module RunLoop
617
601
  end
618
602
  end
619
603
 
620
- def self.default_script_for_uia_strategy(uia_strategy)
621
- case uia_strategy
622
- when :preferences
623
- Core.script_for_key(:run_loop_fast_uia)
624
- when :host
625
- Core.script_for_key(:run_loop_host)
626
- when :shared_element
627
- Core.script_for_key(:run_loop_shared_element)
628
- else
629
- Core.script_for_key(:run_loop_basic)
630
- end
631
- end
632
-
633
- def self.run(options={})
634
-
635
- if RunLoop::Instruments.new.instruments_app_running?
636
- msg =
637
- [
638
- "Please quit the Instruments.app.",
639
- "If Instruments.app is open, the instruments command line",
640
- "tool cannot take control of your application."
641
- ]
642
- raise msg.join("\n")
643
- end
644
-
645
- uia_strategy = options[:uia_strategy]
646
- if options[:script]
647
- script = validate_script(options[:script])
648
- else
649
- if uia_strategy
650
- script = default_script_for_uia_strategy(uia_strategy)
651
- else
652
- if options[:calabash_lite]
653
- uia_strategy = :host
654
- script = Core.script_for_key(:run_loop_host)
655
- else
656
- uia_strategy = :preferences
657
- script = default_script_for_uia_strategy(uia_strategy)
658
- end
659
- end
660
- end
661
- # At this point, 'script' has been chosen, but uia_strategy might not
662
- unless uia_strategy
663
- desired_script = options[:script]
664
- if desired_script.is_a?(String) #custom path to script
665
- uia_strategy = :host
666
- elsif desired_script == :run_loop_host
667
- uia_strategy = :host
668
- elsif desired_script == :run_loop_fast_uia
669
- uia_strategy = :preferences
670
- elsif desired_script == :run_loop_shared_element
671
- uia_strategy = :shared_element
672
- else
673
- raise "Inconsistent state: desired script #{desired_script} has not uia_strategy"
674
- end
675
- end
676
- # At this point script and uia_strategy selected
677
-
678
- options[:script] = script
679
- options[:uia_strategy] = uia_strategy
680
-
681
- Core.run_with_options(options)
682
- end
683
-
684
- def self.send_command(run_loop, cmd, options={timeout: 60}, num_retries=0, last_error=nil)
685
- if num_retries > 3
686
- if last_error
687
- raise last_error
688
- else
689
- raise "Max retries exceeded #{num_retries} > 3. No error recorded."
690
- end
691
- end
692
-
693
- if options.is_a?(Numeric)
694
- options = {timeout: options}
695
- end
696
-
697
- if not cmd.is_a?(String)
698
- raise "Illegal command #{cmd} (must be a string)"
699
- end
700
-
701
- if not options.is_a?(Hash)
702
- raise "Illegal options #{options} (must be a Hash (or number for compatibility))"
703
- end
704
-
705
- timeout = options[:timeout] || 60
706
- logger = options[:logger]
707
- interrupt_retry_timeout = options[:interrupt_retry_timeout] || 25
708
-
709
- expected_index = run_loop[:index]
710
- result = nil
711
- begin
712
- expected_index = Core.write_request(run_loop, cmd, logger)
713
- rescue RunLoop::WriteFailedError, Errno::EINTR => write_error
714
- # Attempt recover from interrupt by attempting to read result (assuming write went OK)
715
- # or retry if attempted read result fails
716
- run_loop[:index] = expected_index # restore expected index in case it changed
717
- log_info(logger, "Core.write_request failed: #{write_error}. Attempting recovery...")
718
- log_info(logger, "Attempting read in case the request was received... Please wait (#{interrupt_retry_timeout})...")
719
- begin
720
- Timeout::timeout(interrupt_retry_timeout, TimeoutError) do
721
- result = Core.read_response(run_loop, expected_index)
722
- end
723
- # Update run_loop expected index since we succeeded in reading the index
724
- run_loop[:index] = expected_index + 1
725
- log_info(logger, "Did read response for interrupted request of index #{expected_index}... Proceeding.")
726
- return result
727
- rescue TimeoutError => _
728
- log_info(logger, "Read did not result in a response for index #{expected_index}... Retrying send_command...")
729
- return send_command(run_loop, cmd, options, num_retries+1, write_error)
730
- end
731
- end
732
-
733
-
734
- begin
735
- Timeout::timeout(timeout, TimeoutError) do
736
- result = Core.read_response(run_loop, expected_index)
737
- end
738
- rescue TimeoutError => _
739
- raise TimeoutError, "Time out waiting for UIAutomation run-loop for command #{cmd}. Waiting for index:#{expected_index}"
740
- end
741
-
742
- result
743
- end
744
-
745
- def self.stop(run_loop, out=Dir.pwd)
746
- return if run_loop.nil?
747
- results_dir = run_loop[:results_dir]
748
- dest = out
749
-
750
- RunLoop::Instruments.new.kill_instruments
751
-
752
- FileUtils.mkdir_p(dest)
753
- if results_dir
754
- pngs = Dir.glob(File.join(results_dir, 'Run 1', '*.png'))
755
- else
756
- pngs = []
757
- end
758
- FileUtils.cp(pngs, dest) if pngs and pngs.length > 0
759
- end
760
-
761
-
762
- def self.validate_script(script)
763
- if script.is_a?(String)
764
- unless File.exist?(script)
765
- raise "Unable to find file: #{script}"
766
- end
767
- elsif script.is_a?(Symbol)
768
- script = Core.script_for_key(script)
769
- unless script
770
- raise "Unknown script for symbol: #{script}. Options: #{Core::SCRIPTS.keys.join(', ')}"
771
- end
772
- else
773
- raise "Script must be a symbol or path: #{script}"
774
- end
775
- script
776
- end
777
-
778
- def self.log_info(logger, message)
779
- msg = "#{Time.now}: #{message}"
780
- if logger && logger.respond_to?(:info)
781
- logger.info(msg)
782
- else
783
- puts msg if ENV['DEBUG'] == '1'
784
- end
785
- end
786
604
  end
@@ -11,5 +11,9 @@ module RunLoop
11
11
  def self.debug?
12
12
  ENV['DEBUG'] == '1'
13
13
  end
14
+
15
+ def self.xtc?
16
+ ENV['XAMARIN_TEST_CLOUD'] == '1'
17
+ end
14
18
  end
15
19
  end
@@ -0,0 +1,40 @@
1
+ require 'timeout'
2
+ module RunLoop
3
+ module Fifo
4
+ BUFFER_SIZE = 4096
5
+
6
+ class NoReaderConfiguredError < RuntimeError
7
+ end
8
+
9
+ class WriteTimedOut < RuntimeError
10
+ end
11
+
12
+ def self.write(pipe, msg, options={})
13
+ msg = "#{msg}\n"
14
+ timeout = options[:timeout] || 10
15
+ begin_at = Time.now
16
+ begin
17
+ open(pipe, File::WRONLY | File::NONBLOCK) do |pipe_io|
18
+ bytes_written = 0
19
+ bytes_to_write = msg.length
20
+ until bytes_written >= bytes_to_write do
21
+ begin
22
+ wrote = pipe_io.write_nonblock msg
23
+ bytes_written += wrote
24
+ msg = msg[wrote..-1]
25
+ rescue IO::WaitWritable, Errno::EINTR
26
+ timeout_left = timeout - (Time.now - begin_at)
27
+ raise WriteTimedOut if timeout_left <= 0
28
+ IO.select nil, [pipe_io], nil, timeout_left
29
+ end
30
+ end
31
+ end
32
+ rescue Errno::ENXIO
33
+ sleep(0.5)
34
+ timeout_left = timeout - (Time.now - begin_at)
35
+ raise NoReaderConfiguredError if timeout_left <= 0
36
+ retry
37
+ end
38
+ end
39
+ end
40
+ end
@@ -70,10 +70,8 @@ module RunLoop
70
70
  # @todo Is this jruby compatible?
71
71
  def spawn(automation_template, options, log_file)
72
72
  splat_args = spawn_arguments(automation_template, options)
73
- if ENV['DEBUG'] == '1'
74
- puts "#{Time.now} xcrun #{splat_args.join(' ')} >& #{log_file}"
75
- $stdout.flush
76
- end
73
+ logger = options[:logger]
74
+ RunLoop::Logging.log_debug(logger, "xcrun #{splat_args.join(' ')} >& #{log_file}")
77
75
  pid = Process.spawn('xcrun', *splat_args, {:out => log_file, :err => log_file})
78
76
  Process.detach(pid)
79
77
  pid.to_i
data/lib/run_loop/lldb.rb CHANGED
@@ -34,9 +34,7 @@ module RunLoop
34
34
  def self.kill_lldb_processes
35
35
  self.lldb_pids.each do |pid|
36
36
  unless self.kill_with_signal(pid, 'TERM')
37
- unless self.kill_with_signal(pid, 'QUIT')
38
- self.kill_with_signal(pid, 'KILL')
39
- end
37
+ self.kill_with_signal(pid, 'KILL')
40
38
  end
41
39
  end
42
40
  end
@@ -0,0 +1,36 @@
1
+ module RunLoop
2
+ class Logging
3
+
4
+ def self.log_info(logger, message)
5
+ log_level :info, logger, message
6
+ end
7
+
8
+ def self.log_debug(logger, message)
9
+ log_level :debug, logger, message
10
+ end
11
+
12
+ def self.log_header(logger, message)
13
+ msg = "\n\e[#{35}m### #{message} ###\e[0m"
14
+ if logger.respond_to?(:debug)
15
+ logger.debug(msg)
16
+ else
17
+ debug_puts(msg)
18
+ end
19
+ end
20
+
21
+ def self.log_level(level, logger, message)
22
+ level = level.to_sym
23
+ msg = "#{Time.now} [RunLoop:#{level}]: #{message}"
24
+ if logger.respond_to?(level)
25
+ logger.send(level, msg)
26
+ else
27
+ debug_puts(msg)
28
+ end
29
+ end
30
+
31
+ def self.debug_puts(msg)
32
+ puts msg if RunLoop::Environment.debug?
33
+ end
34
+
35
+ end
36
+ end
@@ -23,6 +23,44 @@ module RunLoop
23
23
  !pids.empty?
24
24
  end
25
25
 
26
+ # Wait for a number of process to start.
27
+ # @param [Integer] n The number of processes to wait for.
28
+ # @raise [ArgumentError] If n < 0
29
+ # @raise [ArgumentError] If n is not an Integer
30
+ def wait_for_n(n)
31
+ unless n.is_a?(Integer)
32
+ raise ArgumentError, "Expected #{n.class} to be #{1.class}"
33
+ end
34
+
35
+ unless n > 0
36
+ raise ArgumentError, "Expected #{n} to be > 0"
37
+ end
38
+
39
+ return true if pids.count == n
40
+
41
+ now = Time.now
42
+ poll_until = now + @options[:timeout]
43
+ delay = @options[:interval]
44
+ there_are_n = false
45
+ while Time.now < poll_until
46
+ there_are_n = pids.count == n
47
+ break if there_are_n
48
+ sleep delay
49
+ end
50
+
51
+ if RunLoop::Environment.debug?
52
+ plural = n > 1 ? "es" : ''
53
+ puts "Waited for #{Time.now - now} seconds for #{n} '#{process_name}' process#{plural} to start."
54
+ end
55
+
56
+ if @options[:raise_on_timeout] and !there_are_n
57
+ plural = n > 1 ? "es" : ''
58
+ raise "Waited #{@options[:timeout]} seconds for #{n} '#{process_name}' process#{plural} to start."
59
+ end
60
+ there_are_n
61
+ end
62
+
63
+
26
64
  # Wait for `process_name` to start.
27
65
  def wait_for_any
28
66
  return true if running_process?
@@ -762,6 +762,7 @@ module RunLoop
762
762
  # updated or if the directory was skipped (see code comments).
763
763
  # @raise [RuntimeError] If called when Xcode 6 is _not_ the active Xcode version.
764
764
  def enable_keyboard_in_sim_data_dir(sim_data_dir, sim_details_keyed_with_udid, opts={})
765
+
765
766
  unless xcode_version_gte_6?
766
767
  raise RuntimeError, 'it is illegal to call this method when the Xcode < 6 is the current Xcode version'
767
768
  end
@@ -1,5 +1,5 @@
1
1
  module RunLoop
2
- VERSION = '1.2.7'
2
+ VERSION = '1.2.8'
3
3
 
4
4
  # A model of a software release version that can be used to compare two versions.
5
5
  #
@@ -0,0 +1,7 @@
1
+ (function(){function m(){return h.frontMostApp()}function r(){return m().mainWindow()}function s(){return m().windows().toArray()}function n(){return m().keyboard()}function l(a,c){this.reason=a;this.a=c||"";this.message=this.toString()}function k(a){return!a||a instanceof UIAElementNil}function t(a,c){var b=c||[],e,d;if(k(a))return b;e=a.elements();for(var f=0,g=e.length;f<g;f+=1)d=e[f],b.push(d),t(d,b);return b}function q(a,c){for(var b=0,e=c.length;b<e;b+=1)a.push(c[b])}function u(a,c){var b=[];
2
+ if(k(c))return b;c instanceof this[a]&&b.push(c);for(var e=c.elements(),d=0,f=e.length;d<f;d+=1)q(b,u(a,e[d]));return b}function x(a,c){var b=null;if(a instanceof Array){if(3===a.length){var e=a[0],b=a[1],d=a[2];if("string"==typeof d)if(-1==d.indexOf("'"))d="'"+d+"'";else if(-1==d.indexOf('"'))d='"'+d+'"';else throw new l("Escaping for filters not supported yet.");b=c.withPredicate(e+" "+b+" "+d);return!k(b)}return!1}for(e in a)if(a.hasOwnProperty(e))if(b=a[e],"marked"==e){if(c.name()!=b&&c.label()!=
3
+ b&&(!c.value||c.value!=b))return!1}else if(b=c.withValueForKey(b,e),k(b))return!1;return!0}function v(a,c){if(c(a))return a;var b,e;if(k(a))return null;b=a.elements();for(var d=0,f=b.length;d<f;d+=1)if(e=b[d],v(e,c))return e;return null}function w(a){h.delay(a);return h}var g={},h=UIATarget.localTarget();h.setTimeout(0);l.prototype=error();l.prototype.toString=function(){var a="UIAutomationError[reason="+this.reason;0<this.a.length&&(a+=", details="+this.a);return a+"]"};g.sleep=w;g.query=function(a,
4
+ c){if(!c)return g.query(a,s());c instanceof UIAElement&&(c=[c]);var b=c,e=null,d=null,f=[],p,h,k,l;p=0;for(k=a.length;p<k;p+=1){e=a[p];h=0;for(l=b.length;h<l;h+=1)d=b[h],"string"===typeof e?"*"===e||"view"==e||"UIAElement"===e?q(f,t(d,[d])):q(f,u(e,d)):x(e,d)&&f.push(d);b=f;f=[]}return b};g.keyboard_visible=function(){return!k(n())};g.keyboard_enter_text=function(a,c){if(!g.keyboard_visible())throw new l("Keyboard not visible");c=c||{};if(c.unsafe)return n().typeString(a),!0;var b=v(r(),function(a){return 1==
5
+ a.hasKeyboardFocus()});if(k(b))return n().typeString(a),!0;var e=c.initial_text||"",d=new Date,f=c.timeout||60,h=n();do try{return h.typeString(a),!0}catch(m){UIALogger.logMessage("keyboard_enter_text failed: "+m),UIALogger.logMessage("keyboard_enter_text retrying with restore to: "+e),b.setValue(e)}while(!(new Date-d>=1E3*f));throw new l("Unable to enter text","text: "+a+" failed after retrying for "+f);};g.deactivate=function(a){h.deactivateAppForDuration(a)};g.tap_offset=function(a,c,b){b=b||{};
6
+ return b.unsafe?function(){return c.apply(this,arguments)}:function(){var e=new Date,d=b.timeout||60,f=b.frequency||.1;do try{return c.apply(this,arguments)}catch(g){UIALogger.logMessage(a+"Error: "+g+". Arguments: "+arguments[0]+", "+arguments[1]),w(f)}while(!(new Date-e>=1E3*d));throw new l(a,"Arguments: "+arguments[0]+", "+arguments[1]);}}("tap_offset failed",function(a,c){h.tapWithOptions(a,c||{})},{timeout:60,frequency:.5});this.target=h;this.uia=g;g.app=m;g.window=r;g.windows=s;g.keyboard=n;
7
+ g.alert=function(){return m().alert()}})();
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: 1.2.7
4
+ version: 1.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Karl Krukow
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-26 00:00:00.000000000 Z
11
+ date: 2015-03-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -241,10 +241,12 @@ files:
241
241
  - lib/run_loop/device.rb
242
242
  - lib/run_loop/dylib_injector.rb
243
243
  - lib/run_loop/environment.rb
244
+ - lib/run_loop/fifo.rb
244
245
  - lib/run_loop/host_cache.rb
245
246
  - lib/run_loop/instruments.rb
246
247
  - lib/run_loop/lipo.rb
247
248
  - lib/run_loop/lldb.rb
249
+ - lib/run_loop/logging.rb
248
250
  - lib/run_loop/monkey_patch.rb
249
251
  - lib/run_loop/plist_buddy.rb
250
252
  - lib/run_loop/process_terminator.rb
@@ -253,6 +255,7 @@ files:
253
255
  - lib/run_loop/simctl/bridge.rb
254
256
  - lib/run_loop/version.rb
255
257
  - lib/run_loop/xctools.rb
258
+ - scripts/calabash-uia-min.js
256
259
  - scripts/calabash.lldb.erb
257
260
  - scripts/calabash_script_uia.js
258
261
  - scripts/json2-min.js
@@ -285,7 +288,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
285
288
  version: '0'
286
289
  requirements: []
287
290
  rubyforge_project:
288
- rubygems_version: 2.4.5
291
+ rubygems_version: 2.2.2
289
292
  signing_key:
290
293
  specification_version: 4
291
294
  summary: Tools related to running Calabash iOS tests