run_loop_tcc 2.1.3

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