run_loop_tcc 2.1.3

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.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/bin/run-loop +19 -0
  4. data/lib/run_loop/abstract.rb +18 -0
  5. data/lib/run_loop/app.rb +372 -0
  6. data/lib/run_loop/cache/cache.rb +68 -0
  7. data/lib/run_loop/cli/cli.rb +48 -0
  8. data/lib/run_loop/cli/codesign.rb +24 -0
  9. data/lib/run_loop/cli/errors.rb +11 -0
  10. data/lib/run_loop/cli/instruments.rb +160 -0
  11. data/lib/run_loop/cli/locale.rb +31 -0
  12. data/lib/run_loop/cli/simctl.rb +257 -0
  13. data/lib/run_loop/cli/tcc.rb +139 -0
  14. data/lib/run_loop/codesign.rb +76 -0
  15. data/lib/run_loop/core.rb +902 -0
  16. data/lib/run_loop/core_simulator.rb +960 -0
  17. data/lib/run_loop/detect_aut/detect.rb +185 -0
  18. data/lib/run_loop/detect_aut/errors.rb +126 -0
  19. data/lib/run_loop/detect_aut/xamarin_studio.rb +46 -0
  20. data/lib/run_loop/detect_aut/xcode.rb +157 -0
  21. data/lib/run_loop/device.rb +722 -0
  22. data/lib/run_loop/device_agent/app/CBX-Runner.app.zip +0 -0
  23. data/lib/run_loop/device_agent/bin/xctestctl +0 -0
  24. data/lib/run_loop/device_agent/cbxrunner.rb +156 -0
  25. data/lib/run_loop/device_agent/frameworks/Frameworks.zip +0 -0
  26. data/lib/run_loop/device_agent/frameworks.rb +65 -0
  27. data/lib/run_loop/device_agent/ipa/CBX-Runner.app.zip +0 -0
  28. data/lib/run_loop/device_agent/launcher.rb +51 -0
  29. data/lib/run_loop/device_agent/xcodebuild.rb +91 -0
  30. data/lib/run_loop/device_agent/xctestctl.rb +109 -0
  31. data/lib/run_loop/directory.rb +179 -0
  32. data/lib/run_loop/dnssd.rb +148 -0
  33. data/lib/run_loop/dot_dir.rb +87 -0
  34. data/lib/run_loop/dylib_injector.rb +145 -0
  35. data/lib/run_loop/encoding.rb +56 -0
  36. data/lib/run_loop/environment.rb +361 -0
  37. data/lib/run_loop/fifo.rb +40 -0
  38. data/lib/run_loop/host_cache.rb +128 -0
  39. data/lib/run_loop/http/error.rb +15 -0
  40. data/lib/run_loop/http/request.rb +44 -0
  41. data/lib/run_loop/http/retriable_client.rb +166 -0
  42. data/lib/run_loop/http/server.rb +17 -0
  43. data/lib/run_loop/instruments.rb +436 -0
  44. data/lib/run_loop/ipa.rb +142 -0
  45. data/lib/run_loop/l10n.rb +93 -0
  46. data/lib/run_loop/language.rb +63 -0
  47. data/lib/run_loop/lipo.rb +132 -0
  48. data/lib/run_loop/lldb.rb +52 -0
  49. data/lib/run_loop/locale.rb +101 -0
  50. data/lib/run_loop/logging.rb +111 -0
  51. data/lib/run_loop/otool.rb +76 -0
  52. data/lib/run_loop/patches/awesome_print.rb +17 -0
  53. data/lib/run_loop/physical_device/life_cycle.rb +268 -0
  54. data/lib/run_loop/plist_buddy.rb +189 -0
  55. data/lib/run_loop/process_terminator.rb +128 -0
  56. data/lib/run_loop/process_waiter.rb +117 -0
  57. data/lib/run_loop/regex.rb +19 -0
  58. data/lib/run_loop/shell.rb +103 -0
  59. data/lib/run_loop/sim_control.rb +1264 -0
  60. data/lib/run_loop/simctl.rb +275 -0
  61. data/lib/run_loop/sqlite.rb +61 -0
  62. data/lib/run_loop/strings.rb +88 -0
  63. data/lib/run_loop/tcc/TCC.db +0 -0
  64. data/lib/run_loop/tcc/tcc.rb +240 -0
  65. data/lib/run_loop/template.rb +61 -0
  66. data/lib/run_loop/version.rb +182 -0
  67. data/lib/run_loop/xcode.rb +318 -0
  68. data/lib/run_loop/xcrun.rb +107 -0
  69. data/lib/run_loop/xcuitest.rb +550 -0
  70. data/lib/run_loop.rb +230 -0
  71. data/plists/simctl/com.apple.UIAutomation.plist +0 -0
  72. data/plists/simctl/com.apple.UIAutomationPlugIn.plist +0 -0
  73. data/scripts/calabash_script_uia.js +28184 -0
  74. data/scripts/lib/json2.min.js +26 -0
  75. data/scripts/lib/log.js +26 -0
  76. data/scripts/lib/on_alert.js +224 -0
  77. data/scripts/read-cmd.sh +2 -0
  78. data/scripts/run_dismiss_location.js +89 -0
  79. data/scripts/run_loop_basic.js +34 -0
  80. data/scripts/run_loop_fast_uia.js +188 -0
  81. data/scripts/run_loop_host.js +117 -0
  82. data/scripts/run_loop_shared_element.js +125 -0
  83. data/scripts/timeout3 +23 -0
  84. data/scripts/udidetect +0 -0
  85. data/vendor-licenses/FBSimulatorControl.LICENSE +30 -0
  86. data/vendor-licenses/xctestctl.LICENSE +32 -0
  87. metadata +443 -0
@@ -0,0 +1,550 @@
1
+ module RunLoop
2
+
3
+ # @!visibility private
4
+ class XCUITest
5
+
6
+ require "run_loop/shell"
7
+ include RunLoop::Shell
8
+
9
+ require "run_loop/encoding"
10
+ include RunLoop::Encoding
11
+
12
+ class HTTPError < RuntimeError; end
13
+
14
+ # @!visibility private
15
+ DEFAULTS = {
16
+ :port => 27753,
17
+ :simulator_ip => "127.0.0.1",
18
+ :http_timeout => RunLoop::Environment.ci? ? 120 : 60,
19
+ :version => "1.0"
20
+ }
21
+
22
+ # @!visibility private
23
+ def self.run(options={})
24
+ # logger = options[:logger]
25
+ simctl = options[:sim_control] || options[:simctl] || RunLoop::Simctl.new
26
+ xcode = options[:xcode] || RunLoop::Xcode.new
27
+ instruments = options[:instruments] || RunLoop::Instruments.new
28
+
29
+ # Find the Device under test, the App under test, UIA strategy, and reset options
30
+ device = RunLoop::Device.detect_device(options, xcode, simctl, instruments)
31
+ app_details = RunLoop::DetectAUT.detect_app_under_test(options)
32
+ reset_options = RunLoop::Core.send(:detect_reset_options, options)
33
+
34
+ app = app_details[:app]
35
+ bundle_id = app_details[:bundle_id]
36
+
37
+ if device.simulator? && app
38
+ core_sim = RunLoop::CoreSimulator.new(device, app, :xcode => xcode)
39
+ if reset_options
40
+ core_sim.reset_app_sandbox
41
+ end
42
+
43
+ simctl.ensure_software_keyboard(device)
44
+ core_sim.install
45
+ end
46
+
47
+ cbx_launcher = XCUITest.detect_cbx_launcher(options, device)
48
+
49
+ xcuitest = RunLoop::XCUITest.new(bundle_id, device, cbx_launcher)
50
+ xcuitest.launch
51
+ xcuitest
52
+ end
53
+
54
+ # @!visibility private
55
+ #
56
+ # @param [RunLoop::Device] device the device under test
57
+ def self.default_cbx_launcher(device)
58
+ RunLoop::DeviceAgent::XCTestctl.new(device)
59
+ end
60
+
61
+ # @!visibility private
62
+ # @param [Hash] options the options passed by the user
63
+ # @param [RunLoop::Device] device the device under test
64
+ def self.detect_cbx_launcher(options, device)
65
+ value = options[:cbx_launcher]
66
+ if value
67
+ if value == :xcodebuild
68
+ RunLoop::DeviceAgent::Xcodebuild.new(device)
69
+ elsif value == :xctestctl
70
+ RunLoop::DeviceAgent::XCTestctl.new(device)
71
+ else
72
+ raise(ArgumentError,
73
+ "Expected :cbx_launcher => #{value} to be :xcodebuild or :xctestctl")
74
+ end
75
+ else
76
+ XCUITest.default_cbx_launcher(device)
77
+ end
78
+ end
79
+
80
+ attr_reader :bundle_id, :device, :cbx_launcher
81
+
82
+ # @!visibility private
83
+ #
84
+ # The app with `bundle_id` needs to be installed.
85
+ #
86
+ # @param [String] bundle_id The identifier of the app under test.
87
+ # @param [RunLoop::Device] device The device device.
88
+ def initialize(bundle_id, device, cbx_launcher)
89
+ @bundle_id = bundle_id
90
+ @device = device
91
+ @cbx_launcher = cbx_launcher
92
+ end
93
+
94
+ # @!visibility private
95
+ def to_s
96
+ "#<XCUITest #{url} : #{bundle_id} : #{device} : #{cbx_launcher}>"
97
+ end
98
+
99
+ # @!visibility private
100
+ def inspect
101
+ to_s
102
+ end
103
+
104
+ # @!visibility private
105
+ def launch
106
+ start = Time.now
107
+ launch_cbx_runner
108
+ launch_aut
109
+ elapsed = Time.now - start
110
+ RunLoop.log_debug("Took #{elapsed} seconds to launch #{bundle_id} on #{device}")
111
+ true
112
+ end
113
+
114
+ # @!visibility private
115
+ def running?
116
+ begin
117
+ health(ping_options)
118
+ rescue => _
119
+ nil
120
+ end
121
+ end
122
+
123
+ # @!visibility private
124
+ def stop
125
+ begin
126
+ shutdown
127
+ rescue => _
128
+ nil
129
+ end
130
+ end
131
+
132
+ # @!visibility private
133
+ def launch_other_app(bundle_id)
134
+ launch_aut(bundle_id)
135
+ end
136
+
137
+ # @!visibility private
138
+ def runtime
139
+ options = http_options
140
+ request = request("device")
141
+ client = client(options)
142
+ response = client.get(request)
143
+ expect_200_response(response)
144
+ end
145
+
146
+ # @!visibility private
147
+ def tree
148
+ options = http_options
149
+ request = request("tree")
150
+ client = client(options)
151
+ response = client.get(request)
152
+ expect_200_response(response)
153
+ end
154
+
155
+ # @!visibility private
156
+ def keyboard_visible?
157
+ options = http_options
158
+ parameters = { :type => "Keyboard" }
159
+ request = request("query", parameters)
160
+ client = client(options)
161
+ response = client.post(request)
162
+ hash = expect_200_response(response)
163
+ result = hash["result"]
164
+ result.count != 0
165
+ end
166
+
167
+ # @!visibility private
168
+ def enter_text(string)
169
+ if !keyboard_visible?
170
+ raise RuntimeError, "Keyboard must be visible"
171
+ end
172
+ options = http_options
173
+ parameters = {
174
+ :gesture => "enter_text",
175
+ :options => {
176
+ :string => string
177
+ }
178
+ }
179
+ request = request("gesture", parameters)
180
+ client = client(options)
181
+ response = client.post(request)
182
+ expect_200_response(response)
183
+ end
184
+
185
+ # @!visibility private
186
+ def query(mark)
187
+ options = http_options
188
+ parameters = { :id => mark }
189
+ request = request("query", parameters)
190
+ client = client(options)
191
+ response = client.post(request)
192
+ expect_200_response(response)
193
+ end
194
+
195
+ # @!visibility private
196
+ def query_for_coordinate(mark)
197
+ body = query(mark)
198
+ coordinate_from_query_result(body)
199
+ end
200
+
201
+ # @!visibility private
202
+ def touch(mark, options={})
203
+ coordinate = query_for_coordinate(mark)
204
+ perform_coordinate_gesture("touch",
205
+ coordinate[:x], coordinate[:y],
206
+ options)
207
+ end
208
+
209
+ alias_method :tap, :touch
210
+
211
+ # @!visibility private
212
+ def double_tap(mark, options={})
213
+ coordinate = query_for_coordinate(mark)
214
+ perform_coordinate_gesture("double_tap",
215
+ coordinate[:x], coordinate[:y],
216
+ options)
217
+ end
218
+
219
+ # @!visibility private
220
+ def two_finger_tap(mark, options={})
221
+ coordinate = query_for_coordinate(mark)
222
+ perform_coordinate_gesture("two_finger_tap",
223
+ coordinate[:x], coordinate[:y],
224
+ options)
225
+ end
226
+
227
+ # @!visibility private
228
+ def rotate_home_button_to(position, sleep_for=1.0)
229
+ orientation = orientation_for_position(position)
230
+ parameters = {
231
+ :orientation => orientation
232
+ }
233
+ request = request("rotate_home_button_to", parameters)
234
+ client = client(http_options)
235
+ response = client.post(request)
236
+ json = expect_200_response(response)
237
+ sleep(sleep_for)
238
+ json
239
+ end
240
+
241
+ # @!visibility private
242
+ def perform_coordinate_gesture(gesture, x, y, options={})
243
+ parameters = {
244
+ :gesture => gesture,
245
+ :specifiers => {
246
+ :coordinate => {x: x, y: y}
247
+ },
248
+ :options => options
249
+ }
250
+
251
+ RunLoop.log_debug(%Q[
252
+ Sending request to perform '#{gesture}' with:
253
+
254
+ #{JSON.pretty_generate(parameters)}
255
+
256
+ ])
257
+ request = request("gesture", parameters)
258
+ client = client(http_options)
259
+ response = client.post(request)
260
+ expect_200_response(response)
261
+ end
262
+
263
+ # @!visibility private
264
+ def coordinate_from_query_result(hash)
265
+ matches = hash["result"]
266
+
267
+ if matches.nil? || matches.empty?
268
+ raise "Expected #{hash} to contain some results"
269
+ end
270
+
271
+ rect = matches.first["rect"]
272
+ h = rect["height"]
273
+ w = rect["width"]
274
+ x = rect["x"]
275
+ y = rect["y"]
276
+
277
+ touchx = x + (w/2.0)
278
+ touchy = y + (h/2.0)
279
+
280
+ new_rect = rect.dup
281
+ new_rect[:center_x] = touchx
282
+ new_rect[:center_y] = touchy
283
+
284
+ RunLoop.log_debug(%Q[Rect from query:
285
+
286
+ #{JSON.pretty_generate(new_rect)}
287
+
288
+ ])
289
+ {:x => touchx,
290
+ :y => touchy}
291
+ end
292
+
293
+ private
294
+
295
+ # @!visibility private
296
+ def xcrun
297
+ RunLoop::Xcrun.new
298
+ end
299
+
300
+ # @!visibility private
301
+ def url
302
+ @url ||= detect_device_agent_url
303
+ end
304
+
305
+ # @!visibility private
306
+ def detect_device_agent_url
307
+ url_from_environment ||
308
+ url_for_simulator ||
309
+ url_from_device_endpoint ||
310
+ url_from_device_name
311
+ end
312
+
313
+ # @!visibility private
314
+ def url_from_environment
315
+ url = RunLoop::Environment.device_agent_url
316
+ return if url.nil?
317
+
318
+ if url.end_with?("/")
319
+ url
320
+ else
321
+ "#{url}/"
322
+ end
323
+ end
324
+
325
+ # @!visibility private
326
+ def url_for_simulator
327
+ if device.simulator?
328
+ "http://#{DEFAULTS[:simulator_ip]}:#{DEFAULTS[:port]}/"
329
+ else
330
+ nil
331
+ end
332
+ end
333
+
334
+ # @!visibility private
335
+ def url_from_device_endpoint
336
+ calabash_endpoint = RunLoop::Environment.device_endpoint
337
+ if calabash_endpoint
338
+ base = calabash_endpoint.split(":")[0..1].join(":")
339
+ "#{base}:#{DEFAULTS[:port]}/"
340
+ else
341
+ nil
342
+ end
343
+ end
344
+
345
+ # @!visibility private
346
+ # TODO This block is not well tested
347
+ # TODO extract to a module; Calabash can use to detect device endpoint
348
+ def url_from_device_name
349
+ # Transforms the default "Joshua's iPhone" to a DNS name.
350
+ device_name = device.name.gsub(/[']/, "").gsub(/[\s]/, "-")
351
+
352
+ # Replace diacritic markers and unknown characters.
353
+ transliterated = transliterate(device_name).tr("?", "")
354
+
355
+ # Anything that cannot be transliterated is a ?
356
+ replaced = transliterated.tr("?", "")
357
+
358
+ "http://#{replaced}.local:#{DEFAULTS[:port]}/"
359
+ end
360
+
361
+ # @!visibility private
362
+ def server
363
+ @server ||= RunLoop::HTTP::Server.new(url)
364
+ end
365
+
366
+ # @!visibility private
367
+ def client(options={})
368
+ RunLoop::HTTP::RetriableClient.new(server, options)
369
+ end
370
+
371
+ # @!visibility private
372
+ def versioned_route(route)
373
+ if ["health", "ping", "sessionIdentifier"].include?(route)
374
+ route
375
+ else
376
+ "#{DEFAULTS[:version]}/#{route}"
377
+ end
378
+ end
379
+
380
+ # @!visibility private
381
+ def request(route, parameters={})
382
+ versioned = versioned_route(route)
383
+ RunLoop::HTTP::Request.request(versioned, parameters)
384
+ end
385
+
386
+ # @!visibility private
387
+ def ping_options
388
+ @ping_options ||= { :timeout => 0.5, :retries => 1 }
389
+ end
390
+
391
+ # @!visibility private
392
+ def http_options
393
+ {
394
+ :timeout => DEFAULTS[:http_timeout],
395
+ :interval => 0.1,
396
+ :retries => (DEFAULTS[:http_timeout]/0.1).to_i
397
+ }
398
+ end
399
+
400
+ # @!visibility private
401
+ def session_delete
402
+ options = ping_options
403
+ request = request("delete")
404
+ client = client(options)
405
+ begin
406
+ response = client.delete(request)
407
+ body = expect_200_response(response)
408
+ RunLoop.log_debug("CBX-Runner says, #{body}")
409
+ body
410
+ rescue => e
411
+ RunLoop.log_debug("CBX-Runner session delete error: #{e}")
412
+ nil
413
+ end
414
+ end
415
+
416
+ # @!visibility private
417
+ # TODO expect 200 response and parse body (atm the body in not valid JSON)
418
+ def shutdown
419
+ session_delete
420
+ options = ping_options
421
+ request = request("shutdown")
422
+ client = client(options)
423
+ begin
424
+ response = client.post(request)
425
+ body = response.body
426
+ RunLoop.log_debug("CBX-Runner says, \"#{body}\"")
427
+ body
428
+ rescue => e
429
+ RunLoop.log_debug("CBX-Runner shutdown error: #{e}")
430
+ nil
431
+ end
432
+ end
433
+
434
+ # @!visibility private
435
+ # TODO expect 200 response and parse body (atm the body is not valid JSON)
436
+ def health(options={})
437
+ merged_options = http_options.merge(options)
438
+ request = request("health")
439
+ client = client(merged_options)
440
+ response = client.get(request)
441
+ body = response.body
442
+ RunLoop.log_debug("CBX-Runner driver says, \"#{body}\"")
443
+ body
444
+ end
445
+
446
+ # @!visibility private
447
+ def launch_cbx_runner
448
+ shutdown
449
+
450
+ options = {:log_cmd => true}
451
+ run_shell_command(["pkill", "xctestctl"], options)
452
+ run_shell_command(["pkill", "testmanagerd"], options)
453
+ run_shell_command(["pkill", "xcodebuild"], options)
454
+
455
+ start = Time.now
456
+ RunLoop.log_debug("Waiting for CBX-Runner to launch...")
457
+ pid = cbx_launcher.launch
458
+ health
459
+ RunLoop.log_debug("Took #{Time.now - start} launch and respond to /health")
460
+
461
+ pid
462
+ end
463
+
464
+ # @!visibility private
465
+ def launch_aut(bundle_id = @bundle_id)
466
+ client = client(http_options)
467
+ request = request("session", {:bundleID => bundle_id})
468
+
469
+ begin
470
+ response = client.post(request)
471
+ RunLoop.log_debug("Launched #{bundle_id} on #{device}")
472
+ RunLoop.log_debug("#{response.body}")
473
+ if device.simulator?
474
+ # It is not clear yet whether we should do this. There is a problem
475
+ # in the simulator_wait_for_stable_state; it waits too long.
476
+ # device.simulator_wait_for_stable_state
477
+ end
478
+ expect_200_response(response)
479
+ rescue => e
480
+ raise e.class, %Q[Could not launch #{bundle_id} on #{device}:
481
+
482
+ #{e.message}
483
+
484
+ Something went wrong.
485
+ ]
486
+ end
487
+ end
488
+
489
+ # @!visibility private
490
+ def response_body_to_hash(response)
491
+ body = response.body
492
+ begin
493
+ JSON.parse(body)
494
+ rescue TypeError, JSON::ParserError => _
495
+ raise RunLoop::XCUITest::HTTPError,
496
+ "Could not parse response '#{body}'; the app has probably crashed"
497
+ end
498
+ end
499
+
500
+ # @!visibility private
501
+ def expect_200_response(response)
502
+ body = response_body_to_hash(response)
503
+ if response.status_code < 300 && !body["error"]
504
+ return body
505
+ end
506
+
507
+ if response.status_code > 300
508
+ raise RunLoop::XCUITest::HTTPError,
509
+ %Q[Expected status code < 300, found #{response.status_code}.
510
+
511
+ Server replied with:
512
+
513
+ #{body}
514
+
515
+ ]
516
+ else
517
+ raise RunLoop::XCUITest::HTTPError,
518
+ %Q[Expected JSON response with no error, but found
519
+
520
+ #{body["error"]}
521
+
522
+ ]
523
+
524
+ end
525
+ end
526
+
527
+ # @!visibility private
528
+ def orientation_for_position(position)
529
+ symbol = position.to_sym
530
+
531
+ case symbol
532
+ when :down, :bottom
533
+ return 1
534
+ when :up, :top
535
+ return 2
536
+ when :right
537
+ return 3
538
+ when :left
539
+ return 4
540
+ else
541
+ raise ArgumentError, %Q[
542
+ Could not coerce '#{position}' into a valid orientation.
543
+
544
+ Valid values are: :down, :up, :right, :left, :bottom, :top
545
+ ]
546
+ end
547
+ end
548
+ end
549
+ end
550
+