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,148 @@
|
|
1
|
+
module RunLoop
|
2
|
+
|
3
|
+
# @!visibility private
|
4
|
+
# This class is a work in progress.
|
5
|
+
#
|
6
|
+
# At the moment, it is only useful for debugging the bonjour
|
7
|
+
# server that is started by DeviceAgent.
|
8
|
+
class DNSSD
|
9
|
+
|
10
|
+
# @!visibility private
|
11
|
+
def self.factory(json)
|
12
|
+
array = JSON.parse(json, {:symbolize_names => true})
|
13
|
+
array.map do |dns|
|
14
|
+
RunLoop::DNSSD.new(dns[:service],
|
15
|
+
dns[:ip],
|
16
|
+
dns[:port],
|
17
|
+
dns[:txt])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# @!visibility private
|
22
|
+
attr_reader :service
|
23
|
+
|
24
|
+
# @!visibility private
|
25
|
+
attr_reader :ip
|
26
|
+
|
27
|
+
# @!visibility private
|
28
|
+
attr_reader :port
|
29
|
+
|
30
|
+
# @!visibility private
|
31
|
+
attr_reader :txt
|
32
|
+
|
33
|
+
# @!visibility private
|
34
|
+
def initialize(service, ip, port, txt)
|
35
|
+
@service = service
|
36
|
+
@ip = ip
|
37
|
+
@port = port
|
38
|
+
@txt = txt
|
39
|
+
end
|
40
|
+
|
41
|
+
# @!visibility private
|
42
|
+
def url
|
43
|
+
@url ||= "http://#{ip}:#{port}"
|
44
|
+
end
|
45
|
+
|
46
|
+
# @!visibility private
|
47
|
+
def to_s
|
48
|
+
"#<#{service}: #{url} TXT: #{txt}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# @!visibility private
|
52
|
+
def inspect
|
53
|
+
to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
# @!visibility private
|
57
|
+
def ==(other)
|
58
|
+
service == other.service
|
59
|
+
end
|
60
|
+
|
61
|
+
DEVICE_AGENT = "_calabus._tcp"
|
62
|
+
|
63
|
+
def self.wait_for_new_device_agent(old, options={})
|
64
|
+
new = self.wait_for_device_agents(options)
|
65
|
+
new.find do |new_dns|
|
66
|
+
!old.include?(new_dns)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.wait_for_device_agents(options={})
|
71
|
+
default_opts = {
|
72
|
+
:timeout => 0.1,
|
73
|
+
:retries => 150,
|
74
|
+
:interval => 0.1
|
75
|
+
}
|
76
|
+
|
77
|
+
merged_opts = default_opts.merge(options)
|
78
|
+
timeout = merged_opts[:timeout]
|
79
|
+
retries = merged_opts[:retries]
|
80
|
+
interval = merged_opts[:interval]
|
81
|
+
|
82
|
+
start_time = Time.now
|
83
|
+
|
84
|
+
retries.times do |try|
|
85
|
+
time_diff = start_time + timeout - Time.now
|
86
|
+
|
87
|
+
if time_diff <= 0
|
88
|
+
elapsed = Time.now - start_time
|
89
|
+
RunLoop.log_debug("Timed out waiting after #{elapsed} seconds for DeviceAgents")
|
90
|
+
return []
|
91
|
+
end
|
92
|
+
|
93
|
+
agents = self.device_agents(timeout)
|
94
|
+
|
95
|
+
if !agents.empty?
|
96
|
+
elapsed = Time.now - start_time
|
97
|
+
RunLoop.log_debug("Found #{agents.count} DeviceAgents after #{elapsed} seconds")
|
98
|
+
return agents
|
99
|
+
end
|
100
|
+
|
101
|
+
sleep(interval)
|
102
|
+
end
|
103
|
+
[]
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.device_agents(timeout)
|
107
|
+
services = []
|
108
|
+
addresses = []
|
109
|
+
self.browse(DEVICE_AGENT, timeout) do |reply|
|
110
|
+
if reply.flags.add?
|
111
|
+
services << reply
|
112
|
+
end
|
113
|
+
next if reply.flags.more_coming?
|
114
|
+
|
115
|
+
services.each do |service|
|
116
|
+
resolved = service.resolve
|
117
|
+
addr = Socket.getaddrinfo(resolved.target, nil, Socket::AF_INET)
|
118
|
+
addr.each do |address|
|
119
|
+
match = addresses.find do |dns|
|
120
|
+
dns.service == service.name
|
121
|
+
end
|
122
|
+
|
123
|
+
if !match
|
124
|
+
dns = RunLoop::DNSSD.new(service.name,
|
125
|
+
addr[0][2],
|
126
|
+
resolved.port,
|
127
|
+
resolved.text_record)
|
128
|
+
addresses << dns
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
addresses
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.browse(type, timeout)
|
137
|
+
domain = nil
|
138
|
+
flags = 0
|
139
|
+
interface = ::DNSSD::InterfaceAny
|
140
|
+
service = ::DNSSD::Service.browse(type, domain, flags, interface)
|
141
|
+
service.each(timeout) { |r| yield r }
|
142
|
+
ensure
|
143
|
+
service.stop if service
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# A module for managing the ~/.run-loop directory.
|
2
|
+
module RunLoop::DotDir
|
3
|
+
|
4
|
+
def self.directory
|
5
|
+
home = RunLoop::Environment.user_home_directory
|
6
|
+
dir = File.join(home, ".run-loop")
|
7
|
+
if !File.exist?(dir)
|
8
|
+
FileUtils.mkdir_p(dir)
|
9
|
+
end
|
10
|
+
dir
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.make_results_dir
|
14
|
+
if RunLoop::Environment.xtc?
|
15
|
+
next_results_dir = Dir.mktmpdir("run_loop")
|
16
|
+
else
|
17
|
+
results_dir = File.join(self.directory, 'results')
|
18
|
+
next_results_dir = self.next_timestamped_dirname(results_dir)
|
19
|
+
FileUtils.mkdir_p(next_results_dir)
|
20
|
+
|
21
|
+
current = File.join(self.directory, "results", "current")
|
22
|
+
FileUtils.rm_rf(current)
|
23
|
+
FileUtils.ln_s(next_results_dir, current)
|
24
|
+
end
|
25
|
+
|
26
|
+
next_results_dir
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.rotate_result_directories
|
30
|
+
return :xtc if RunLoop::Environment.xtc?
|
31
|
+
|
32
|
+
start = Time.now
|
33
|
+
|
34
|
+
glob = "#{self.directory}/results/*"
|
35
|
+
|
36
|
+
RunLoop.log_debug("Searching for run-loop results with glob: #{glob}")
|
37
|
+
|
38
|
+
directories = Dir.glob(glob).select do |path|
|
39
|
+
File.directory?(path) && !File.symlink?(path)
|
40
|
+
end
|
41
|
+
|
42
|
+
oldest_first = directories.sort_by { |f| File.mtime(f) }
|
43
|
+
|
44
|
+
RunLoop.log_debug("Found #{oldest_first.count} previous run-loop results")
|
45
|
+
oldest_first.pop(5)
|
46
|
+
|
47
|
+
RunLoop.log_debug("Will delete #{oldest_first.count} previous run-loop results")
|
48
|
+
|
49
|
+
oldest_first.each do |path|
|
50
|
+
FileUtils.rm_rf(path)
|
51
|
+
end
|
52
|
+
|
53
|
+
elapsed = Time.now - start
|
54
|
+
|
55
|
+
RunLoop.log_debug("Deleted #{oldest_first.count} previous results in #{elapsed} seconds")
|
56
|
+
rescue StandardError => e
|
57
|
+
RunLoop.log_error("While rotating previous results, encounterd: #{e}")
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def self.timestamped_dirname(plus_seconds = 0)
|
63
|
+
(Time.now + plus_seconds).strftime("%Y-%m-%d_%H-%M-%S")
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.next_timestamped_dirname(base_dir)
|
67
|
+
dir = File.join(base_dir, self.timestamped_dirname)
|
68
|
+
return dir if !File.exist?(dir)
|
69
|
+
|
70
|
+
# Rather than wait, just increment the second. Per-second accuracy
|
71
|
+
# is not important; uniqueness is.
|
72
|
+
counter = 0
|
73
|
+
loop do
|
74
|
+
break if !File.exist?(dir)
|
75
|
+
break if counter == 4
|
76
|
+
counter = counter + 1
|
77
|
+
dir = File.join(base_dir, self.timestamped_dirname(counter))
|
78
|
+
end
|
79
|
+
|
80
|
+
# If all else fails, just return a unique UUID
|
81
|
+
if File.exist?(dir)
|
82
|
+
dir = File.join(base_dir, SecureRandom.uuid)
|
83
|
+
end
|
84
|
+
dir
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
@@ -0,0 +1,145 @@
|
|
1
|
+
module RunLoop
|
2
|
+
|
3
|
+
# @!visibility private
|
4
|
+
#
|
5
|
+
# This is experimental.
|
6
|
+
#
|
7
|
+
# Injects dylibs into running executables using lldb.
|
8
|
+
class DylibInjector
|
9
|
+
|
10
|
+
# Options for controlling how often to retry dylib injection.
|
11
|
+
#
|
12
|
+
# Try 3 times for 10 seconds each try with a sleep of 2 seconds
|
13
|
+
# between tries.
|
14
|
+
#
|
15
|
+
# You can override these values if they do not work in your environment.
|
16
|
+
#
|
17
|
+
# For cucumber users, the best place to override would be in your
|
18
|
+
# features/support/env.rb.
|
19
|
+
#
|
20
|
+
# For example:
|
21
|
+
#
|
22
|
+
# RunLoop::DylibInjector::RETRY_OPTIONS[:timeout] = 60
|
23
|
+
RETRY_OPTIONS = {
|
24
|
+
:tries => 3,
|
25
|
+
:interval => 2,
|
26
|
+
:timeout => RunLoop::Environment.ci? ? 40 : 20
|
27
|
+
}
|
28
|
+
|
29
|
+
# @!attribute [r] process_name
|
30
|
+
# The name of the process to inject the dylib into. This should be obtained
|
31
|
+
# by inspecting the Info.plist in the app bundle.
|
32
|
+
# @return [String] The process_name
|
33
|
+
attr_reader :process_name
|
34
|
+
|
35
|
+
# @!attribute [r] dylib_path
|
36
|
+
# The path to the dylib that is to be injected.
|
37
|
+
# @return [String] The dylib_path
|
38
|
+
attr_reader :dylib_path
|
39
|
+
|
40
|
+
# @!visibility private
|
41
|
+
attr_reader :xcrun
|
42
|
+
|
43
|
+
# Create a new dylib injector.
|
44
|
+
# @param [String] process_name The name of the process to inject the dylib
|
45
|
+
# into. This should be obtained by inspecting the Info.plist in the app
|
46
|
+
# bundle.
|
47
|
+
# @param [String] dylib_path The path the dylib to inject.
|
48
|
+
def initialize(process_name, dylib_path)
|
49
|
+
@process_name = process_name
|
50
|
+
@dylib_path = Shellwords.shellescape(dylib_path)
|
51
|
+
end
|
52
|
+
|
53
|
+
def xcrun
|
54
|
+
@xcrun ||= RunLoop::Xcrun.new
|
55
|
+
end
|
56
|
+
|
57
|
+
# Injects a dylib into a a currently running process.
|
58
|
+
def inject_dylib(timeout)
|
59
|
+
RunLoop.log_debug("Starting lldb injection with a timeout of #{timeout} seconds")
|
60
|
+
|
61
|
+
script_path = write_script
|
62
|
+
|
63
|
+
start = Time.now
|
64
|
+
|
65
|
+
options = {
|
66
|
+
:timeout => timeout,
|
67
|
+
:log_cmd => true
|
68
|
+
}
|
69
|
+
|
70
|
+
hash = nil
|
71
|
+
success = false
|
72
|
+
begin
|
73
|
+
hash = xcrun.run_command_in_context(["lldb", "--no-lldbinit", "--source", script_path], options)
|
74
|
+
pid = hash[:pid]
|
75
|
+
exit_status = hash[:exit_status]
|
76
|
+
success = exit_status == 0
|
77
|
+
|
78
|
+
RunLoop.log_debug("lldb '#{pid}' exited with value '#{exit_status}'.")
|
79
|
+
|
80
|
+
success = exit_status == 0
|
81
|
+
elapsed = Time.now - start
|
82
|
+
|
83
|
+
if success
|
84
|
+
RunLoop.log_debug("Took #{elapsed} seconds for lldb to inject calabash dylib.")
|
85
|
+
else
|
86
|
+
RunLoop.log_debug("Could not inject dylib after #{elapsed} seconds.")
|
87
|
+
if hash[:out]
|
88
|
+
hash[:out].split("\n").each do |line|
|
89
|
+
RunLoop.log_debug(line)
|
90
|
+
end
|
91
|
+
else
|
92
|
+
RunLoop.log_debug("lldb returned no output to stdout or stderr")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
rescue RunLoop::Xcrun::TimeoutError
|
96
|
+
elapsed = Time.now - start
|
97
|
+
RunLoop.log_debug("lldb tried for #{elapsed} seconds to inject calabash dylib before giving up.")
|
98
|
+
end
|
99
|
+
|
100
|
+
success
|
101
|
+
end
|
102
|
+
|
103
|
+
def retriable_inject_dylib(options={})
|
104
|
+
merged_options = RETRY_OPTIONS.merge(options)
|
105
|
+
|
106
|
+
tries = merged_options[:tries]
|
107
|
+
timeout = merged_options[:timeout]
|
108
|
+
interval = merged_options[:interval]
|
109
|
+
|
110
|
+
success = false
|
111
|
+
|
112
|
+
tries.times do
|
113
|
+
|
114
|
+
success = inject_dylib(timeout)
|
115
|
+
break if success
|
116
|
+
|
117
|
+
sleep(interval)
|
118
|
+
end
|
119
|
+
|
120
|
+
if !success
|
121
|
+
raise RuntimeError, "Could not inject dylib"
|
122
|
+
end
|
123
|
+
success
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def write_script
|
129
|
+
script = File.join(DotDir.directory, "inject-dylib.lldb")
|
130
|
+
|
131
|
+
if File.exist?(script)
|
132
|
+
FileUtils.rm_rf(script)
|
133
|
+
end
|
134
|
+
|
135
|
+
File.open(script, "w") do |file|
|
136
|
+
file.write("process attach -n \"#{process_name}\"\n")
|
137
|
+
file.write("expr (void*)dlopen(\"#{dylib_path}\", 0x2)\n")
|
138
|
+
file.write("detach\n")
|
139
|
+
file.write("exit\n")
|
140
|
+
end
|
141
|
+
|
142
|
+
script
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
|
2
|
+
module RunLoop
|
3
|
+
module Encoding
|
4
|
+
|
5
|
+
# Removes diacritic markers from string.
|
6
|
+
#
|
7
|
+
# The ruby Encoding tools cannot perform this action, they can only change
|
8
|
+
# convert one encodign to another by substituting characters.
|
9
|
+
#
|
10
|
+
# In ruby 1.9.3 we would have used Iconv, but that does not exist in 2.0.
|
11
|
+
#
|
12
|
+
# The Encoding::Convert in 2.0 does not work on string with UTF-16 characters.
|
13
|
+
def transliterate(string)
|
14
|
+
require "i18n"
|
15
|
+
locales = I18n.available_locales
|
16
|
+
if !locales.include?(:en)
|
17
|
+
I18n.available_locales = locales + [:en]
|
18
|
+
end
|
19
|
+
I18n.transliterate(string)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Raised when a string cannot be coerced to UTF8
|
23
|
+
class UTF8Error < RuntimeError; end
|
24
|
+
|
25
|
+
# @!visibility private
|
26
|
+
def ensure_command_output_utf8(string, command)
|
27
|
+
return '' if !string
|
28
|
+
|
29
|
+
utf8 = string.force_encoding("UTF-8").chomp
|
30
|
+
|
31
|
+
return utf8 if utf8.valid_encoding?
|
32
|
+
|
33
|
+
encoded = utf8.encode("UTF-8", "UTF-8",
|
34
|
+
invalid: :replace,
|
35
|
+
undef: :replace,
|
36
|
+
replace: "")
|
37
|
+
|
38
|
+
return encoded if encoded.valid_encoding?
|
39
|
+
|
40
|
+
raise UTF8Error, %Q{
|
41
|
+
Could not force UTF-8 encoding on this string:
|
42
|
+
|
43
|
+
#{string}
|
44
|
+
|
45
|
+
which is the output of this command:
|
46
|
+
|
47
|
+
#{command}
|
48
|
+
|
49
|
+
Please file an issue with a stacktrace and the text of this error.
|
50
|
+
|
51
|
+
https://github.com/calabash/run_loop/issues
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|