run_loop 1.2.7 → 1.2.8

Sign up to get free protection for your applications and to get access to all the features.
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