run_loop_tcc 2.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/bin/run-loop +19 -0
- data/lib/run_loop/abstract.rb +18 -0
- data/lib/run_loop/app.rb +372 -0
- data/lib/run_loop/cache/cache.rb +68 -0
- data/lib/run_loop/cli/cli.rb +48 -0
- data/lib/run_loop/cli/codesign.rb +24 -0
- data/lib/run_loop/cli/errors.rb +11 -0
- data/lib/run_loop/cli/instruments.rb +160 -0
- data/lib/run_loop/cli/locale.rb +31 -0
- data/lib/run_loop/cli/simctl.rb +257 -0
- data/lib/run_loop/cli/tcc.rb +139 -0
- data/lib/run_loop/codesign.rb +76 -0
- data/lib/run_loop/core.rb +902 -0
- data/lib/run_loop/core_simulator.rb +960 -0
- data/lib/run_loop/detect_aut/detect.rb +185 -0
- data/lib/run_loop/detect_aut/errors.rb +126 -0
- data/lib/run_loop/detect_aut/xamarin_studio.rb +46 -0
- data/lib/run_loop/detect_aut/xcode.rb +157 -0
- data/lib/run_loop/device.rb +722 -0
- data/lib/run_loop/device_agent/app/CBX-Runner.app.zip +0 -0
- data/lib/run_loop/device_agent/bin/xctestctl +0 -0
- data/lib/run_loop/device_agent/cbxrunner.rb +156 -0
- data/lib/run_loop/device_agent/frameworks/Frameworks.zip +0 -0
- data/lib/run_loop/device_agent/frameworks.rb +65 -0
- data/lib/run_loop/device_agent/ipa/CBX-Runner.app.zip +0 -0
- data/lib/run_loop/device_agent/launcher.rb +51 -0
- data/lib/run_loop/device_agent/xcodebuild.rb +91 -0
- data/lib/run_loop/device_agent/xctestctl.rb +109 -0
- data/lib/run_loop/directory.rb +179 -0
- data/lib/run_loop/dnssd.rb +148 -0
- data/lib/run_loop/dot_dir.rb +87 -0
- data/lib/run_loop/dylib_injector.rb +145 -0
- data/lib/run_loop/encoding.rb +56 -0
- data/lib/run_loop/environment.rb +361 -0
- data/lib/run_loop/fifo.rb +40 -0
- data/lib/run_loop/host_cache.rb +128 -0
- data/lib/run_loop/http/error.rb +15 -0
- data/lib/run_loop/http/request.rb +44 -0
- data/lib/run_loop/http/retriable_client.rb +166 -0
- data/lib/run_loop/http/server.rb +17 -0
- data/lib/run_loop/instruments.rb +436 -0
- data/lib/run_loop/ipa.rb +142 -0
- data/lib/run_loop/l10n.rb +93 -0
- data/lib/run_loop/language.rb +63 -0
- data/lib/run_loop/lipo.rb +132 -0
- data/lib/run_loop/lldb.rb +52 -0
- data/lib/run_loop/locale.rb +101 -0
- data/lib/run_loop/logging.rb +111 -0
- data/lib/run_loop/otool.rb +76 -0
- data/lib/run_loop/patches/awesome_print.rb +17 -0
- data/lib/run_loop/physical_device/life_cycle.rb +268 -0
- data/lib/run_loop/plist_buddy.rb +189 -0
- data/lib/run_loop/process_terminator.rb +128 -0
- data/lib/run_loop/process_waiter.rb +117 -0
- data/lib/run_loop/regex.rb +19 -0
- data/lib/run_loop/shell.rb +103 -0
- data/lib/run_loop/sim_control.rb +1264 -0
- data/lib/run_loop/simctl.rb +275 -0
- data/lib/run_loop/sqlite.rb +61 -0
- data/lib/run_loop/strings.rb +88 -0
- data/lib/run_loop/tcc/TCC.db +0 -0
- data/lib/run_loop/tcc/tcc.rb +240 -0
- data/lib/run_loop/template.rb +61 -0
- data/lib/run_loop/version.rb +182 -0
- data/lib/run_loop/xcode.rb +318 -0
- data/lib/run_loop/xcrun.rb +107 -0
- data/lib/run_loop/xcuitest.rb +550 -0
- data/lib/run_loop.rb +230 -0
- data/plists/simctl/com.apple.UIAutomation.plist +0 -0
- data/plists/simctl/com.apple.UIAutomationPlugIn.plist +0 -0
- data/scripts/calabash_script_uia.js +28184 -0
- data/scripts/lib/json2.min.js +26 -0
- data/scripts/lib/log.js +26 -0
- data/scripts/lib/on_alert.js +224 -0
- data/scripts/read-cmd.sh +2 -0
- data/scripts/run_dismiss_location.js +89 -0
- data/scripts/run_loop_basic.js +34 -0
- data/scripts/run_loop_fast_uia.js +188 -0
- data/scripts/run_loop_host.js +117 -0
- data/scripts/run_loop_shared_element.js +125 -0
- data/scripts/timeout3 +23 -0
- data/scripts/udidetect +0 -0
- data/vendor-licenses/FBSimulatorControl.LICENSE +30 -0
- data/vendor-licenses/xctestctl.LICENSE +32 -0
- metadata +443 -0
@@ -0,0 +1,275 @@
|
|
1
|
+
module RunLoop
|
2
|
+
|
3
|
+
# @!visibility private
|
4
|
+
# An interface to the `simctl` command line tool for CoreSimulator.
|
5
|
+
#
|
6
|
+
# Replacement for SimControl.
|
7
|
+
class Simctl
|
8
|
+
|
9
|
+
# @!visibility private
|
10
|
+
DEFAULTS = {
|
11
|
+
:timeout => RunLoop::Environment.ci? ? 90 : 30,
|
12
|
+
:log_cmd => true
|
13
|
+
}
|
14
|
+
|
15
|
+
# @!visibility private
|
16
|
+
SIMCTL_PLIST_DIR = lambda {
|
17
|
+
dirname = File.dirname(__FILE__)
|
18
|
+
joined = File.join(dirname, '..', '..', 'plists', 'simctl')
|
19
|
+
File.expand_path(joined)
|
20
|
+
}.call
|
21
|
+
|
22
|
+
# @!visibility private
|
23
|
+
def self.uia_automation_plist
|
24
|
+
File.join(SIMCTL_PLIST_DIR, 'com.apple.UIAutomation.plist')
|
25
|
+
end
|
26
|
+
|
27
|
+
# @!visibility private
|
28
|
+
def self.uia_automation_plugin_plist
|
29
|
+
File.join(SIMCTL_PLIST_DIR, 'com.apple.UIAutomationPlugIn.plist')
|
30
|
+
end
|
31
|
+
|
32
|
+
# @!visibility private
|
33
|
+
attr_reader :device
|
34
|
+
|
35
|
+
# @!visibility private
|
36
|
+
def initialize
|
37
|
+
@ios_devices = []
|
38
|
+
@tvos_devices = []
|
39
|
+
@watchos_devices = []
|
40
|
+
end
|
41
|
+
|
42
|
+
# @!visibility private
|
43
|
+
def to_s
|
44
|
+
"#<Simctl: #{xcode.version}>"
|
45
|
+
end
|
46
|
+
|
47
|
+
# @!visibility private
|
48
|
+
def inspect
|
49
|
+
to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
# @!visibility private
|
53
|
+
def simulators
|
54
|
+
simulators = ios_devices
|
55
|
+
if simulators.empty?
|
56
|
+
simulators = fetch_devices![:ios]
|
57
|
+
end
|
58
|
+
simulators
|
59
|
+
end
|
60
|
+
|
61
|
+
# @!visibility private
|
62
|
+
#
|
63
|
+
# This method is not supported on Xcode < 7 - returns nil.
|
64
|
+
#
|
65
|
+
# Simulator must be booted in El Cap, which makes this method useless for us
|
66
|
+
# because we have to do a bunch of pre-launch checks for sandbox resetting.
|
67
|
+
#
|
68
|
+
# Testing has shown that moving the device in and out of the booted state
|
69
|
+
# takes a long time (seconds) and is unpredictable.
|
70
|
+
#
|
71
|
+
# TODO ensure a booted state.
|
72
|
+
#
|
73
|
+
# @param [String] bundle_id The CFBundleIdentifier of the app.
|
74
|
+
# @param [RunLoop::Device] device The device under test.
|
75
|
+
# @return [String] The path to the .app bundle if it exists; nil otherwise.
|
76
|
+
def app_container(device, bundle_id)
|
77
|
+
return nil if !xcode.version_gte_7?
|
78
|
+
cmd = ["simctl", "get_app_container", device.udid, bundle_id]
|
79
|
+
hash = execute(cmd, DEFAULTS)
|
80
|
+
|
81
|
+
exit_status = hash[:exit_status]
|
82
|
+
if exit_status != 0
|
83
|
+
nil
|
84
|
+
else
|
85
|
+
hash[:out].strip
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# @!visibility private
|
90
|
+
#
|
91
|
+
# SimControl compatibility
|
92
|
+
def ensure_accessibility(device)
|
93
|
+
sim_control.ensure_accessibility(device)
|
94
|
+
end
|
95
|
+
|
96
|
+
# @!visibility private
|
97
|
+
#
|
98
|
+
# SimControl compatibility
|
99
|
+
def ensure_software_keyboard(device)
|
100
|
+
sim_control.ensure_software_keyboard(device)
|
101
|
+
end
|
102
|
+
|
103
|
+
# @!visibility private
|
104
|
+
#
|
105
|
+
# TODO Make this private again; exposed for SimControl compatibility.
|
106
|
+
def xcode
|
107
|
+
@xcode ||= RunLoop::Xcode.new
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
# @!visibility private
|
113
|
+
attr_reader :ios_devices, :tvos_devices, :watchos_devices
|
114
|
+
|
115
|
+
# @!visibility private
|
116
|
+
def execute(array, options)
|
117
|
+
merged = DEFAULTS.merge(options)
|
118
|
+
xcrun.run_command_in_context(array, merged)
|
119
|
+
end
|
120
|
+
|
121
|
+
# @!visibility private
|
122
|
+
#
|
123
|
+
# Starting in Xcode 7, simctl allows a --json option for listing devices.
|
124
|
+
#
|
125
|
+
# On Xcode 6, we will fall back to SimControl which does a line-by-line
|
126
|
+
# processing of `simctl list devices`. tvOS and watchOS devices are not
|
127
|
+
# available on Xcode < 7.
|
128
|
+
#
|
129
|
+
# This is a destructive operation on `@ios_devices`, `@tvos_devices`, and
|
130
|
+
# `@watchos_devices`. Callers should check for existing devices to avoid
|
131
|
+
# the overhead of calling `simctl list devices --json`.
|
132
|
+
def fetch_devices!
|
133
|
+
if !xcode.version_gte_7?
|
134
|
+
return {
|
135
|
+
:ios => sim_control.simulators,
|
136
|
+
:tvos => [],
|
137
|
+
:watchos => []
|
138
|
+
}
|
139
|
+
end
|
140
|
+
|
141
|
+
@ios_devices = []
|
142
|
+
@tvos_devices = []
|
143
|
+
@watchos_devices = []
|
144
|
+
|
145
|
+
cmd = ["simctl", "list", "devices", "--json"]
|
146
|
+
hash = execute(cmd, DEFAULTS)
|
147
|
+
|
148
|
+
out = hash[:out]
|
149
|
+
exit_status = hash[:exit_status]
|
150
|
+
if exit_status != 0
|
151
|
+
raise RuntimeError, %Q[simctl exited #{exit_status}:
|
152
|
+
|
153
|
+
#{out}
|
154
|
+
|
155
|
+
while trying to list devices.
|
156
|
+
]
|
157
|
+
end
|
158
|
+
|
159
|
+
devices = json_to_hash(out)["devices"]
|
160
|
+
|
161
|
+
devices.each do |key, device_list|
|
162
|
+
version = device_key_to_version(key)
|
163
|
+
bucket = bucket_for_key(key)
|
164
|
+
|
165
|
+
device_list.each do |record|
|
166
|
+
if device_available?(record)
|
167
|
+
bucket << device_from_record(record, version)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
{
|
172
|
+
:ios => ios_devices,
|
173
|
+
:tvos => tvos_devices,
|
174
|
+
:watchos => watchos_devices
|
175
|
+
}
|
176
|
+
end
|
177
|
+
|
178
|
+
# @!visibility private
|
179
|
+
#
|
180
|
+
# command_runner_ng combines stderr and stdout and starting in Xcode 7.3,
|
181
|
+
# simctl has started generating stderr output. This must be filtered out
|
182
|
+
# so that we can parse the JSON response.
|
183
|
+
def filter_stderr(out)
|
184
|
+
out.split($-0).map do |line|
|
185
|
+
if stderr_line?(line)
|
186
|
+
nil
|
187
|
+
else
|
188
|
+
line
|
189
|
+
end
|
190
|
+
end.compact.join($-0)
|
191
|
+
end
|
192
|
+
|
193
|
+
# @!visibility private
|
194
|
+
def stderr_line?(line)
|
195
|
+
line[/CoreSimulatorService/, 0] || line[/simctl\[.+\]/, 0]
|
196
|
+
end
|
197
|
+
|
198
|
+
# @!visibility private
|
199
|
+
def json_to_hash(json)
|
200
|
+
filtered = filter_stderr(json)
|
201
|
+
begin
|
202
|
+
JSON.parse(filtered)
|
203
|
+
rescue TypeError, JSON::ParserError => e
|
204
|
+
raise RuntimeError, %Q[Could not parse simctl JSON response:
|
205
|
+
|
206
|
+
#{e}
|
207
|
+
]
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# @!visibility private
|
212
|
+
def device_key_is_ios?(key)
|
213
|
+
key[/iOS/, 0]
|
214
|
+
end
|
215
|
+
|
216
|
+
# @!visibility private
|
217
|
+
def device_key_is_tvos?(key)
|
218
|
+
key[/tvOS/, 0]
|
219
|
+
end
|
220
|
+
|
221
|
+
# @!visibility private
|
222
|
+
def device_key_is_watchos?(key)
|
223
|
+
key[/watchOS/, 0]
|
224
|
+
end
|
225
|
+
|
226
|
+
# @!visibility private
|
227
|
+
def device_key_to_version(key)
|
228
|
+
str = key.split(" ").last
|
229
|
+
RunLoop::Version.new(str)
|
230
|
+
end
|
231
|
+
|
232
|
+
# @!visibility private
|
233
|
+
def device_available?(record)
|
234
|
+
record["availability"] == "(available)"
|
235
|
+
end
|
236
|
+
|
237
|
+
# @!visibility private
|
238
|
+
def device_from_record(record, version)
|
239
|
+
RunLoop::Device.new(record["name"],
|
240
|
+
version,
|
241
|
+
record["udid"],
|
242
|
+
record["state"])
|
243
|
+
end
|
244
|
+
|
245
|
+
# @!visibility private
|
246
|
+
def bucket_for_key(key)
|
247
|
+
if device_key_is_ios?(key)
|
248
|
+
bin = @ios_devices
|
249
|
+
elsif device_key_is_tvos?(key)
|
250
|
+
bin = @tvos_devices
|
251
|
+
elsif device_key_is_watchos?(key)
|
252
|
+
bin = @watchos_devices
|
253
|
+
else
|
254
|
+
raise RuntimeError, "Unexpected key while processing simctl output:
|
255
|
+
|
256
|
+
key = #{key}
|
257
|
+
|
258
|
+
is not an iOS, tvOS, or watchOS device"
|
259
|
+
end
|
260
|
+
bin
|
261
|
+
end
|
262
|
+
|
263
|
+
# @!visibility private
|
264
|
+
def xcrun
|
265
|
+
@xcrun ||= RunLoop::Xcrun.new
|
266
|
+
end
|
267
|
+
|
268
|
+
# @!visibility private
|
269
|
+
# Support for Xcode < 7 when trying to collect simulators. Xcode 7 allows
|
270
|
+
# a --json option which is much easier to parse.
|
271
|
+
def sim_control
|
272
|
+
@sim_control ||= RunLoop::SimControl.new
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module RunLoop
|
2
|
+
# @!visibility private
|
3
|
+
class Sqlite
|
4
|
+
|
5
|
+
# @!visibility private
|
6
|
+
# MacOS ships with sqlite3
|
7
|
+
SQLITE3 = "/usr/bin/sqlite3"
|
8
|
+
|
9
|
+
# @!visibility private
|
10
|
+
def self.exec(file, sql)
|
11
|
+
if !File.exist?(file)
|
12
|
+
raise ArgumentError,
|
13
|
+
%Q{sqlite database must exist at path:
|
14
|
+
|
15
|
+
#{file}
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
if sql.nil? || sql == ""
|
20
|
+
raise ArgumentError, "Sql argument must not be nil or the empty string"
|
21
|
+
end
|
22
|
+
|
23
|
+
args = [SQLITE3, file, sql]
|
24
|
+
hash = self.xcrun.exec(args, {:log_cmd => true})
|
25
|
+
|
26
|
+
out = hash[:out]
|
27
|
+
exit_status = hash[:exit_status]
|
28
|
+
|
29
|
+
if exit_status.nil? || exit_status != 0
|
30
|
+
raise RuntimeError,
|
31
|
+
%Q{
|
32
|
+
Could not complete sqlite operation:
|
33
|
+
|
34
|
+
file: #{file}
|
35
|
+
sql: #{sql}
|
36
|
+
out: #{out}
|
37
|
+
|
38
|
+
Exited with status: '#{exit_status}'
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
out
|
43
|
+
end
|
44
|
+
|
45
|
+
# @!visibilty private
|
46
|
+
def self.parse(string, delimiter="|")
|
47
|
+
if string == nil
|
48
|
+
[]
|
49
|
+
else
|
50
|
+
string.split(delimiter)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def self.xcrun
|
57
|
+
RunLoop::Xcrun.new
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module RunLoop
|
2
|
+
# @!visibility private
|
3
|
+
#
|
4
|
+
# A class for interacting with the strings tool
|
5
|
+
class Strings
|
6
|
+
|
7
|
+
# @!visibility private
|
8
|
+
attr_reader :path
|
9
|
+
|
10
|
+
# @!visibility private
|
11
|
+
def initialize(path)
|
12
|
+
@path = path
|
13
|
+
|
14
|
+
if !Strings.valid_path?(path)
|
15
|
+
raise ArgumentError,
|
16
|
+
%Q{File:
|
17
|
+
|
18
|
+
#{path}
|
19
|
+
|
20
|
+
must exist and not be a directory.
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# @!visibility private
|
26
|
+
def to_s
|
27
|
+
"#<STRINGS: #{path}>"
|
28
|
+
end
|
29
|
+
|
30
|
+
# @!visibility private
|
31
|
+
def inspect
|
32
|
+
to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
# @!visibility private
|
36
|
+
#
|
37
|
+
# @return [RunLoop::Version] A version instance or nil if the file
|
38
|
+
# at path does not contain server version information.
|
39
|
+
def server_version
|
40
|
+
regex = /CALABASH VERSION: (\d+\.\d+\.\d+(\.pre\d+)?)/
|
41
|
+
match = dump[regex, 0]
|
42
|
+
|
43
|
+
if match
|
44
|
+
str = match.split(":")[1]
|
45
|
+
RunLoop::Version.new(str)
|
46
|
+
else
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# @!visibility private
|
54
|
+
def dump
|
55
|
+
args = ["strings", path]
|
56
|
+
opts = { :log_cmd => true }
|
57
|
+
|
58
|
+
hash = xcrun.run_command_in_context(args, opts)
|
59
|
+
|
60
|
+
if hash[:exit_status] != 0
|
61
|
+
raise RuntimeError,
|
62
|
+
%Q{Could not get strings info from file:
|
63
|
+
|
64
|
+
#{path}
|
65
|
+
|
66
|
+
#{args.join(" ")}
|
67
|
+
|
68
|
+
exited #{hash[:exit_status]} with the following output:
|
69
|
+
|
70
|
+
#{hash[:out]}
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
@dump = hash[:out]
|
75
|
+
end
|
76
|
+
|
77
|
+
# @!visibility private
|
78
|
+
def self.valid_path?(path)
|
79
|
+
File.exist?(path) && !File.directory?(path)
|
80
|
+
end
|
81
|
+
|
82
|
+
# @!visibility private
|
83
|
+
def xcrun
|
84
|
+
RunLoop::Xcrun.new
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
Binary file
|
@@ -0,0 +1,240 @@
|
|
1
|
+
module RunLoop
|
2
|
+
# @!visibility private
|
3
|
+
class TCC
|
4
|
+
|
5
|
+
# @!visibility private
|
6
|
+
PRIVACY_SERVICES = {
|
7
|
+
:calendar => "kTCCServiceCalendar",
|
8
|
+
:camera => "kTCCServiceCamera",
|
9
|
+
:contacts => "kTCCServiceAddressBook",
|
10
|
+
:microphone => "kTCCServiceMicrophone",
|
11
|
+
:motion => "kTCCServiceMotion",
|
12
|
+
:photos => "kTCCServicePhotos",
|
13
|
+
:reminders => "kTCCServiceReminders",
|
14
|
+
:twitter => "kTCCServiceTwitter"
|
15
|
+
}
|
16
|
+
|
17
|
+
# Returns a list of known services as keys that can be passed
|
18
|
+
# to RunLoop::TCC.allow or RunLoop::TCC.deny.
|
19
|
+
def self.services
|
20
|
+
PRIVACY_SERVICES.map do |key, _|
|
21
|
+
key
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Prohibits the `device` from popping a Privacy Alert for `service`.
|
26
|
+
#
|
27
|
+
# Only works on iOS Simulator.
|
28
|
+
#
|
29
|
+
# There is a set of known services like :camera, :microphone, and :twitter,
|
30
|
+
# but you can pass arbritary services - this may or may not have an effect
|
31
|
+
# on your application.
|
32
|
+
#
|
33
|
+
# @param [String, RunLoop::Device] device
|
34
|
+
# @param [String] bundle_id
|
35
|
+
# @param [Array] services An array of services. The default is to allow
|
36
|
+
# all services.
|
37
|
+
#
|
38
|
+
# @raise [ArgumentError] If device is a physical device.
|
39
|
+
# @raise [ArgumentError] If not device with identifier can be found.
|
40
|
+
def self.allow(device, bundle_id, services = [])
|
41
|
+
_device = self.ensure_device(device)
|
42
|
+
|
43
|
+
if services.empty?
|
44
|
+
_services = self.services
|
45
|
+
else
|
46
|
+
_services = services
|
47
|
+
end
|
48
|
+
|
49
|
+
tcc = self.tcc(device, bundle_id)
|
50
|
+
|
51
|
+
_services.each do |service|
|
52
|
+
tcc.allow_service(service)
|
53
|
+
end
|
54
|
+
_services
|
55
|
+
end
|
56
|
+
|
57
|
+
# Force the `device` to pop a Privacy Alert for `service`.
|
58
|
+
#
|
59
|
+
# Only works on iOS Simulator.
|
60
|
+
#
|
61
|
+
# @param [String, RunLoop::Device] device
|
62
|
+
# @param [String] bundle_id
|
63
|
+
# @param [Array] services An array of services. The default is to deny
|
64
|
+
# all services.
|
65
|
+
#
|
66
|
+
# @raise [ArgumentError] If device is a physical device.
|
67
|
+
# @raise [ArgumentError] If not device with identifier can be found.
|
68
|
+
def self.deny(device, bundle_id, services = [])
|
69
|
+
_device = self.ensure_device(device)
|
70
|
+
|
71
|
+
if services.empty?
|
72
|
+
_services = self.services
|
73
|
+
else
|
74
|
+
_services = services
|
75
|
+
end
|
76
|
+
|
77
|
+
tcc = self.tcc(device, bundle_id)
|
78
|
+
|
79
|
+
_services.each do |service|
|
80
|
+
tcc.deny_service(service)
|
81
|
+
end
|
82
|
+
_services
|
83
|
+
end
|
84
|
+
|
85
|
+
# @!visibility private
|
86
|
+
def initialize(device, bundle_id)
|
87
|
+
@device = device
|
88
|
+
@bundle_id = bundle_id
|
89
|
+
|
90
|
+
if device.physical_device?
|
91
|
+
raise(ArgumentError, "Managing the TCC.db only works on simulators")
|
92
|
+
end
|
93
|
+
|
94
|
+
if !bundle_id.is_a?(String)
|
95
|
+
raise(ArgumentError, "Managing the TCC.db requires a valid bundle_id")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# @!visibility private
|
100
|
+
def allow_service(service)
|
101
|
+
service_name = service_name(service)
|
102
|
+
state = service_is_allowed(service_name)
|
103
|
+
|
104
|
+
return true if state == true
|
105
|
+
|
106
|
+
if state == nil
|
107
|
+
insert_allowed(service_name, 1)
|
108
|
+
else
|
109
|
+
update_allowed(service_name, 1)
|
110
|
+
end
|
111
|
+
true
|
112
|
+
end
|
113
|
+
|
114
|
+
# @!visibility private
|
115
|
+
def deny_service(service)
|
116
|
+
service_name = service_name(service)
|
117
|
+
state = service_is_allowed(service_name)
|
118
|
+
|
119
|
+
# state == false; need to update prompt_count
|
120
|
+
if state == nil
|
121
|
+
insert_allowed(service_name, 0)
|
122
|
+
else
|
123
|
+
update_allowed(service_name, 0)
|
124
|
+
end
|
125
|
+
true
|
126
|
+
end
|
127
|
+
|
128
|
+
# @!visibility private
|
129
|
+
def delete_service(service)
|
130
|
+
service_name = service_name(service)
|
131
|
+
state = service_is_allowed(service_name)
|
132
|
+
|
133
|
+
return true if state.nil?
|
134
|
+
delete_allowed(service_name)
|
135
|
+
true
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
# @!visibility private
|
141
|
+
attr_reader :device, :bundle_id
|
142
|
+
|
143
|
+
# @!visibility private
|
144
|
+
ACCESS_COLUMNS = [
|
145
|
+
"service",
|
146
|
+
"client",
|
147
|
+
"client_type",
|
148
|
+
"allowed",
|
149
|
+
"prompt_count"
|
150
|
+
]
|
151
|
+
|
152
|
+
# @!visibility private
|
153
|
+
def where(service)
|
154
|
+
%Q{WHERE client="#{client}" AND service="#{service}"}
|
155
|
+
end
|
156
|
+
|
157
|
+
# @!visibility private
|
158
|
+
def service_is_allowed(service)
|
159
|
+
service_name = service_name(service)
|
160
|
+
sql = %Q{SELECT allowed FROM access #{where(service_name)}}
|
161
|
+
out = RunLoop::Sqlite.exec(db, sql)
|
162
|
+
|
163
|
+
case out
|
164
|
+
when ""
|
165
|
+
return nil
|
166
|
+
when "1"
|
167
|
+
return true
|
168
|
+
when "0"
|
169
|
+
return false
|
170
|
+
else
|
171
|
+
raise RuntimeError, %Q{Expected '', '1', or '0' found: '#{out}'"}
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# @!visibility private
|
176
|
+
def access_columns
|
177
|
+
"service, client, client_type, allowed, prompt_count"
|
178
|
+
end
|
179
|
+
|
180
|
+
# @!visibility private
|
181
|
+
def access_values(service, state)
|
182
|
+
%Q{"#{service}", "#{client}", 0, #{state}, #{state}}
|
183
|
+
end
|
184
|
+
|
185
|
+
# @!visibility private
|
186
|
+
def insert_allowed(service, state)
|
187
|
+
sql = %Q{INSERT INTO access (#{access_columns}) VALUES (#{access_values(service, state)})}
|
188
|
+
RunLoop::Sqlite.exec(db, sql)
|
189
|
+
end
|
190
|
+
|
191
|
+
# @!visibility private
|
192
|
+
def update_allowed(service, state)
|
193
|
+
sql = %Q{UPDATE access SET allowed=#{state}, prompt_count=#{state} #{where(service)}}
|
194
|
+
RunLoop::Sqlite.exec(db, sql)
|
195
|
+
end
|
196
|
+
|
197
|
+
# @!visibility private
|
198
|
+
def delete_allowed(service)
|
199
|
+
sql = %Q{DELETE FROM access #{where(service)}}
|
200
|
+
RunLoop::Sqlite.exec(db, sql)
|
201
|
+
end
|
202
|
+
|
203
|
+
# @!visibility private
|
204
|
+
def service_name(key)
|
205
|
+
PRIVACY_SERVICES[key] || key
|
206
|
+
end
|
207
|
+
|
208
|
+
# @!visibility private
|
209
|
+
def client
|
210
|
+
bundle_id
|
211
|
+
end
|
212
|
+
|
213
|
+
# @!visibility private
|
214
|
+
def db
|
215
|
+
device.simulator_tcc_db
|
216
|
+
end
|
217
|
+
|
218
|
+
# @!visibility private
|
219
|
+
def self.tcc(device, bundle_id)
|
220
|
+
RunLoop::TCC.new(device, bundle_id)
|
221
|
+
end
|
222
|
+
|
223
|
+
# @!visibility private
|
224
|
+
def self.ensure_device(device)
|
225
|
+
if device.is_a?(RunLoop::Device)
|
226
|
+
simulator = device
|
227
|
+
else
|
228
|
+
simulator = RunLoop::Device.device_with_identifier(device)
|
229
|
+
end
|
230
|
+
|
231
|
+
if simulator.physical_device?
|
232
|
+
raise ArgumentError,
|
233
|
+
"Cannot manage Privacy Settings on physical devices"
|
234
|
+
end
|
235
|
+
|
236
|
+
simulator
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module RunLoop
|
4
|
+
|
5
|
+
# @!visibility private
|
6
|
+
# Class to break up javascript templates in to reusable chunks
|
7
|
+
class UIAScriptTemplate < ERB
|
8
|
+
|
9
|
+
# @!visibility private
|
10
|
+
def initialize(template_root, template_relative_path)
|
11
|
+
@template_root = template_root
|
12
|
+
template_path = File.join(@template_root, template_relative_path)
|
13
|
+
@template = File.read(template_path).force_encoding("utf-8")
|
14
|
+
super(@template)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @!visibility private
|
18
|
+
def render_template(template_relative_path)
|
19
|
+
UIAScriptTemplate.new(@template_root, template_relative_path).result
|
20
|
+
end
|
21
|
+
|
22
|
+
# @!visibility private
|
23
|
+
def result
|
24
|
+
super(binding)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @!visibility private
|
28
|
+
def self.sub_path_var!(javascript, results_dir)
|
29
|
+
self.substitute_variable!(javascript, "PATH", results_dir)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @!visibility private
|
33
|
+
def self.sub_read_script_path_var!(javascript, read_cmd_sh)
|
34
|
+
self.substitute_variable!(javascript, "READ_SCRIPT_PATH", read_cmd_sh)
|
35
|
+
end
|
36
|
+
|
37
|
+
# @!visibility private
|
38
|
+
def self.sub_timeout_script_path_var!(javascript, timeout_sh)
|
39
|
+
self.substitute_variable!(javascript, "TIMEOUT_SCRIPT_PATH", timeout_sh)
|
40
|
+
end
|
41
|
+
|
42
|
+
# @!visibility private
|
43
|
+
def self.sub_flush_uia_logs_var!(javascript, value)
|
44
|
+
self.substitute_variable!(javascript, "FLUSH_LOGS", value)
|
45
|
+
end
|
46
|
+
|
47
|
+
# @!visibility private
|
48
|
+
#
|
49
|
+
# Legacy and XTC - related to :no_flush which is a deprecated option.
|
50
|
+
#
|
51
|
+
# Replaced with :flush_uia_logs
|
52
|
+
def self.sub_mode_var!(javascript, value)
|
53
|
+
self.substitute_variable!(javascript, "MODE", value)
|
54
|
+
end
|
55
|
+
|
56
|
+
# @!visibility private
|
57
|
+
def self.substitute_variable!(javascript, variable, value)
|
58
|
+
javascript.gsub!(/\$#{variable}/, value)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|