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,722 @@
1
+ module RunLoop
2
+ class Device
3
+
4
+ require 'securerandom'
5
+ include RunLoop::Regex
6
+
7
+ # Starting in Xcode 7, iOS 9 simulators have a new "booting" state.
8
+ #
9
+ # The simulator must completely boot before run-loop tries to do things
10
+ # like installing an app or clearing an app sandbox. Run-loop tries to
11
+ # wait for a the simulator stabilize by watching the checksum of the
12
+ # simulator directory and the simulator log.
13
+ #
14
+ # On resource constrained devices or CI systems, the default settings may
15
+ # not work.
16
+ #
17
+ # You can override these values if they do not work in your environment.
18
+ #
19
+ # For cucumber users, the best place to override would be in your
20
+ # features/support/env.rb.
21
+ #
22
+ # For example:
23
+ #
24
+ # RunLoop::Device::SIM_STABLE_STATE_OPTIONS[:timeout] = 60
25
+ SIM_STABLE_STATE_OPTIONS = {
26
+ # The maximum amount of time to wait for the simulator
27
+ # to stabilize. No errors are raised if this timeout is
28
+ # exceeded - if the default 30 seconds has passed, the
29
+ # simulator is probably stable enough for subsequent
30
+ # operations.
31
+ :timeout => RunLoop::Environment.ci? ? 120 : 30
32
+ }
33
+
34
+ attr_reader :name
35
+ attr_reader :version
36
+ attr_reader :udid
37
+ attr_reader :state
38
+ attr_reader :simulator_root_dir
39
+ attr_reader :simulator_accessibility_plist_path
40
+ attr_reader :simulator_preferences_plist_path
41
+ attr_reader :simulator_log_file_path
42
+ attr_reader :pbuddy
43
+
44
+ # Create a new device.
45
+ #
46
+ # @param [String] name The name of the device. For sims this should be
47
+ # 'iPhone 5s' and for physical devices it will be the name the user gave
48
+ # to the device.
49
+ # @param [String, RunLoop::Version] version The iOS version that is running
50
+ # on the device. Can be a string or a Version instance.
51
+ # @param [String] udid The device identifier.
52
+ # @param [String] state (nil) This a simulator only value. It refers to
53
+ # the Booted/Shutdown/Creating state of the simulator. For pre-Xcode 6
54
+ # simulators, this value should be nil.
55
+ def initialize(name, version, udid, state=nil)
56
+ @name = name
57
+ @udid = udid
58
+ @state = state
59
+
60
+ if version.is_a? String
61
+ @version = RunLoop::Version.new version
62
+ else
63
+ @version = version
64
+ end
65
+ end
66
+
67
+ # Returns a device given a udid or name. In the case of a physical device,
68
+ # the udid is the device identifier. In the case of a simulator the name
69
+ # is the _instruments identifier_ as reported by
70
+ # `$ xcrun instruments -s devices` - this is the identifier that can be
71
+ # passed to instruments.
72
+ #
73
+ # @example
74
+ # RunLoop::Device.device_with_identifier('iPhone 4s (8.3 Simulator'))
75
+ # RunLoop::Device.device_with_identifier('6E43E3CF-25F5-41CC-A833-588F043AE749')
76
+ # RunLoop::Device.device_with_identifier('denis') # Simulator or device named 'denis'
77
+ # RunLoop::Device.device_with_identifier('893688959205dc7eb48d603c558ede919ad8dd0c')
78
+ #
79
+ # Note that if you have a device and simulator with the same name, the
80
+ # simulator will always be selected.
81
+ #
82
+ # @param [String] udid_or_name A name or udid that identifies the device you
83
+ # are looking for.
84
+ # @param [Hash] options Allows callers to pass runtime models that might
85
+ # optimize performance (via memoization).
86
+ # @option options [RunLoop::Simctl] :simctl An instance of
87
+ # Simctl.
88
+ # @option options [RunLoop::Instruments] :instruments An instance of
89
+ # Instruments.
90
+ # @option options [RunLoop::Xcode] :xcode An instance of Xcode
91
+ #
92
+ # @return [RunLoop::Device] A device that matches `udid_or_name`.
93
+ # @raise [ArgumentError] If no matching device can be found.
94
+ def self.device_with_identifier(udid_or_name, options={})
95
+ if options.is_a?(RunLoop::SimControl)
96
+ raise ArgumentError, %q[Support for the 'sim_control' argument has been
97
+ removed (1.5.0). It has been replaced by an options hash with two keys:
98
+ :simctl and :instruments. Please update your sources.))]
99
+ end
100
+
101
+ default_options = {
102
+ :simctl => RunLoop::Simctl.new,
103
+ :instruments => RunLoop::Instruments.new,
104
+ :xcode => RunLoop::Xcode.new
105
+ }
106
+
107
+ merged_options = default_options.merge(options)
108
+
109
+ instruments = merged_options[:instruments]
110
+ simctl = merged_options[:simctl]
111
+
112
+ xcode = RunLoop::Xcode.new
113
+ simulator = simctl.simulators.detect do |sim|
114
+ sim.instruments_identifier(xcode) == udid_or_name ||
115
+ sim.udid == udid_or_name
116
+ end
117
+
118
+ return simulator if !simulator.nil?
119
+
120
+ physical_device = instruments.physical_devices.detect do |device|
121
+ device.name == udid_or_name ||
122
+ device.udid == udid_or_name
123
+ end
124
+
125
+ return physical_device if !physical_device.nil?
126
+
127
+ raise ArgumentError, "Could not find a device with a UDID or name matching '#{udid_or_name}'"
128
+ end
129
+
130
+ # @!visibility private
131
+ #
132
+ # Please don't call this method. It is for internal use only. The behavior
133
+ # may change at any time! You have been warned.
134
+ #
135
+ # @param [Hash] options The launch options passed to RunLoop::Core
136
+ # @param [RunLoop::Xcode] xcode An Xcode instance
137
+ # @param [RunLoop::Simctl] simctl A Simctl instance
138
+ # @param [RunLoop::Instruments] instruments An Instruments instance
139
+ #
140
+ # @raise [ArgumentError] If "device" is detected as the device target and
141
+ # there is no matching device.
142
+ # @raise [ArgumentError] If DEVICE_TARGET or options specify an identifier
143
+ # that does not match an iOS Simulator or physical device.
144
+ def self.detect_device(options, xcode, simctl, instruments)
145
+ device = self.device_from_opts_or_env(options)
146
+
147
+ # Passed an instance of RunLoop::Device
148
+ return device if device && device.is_a?(RunLoop::Device)
149
+
150
+ # Need to infer what what the user wants from the environment and options.
151
+ if device == "device"
152
+ identifier = self.detect_physical_device_on_usb
153
+ self.ensure_physical_device_connected(identifier, options)
154
+ elsif device.nil? || device == "" || device == "simulator"
155
+ identifier = RunLoop::Core.default_simulator(xcode)
156
+ else
157
+ identifier = device
158
+ end
159
+
160
+ # Raises ArgumentError if no matching device can be found.
161
+ self.device_with_identifier(identifier,
162
+ simctl: simctl,
163
+ instruments: instruments)
164
+ end
165
+
166
+ # @!visibility private
167
+ def to_s
168
+ if simulator?
169
+ "#<Simulator: #{name} (#{version.to_s}) #{udid} #{instruction_set}>"
170
+ else
171
+ "#<Device: #{name} (#{version.to_s}) #{udid}>"
172
+ end
173
+ end
174
+
175
+ # @!visibility private
176
+ def inspect
177
+ to_s
178
+ end
179
+
180
+ # Returns and instruments-ready device identifier that is a suitable value
181
+ # for DEVICE_TARGET environment variable.
182
+ #
183
+ # @param [RunLoop::Xcode] xcode The version of the active
184
+ # Xcode.
185
+ # @return [String] An instruments-ready device identifier.
186
+ # @raise [RuntimeError] If trying to obtain a instruments-ready identifier
187
+ # for a simulator when Xcode < 6.
188
+ def instruments_identifier(xcode)
189
+ if physical_device?
190
+ udid
191
+ else
192
+ if version == RunLoop::Version.new('7.0.3')
193
+ version_part = version.to_s
194
+ else
195
+ version_part = "#{version.major}.#{version.minor}"
196
+ end
197
+
198
+ if xcode.version_gte_7?
199
+ "#{name} (#{version_part})"
200
+ else
201
+ "#{name} (#{version_part} Simulator)"
202
+ end
203
+ end
204
+ end
205
+
206
+ # Is this a physical device?
207
+ # @return [Boolean] Returns true if this is a device.
208
+ def physical_device?
209
+ if udid.nil?
210
+ stack = Kernel.caller(0, 6)[0..-1].join("\n")
211
+ raise RuntimeError,
212
+ %Q[udid is nil
213
+
214
+ #{stack}
215
+
216
+ name: #{name}
217
+ version: #{version}
218
+ ]
219
+ end
220
+ !udid[DEVICE_UDID_REGEX, 0].nil?
221
+ end
222
+
223
+ # Is this a simulator?
224
+ # @return [Boolean] Returns true if this is a simulator.
225
+ def simulator?
226
+ !physical_device?
227
+ end
228
+
229
+ # Return the instruction set for this device.
230
+ #
231
+ # **Simulator**
232
+ # The simulator instruction set will be i386 or x86_64 depending on the
233
+ # the (marketing) name of the device.
234
+ #
235
+ # @note Finding the instruction set of a device requires a third-party tool
236
+ # like ideviceinfo. Example:
237
+ # `$ ideviceinfo -u 89b59 < snip > ab7ba --key 'CPUArchitecture' => arm64`
238
+ #
239
+ # @raise [RuntimeError] Raises an error if this device is a physical device.
240
+ # @return [String] An instruction set.
241
+ def instruction_set
242
+ if simulator?
243
+ if ['iPhone 4s', 'iPhone 5', 'iPad 2', 'iPad Retina'].include?(self.name)
244
+ 'i386'
245
+ else
246
+ 'x86_64'
247
+ end
248
+ else
249
+ raise 'Finding the instruction set of a device requires a third-party tool like ideviceinfo'
250
+ end
251
+ end
252
+
253
+ # @!visibility private
254
+ # The device `state` is reported by the simctl tool.
255
+ #
256
+ # The expected values from simctl are:
257
+ #
258
+ # * Booted
259
+ # * Shutdown
260
+ # * Shutting Down
261
+ #
262
+ # To handle exceptional cases, there are these two additional states:
263
+ #
264
+ # * Unavailable # Should never occur
265
+ # * Unknown # A stub for future changes
266
+ def update_simulator_state
267
+ if physical_device?
268
+ raise RuntimeError, 'This method is available only for simulators'
269
+ end
270
+
271
+ @state = fetch_simulator_state
272
+ end
273
+
274
+ # @!visibility private
275
+ def simulator_root_dir
276
+ @simulator_root_dir ||= lambda {
277
+ return nil if physical_device?
278
+ File.join(CORE_SIMULATOR_DEVICE_DIR, udid)
279
+ }.call
280
+ end
281
+
282
+ # @!visibility private
283
+ def simulator_accessibility_plist_path
284
+ @simulator_accessibility_plist_path ||= lambda {
285
+ return nil if physical_device?
286
+ File.join(simulator_root_dir, 'data/Library/Preferences/com.apple.Accessibility.plist')
287
+ }.call
288
+ end
289
+
290
+ # @!visibility private
291
+ def simulator_preferences_plist_path
292
+ @simulator_preferences_plist_path ||= lambda {
293
+ return nil if physical_device?
294
+ File.join(simulator_root_dir, 'data/Library/Preferences/com.apple.Preferences.plist')
295
+ }.call
296
+ end
297
+
298
+ # @!visibility private
299
+ def simulator_log_file_path
300
+ @simulator_log_file_path ||= lambda {
301
+ return nil if physical_device?
302
+ File.join(CORE_SIMULATOR_LOGS_DIR, udid, 'system.log')
303
+ }.call
304
+ end
305
+
306
+ # @!visibility private
307
+ def simulator_device_plist
308
+ @simulator_device_plist ||= lambda do
309
+ return nil if physical_device?
310
+ File.join(simulator_root_dir, 'device.plist')
311
+ end.call
312
+ end
313
+
314
+ # @!visibility private
315
+ def simulator_global_preferences_path
316
+ @simulator_global_preferences_path ||= lambda do
317
+ return nil if physical_device?
318
+ File.join(simulator_root_dir, "data/Library/Preferences/.GlobalPreferences.plist")
319
+ end.call
320
+ end
321
+
322
+ # @!visibility private
323
+ def simulator_tcc_db
324
+ @simulator_tcc_db ||= lambda do
325
+ return nil if physical_device?
326
+ path = File.join(simulator_root_dir, "data/Library/TCC/TCC.db")
327
+ simulator_ensure_tcc_db(path)
328
+ end.call
329
+ end
330
+
331
+ # @!visibility private
332
+ #
333
+ # Waits for three conditions:
334
+ #
335
+ # 1. The SHA sum of the simulator data/ directory to be stable.
336
+ # 2. No more log messages are begin generated.
337
+ # 3. 1 and 2 must hold for 1.5 seconds.
338
+ #
339
+ # When the simulator version is >= iOS 9, two more conditions are added to
340
+ # get past the iOS 9+ boot screen.
341
+ #
342
+ # 4. Wait for com.apple.audio.SystemSoundServer-iOS-Simulator process to
343
+ # start.
344
+ # 5. 1 and 2 must hold for 1.5 seconds.
345
+ #
346
+ # When the simulator version is >= iOS 9 and the device is an iPad another
347
+ # condition is added because simctl fails to correctly install applications;
348
+ # the app and data container exists, but Springboard does not detect them.
349
+ #
350
+ # 6. 1 and 2 must hold for 1.5 seconds.
351
+ def simulator_wait_for_stable_state
352
+
353
+ # How long to wait between stability checks.
354
+ # Shorter than this gives false positives.
355
+ delay = 0.5
356
+
357
+ # How many times to wait for stable state.
358
+ max_stable_count = 3
359
+
360
+ # How long to wait for iOS 9 boot screen.
361
+ boot_screen_wait_options = {
362
+ :max_boot_screen_wait => 10,
363
+ :raise_on_timeout => false
364
+ }
365
+
366
+ # How much additional time to wait for iOS 9+ iPads.
367
+ #
368
+ # Installing and launching on iPads is problematic.
369
+ # Sometimes the app is installed, but SpringBoard does
370
+ # not recognize that the app is installed even though
371
+ # simctl says that it is.
372
+ additional_ipad_delay = delay * 2
373
+
374
+ # Adjust for CI environments
375
+ if RunLoop::Environment.ci?
376
+ max_stable_count = 5
377
+ boot_screen_wait_options[:max_boot_screen_wait] = 20
378
+ additional_ipad_delay = delay * 4
379
+ end
380
+
381
+ # iOS 9 simulators have an additional boot screen.
382
+ is_gte_ios9 = version >= RunLoop::Version.new('9.0')
383
+
384
+ # iOS 9 iPad simulators need additional time to stabilize.
385
+ is_ipad = simulator_is_ipad?
386
+
387
+ timeout = SIM_STABLE_STATE_OPTIONS[:timeout]
388
+ now = Time.now
389
+ poll_until = now + timeout
390
+
391
+ RunLoop.log_debug("Waiting for simulator to stabilize with timeout: #{timeout} seconds")
392
+
393
+ current_dir_sha = simulator_data_directory_sha
394
+ current_log_sha = simulator_log_file_sha
395
+ is_stable = false
396
+ waited_for_boot = false
397
+ waited_for_ipad = false
398
+ stable_count = 0
399
+
400
+ while Time.now < poll_until do
401
+ latest_dir_sha = simulator_data_directory_sha
402
+ latest_log_sha = simulator_log_file_sha
403
+
404
+ is_stable = [current_dir_sha == latest_dir_sha,
405
+ current_log_sha == latest_log_sha].all?
406
+
407
+ if is_stable
408
+ stable_count = stable_count + 1
409
+ if stable_count == max_stable_count
410
+ if is_gte_ios9 && !waited_for_boot
411
+ process_name = "com.apple.audio.SystemSoundServer-iOS-Simulator"
412
+ RunLoop::ProcessWaiter.new(process_name, boot_screen_wait_options).wait_for_any
413
+ waited_for_boot = true
414
+ stable_count = 0
415
+ elsif is_gte_ios9 && is_ipad && !waited_for_ipad
416
+ RunLoop.log_debug("Waiting additional time for iOS 9 iPad to stabilize")
417
+ sleep(additional_ipad_delay)
418
+ waited_for_ipad = true
419
+ stable_count = 0
420
+ else
421
+ break
422
+ end
423
+ end
424
+ end
425
+
426
+ current_dir_sha = latest_dir_sha
427
+ current_log_sha = latest_log_sha
428
+ sleep(delay)
429
+ end
430
+
431
+ if is_stable
432
+ elapsed = Time.now - now
433
+ RunLoop.log_debug("Waited a total of #{elapsed} seconds for simulator to stabilize")
434
+ else
435
+ RunLoop.log_debug("Timed out after #{timeout} seconds waiting for simulator to stabilize")
436
+ end
437
+ end
438
+
439
+ # @!visibility private
440
+ #
441
+ # Sets the AppleLocale key in the .GlobalPreferences.plist file
442
+ #
443
+ # @param [String] locale_code a locale code
444
+ #
445
+ # @return [RunLoop::Locale] a locale instance
446
+ #
447
+ # @raise [RuntimeError] if this is a physical device
448
+ # @raise [ArgumentError] if the locale code is invalid
449
+ def simulator_set_locale(locale_code)
450
+ if physical_device?
451
+ raise RuntimeError, "This method is for Simulators only"
452
+ end
453
+
454
+ locale = RunLoop::Locale.locale_for_code(locale_code, self)
455
+
456
+ global_plist = simulator_global_preferences_path
457
+ pbuddy.plist_set("AppleLocale", "string", locale.code, global_plist)
458
+
459
+ locale
460
+ end
461
+
462
+ # @!visibility private
463
+ #
464
+ # Returns the AppleLanguages array in global plist as an array
465
+ #
466
+ # @return [Array<String>] list of language codes
467
+ def simulator_languages
468
+ global_plist = simulator_global_preferences_path
469
+ out = pbuddy.plist_read("AppleLanguages", global_plist)
470
+
471
+ # example: "Array {\n en\n en-US\n}"
472
+ # I am intentionally punting on this because I don't want
473
+ # to track down edge cases until the output of this method
474
+ # is actually used.
475
+ result = [out]
476
+ begin
477
+ result = out.strip.gsub(/[\{\}]/, "").split($-0).map do |elm|
478
+ elm.strip
479
+ end[1..-1]
480
+ rescue => e
481
+ RunLoop.log_debug("Caught error #{e.message} trying to parse '#{out}'")
482
+ end
483
+
484
+ result
485
+ end
486
+
487
+ # @!visibility private
488
+ #
489
+ # Sets the first element in the AppleLanguages array to lang_code.
490
+ #
491
+ # @param [String] lang_code a language code
492
+ #
493
+ # @return [Array<String>] list of language codes
494
+ #
495
+ # @raise [RuntimeError] if this is a physical device
496
+ # @raise [ArgumentError] if the language code is invalid
497
+ def simulator_set_language(lang_code)
498
+ if physical_device?
499
+ raise RuntimeError, "This method is for Simulators only"
500
+ end
501
+
502
+ if !RunLoop::Language.valid_code_for_device?(lang_code, self)
503
+ raise ArgumentError,
504
+ "The language code '#{lang_code}' is not valid for this device"
505
+ end
506
+
507
+ global_plist = simulator_global_preferences_path
508
+
509
+ cmd = [
510
+ "PlistBuddy",
511
+ "-c",
512
+ "Add :AppleLanguages:0 string '#{lang_code}'",
513
+ global_plist
514
+ ]
515
+
516
+ # RunLoop::PlistBuddy cannot add items to arrays.
517
+ xcrun.run_command_in_context(cmd, {:log_cmd => true})
518
+
519
+ simulator_languages
520
+ end
521
+
522
+ private
523
+
524
+ # @!visibility private
525
+ def xcrun
526
+ RunLoop::Xcrun.new
527
+ end
528
+
529
+ # @!visibility private
530
+ def pbuddy
531
+ RunLoop::PlistBuddy.new
532
+ end
533
+
534
+ # @!visibility private
535
+ def detect_state_from_line(line)
536
+
537
+ if line[/unavailable/, 0]
538
+ RunLoop.log_debug("Simulator state is unavailable: #{line}")
539
+ return 'Unavailable'
540
+ end
541
+
542
+ state = line[/(Booted|Shutdown|Shutting Down)/,0]
543
+
544
+ if state.nil?
545
+ RunLoop.log_debug("Simulator state is unknown: #{line}")
546
+ 'Unknown'
547
+ else
548
+ state
549
+ end
550
+ end
551
+
552
+ # @!visibility private
553
+ def fetch_simulator_state
554
+ if physical_device?
555
+ raise RuntimeError, 'This method is available only for simulators'
556
+ end
557
+
558
+ args = ['simctl', 'list', 'devices']
559
+ hash = xcrun.run_command_in_context(args)
560
+ out = hash[:out]
561
+
562
+ matched_line = out.split("\n").find do |line|
563
+ line.include?(udid)
564
+ end
565
+
566
+ if matched_line.nil?
567
+ raise RuntimeError,
568
+ "Expected a simulator with udid '#{udid}', but found none"
569
+ end
570
+
571
+ detect_state_from_line(matched_line)
572
+ end
573
+
574
+ # @!visibility private
575
+ CORE_SIMULATOR_DEVICE_DIR = File.join(RunLoop::Environment.user_home_directory,
576
+ "Library",
577
+ "Developer",
578
+ "CoreSimulator",
579
+ "Devices")
580
+
581
+ # @!visibility private
582
+ CORE_SIMULATOR_LOGS_DIR = File.join(RunLoop::Environment.user_home_directory,
583
+ "Library",
584
+ "Logs",
585
+ "CoreSimulator")
586
+
587
+ # @!visibility private
588
+ def self.device_from_options(options)
589
+ options[:device] || options[:device_target] || options[:udid]
590
+ end
591
+
592
+ # @!visibility private
593
+ def self.device_from_environment
594
+ RunLoop::Environment.device_target
595
+ end
596
+
597
+ # @!visibility private
598
+ def self.device_from_opts_or_env(options)
599
+ self.device_from_options(options) || self.device_from_environment
600
+ end
601
+
602
+ # @!visibility private
603
+ UDID_DETECT = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", 'scripts', "udidetect"))
604
+
605
+ # @!visibility private
606
+ def self.detect_physical_device_on_usb
607
+ require "command_runner"
608
+
609
+ udid = nil
610
+ begin
611
+ hash = CommandRunner.run([UDID_DETECT], timeout: 1)
612
+ udid = hash[:out].chomp
613
+ if udid == ""
614
+ udid = nil
615
+ end
616
+ rescue => e
617
+ RunLoop.log_debug("Running `udidetect` raised: #{e}")
618
+ ensure
619
+ `killall udidetect &> /dev/null`
620
+ end
621
+ udid
622
+ end
623
+
624
+ # @!visibility private
625
+ def simulator_data_directory_sha
626
+ path = File.join(simulator_root_dir, 'data')
627
+ begin
628
+ # Typically, this returns in < 0.3 seconds.
629
+ Timeout.timeout(10, TimeoutError) do
630
+ # Errors are ignorable and users are confused by the messages.
631
+ options = { :handle_errors_by => :ignoring }
632
+ RunLoop::Directory.directory_digest(path, options)
633
+ end
634
+ rescue => _
635
+ SecureRandom.uuid
636
+ end
637
+ end
638
+
639
+ def simulator_install_tcc_db(path)
640
+ dir = File.expand_path(File.dirname(__FILE__))
641
+ source = File.join(dir, "tcc", "TCC.db")
642
+
643
+ FileUtils.mkdir_p(File.expand_path(File.dirname(path)))
644
+ FileUtils.cp(source, path)
645
+ path
646
+ end
647
+
648
+ # @!visibility private
649
+ def simulator_ensure_tcc_db(path)
650
+ if File.exist?(path)
651
+ path
652
+ else
653
+ simulator_install_tcc_db(path)
654
+ end
655
+ end
656
+
657
+ # @!visibility private
658
+ def simulator_log_file_sha
659
+ file = simulator_log_file_path
660
+
661
+ return nil if !File.exist?(file)
662
+
663
+ sha = OpenSSL::Digest::SHA256.new
664
+
665
+ begin
666
+ sha << File.read(file)
667
+ rescue => _
668
+ sha = SecureRandom.uuid
669
+ end
670
+
671
+ sha
672
+ end
673
+
674
+ # @!visibility private
675
+ # Value of <UDID>/.device.plist 'deviceType' key.
676
+ def simulator_device_type
677
+ plist = File.join(simulator_device_plist)
678
+ pbuddy.plist_read("deviceType", plist)
679
+ end
680
+
681
+ # @!visibility private
682
+ def simulator_is_ipad?
683
+ simulator_device_type[/iPad/, 0]
684
+ end
685
+
686
+ # @!visibility private
687
+ def self.ensure_physical_device_connected(identifier, options)
688
+ if identifier.nil?
689
+ env = self.device_from_environment
690
+ if env == "device"
691
+ message = "DEVICE_TARGET=device means that you want to test on physical device"
692
+ elsif env && env[DEVICE_UDID_REGEX, 0]
693
+ message = "DEVICE_TARGET=#{env} did not match any connected device"
694
+ else
695
+ if options[:device]
696
+ key = ":device"
697
+ elsif options[:device_target]
698
+ key = ":device_target"
699
+ else
700
+ key = ":udid"
701
+ end
702
+ message = "#{key} => \"device\" means that you want to test on a physical device"
703
+ end
704
+
705
+ raise ArgumentError, %Q[Expected a physical device to be connected via USB.
706
+
707
+ #{message}
708
+
709
+ 1. Is your device connected?
710
+ 2. Does your device appear in the output of `xcrun instruments -s devices`?
711
+ 3. Does your device appear in Xcode > Windows > Devices without a warning message?
712
+
713
+ Please see the documentation about testing on physical devices.
714
+
715
+ https://github.com/calabash/calabash-ios/wiki/Testing-on-Physical-Devices
716
+ ]
717
+ end
718
+ true
719
+ end
720
+ end
721
+ end
722
+