run_loop 1.3.1 → 1.3.2
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.
- checksums.yaml +4 -4
- data/lib/run_loop.rb +2 -0
- data/lib/run_loop/cli/cli.rb +4 -0
- data/lib/run_loop/cli/instruments.rb +2 -2
- data/lib/run_loop/cli/simctl.rb +200 -0
- data/lib/run_loop/core.rb +3 -1
- data/lib/run_loop/device.rb +13 -0
- data/lib/run_loop/directory.rb +51 -0
- data/lib/run_loop/lipo.rb +4 -3
- data/lib/run_loop/patches/retriable.rb +21 -0
- data/lib/run_loop/simctl/bridge.rb +212 -19
- data/lib/run_loop/simctl/plists.rb +20 -0
- data/lib/run_loop/version.rb +1 -1
- data/lib/run_loop/xctools.rb +16 -1
- data/plists/simctl/com.apple.UIAutomation.plist +0 -0
- data/plists/simctl/com.apple.UIAutomationPlugIn.plist +0 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 66f671a88ff045375fc68e725a7718dd22a99769
|
4
|
+
data.tar.gz: 1c9ea08167d06e4a168af8b14a731a5448ea16ca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f4e9da22b7d00d2228663a0009decf562004e5def1e68245179fef574c9e9e0ea94375e92473cd37007fb13d6c477cce06e1346caa56dd7350871fed6e3b3ebb
|
7
|
+
data.tar.gz: 39095dd3bbe9f8b6b67bf3e51c081568513ad7dcdcde681332faec200bbd9bb527fa19c17959172744285a4e7bf539cd7bab028696ea0644bf64db466ea68e68
|
data/lib/run_loop.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'run_loop/directory'
|
1
2
|
require 'run_loop/environment'
|
2
3
|
require 'run_loop/logging'
|
3
4
|
require 'run_loop/process_terminator'
|
@@ -18,6 +19,7 @@ require 'run_loop/host_cache'
|
|
18
19
|
require 'run_loop/patches/awesome_print'
|
19
20
|
require 'run_loop/patches/retriable'
|
20
21
|
require 'run_loop/simctl/bridge'
|
22
|
+
require 'run_loop/simctl/plists'
|
21
23
|
|
22
24
|
module RunLoop
|
23
25
|
|
data/lib/run_loop/cli/cli.rb
CHANGED
@@ -2,6 +2,7 @@ require 'thor'
|
|
2
2
|
require 'run_loop'
|
3
3
|
require 'run_loop/cli/errors'
|
4
4
|
require 'run_loop/cli/instruments'
|
5
|
+
require 'run_loop/cli/simctl'
|
5
6
|
|
6
7
|
trap 'SIGINT' do
|
7
8
|
puts 'Trapped SIGINT - exiting'
|
@@ -27,6 +28,9 @@ module RunLoop
|
|
27
28
|
desc 'instruments', "Interact with Xcode's command-line instruments"
|
28
29
|
subcommand 'instruments', RunLoop::CLI::Instruments
|
29
30
|
|
31
|
+
desc 'simctl', "Interact with Xcode's command-line simctl"
|
32
|
+
subcommand 'simctl', RunLoop::CLI::Simctl
|
33
|
+
|
30
34
|
end
|
31
35
|
end
|
32
36
|
end
|
@@ -8,7 +8,7 @@ module RunLoop
|
|
8
8
|
|
9
9
|
attr_accessor :signal
|
10
10
|
|
11
|
-
desc '
|
11
|
+
desc 'quit', 'Send a kill signal to all instruments processes.'
|
12
12
|
|
13
13
|
method_option 'signal',
|
14
14
|
:desc => 'The kill signal to send.',
|
@@ -39,7 +39,7 @@ module RunLoop
|
|
39
39
|
end
|
40
40
|
|
41
41
|
|
42
|
-
desc '
|
42
|
+
desc 'launch [--app | [--ipa | --bundle-id]] [OPTIONS]', 'Launch an app with instruments.'
|
43
43
|
|
44
44
|
# This is the description we want, but Thor doesn't handle newlines well(?).
|
45
45
|
# long_desc <<EOF
|
@@ -0,0 +1,200 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'run_loop'
|
3
|
+
require 'run_loop/cli/errors'
|
4
|
+
|
5
|
+
module RunLoop
|
6
|
+
module CLI
|
7
|
+
class Simctl < Thor
|
8
|
+
|
9
|
+
attr_reader :sim_control
|
10
|
+
|
11
|
+
desc 'tail', 'Tail the log file of the booted simulator'
|
12
|
+
def tail
|
13
|
+
tail_booted
|
14
|
+
end
|
15
|
+
|
16
|
+
no_commands do
|
17
|
+
def tail_booted
|
18
|
+
device = booted_device
|
19
|
+
log_file = device.simulator_log_file_path
|
20
|
+
exec('tail', *['-F', log_file])
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
desc 'booted', 'Prints details about the booted simulator'
|
25
|
+
def booted
|
26
|
+
device = booted_device
|
27
|
+
if device.nil?
|
28
|
+
puts 'No simulator is booted.'
|
29
|
+
else
|
30
|
+
puts device
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
no_commands do
|
35
|
+
def sim_control
|
36
|
+
@sim_control ||= RunLoop::SimControl.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def booted_device
|
40
|
+
sim_control.simulators.detect(nil) do |device|
|
41
|
+
device.state == 'Booted'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
desc 'install --app [OPTIONS]', 'Installs an app on a device'
|
47
|
+
|
48
|
+
method_option 'app',
|
49
|
+
:desc => 'Path to a .app bundle to launch on simulator.',
|
50
|
+
:aliases => '-a',
|
51
|
+
:required => true,
|
52
|
+
:type => :string
|
53
|
+
|
54
|
+
method_option 'device',
|
55
|
+
:desc => 'The device UDID or simulator identifier.',
|
56
|
+
:aliases => '-d',
|
57
|
+
:required => false,
|
58
|
+
:type => :string
|
59
|
+
|
60
|
+
method_option 'force',
|
61
|
+
:desc => 'Force a re-install the existing app.',
|
62
|
+
:aliases => '-f',
|
63
|
+
:required => false,
|
64
|
+
:default => false,
|
65
|
+
:type => :boolean
|
66
|
+
|
67
|
+
method_option 'debug',
|
68
|
+
:desc => 'Enable debug logging.',
|
69
|
+
:aliases => '-v',
|
70
|
+
:required => false,
|
71
|
+
:default => false,
|
72
|
+
:type => :boolean
|
73
|
+
|
74
|
+
def install
|
75
|
+
debug = options[:debug]
|
76
|
+
|
77
|
+
if debug
|
78
|
+
ENV['DEBUG'] = '1'
|
79
|
+
end
|
80
|
+
|
81
|
+
debug_logging = RunLoop::Environment.debug?
|
82
|
+
|
83
|
+
device = expect_device(options)
|
84
|
+
app = expect_app(options, device)
|
85
|
+
|
86
|
+
bridge = RunLoop::Simctl::Bridge.new(device, app.path)
|
87
|
+
|
88
|
+
force_reinstall = options[:force]
|
89
|
+
|
90
|
+
before = Time.now
|
91
|
+
|
92
|
+
if bridge.app_is_installed?
|
93
|
+
if debug_logging
|
94
|
+
puts "App with bundle id '#{app.bundle_identifier}' is already installed."
|
95
|
+
end
|
96
|
+
|
97
|
+
if force_reinstall
|
98
|
+
if debug_logging
|
99
|
+
puts 'Will force a re-install.'
|
100
|
+
end
|
101
|
+
bridge.uninstall
|
102
|
+
bridge.install
|
103
|
+
else
|
104
|
+
new_digest = RunLoop::Directory.directory_digest(app.path)
|
105
|
+
if debug_logging
|
106
|
+
puts " New app has SHA: '#{new_digest}'."
|
107
|
+
end
|
108
|
+
installed_app_bundle = bridge.fetch_app_dir
|
109
|
+
old_digest = RunLoop::Directory.directory_digest(installed_app_bundle)
|
110
|
+
if debug_logging
|
111
|
+
puts "Installed app has SHA: '#{old_digest}'."
|
112
|
+
end
|
113
|
+
if new_digest != old_digest
|
114
|
+
if debug_logging
|
115
|
+
puts "Will re-install '#{app.bundle_identifier}' because the SHAs don't match."
|
116
|
+
end
|
117
|
+
bridge.uninstall
|
118
|
+
bridge.install
|
119
|
+
else
|
120
|
+
if debug_logging
|
121
|
+
puts "Will not re-install '#{app.bundle_identifier}' because the SHAs match."
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
else
|
126
|
+
bridge.install
|
127
|
+
end
|
128
|
+
|
129
|
+
if debug_logging
|
130
|
+
"Launching took #{Time.now-before} seconds"
|
131
|
+
puts "Installed '#{app.bundle_identifier}' on #{device} in #{Time.now-before} seconds."
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
no_commands do
|
136
|
+
def expect_device(options)
|
137
|
+
device_from_options = options[:device]
|
138
|
+
simulators = sim_control.simulators
|
139
|
+
if device_from_options.nil?
|
140
|
+
default_name = RunLoop::Core.default_simulator
|
141
|
+
device = simulators.detect do |sim|
|
142
|
+
sim.instruments_identifier == default_name
|
143
|
+
end
|
144
|
+
|
145
|
+
if device.nil?
|
146
|
+
raise RunLoop::CLI::ValidationError,
|
147
|
+
"Could not find a simulator with name that matches '#{device_from_options}'"
|
148
|
+
end
|
149
|
+
else
|
150
|
+
device = simulators.detect do |sim|
|
151
|
+
sim.udid == device_from_options ||
|
152
|
+
sim.instruments_identifier == device_from_options
|
153
|
+
end
|
154
|
+
|
155
|
+
if device.nil?
|
156
|
+
raise RunLoop::CLI::ValidationError,
|
157
|
+
"Could not find a simulator with name or UDID that matches '#{device_from_options}'"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
device
|
161
|
+
end
|
162
|
+
|
163
|
+
def expect_app(options, device_obj)
|
164
|
+
app_bundle_path = options[:app]
|
165
|
+
unless File.exist?(app_bundle_path)
|
166
|
+
raise RunLoop::CLI::ValidationError, "Expected '#{app_bundle_path}' to exist."
|
167
|
+
end
|
168
|
+
|
169
|
+
unless File.directory?(app_bundle_path)
|
170
|
+
raise RunLoop::CLI::ValidationError,
|
171
|
+
"Expected '#{app_bundle_path}' to be a directory."
|
172
|
+
end
|
173
|
+
|
174
|
+
unless File.extname(app_bundle_path) == '.app'
|
175
|
+
raise RunLoop::CLI::ValidationError,
|
176
|
+
"Expected '#{app_bundle_path}' to end in .app."
|
177
|
+
end
|
178
|
+
|
179
|
+
app = RunLoop::App.new(app_bundle_path)
|
180
|
+
|
181
|
+
begin
|
182
|
+
app.bundle_identifier
|
183
|
+
app.executable_name
|
184
|
+
rescue RuntimeError => e
|
185
|
+
raise RunLoop::CLI::ValidationError, e.message
|
186
|
+
end
|
187
|
+
|
188
|
+
lipo = RunLoop::Lipo.new(app.path)
|
189
|
+
begin
|
190
|
+
lipo.expect_compatible_arch(device_obj)
|
191
|
+
rescue RunLoop::IncompatibleArchitecture => e
|
192
|
+
raise RunLoop::CLI::ValidationError, e.message
|
193
|
+
end
|
194
|
+
|
195
|
+
app
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
data/lib/run_loop/core.rb
CHANGED
@@ -391,7 +391,9 @@ module RunLoop
|
|
391
391
|
# @param [RunLoop::XCTools] xcode_tools Used to detect the current xcode
|
392
392
|
# version.
|
393
393
|
def self.default_simulator(xcode_tools=RunLoop::XCTools.new)
|
394
|
-
if xcode_tools.
|
394
|
+
if xcode_tools.xcode_version_gte_64?
|
395
|
+
'iPhone 5s (8.4 Simulator)'
|
396
|
+
elsif xcode_tools.xcode_version_gte_63?
|
395
397
|
'iPhone 5s (8.3 Simulator)'
|
396
398
|
elsif xcode_tools.xcode_version_gte_62?
|
397
399
|
'iPhone 5s (8.2 Simulator)'
|
data/lib/run_loop/device.rb
CHANGED
@@ -8,6 +8,7 @@ module RunLoop
|
|
8
8
|
attr_reader :simulator_root_dir
|
9
9
|
attr_reader :simulator_accessibility_plist_path
|
10
10
|
attr_reader :simulator_preferences_plist_path
|
11
|
+
attr_reader :simulator_log_file_path
|
11
12
|
|
12
13
|
# Create a new device.
|
13
14
|
#
|
@@ -32,6 +33,10 @@ module RunLoop
|
|
32
33
|
end
|
33
34
|
end
|
34
35
|
|
36
|
+
def to_s
|
37
|
+
"#{instruments_identifier} #{udid} #{instruction_set}"
|
38
|
+
end
|
39
|
+
|
35
40
|
# Returns and instruments-ready device identifier that is a suitable value
|
36
41
|
# for DEVICE_TARGET environment variable.
|
37
42
|
#
|
@@ -111,8 +116,16 @@ module RunLoop
|
|
111
116
|
}.call
|
112
117
|
end
|
113
118
|
|
119
|
+
def simulator_log_file_path
|
120
|
+
@simulator_log_file_path ||= lambda {
|
121
|
+
return nil if physical_device?
|
122
|
+
File.join(CORE_SIMULATOR_LOGS_DIR, udid, 'system.log')
|
123
|
+
}.call
|
124
|
+
end
|
125
|
+
|
114
126
|
private
|
115
127
|
|
116
128
|
CORE_SIMULATOR_DEVICE_DIR = File.expand_path('~/Library/Developer/CoreSimulator/Devices')
|
129
|
+
CORE_SIMULATOR_LOGS_DIR = File.expand_path('~/Library/Logs/CoreSimulator')
|
117
130
|
end
|
118
131
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'digest'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
module RunLoop
|
5
|
+
|
6
|
+
# Class for performing operations on directories.
|
7
|
+
class Directory
|
8
|
+
|
9
|
+
# Dir.glob ignores files that start with '.', but we often need to find
|
10
|
+
# dotted files and directories.
|
11
|
+
#
|
12
|
+
# Ruby 2.* does the right thing by ignoring '..' and '.'.
|
13
|
+
#
|
14
|
+
# Ruby < 2.0 includes '..' and '.' in results which causes problems for some
|
15
|
+
# of run-loop's internal methods. In particular `reset_app_sandbox`.
|
16
|
+
def self.recursive_glob_for_entries(base_dir)
|
17
|
+
Dir.glob("#{base_dir}/{**/.*,**/*}").select do |entry|
|
18
|
+
!(entry.end_with?('..') || entry.end_with?('.'))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Computes the digest of directory.
|
23
|
+
#
|
24
|
+
# @param path A path to a directory.
|
25
|
+
# @raise ArgumentError When `path` is not a directory or path does not exist.
|
26
|
+
def self.directory_digest(path)
|
27
|
+
|
28
|
+
unless File.exist?(path)
|
29
|
+
raise ArgumentError, "Expected '#{path}' to exist"
|
30
|
+
end
|
31
|
+
|
32
|
+
unless File.directory?(path)
|
33
|
+
raise ArgumentError, "Expected '#{path}' to be a directory"
|
34
|
+
end
|
35
|
+
|
36
|
+
entries = self.recursive_glob_for_entries(path)
|
37
|
+
|
38
|
+
if entries.empty?
|
39
|
+
raise ArgumentError, "Expected a non-empty dir at '#{path}' found '#{entries}'"
|
40
|
+
end
|
41
|
+
|
42
|
+
sha = OpenSSL::Digest::SHA256.new
|
43
|
+
self.recursive_glob_for_entries(path).each do |file|
|
44
|
+
unless File.directory?(file)
|
45
|
+
sha << File.read(file)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
sha.hexdigest
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/run_loop/lipo.rb
CHANGED
@@ -71,7 +71,7 @@ module RunLoop
|
|
71
71
|
raise RunLoop::IncompatibleArchitecture,
|
72
72
|
['Binary at:',
|
73
73
|
binary_path,
|
74
|
-
|
74
|
+
'does not contain a compatible architecture for target device.',
|
75
75
|
"Expected '#{instruction_set}' but found #{arches}."].join("\n")
|
76
76
|
end
|
77
77
|
end
|
@@ -85,9 +85,9 @@ module RunLoop
|
|
85
85
|
output = stdout.read.strip
|
86
86
|
begin
|
87
87
|
output.split(':')[-1].strip.split
|
88
|
-
rescue
|
88
|
+
rescue StandardError => e
|
89
89
|
msg = ['Expected to be able to parse the output of lipo.',
|
90
|
-
"cmd: 'lipo -info #{
|
90
|
+
"cmd: 'lipo -info \"#{binary_path}\"'",
|
91
91
|
"stdout: '#{output}'",
|
92
92
|
"stderr: '#{stderr.read.strip}'",
|
93
93
|
"exit code: '#{wait_thr.value}'",
|
@@ -102,6 +102,7 @@ module RunLoop
|
|
102
102
|
# Caller is responsible for correctly escaping arguments.
|
103
103
|
# For example, the caller must proper quote `"` paths to avoid errors
|
104
104
|
# when dealing with paths that contain spaces.
|
105
|
+
# @todo #execute_lipo should take an [] of arguments
|
105
106
|
def execute_lipo(argument)
|
106
107
|
command = "xcrun lipo #{argument}"
|
107
108
|
Open3.popen3(command) do |_, stdout, stderr, wait_thr|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'retriable'
|
2
|
+
require 'retriable/version'
|
2
3
|
|
3
4
|
module RunLoop
|
4
5
|
# A class to bridge the gap between retriable 1.x and 2.0.
|
@@ -22,3 +23,23 @@ module RunLoop
|
|
22
23
|
end
|
23
24
|
end
|
24
25
|
end
|
26
|
+
|
27
|
+
# Only in retriable 1.4.0
|
28
|
+
unless Retriable.public_instance_methods.include?(:retriable)
|
29
|
+
require 'retriable/retry'
|
30
|
+
module Retriable
|
31
|
+
extend self
|
32
|
+
|
33
|
+
def retriable(opts = {}, &block)
|
34
|
+
raise LocalJumpError unless block_given?
|
35
|
+
|
36
|
+
Retry.new do |r|
|
37
|
+
r.tries = opts[:tries] if opts[:tries]
|
38
|
+
r.on = opts[:on] if opts[:on]
|
39
|
+
r.interval = opts[:interval] if opts[:interval]
|
40
|
+
r.timeout = opts[:timeout] if opts[:timeout]
|
41
|
+
r.on_retry = opts[:on_retry] if opts[:on_retry]
|
42
|
+
end.perform(&block)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -1,3 +1,7 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'open3'
|
3
|
+
#require 'retriable'
|
4
|
+
|
1
5
|
module RunLoop::Simctl
|
2
6
|
|
3
7
|
class SimctlError < StandardError
|
@@ -9,15 +13,19 @@ module RunLoop::Simctl
|
|
9
13
|
#
|
10
14
|
# TODO Some code is duplicated from sim_control.rb
|
11
15
|
# TODO Reinstall if checksum does not match.
|
12
|
-
# TODO Analyze terminate_core_simulator_processes
|
16
|
+
# TODO Analyze terminate_core_simulator_processes.
|
17
|
+
# TODO Figure out when CoreSimulator appears and does not appear.
|
13
18
|
class Bridge
|
14
19
|
|
15
20
|
attr_reader :device
|
16
21
|
attr_reader :app
|
17
22
|
attr_reader :sim_control
|
23
|
+
attr_reader :pbuddy
|
18
24
|
|
19
25
|
def initialize(device, app_bundle_path)
|
20
26
|
|
27
|
+
@pbuddy = RunLoop::PlistBuddy.new
|
28
|
+
|
21
29
|
@sim_control = RunLoop::SimControl.new
|
22
30
|
@path_to_ios_sim_app_bundle = lambda {
|
23
31
|
dev_dir = @sim_control.xctools.xcode_developer_dir
|
@@ -42,24 +50,96 @@ module RunLoop::Simctl
|
|
42
50
|
terminate_core_simulator_processes
|
43
51
|
end
|
44
52
|
|
45
|
-
|
53
|
+
# @!visibility private
|
54
|
+
def is_sdk_8?
|
55
|
+
@is_sdk_8 ||= device.version >= RunLoop::Version.new('8.0')
|
56
|
+
end
|
57
|
+
|
58
|
+
def device_data_dir
|
59
|
+
@device_data_dir ||= File.join(CORE_SIMULATOR_DEVICE_DIR, device.udid, 'data')
|
60
|
+
end
|
61
|
+
|
62
|
+
def device_applications_dir
|
46
63
|
@simulator_app_dir ||= lambda {
|
47
|
-
|
48
|
-
|
49
|
-
File.join(device_dir, device.udid, 'data', 'Applications')
|
64
|
+
if is_sdk_8?
|
65
|
+
File.join(device_data_dir, 'Containers', 'Bundle', 'Application')
|
50
66
|
else
|
51
|
-
File.join(
|
67
|
+
File.join(device_data_dir, 'Applications')
|
52
68
|
end
|
53
69
|
}.call
|
54
70
|
end
|
55
71
|
|
72
|
+
def app_data_dir
|
73
|
+
app_install_dir = fetch_app_dir
|
74
|
+
return nil if app_install_dir.nil?
|
75
|
+
if is_sdk_8?
|
76
|
+
containers_data_dir = File.join(device_data_dir, 'Containers', 'Data', 'Application')
|
77
|
+
apps = Dir.glob("#{containers_data_dir}/**/#{METADATA_PLIST}")
|
78
|
+
match = apps.detect do |metadata_plist|
|
79
|
+
pbuddy.plist_read('MCMMetadataIdentifier', metadata_plist) == app.bundle_identifier
|
80
|
+
end
|
81
|
+
if match
|
82
|
+
File.dirname(match)
|
83
|
+
else
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
else
|
87
|
+
app_install_dir
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def app_library_dir
|
92
|
+
base_dir = app_data_dir
|
93
|
+
if base_dir.nil?
|
94
|
+
nil
|
95
|
+
else
|
96
|
+
File.join(base_dir, 'Library')
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def app_library_preferences_dir
|
101
|
+
base_dir = app_library_dir
|
102
|
+
if base_dir.nil?
|
103
|
+
nil
|
104
|
+
else
|
105
|
+
File.join(base_dir, 'Preferences')
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def app_documents_dir
|
110
|
+
base_dir = app_data_dir
|
111
|
+
if base_dir.nil?
|
112
|
+
nil
|
113
|
+
else
|
114
|
+
File.join(base_dir, 'Documents')
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def app_tmp_dir
|
119
|
+
base_dir = app_data_dir
|
120
|
+
if base_dir.nil?
|
121
|
+
nil
|
122
|
+
else
|
123
|
+
File.join(base_dir, 'tmp')
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def reset_app_sandbox
|
128
|
+
return true if !app_is_installed?
|
129
|
+
|
130
|
+
shutdown
|
131
|
+
|
132
|
+
reset_app_sandbox_internal
|
133
|
+
end
|
134
|
+
|
56
135
|
def update_device_state(options={})
|
57
136
|
merged_options = UPDATE_DEVICE_STATE_OPTS.merge(options)
|
58
|
-
debug_logging = RunLoop::Environment.debug?
|
59
137
|
|
60
138
|
interval = merged_options[:interval]
|
61
139
|
tries = merged_options[:tries]
|
62
140
|
|
141
|
+
debug_logging = RunLoop::Environment.debug?
|
142
|
+
|
63
143
|
on_retry = Proc.new do |_, try, elapsed_time, next_interval|
|
64
144
|
if debug_logging
|
65
145
|
# Retriable 2.0
|
@@ -95,13 +175,30 @@ module RunLoop::Simctl
|
|
95
175
|
end
|
96
176
|
|
97
177
|
def terminate_core_simulator_processes
|
98
|
-
|
178
|
+
debug_logging = RunLoop::Environment.debug?
|
179
|
+
[
|
180
|
+
# Probably no.
|
181
|
+
#'com.apple.CoreSimulator.CoreSimulatorService',
|
182
|
+
#'com.apple.CoreSimulator.SimVerificationService',
|
183
|
+
|
184
|
+
# Yes.
|
185
|
+
'SimulatorBridge',
|
186
|
+
'configd_sim',
|
187
|
+
'launchd_sim',
|
188
|
+
|
189
|
+
# Yes, but does not always appear.
|
190
|
+
'CoreSimulatorBridge'
|
191
|
+
].each do |name|
|
99
192
|
pids = RunLoop::ProcessWaiter.new(name).pids
|
100
193
|
pids.each do |pid|
|
101
|
-
|
194
|
+
if debug_logging
|
195
|
+
puts "Sending 'TERM' to #{name} '#{pid}'"
|
196
|
+
end
|
102
197
|
term = RunLoop::ProcessTerminator.new(pid, 'TERM', name)
|
103
198
|
unless term.kill_process
|
104
|
-
|
199
|
+
if debug_logging
|
200
|
+
puts "Sending 'KILL' to #{name} '#{pid}'"
|
201
|
+
end
|
105
202
|
term = RunLoop::ProcessTerminator.new(pid, 'KILL', name)
|
106
203
|
term.kill_process
|
107
204
|
end
|
@@ -123,7 +220,10 @@ module RunLoop::Simctl
|
|
123
220
|
sleep delay
|
124
221
|
end
|
125
222
|
|
126
|
-
|
223
|
+
if RunLoop::Environment.debug?
|
224
|
+
puts "Waited for #{timeout} seconds for device to have state: '#{target_state}'."
|
225
|
+
end
|
226
|
+
|
127
227
|
unless in_state
|
128
228
|
raise "Expected '#{target_state} but found '#{device.state}' after waiting."
|
129
229
|
end
|
@@ -131,13 +231,16 @@ module RunLoop::Simctl
|
|
131
231
|
end
|
132
232
|
|
133
233
|
def app_is_installed?
|
134
|
-
|
135
|
-
|
136
|
-
|
234
|
+
!fetch_app_dir.nil?
|
235
|
+
end
|
236
|
+
|
237
|
+
# @!visibility private
|
238
|
+
def fetch_app_dir
|
239
|
+
sim_app_dir = device_applications_dir
|
240
|
+
return nil if !File.exist?(sim_app_dir)
|
241
|
+
Dir.glob("#{sim_app_dir}/**/*.app").detect(nil) do |path|
|
137
242
|
RunLoop::App.new(path).bundle_identifier == app.bundle_identifier
|
138
243
|
end
|
139
|
-
|
140
|
-
!app_path.nil?
|
141
244
|
end
|
142
245
|
|
143
246
|
def wait_for_app_install
|
@@ -154,7 +257,9 @@ module RunLoop::Simctl
|
|
154
257
|
sleep delay
|
155
258
|
end
|
156
259
|
|
157
|
-
|
260
|
+
if RunLoop::Environment.debug?
|
261
|
+
puts "Waited for #{timeout} seconds for '#{app.bundle_identifier}' to install."
|
262
|
+
end
|
158
263
|
|
159
264
|
unless is_installed
|
160
265
|
raise "Expected app to be installed on #{device.instruments_identifier}"
|
@@ -177,7 +282,9 @@ module RunLoop::Simctl
|
|
177
282
|
sleep delay
|
178
283
|
end
|
179
284
|
|
180
|
-
|
285
|
+
if RunLoop::Environment.debug?
|
286
|
+
puts "Waited for #{timeout} seconds for '#{app.bundle_identifier}' to uninstall."
|
287
|
+
end
|
181
288
|
|
182
289
|
unless not_installed
|
183
290
|
raise "Expected app to be installed on #{device.instruments_identifier}"
|
@@ -262,7 +369,9 @@ module RunLoop::Simctl
|
|
262
369
|
args = ['open', '-a', @path_to_ios_sim_app_bundle, '--args', '-CurrentDeviceUDID', device.udid]
|
263
370
|
pid = spawn('xcrun', *args)
|
264
371
|
Process.detach(pid)
|
265
|
-
|
372
|
+
|
373
|
+
# @todo Does not always appear?
|
374
|
+
# RunLoop::ProcessWaiter.new('CoreSimulatorBridge', WAIT_FOR_APP_LAUNCH_OPTS).wait_for_any
|
266
375
|
RunLoop::ProcessWaiter.new('iOS Simulator', WAIT_FOR_APP_LAUNCH_OPTS).wait_for_any
|
267
376
|
RunLoop::ProcessWaiter.new('SimulatorBridge', WAIT_FOR_APP_LAUNCH_OPTS).wait_for_any
|
268
377
|
wait_for_device_state 'Booted'
|
@@ -315,11 +424,95 @@ module RunLoop::Simctl
|
|
315
424
|
|
316
425
|
SIM_POST_LAUNCH_WAIT = RunLoop::Environment.sim_post_launch_wait || 1.0
|
317
426
|
|
427
|
+
METADATA_PLIST = '.com.apple.mobile_container_manager.metadata.plist'
|
428
|
+
CORE_SIMULATOR_DEVICE_DIR = File.expand_path('~/Library/Developer/CoreSimulator/Devices')
|
429
|
+
|
318
430
|
# @!visibility private
|
319
431
|
def fetch_matching_device
|
320
432
|
sim_control.simulators.detect do |sim|
|
321
433
|
sim.udid == device.udid
|
322
434
|
end
|
323
435
|
end
|
436
|
+
|
437
|
+
# @!visibility private
|
438
|
+
def reset_app_sandbox_internal_shared
|
439
|
+
[app_documents_dir, app_tmp_dir].each do |dir|
|
440
|
+
FileUtils.rm_rf dir
|
441
|
+
FileUtils.mkdir dir
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
# @!visibility private
|
446
|
+
def reset_app_sandbox_internal_sdk_gte_8
|
447
|
+
lib_dir = app_library_dir
|
448
|
+
RunLoop::Directory.recursive_glob_for_entries(lib_dir).each do |entry|
|
449
|
+
if entry.include?('Preferences')
|
450
|
+
# nop
|
451
|
+
else
|
452
|
+
if File.exist?(entry)
|
453
|
+
FileUtils.rm_rf(entry)
|
454
|
+
end
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
prefs_dir = app_library_preferences_dir
|
459
|
+
protected = ['com.apple.UIAutomation.plist',
|
460
|
+
'com.apple.UIAutomationPlugIn.plist']
|
461
|
+
RunLoop::Directory.recursive_glob_for_entries(prefs_dir).each do |entry|
|
462
|
+
unless protected.include?(File.basename(entry))
|
463
|
+
if File.exist?(entry)
|
464
|
+
FileUtils.rm_rf entry
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
# @!visibility private
|
471
|
+
def reset_app_sandbox_internal_sdk_lt_8
|
472
|
+
prefs_dir = app_library_preferences_dir
|
473
|
+
RunLoop::Directory.recursive_glob_for_entries(prefs_dir).each do |entry|
|
474
|
+
if entry.end_with?('.GlobalPreferences.plist') ||
|
475
|
+
entry.end_with?('com.apple.PeoplePicker.plist')
|
476
|
+
# nop
|
477
|
+
else
|
478
|
+
if File.exist?(entry)
|
479
|
+
FileUtils.rm_rf entry
|
480
|
+
end
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
# app preferences lives in device Library/Preferences
|
485
|
+
device_prefs_dir = File.join(app_data_dir, 'Library', 'Preferences')
|
486
|
+
app_prefs_plist = File.join(device_prefs_dir, "#{app.bundle_identifier}.plist")
|
487
|
+
if File.exist?(app_prefs_plist)
|
488
|
+
FileUtils.rm_rf(app_prefs_plist)
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
# @!visibility private
|
493
|
+
def reset_app_sandbox_internal
|
494
|
+
reset_app_sandbox_internal_shared
|
495
|
+
|
496
|
+
if is_sdk_8?
|
497
|
+
reset_app_sandbox_internal_sdk_gte_8
|
498
|
+
else
|
499
|
+
reset_app_sandbox_internal_sdk_lt_8
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
# @!visibility private
|
504
|
+
def app_uia_crash_logs
|
505
|
+
base_dir = app_data_dir
|
506
|
+
if base_dir.nil?
|
507
|
+
nil
|
508
|
+
else
|
509
|
+
dir = File.join(base_dir, 'Library', 'CrashReporter', 'UIALogs')
|
510
|
+
if Dir.exist?(dir)
|
511
|
+
Dir.glob("#{dir}/*.plist")
|
512
|
+
else
|
513
|
+
nil
|
514
|
+
end
|
515
|
+
end
|
516
|
+
end
|
324
517
|
end
|
325
518
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module RunLoop
|
2
|
+
module Simctl
|
3
|
+
class Plists
|
4
|
+
|
5
|
+
SIMCTL_PLIST_DIR = lambda {
|
6
|
+
dirname = File.dirname(__FILE__)
|
7
|
+
joined = File.join(dirname, '..', '..', '..', 'plists', 'simctl')
|
8
|
+
File.expand_path(joined)
|
9
|
+
}.call
|
10
|
+
|
11
|
+
def self.uia_automation_plist
|
12
|
+
File.join(SIMCTL_PLIST_DIR, 'com.apple.UIAutomation.plist')
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.uia_automation_plugin_plist
|
16
|
+
File.join(SIMCTL_PLIST_DIR, 'com.apple.UIAutomationPlugIn.plist')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/run_loop/version.rb
CHANGED
data/lib/run_loop/xctools.rb
CHANGED
@@ -15,6 +15,14 @@ module RunLoop
|
|
15
15
|
# @todo Refactor instruments related code to instruments class.
|
16
16
|
class XCTools
|
17
17
|
|
18
|
+
# Returns a version instance for `Xcode 6.4`; used to check for the
|
19
|
+
# availability of features and paths to various items on the filesystem.
|
20
|
+
#
|
21
|
+
# @return [RunLoop::Version] 6.3
|
22
|
+
def v64
|
23
|
+
@xc64 ||= RunLoop::Version.new('6.4')
|
24
|
+
end
|
25
|
+
|
18
26
|
# Returns a version instance for `Xcode 6.3`; used to check for the
|
19
27
|
# availability of features and paths to various items on the filesystem.
|
20
28
|
#
|
@@ -63,9 +71,16 @@ module RunLoop
|
|
63
71
|
@xc50 ||= RunLoop::Version.new('5.0')
|
64
72
|
end
|
65
73
|
|
74
|
+
# Are we running Xcode 6.4 or above?
|
75
|
+
#
|
76
|
+
# @return [Boolean] `true` if the current Xcode version is >= 6.4
|
77
|
+
def xcode_version_gte_64?
|
78
|
+
@xcode_gte_64 ||= xcode_version >= v64
|
79
|
+
end
|
80
|
+
|
66
81
|
# Are we running Xcode 6.3 or above?
|
67
82
|
#
|
68
|
-
# @return [Boolean] `true` if the current Xcode version is >= 6.
|
83
|
+
# @return [Boolean] `true` if the current Xcode version is >= 6.3
|
69
84
|
def xcode_version_gte_63?
|
70
85
|
@xcode_gte_63 ||= xcode_version >= v63
|
71
86
|
end
|
Binary file
|
Binary file
|
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.3.
|
4
|
+
version: 1.3.2
|
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-04-
|
11
|
+
date: 2015-04-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -287,8 +287,10 @@ files:
|
|
287
287
|
- lib/run_loop/cli/cli.rb
|
288
288
|
- lib/run_loop/cli/errors.rb
|
289
289
|
- lib/run_loop/cli/instruments.rb
|
290
|
+
- lib/run_loop/cli/simctl.rb
|
290
291
|
- lib/run_loop/core.rb
|
291
292
|
- lib/run_loop/device.rb
|
293
|
+
- lib/run_loop/directory.rb
|
292
294
|
- lib/run_loop/dylib_injector.rb
|
293
295
|
- lib/run_loop/environment.rb
|
294
296
|
- lib/run_loop/fifo.rb
|
@@ -304,8 +306,11 @@ files:
|
|
304
306
|
- lib/run_loop/process_waiter.rb
|
305
307
|
- lib/run_loop/sim_control.rb
|
306
308
|
- lib/run_loop/simctl/bridge.rb
|
309
|
+
- lib/run_loop/simctl/plists.rb
|
307
310
|
- lib/run_loop/version.rb
|
308
311
|
- lib/run_loop/xctools.rb
|
312
|
+
- plists/simctl/com.apple.UIAutomation.plist
|
313
|
+
- plists/simctl/com.apple.UIAutomationPlugIn.plist
|
309
314
|
- scripts/calabash_script_uia.js
|
310
315
|
- scripts/json2-min.js
|
311
316
|
- scripts/json2.js
|