Module: RunLoop::Core
- Defined in:
- lib/run_loop/core.rb
Constant Summary collapse
- START_DELIMITER =
"OUTPUT_JSON:\n"- END_DELIMITER =
"\nEND_OUTPUT"- SCRIPTS_PATH =
File.(File.join(File.dirname(__FILE__), '..', '..', 'scripts'))
- SCRIPTS =
{ :dismiss => 'run_dismiss_location.js', :run_loop_fast_uia => 'run_loop_fast_uia.js', :run_loop_host => 'run_loop_host.js' }
Class Method Summary collapse
- .above_or_eql_version?(target_version, xcode_version) ⇒ Boolean
- .automation_template(candidate = ) ⇒ Object
- .default_tracetemplate ⇒ Object
- .detect_connected_device ⇒ Object
- .ensure_instruments_not_running! ⇒ Object
- .instruments_command(options) ⇒ Object
- .instruments_command_prefix(udid, results_dir_trace) ⇒ Object
- .instruments_pids ⇒ Object
- .instruments_running? ⇒ Boolean
- .jruby? ⇒ Boolean
- .log(message) ⇒ Object
- .log_header(message) ⇒ Object
- .pids_for_run_loop(run_loop, &block) ⇒ Object
- .read_response(run_loop, expected_index, empty_file_timeout = 10) ⇒ Object
- .run_with_options(options) ⇒ Object
- .script_for_key(key) ⇒ Object
- .scripts_path ⇒ Object
- .udid_and_bundle_for_launcher(device_target, options) ⇒ Object
- .write_request(run_loop, cmd) ⇒ Object
- .xcode_version ⇒ Object
Class Method Details
.above_or_eql_version?(target_version, xcode_version) ⇒ Boolean
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/run_loop/core.rb', line 213 def self.above_or_eql_version?(target_version, xcode_version) t_major,t_minor,t_patch = target_version.split('.') x_major,x_minor,x_patch = xcode_version.split('.') return true if x_major.to_i > t_major.to_i return false if x_major.to_i < t_major.to_i #major versions are equal t_minor_i = (t_minor && t_minor.to_i || 0) x_minor_i = (x_minor && x_minor.to_i || 0) return true if x_minor_i > t_minor_i return false if x_minor_i < t_minor_i #minor versions are equal t_patch_i = (t_patch && t_patch.to_i || 0) x_patch_i = (x_patch && x_patch.to_i || 0) x_patch_i >= t_patch_i end |
.automation_template(candidate = ) ⇒ Object
396 397 398 399 400 401 |
# File 'lib/run_loop/core.rb', line 396 def self.automation_template(candidate = ENV['TRACE_TEMPLATE']) unless candidate && File.exist?(candidate) candidate = default_tracetemplate end candidate end |
.default_tracetemplate ⇒ Object
403 404 405 406 407 408 409 410 411 |
# File 'lib/run_loop/core.rb', line 403 def self.default_tracetemplate xcode_path = `xcode-select -print-path`.chomp automation_bundle = File.(File.join(xcode_path, '..', 'Applications', 'Instruments.app', 'Contents', 'PlugIns', 'AutomationInstrument.bundle')) if not File.exist? automation_bundle automation_bundle= File.(File.join(xcode_path, 'Platforms', 'iPhoneOS.platform', 'Developer', 'Library', 'Instruments', 'PlugIns', 'AutomationInstrument.bundle')) raise 'Unable to find AutomationInstrument.bundle' if not File.exist? automation_bundle end File.join(automation_bundle, 'Contents', 'Resources', 'Automation.tracetemplate') end |
.detect_connected_device ⇒ Object
34 35 36 37 38 39 40 41 42 43 |
# File 'lib/run_loop/core.rb', line 34 def self.detect_connected_device begin Timeout::timeout(1, TimeoutError) do return `#{File.join(scripts_path, 'udidetect')}`.chomp end rescue TimeoutError => e `killall udidetect &> /dev/null` end nil end |
.ensure_instruments_not_running! ⇒ Object
427 428 429 430 431 432 433 434 |
# File 'lib/run_loop/core.rb', line 427 def self.ensure_instruments_not_running! instruments_pids.each do |pid| if ENV['DEBUG']=='1' puts "Found instruments #{pid}. Killing..." end `kill -9 #{pid} && wait #{pid} &> /dev/null` end end |
.instruments_command(options) ⇒ Object
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 |
# File 'lib/run_loop/core.rb', line 372 def self.instruments_command() udid = [:udid] results_dir_trace = [:results_dir_trace] bundle_dir_or_bundle_id = [:bundle_dir_or_bundle_id] results_dir = [:results_dir] script = [:script] log_file = [:log_file] args= [:args] || [] instruments_prefix = instruments_command_prefix(udid, results_dir_trace) cmd = [ instruments_prefix, '-t', "\"#{automation_template}\"", "\"#{bundle_dir_or_bundle_id}\"", '-e', 'UIARESULTSPATH', results_dir, '-e', 'UIASCRIPT', script, args.join(' ') ] if log_file cmd << "&> #{log_file}" end cmd end |
.instruments_command_prefix(udid, results_dir_trace) ⇒ Object
362 363 364 365 366 367 368 369 |
# File 'lib/run_loop/core.rb', line 362 def self.instruments_command_prefix(udid, results_dir_trace) instruments_path = 'instruments' if udid instruments_path = "#{instruments_path} -w \"#{udid}\"" end instruments_path << " -D \"#{results_dir_trace}\"" if results_dir_trace instruments_path end |
.instruments_pids ⇒ Object
440 441 442 443 |
# File 'lib/run_loop/core.rb', line 440 def self.instruments_pids pids_str = `ps x -o pid,command | grep -v grep | grep "instruments" | awk '{printf "%s,", $1}'`.strip pids_str.split(',').map { |pid| pid.to_i } end |
.instruments_running? ⇒ Boolean
436 437 438 |
# File 'lib/run_loop/core.rb', line 436 def self.instruments_running? instruments_pids.size > 0 end |
.jruby? ⇒ Boolean
239 240 241 |
# File 'lib/run_loop/core.rb', line 239 def self.jruby? RUBY_PLATFORM == 'java' end |
.log(message) ⇒ Object
413 414 415 416 417 418 |
# File 'lib/run_loop/core.rb', line 413 def self.log() if ENV['DEBUG']=='1' puts "#{Time.now } #{message}" $stdout.flush end end |
.log_header(message) ⇒ Object
420 421 422 423 424 425 |
# File 'lib/run_loop/core.rb', line 420 def self.log_header() if ENV['DEBUG']=='1' puts "\n\e[#{35}m### #{message} ###\e[0m" $stdout.flush end end |
.pids_for_run_loop(run_loop, &block) ⇒ Object
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 |
# File 'lib/run_loop/core.rb', line 346 def self.pids_for_run_loop(run_loop, &block) results_dir = run_loop[:results_dir] udid = run_loop[:udid] instruments_prefix = instruments_command_prefix(udid, results_dir) pids_str = `ps x -o pid,command | grep -v grep | grep "#{instruments_prefix.gsub(%Q["], %Q[\\"])}" | awk '{printf "%s,", $1}'` pids = pids_str.split(',').map { |pid| pid.to_i } if block_given? pids.each do |pid| block.call(pid) end else pids end end |
.read_response(run_loop, expected_index, empty_file_timeout = 10) ⇒ Object
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 |
# File 'lib/run_loop/core.rb', line 268 def self.read_response(run_loop, expected_index, empty_file_timeout=10) log_file = run_loop[:log_file] initial_offset = run_loop[:initial_offset] || 0 offset = initial_offset result = nil loop do unless File.exist?(log_file) && File.size?(log_file) sleep(0.2) next end size = File.size(log_file) output = File.read(log_file, size-offset, offset) if /AXError: Could not auto-register for pid status change/.match(output) if /kAXErrorServerNotFound/.match(output) $stderr.puts "\n\n****** Accessibility is not enabled on device/simulator, please enable it *** \n\n" $stderr.flush end raise TimeoutError.new('AXError: Could not auto-register for pid status change') end if /Automation Instrument ran into an exception/.match(output) raise TimeoutError.new('Exception while running script') end index_if_found = output.index(START_DELIMITER) if ENV['DEBUG_READ']=='1' puts output.gsub('*', '') puts "Size #{size}" puts "offset #{offset}" puts "index_of #{START_DELIMITER}: #{index_if_found}" end if index_if_found offset = offset + index_if_found rest = output[index_if_found+START_DELIMITER.size..output.length] index_of_json = rest.index("}#{END_DELIMITER}") if index_of_json.nil? #Wait for rest of json sleep(0.1) next end json = rest[0..index_of_json] if ENV['DEBUG_READ']=='1' puts "Index #{index_if_found}, Size: #{size} Offset #{offset}" puts ("parse #{json}") end offset = offset + json.size parsed_result = JSON.parse(json) if ENV['DEBUG_READ']=='1' p parsed_result end json_index_if_present = parsed_result['index'] if json_index_if_present && json_index_if_present == expected_index result = parsed_result break end else sleep(0.1) end end run_loop[:initial_offset] = offset result end |
.run_with_options(options) ⇒ Object
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/run_loop/core.rb', line 45 def self.() before = Time.now ensure_instruments_not_running! device_target = [:udid] || [:device_target] || detect_connected_device || 'simulator' if device_target && device_target.to_s.downcase == 'device' device_target = detect_connected_device end log_file = [:log_path] timeout = [:timeout] || 30 results_dir = [:results_dir] || Dir.mktmpdir('run_loop') results_dir_trace = File.join(results_dir, 'trace') FileUtils.mkdir_p(results_dir_trace) dependencies = [:dependencies] || [] dependencies << File.join(scripts_path, 'calabash_script_uia.js') dependencies.each do |dep| FileUtils.cp(dep, results_dir) end script = File.join(results_dir, '_run_loop.js') code = File.read([:script]) code = code.gsub(/\$PATH/, results_dir) code = code.gsub(/\$MODE/, 'FLUSH') unless [:no_flush] repl_path = File.join(results_dir, 'repl-cmd.txt') File.open(repl_path, 'w') { |file| file.puts "0:UIALogger.logMessage('Listening for run loop commands');" } File.open(script, 'w') { |file| file.puts code } # Compute udid and bundle_dir / bundle_id from options and target depending on Xcode version udid, bundle_dir_or_bundle_id = udid_and_bundle_for_launcher(device_target, ) args = .fetch(:args, []) log_file ||= File.join(results_dir, 'run_loop.out') if ENV['DEBUG']=='1' p puts "device_target=#{device_target}" puts "udid=#{udid}" puts "bundle_dir_or_bundle_id=#{bundle_dir_or_bundle_id}" puts "script=#{script}" puts "log_file=#{log_file}" puts "timeout=#{timeout}" puts "args=#{args}" end after = Time.now if ENV['DEBUG']=='1' puts "Preparation took #{after-before} seconds" end cmd = instruments_command(.merge(:udid => udid, :results_dir_trace => results_dir_trace, :bundle_dir_or_bundle_id => bundle_dir_or_bundle_id, :results_dir => results_dir, :script => script, :log_file => log_file, :args => args)) log_header("Starting on #{device_target} App: #{bundle_dir_or_bundle_id}") cmd_str = cmd.join(' ') if ENV['DEBUG'] log(cmd_str) end if !jruby? && RUBY_VERSION && RUBY_VERSION.start_with?('1.8') pid = fork do exec(cmd_str) end else pid = spawn(cmd_str) end Process.detach(pid) File.open(File.join(results_dir, 'run_loop.pid'), 'w') do |f| f.write pid end run_loop = {:pid => pid, :udid => udid, :app => bundle_dir_or_bundle_id, :repl_path => repl_path, :log_file => log_file, :results_dir => results_dir} uia_timeout = [:uia_timeout] || (ENV['UIA_TIMEOUT'] && ENV['UIA_TIMEOUT'].to_f) || 10 before = Time.now begin Timeout::timeout(timeout, TimeoutError) do read_response(run_loop, 0, uia_timeout) end rescue TimeoutError => e if ENV['DEBUG'] puts "Failed to launch\n" puts "reason=#{e}: #{e && e.message} " puts "device_target=#{device_target}" puts "udid=#{udid}" puts "bundle_dir_or_bundle_id=#{bundle_dir_or_bundle_id}" puts "script=#{script}" puts "log_file=#{log_file}" puts "timeout=#{timeout}" puts "args=#{args}" end raise TimeoutError, "Time out waiting for UIAutomation run-loop to Start. \n Logfile #{log_file} \n\n #{File.read(log_file)}\n" end after = Time.now() if ENV['DEBUG']=='1' puts "Launching took #{after-before} seconds" end run_loop end |
.script_for_key(key) ⇒ Object
27 28 29 30 31 32 |
# File 'lib/run_loop/core.rb', line 27 def self.script_for_key(key) if SCRIPTS[key].nil? return nil end File.join(scripts_path, SCRIPTS[key]) end |
.scripts_path ⇒ Object
23 24 25 |
# File 'lib/run_loop/core.rb', line 23 def self.scripts_path SCRIPTS_PATH end |
.udid_and_bundle_for_launcher(device_target, options) ⇒ Object
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/run_loop/core.rb', line 166 def self.udid_and_bundle_for_launcher(device_target, ) bundle_dir_or_bundle_id = [:app] || ENV['BUNDLE_ID']|| ENV['APP_BUNDLE_PATH'] || ENV['APP'] unless bundle_dir_or_bundle_id raise 'key :app or environment variable APP_BUNDLE_PATH, BUNDLE_ID or APP must be specified as path to app bundle (simulator) or bundle id (device)' end udid = nil if above_or_eql_version?('5.1', xcode_version) if device_target.nil? || device_target.empty? || device_target == 'simulator' device_target = 'iPhone Retina (4-inch) - Simulator - iOS 7.1' end udid = device_target unless /simulator/i.match(device_target) bundle_dir_or_bundle_id = [:bundle_id] if [:bundle_id] end else if device_target == 'simulator' unless File.exist?(bundle_dir_or_bundle_id) raise "Unable to find app in directory #{bundle_dir_or_bundle_id} when trying to launch simulator" end device = [:device] || :iphone device = device && device.to_sym plistbuddy='/usr/libexec/PlistBuddy' plistfile="#{bundle_dir_or_bundle_id}/Info.plist" if device == :iphone uidevicefamily=1 else uidevicefamily=2 end system("#{plistbuddy} -c 'Delete :UIDeviceFamily' '#{plistfile}'") system("#{plistbuddy} -c 'Add :UIDeviceFamily array' '#{plistfile}'") system("#{plistbuddy} -c 'Add :UIDeviceFamily:0 integer #{uidevicefamily}' '#{plistfile}'") else udid = device_target bundle_dir_or_bundle_id = [:bundle_id] if [:bundle_id] end end return udid, bundle_dir_or_bundle_id end |
.write_request(run_loop, cmd) ⇒ Object
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
# File 'lib/run_loop/core.rb', line 243 def self.write_request(run_loop, cmd) repl_path = run_loop[:repl_path] cur = File.read(repl_path) colon = cur.index(':') if colon.nil? raise "Illegal contents of #{repl_path}: #{cur}" end index = cur[0, colon].to_i + 1 tmp_cmd = File.join(File.dirname(repl_path), '__repl-cmd.txt') File.open(tmp_cmd, 'w') do |f| f.write("#{index}:#{cmd}") if ENV['DEBUG'] puts "Wrote: #{index}:#{cmd}" end end FileUtils.mv(tmp_cmd, repl_path) index end |
.xcode_version ⇒ Object
231 232 233 234 235 236 237 |
# File 'lib/run_loop/core.rb', line 231 def self.xcode_version xcode_build_output = `xcodebuild -version`.split("\n") xcode_build_output.each do |line| match=/^Xcode\s(.*)$/.match(line.strip) return match[1] if match && match.length > 1 end end |