run_loop 2.1.3 → 2.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/run_loop.rb +119 -4
- data/lib/run_loop/{host_cache.rb → cache.rb} +8 -3
- data/lib/run_loop/codesign.rb +3 -3
- data/lib/run_loop/core.rb +26 -5
- data/lib/run_loop/core_simulator.rb +14 -13
- data/lib/run_loop/detect_aut/detect.rb +1 -1
- data/lib/run_loop/device.rb +2 -2
- data/lib/run_loop/device_agent/Frameworks.zip +0 -0
- data/lib/run_loop/device_agent/app/CBX-Runner.app.zip +0 -0
- data/lib/run_loop/device_agent/bin/iOSDeviceManager +0 -0
- data/lib/run_loop/device_agent/cbxrunner.rb +3 -2
- data/lib/run_loop/device_agent/frameworks.rb +7 -13
- data/lib/run_loop/device_agent/{xctestctl.rb → ios_device_manager.rb} +36 -24
- data/lib/run_loop/device_agent/ipa/CBX-Runner.app.zip +0 -0
- data/lib/run_loop/device_agent/launcher.rb +8 -0
- data/lib/run_loop/device_agent/xcodebuild.rb +5 -0
- data/lib/run_loop/dnssd.rb +148 -0
- data/lib/run_loop/dot_dir.rb +1 -1
- data/lib/run_loop/dylib_injector.rb +1 -1
- data/lib/run_loop/encoding.rb +17 -0
- data/lib/run_loop/environment.rb +15 -5
- data/lib/run_loop/instruments.rb +2 -2
- data/lib/run_loop/language.rb +4 -0
- data/lib/run_loop/locale.rb +4 -1
- data/lib/run_loop/otool.rb +1 -1
- data/lib/run_loop/process_terminator.rb +1 -1
- data/lib/run_loop/shell.rb +2 -2
- data/lib/run_loop/sim_control.rb +5 -5
- data/lib/run_loop/simctl.rb +2 -2
- data/lib/run_loop/strings.rb +1 -1
- data/lib/run_loop/version.rb +1 -1
- data/lib/run_loop/xcode.rb +15 -0
- data/lib/run_loop/xcrun.rb +7 -3
- data/lib/run_loop/xcuitest.rb +155 -68
- metadata +41 -21
- data/lib/run_loop/cache/cache.rb +0 -68
- data/lib/run_loop/device_agent/bin/xctestctl +0 -0
- data/lib/run_loop/device_agent/frameworks/Frameworks.zip +0 -0
@@ -4,7 +4,7 @@ module RunLoop
|
|
4
4
|
module DeviceAgent
|
5
5
|
# @!visibility private
|
6
6
|
class Frameworks
|
7
|
-
|
7
|
+
|
8
8
|
require "singleton"
|
9
9
|
include Singleton
|
10
10
|
|
@@ -15,24 +15,23 @@ module RunLoop
|
|
15
15
|
return true
|
16
16
|
end
|
17
17
|
|
18
|
-
RunLoop.log_debug("Installing Frameworks to #{
|
18
|
+
RunLoop.log_debug("Installing Frameworks to #{rootdir}")
|
19
19
|
|
20
20
|
options = { :log_cmd => true }
|
21
21
|
|
22
22
|
Dir.chdir(rootdir) do
|
23
23
|
RunLoop.log_unix_cmd("cd #{rootdir}")
|
24
|
-
shell.
|
24
|
+
shell.run_shell_command(["ditto", "-xk", File.basename(zip), "."], options)
|
25
25
|
end
|
26
|
-
|
27
|
-
shell.exec(["cp", "-r", "#{frameworks}/*.framework", target], options)
|
28
|
-
shell.exec(["cp", "#{frameworks}/*LICENSE", target], options)
|
29
|
-
RunLoop.log_debug("Installed frameworks to #{target}")
|
26
|
+
RunLoop.log_debug("Installed frameworks to #{rootdir}")
|
30
27
|
end
|
31
28
|
|
32
29
|
private
|
33
30
|
|
34
31
|
# @!visibility private
|
32
|
+
# TODO replace with include Shell
|
35
33
|
def shell
|
34
|
+
require "run_loop/shell"
|
36
35
|
Class.new do
|
37
36
|
include RunLoop::Shell
|
38
37
|
def to_s; "#<Frameworks Shell>"; end
|
@@ -40,11 +39,6 @@ module RunLoop
|
|
40
39
|
end.new
|
41
40
|
end
|
42
41
|
|
43
|
-
# @!visibility private
|
44
|
-
def target
|
45
|
-
@target ||= File.join(RunLoop::DotDir.directory, "Frameworks")
|
46
|
-
end
|
47
|
-
|
48
42
|
# @!visibility private
|
49
43
|
def frameworks
|
50
44
|
@frameworks ||= File.join(rootdir, "Frameworks")
|
@@ -57,7 +51,7 @@ module RunLoop
|
|
57
51
|
|
58
52
|
# @!visibility private
|
59
53
|
def rootdir
|
60
|
-
@rootdir ||= File.expand_path(File.join(File.dirname(__FILE__)
|
54
|
+
@rootdir ||= File.expand_path(File.join(File.dirname(__FILE__)))
|
61
55
|
end
|
62
56
|
end
|
63
57
|
end
|
@@ -5,10 +5,10 @@ module RunLoop
|
|
5
5
|
# @!visibility private
|
6
6
|
#
|
7
7
|
# A wrapper around the test-control binary.
|
8
|
-
class
|
8
|
+
class IOSDeviceManager < RunLoop::DeviceAgent::Launcher
|
9
9
|
|
10
10
|
# @!visibility private
|
11
|
-
@@
|
11
|
+
@@ios_device_manager = nil
|
12
12
|
|
13
13
|
# @!visibility private
|
14
14
|
def self.device_agent_dir
|
@@ -16,32 +16,36 @@ module RunLoop
|
|
16
16
|
end
|
17
17
|
|
18
18
|
# @!visibility private
|
19
|
-
def self.
|
20
|
-
@@
|
21
|
-
from_env = RunLoop::Environment.
|
19
|
+
def self.ios_device_manager
|
20
|
+
@@ios_device_manager ||= begin
|
21
|
+
from_env = RunLoop::Environment.ios_device_manager
|
22
22
|
if from_env
|
23
23
|
if File.exist?(from_env)
|
24
|
-
RunLoop.log_debug("Using
|
24
|
+
RunLoop.log_debug("Using IOS_DEVICE_MANAGER=#{from_env}")
|
25
25
|
from_env
|
26
26
|
else
|
27
27
|
raise RuntimeError, %Q[
|
28
|
-
|
28
|
+
IOS_DEVICE_MANAGER environment variable defined:
|
29
29
|
|
30
30
|
#{from_env}
|
31
31
|
|
32
32
|
but binary does not exist at that path.
|
33
|
-
|
33
|
+
]
|
34
34
|
end
|
35
|
-
|
36
35
|
else
|
37
|
-
File.join(self.device_agent_dir, "bin", "
|
36
|
+
File.join(self.device_agent_dir, "bin", "iOSDeviceManager")
|
38
37
|
end
|
39
|
-
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# @!visibility private
|
42
|
+
def name
|
43
|
+
:xctestctl
|
40
44
|
end
|
41
45
|
|
42
46
|
# @!visibility private
|
43
47
|
def to_s
|
44
|
-
"#<
|
48
|
+
"#<iOSDeviceManager: #{IOSDeviceManager.ios_device_manager}>"
|
45
49
|
end
|
46
50
|
|
47
51
|
# @!visibility private
|
@@ -56,7 +60,7 @@ but binary does not exist at that path.
|
|
56
60
|
|
57
61
|
# @!visibility private
|
58
62
|
def self.log_file
|
59
|
-
path = File.join(Launcher.dot_dir, "
|
63
|
+
path = File.join(Launcher.dot_dir, "ios-device-manager.log")
|
60
64
|
FileUtils.touch(path) if !File.exist?(path)
|
61
65
|
path
|
62
66
|
end
|
@@ -65,17 +69,27 @@ but binary does not exist at that path.
|
|
65
69
|
def launch
|
66
70
|
RunLoop::DeviceAgent::Frameworks.instance.install
|
67
71
|
|
72
|
+
# WIP: it is unclear what the behavior should be.
|
68
73
|
if device.simulator?
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
+
# Simulator cannot be running for this version.
|
75
|
+
CoreSimulator.quit_simulator
|
76
|
+
|
77
|
+
# TODO: run-loop is responsible for detecting an outdated CBX-Runner
|
78
|
+
# application and installing a new one. However, iOSDeviceManager
|
79
|
+
# fails if simulator is already running.
|
80
|
+
|
81
|
+
# cbxapp = RunLoop::App.new(runner.runner)
|
82
|
+
#
|
83
|
+
# # quits the simulator
|
84
|
+
# sim = CoreSimulator.new(device, cbxapp)
|
85
|
+
# sim.install
|
86
|
+
# sim.launch_simulator
|
74
87
|
end
|
75
88
|
|
76
|
-
cmd = RunLoop::DeviceAgent::
|
89
|
+
cmd = RunLoop::DeviceAgent::IOSDeviceManager.ios_device_manager
|
77
90
|
|
78
|
-
args = ["
|
91
|
+
args = ["start_test",
|
92
|
+
"-r", runner.runner,
|
79
93
|
"-t", runner.tester,
|
80
94
|
"-d", device.udid]
|
81
95
|
|
@@ -84,14 +98,14 @@ but binary does not exist at that path.
|
|
84
98
|
args << RunLoop::Environment.codesign_identity
|
85
99
|
end
|
86
100
|
|
87
|
-
log_file =
|
101
|
+
log_file = IOSDeviceManager.log_file
|
88
102
|
FileUtils.rm_rf(log_file)
|
89
103
|
FileUtils.touch(log_file)
|
90
104
|
|
91
105
|
options = {:out => log_file, :err => log_file}
|
92
106
|
RunLoop.log_unix_cmd("#{cmd} #{args.join(" ")} >& #{log_file}")
|
93
107
|
|
94
|
-
# Gotta keep the
|
108
|
+
# Gotta keep the ios_device_manager process alive or the connection
|
95
109
|
# to testmanagerd will fail.
|
96
110
|
pid = Process.spawn(cmd, *args, options)
|
97
111
|
Process.detach(pid)
|
@@ -105,5 +119,3 @@ but binary does not exist at that path.
|
|
105
119
|
end
|
106
120
|
end
|
107
121
|
end
|
108
|
-
|
109
|
-
|
Binary file
|
@@ -29,6 +29,14 @@ XCUITest is only available for iOS >= 9.0
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
+
# @!visibility private
|
33
|
+
# The name of this launcher. Must be a symbol (keyword). This value will
|
34
|
+
# be used for the key :cbx_launcher in the RunLoop::Cache so Calabash
|
35
|
+
# iOS can attach and reattach to an XCUITest instance.
|
36
|
+
def name
|
37
|
+
abstract_method!
|
38
|
+
end
|
39
|
+
|
32
40
|
# @!visibility private
|
33
41
|
#
|
34
42
|
# Does whatever it takes to launch the CBX-Runner on the device.
|
@@ -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
|
+
|
data/lib/run_loop/dot_dir.rb
CHANGED
@@ -54,7 +54,7 @@ module RunLoop::DotDir
|
|
54
54
|
|
55
55
|
RunLoop.log_debug("Deleted #{oldest_first.count} previous results in #{elapsed} seconds")
|
56
56
|
rescue StandardError => e
|
57
|
-
RunLoop.log_error("While rotating previous results,
|
57
|
+
RunLoop.log_error("While rotating previous results, encountered: #{e}")
|
58
58
|
end
|
59
59
|
|
60
60
|
private
|
@@ -70,7 +70,7 @@ module RunLoop
|
|
70
70
|
hash = nil
|
71
71
|
success = false
|
72
72
|
begin
|
73
|
-
hash = xcrun.
|
73
|
+
hash = xcrun.run_command_in_context(["lldb", "--no-lldbinit", "--source", script_path], options)
|
74
74
|
pid = hash[:pid]
|
75
75
|
exit_status = hash[:exit_status]
|
76
76
|
success = exit_status == 0
|
data/lib/run_loop/encoding.rb
CHANGED
@@ -2,6 +2,23 @@
|
|
2
2
|
module RunLoop
|
3
3
|
module Encoding
|
4
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
|
+
|
5
22
|
# Raised when a string cannot be coerced to UTF8
|
6
23
|
class UTF8Error < RuntimeError; end
|
7
24
|
|
data/lib/run_loop/environment.rb
CHANGED
@@ -186,13 +186,13 @@ module RunLoop
|
|
186
186
|
end
|
187
187
|
end
|
188
188
|
|
189
|
-
# Returns the value of
|
189
|
+
# Returns the value of IOS_DEVICE_MANAGER
|
190
190
|
#
|
191
|
-
# Use this to specify a non-default
|
191
|
+
# Use this to specify a non-default ios_device_manager binary.
|
192
192
|
#
|
193
|
-
# The default
|
194
|
-
def self.
|
195
|
-
value = ENV["
|
193
|
+
# The default ios_device_manager binary is bundled with this gem.
|
194
|
+
def self.ios_device_manager
|
195
|
+
value = ENV["IOS_DEVICE_MANAGER"]
|
196
196
|
if !value || value == ""
|
197
197
|
nil
|
198
198
|
else
|
@@ -228,6 +228,16 @@ module RunLoop
|
|
228
228
|
end
|
229
229
|
end
|
230
230
|
|
231
|
+
# Returns the value of DEVICE_ENDPOINT
|
232
|
+
def self.device_agent_url
|
233
|
+
value = ENV["DEVICE_AGENT_URL"]
|
234
|
+
if value.nil? || value == ""
|
235
|
+
nil
|
236
|
+
else
|
237
|
+
value
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
231
241
|
# Returns true if running in Jenkins CI
|
232
242
|
#
|
233
243
|
# Checks the value of JENKINS_HOME
|
data/lib/run_loop/instruments.rb
CHANGED
@@ -193,7 +193,7 @@ module RunLoop
|
|
193
193
|
def templates
|
194
194
|
@instruments_templates ||= lambda do
|
195
195
|
args = ['instruments', '-s', 'templates']
|
196
|
-
hash = xcrun.
|
196
|
+
hash = xcrun.run_command_in_context(args, log_cmd: true)
|
197
197
|
hash[:out].chomp.split("\n").map do |elm|
|
198
198
|
stripped = elm.strip.tr('"', '')
|
199
199
|
if stripped == '' || stripped == 'Known Templates:'
|
@@ -269,7 +269,7 @@ module RunLoop
|
|
269
269
|
def fetch_devices
|
270
270
|
@device_hash ||= lambda do
|
271
271
|
args = ['instruments', '-s', 'devices']
|
272
|
-
xcrun.
|
272
|
+
xcrun.run_command_in_context(args, log_cmd: true)
|
273
273
|
end.call
|
274
274
|
end
|
275
275
|
|