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.
- 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
|