run_loop 2.1.2 → 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.
@@ -0,0 +1,91 @@
1
+
2
+ module RunLoop
3
+
4
+ # @!visibility private
5
+ module DeviceAgent
6
+
7
+ # @!visibility private
8
+ class Xcodebuild < RunLoop::DeviceAgent::Launcher
9
+
10
+ # @!visibility private
11
+ def self.log_file
12
+ path = File.join(Xcodebuild.dot_dir, "xcodebuild.log")
13
+ FileUtils.touch(path) if !File.exist?(path)
14
+ path
15
+ end
16
+
17
+ # @!visibility private
18
+ def to_s
19
+ "#<Xcodebuild #{workspace}>"
20
+ end
21
+
22
+ # @!visibility private
23
+ def inspect
24
+ to_s
25
+ end
26
+
27
+ # @!visibility private
28
+ def launch
29
+ workspace
30
+
31
+ if device.simulator?
32
+ # quits the simulator
33
+ sim = CoreSimulator.new(device, "")
34
+ sim.launch_simulator
35
+ end
36
+
37
+ start = Time.now
38
+ RunLoop.log_debug("Waiting for CBX-Runner to build...")
39
+ pid = xcodebuild
40
+ RunLoop.log_debug("Took #{Time.now - start} seconds to build and launch CBX-Runner")
41
+ pid
42
+ end
43
+
44
+ # @!visibility private
45
+ def workspace
46
+ @workspace ||= lambda do
47
+ path = RunLoop::Environment.send(:cbxws)
48
+ if path
49
+ path
50
+ else
51
+ raise "The CBXWS env var is undefined. Are you a maintainer?"
52
+ end
53
+ end.call
54
+ end
55
+
56
+ # @!visibility private
57
+ def xcodebuild
58
+ env = {
59
+ "COMMAND_LINE_BUILD" => "1"
60
+ }
61
+
62
+ args = [
63
+ "xcrun",
64
+ "xcodebuild",
65
+ "-scheme", "CBXAppStub",
66
+ "-workspace", workspace,
67
+ "-config", "Debug",
68
+ "-destination",
69
+ "id=#{device.udid}",
70
+ "clean",
71
+ "test"
72
+ ]
73
+
74
+ log_file = Xcodebuild.log_file
75
+
76
+ options = {
77
+ :out => log_file,
78
+ :err => log_file
79
+ }
80
+
81
+ command = "#{env.map.each { |k, v| "#{k}=#{v}" }.join(" ")} #{args.join(" ")}"
82
+ RunLoop.log_unix_cmd("#{command} >& #{log_file}")
83
+
84
+ pid = Process.spawn(env, *args, options)
85
+ Process.detach(pid)
86
+ pid.to_i
87
+ end
88
+ end
89
+ end
90
+ end
91
+
@@ -0,0 +1,109 @@
1
+
2
+ module RunLoop
3
+ # @!visibility private
4
+ module DeviceAgent
5
+ # @!visibility private
6
+ #
7
+ # A wrapper around the test-control binary.
8
+ class XCTestctl < RunLoop::DeviceAgent::Launcher
9
+
10
+ # @!visibility private
11
+ @@xctestctl = nil
12
+
13
+ # @!visibility private
14
+ def self.device_agent_dir
15
+ @@device_agent_dir ||= File.expand_path(File.dirname(__FILE__))
16
+ end
17
+
18
+ # @!visibility private
19
+ def self.xctestctl
20
+ @@xctestctl ||= lambda do
21
+ from_env = RunLoop::Environment.xctestctl
22
+ if from_env
23
+ if File.exist?(from_env)
24
+ RunLoop.log_debug("Using XCTESTCTL=#{from_env}")
25
+ from_env
26
+ else
27
+ raise RuntimeError, %Q[
28
+ XCTESTCTL environment variable defined:
29
+
30
+ #{from_env}
31
+
32
+ but binary does not exist at that path.
33
+ ]
34
+ end
35
+
36
+ else
37
+ File.join(self.device_agent_dir, "bin", "xctestctl")
38
+ end
39
+ end.call
40
+ end
41
+
42
+ # @!visibility private
43
+ def to_s
44
+ "#<Testctl: #{XCTestctl.xctestctl}>"
45
+ end
46
+
47
+ # @!visibility private
48
+ def inspect
49
+ to_s
50
+ end
51
+
52
+ # @!visibility private
53
+ def runner
54
+ @runner ||= RunLoop::DeviceAgent::CBXRunner.new(device)
55
+ end
56
+
57
+ # @!visibility private
58
+ def self.log_file
59
+ path = File.join(Launcher.dot_dir, "xctestctl.log")
60
+ FileUtils.touch(path) if !File.exist?(path)
61
+ path
62
+ end
63
+
64
+ # @!visibility private
65
+ def launch
66
+ RunLoop::DeviceAgent::Frameworks.instance.install
67
+
68
+ if device.simulator?
69
+ cbxapp = RunLoop::App.new(runner.runner)
70
+
71
+ # quits the simulator
72
+ sim = CoreSimulator.new(device, cbxapp)
73
+ sim.install
74
+ end
75
+
76
+ cmd = RunLoop::DeviceAgent::XCTestctl.xctestctl
77
+
78
+ args = ["-r", runner.runner,
79
+ "-t", runner.tester,
80
+ "-d", device.udid]
81
+
82
+ if device.physical_device?
83
+ args << "-c"
84
+ args << RunLoop::Environment.codesign_identity
85
+ end
86
+
87
+ log_file = XCTestctl.log_file
88
+ FileUtils.rm_rf(log_file)
89
+ FileUtils.touch(log_file)
90
+
91
+ options = {:out => log_file, :err => log_file}
92
+ RunLoop.log_unix_cmd("#{cmd} #{args.join(" ")} >& #{log_file}")
93
+
94
+ # Gotta keep the xctestctl process alive or the connection
95
+ # to testmanagerd will fail.
96
+ pid = Process.spawn(cmd, *args, options)
97
+ Process.detach(pid)
98
+
99
+ if device.simulator?
100
+ device.simulator_wait_for_stable_state
101
+ end
102
+
103
+ pid.to_i
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+
@@ -11,10 +11,19 @@ module RunLoop
11
11
  #
12
12
  # Try 3 times for 10 seconds each try with a sleep of 2 seconds
13
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
14
23
  RETRY_OPTIONS = {
15
24
  :tries => 3,
16
25
  :interval => 2,
17
- :timeout => 10
26
+ :timeout => RunLoop::Environment.ci? ? 40 : 20
18
27
  }
19
28
 
20
29
  # @!attribute [r] process_name
@@ -162,6 +162,72 @@ module RunLoop
162
162
  end
163
163
  end
164
164
 
165
+ # Returns the value of CODESIGN_IDENTITY
166
+ def self.codesign_identity
167
+ value = ENV["CODESIGN_IDENTITY"]
168
+ if !value || value == ""
169
+ nil
170
+ else
171
+ value
172
+ end
173
+ end
174
+
175
+ # Returns the value of KEYCHAIN
176
+ #
177
+ # Use this to specify a non-default KEYCHAIN for code signing.
178
+ #
179
+ # The default KEYCHAIN is login.keychain.
180
+ def self.keychain
181
+ value = ENV["KEYCHAIN"]
182
+ if !value || value == ""
183
+ nil
184
+ else
185
+ value
186
+ end
187
+ end
188
+
189
+ # Returns the value of XCTESTCTL
190
+ #
191
+ # Use this to specify a non-default xctestctl binary.
192
+ #
193
+ # The default xctestctl binary is bundled with this gem.
194
+ def self.xctestctl
195
+ value = ENV["XCTESTCTL"]
196
+ if !value || value == ""
197
+ nil
198
+ else
199
+ value
200
+ end
201
+ end
202
+
203
+ # Returns the value of CBXDEVICE
204
+ #
205
+ # Use this to specify a non-default CBX-Runner for physical devices.
206
+ #
207
+ # The default CBX-Runner is bundled with this gem.
208
+ def self.cbxdevice
209
+ value = ENV["CBXDEVICE"]
210
+ if !value || value == ""
211
+ nil
212
+ else
213
+ value
214
+ end
215
+ end
216
+
217
+ # Returns the value of CBXSIM
218
+ #
219
+ # Use this to specify a non-default CBX-Runner for simulators.
220
+ #
221
+ # The default CBX-Runner is bundled with this gem.
222
+ def self.cbxsim
223
+ value = ENV["CBXSIM"]
224
+ if !value || value == ""
225
+ nil
226
+ else
227
+ value
228
+ end
229
+ end
230
+
165
231
  # Returns true if running in Jenkins CI
166
232
  #
167
233
  # Checks the value of JENKINS_HOME
@@ -24,11 +24,16 @@ module RunLoop
24
24
  # @return [String] Expanded path to the default cache directory.
25
25
  # @raise [RuntimeError] When the ~/.run_loop exists, but is not a directory.
26
26
  def self.default_directory
27
- run_loop_dir = File.expand_path('~/.run-loop')
27
+ run_loop_dir = File.join(RunLoop::Environment.user_home_directory, ".run-loop")
28
28
  if !File.exist?(run_loop_dir)
29
29
  FileUtils.mkdir(run_loop_dir)
30
30
  elsif !File.directory?(run_loop_dir)
31
- raise "Expected '#{run_loop_dir}' to be a directory.\nRunLoop requires this directory to cache files."
31
+ raise %Q[
32
+ Expected ~/.run_loop to be a directory.
33
+
34
+ RunLoop requires this directory to cache files
35
+ ]
36
+
32
37
  end
33
38
  run_loop_dir
34
39
  end
@@ -2,8 +2,54 @@ module RunLoop
2
2
  # @!visibility private
3
3
  module PhysicalDevice
4
4
 
5
+ # Raised when installation fails.
6
+ class InstallError < RuntimeError; end
7
+
8
+ # Raised when uninstall fails.
9
+ class UninstallError < RuntimeError; end
10
+
11
+ # Raised when tool cannot perform task.
12
+ class NotImplementedError < StandardError; end
13
+
14
+ # Controls the behavior of various life cycle commands.
15
+ #
16
+ # You can override these values if they do not work in your environment.
17
+ #
18
+ # For cucumber users, the best place to override would be in your
19
+ # features/support/env.rb.
20
+ #
21
+ # For example:
22
+ #
23
+ # RunLoop::PhysicalDevice::LifeCycle::DEFAULT_OPTIONS[:timeout] = 60
24
+ DEFAULT_OPTIONS = {
25
+ :install_timeout => RunLoop::Environment.ci? ? 120 : 30
26
+ }
27
+
5
28
  # @!visibility private
6
- class IDeviceInstaller < LifeCycle
29
+ class LifeCycle
30
+
31
+ require "run_loop/abstract"
32
+ include RunLoop::Abstract
33
+
34
+ require "run_loop/shell"
35
+ include RunLoop::Shell
36
+
37
+ attr_reader :device
38
+
39
+ # Create a new instance.
40
+ #
41
+ # @param [RunLoop::Device] device A physical device.
42
+ # @raise [ArgumentError] If device is a simulator.
43
+ def initialize(device)
44
+ if !device.physical_device?
45
+ raise ArgumentError, %Q[Device:
46
+
47
+ #{device}
48
+
49
+ must be a physical device.]
50
+ end
51
+ @device = device
52
+ end
7
53
 
8
54
  # Is the tool installed?
9
55
  def self.tool_is_installed?
@@ -19,6 +65,7 @@ module RunLoop
19
65
 
20
66
  # Is the app installed?
21
67
  #
68
+ # @param [String] bundle_id The CFBundleIdentifier of an app.
22
69
  # @return [Boolean] true or false
23
70
  def app_installed?(bundle_id)
24
71
  abstract_method!
@@ -30,14 +77,19 @@ module RunLoop
30
77
  # no version check is performed.
31
78
  #
32
79
  # App data is never preserved. If you want to preserve the app data,
33
- # call `ensure_app_installed`.
80
+ # call `ensure_newest_installed`.
34
81
  #
35
82
  # Possible return values:
36
83
  #
37
84
  # * :reinstalled => app was installed, but app data was not preserved.
38
85
  # * :installed => app was not installed.
39
86
  #
87
+ # @param [RunLoop::Ipa, RunLoop::App] app_or_ipa The ipa to install.
88
+ # The caller is responsible for validating the ipa for the device by
89
+ # checking that the codesign and instruction set is correct.
90
+ #
40
91
  # @raise [InstallError] If app was not installed.
92
+ #
41
93
  # @return [Symbol] A keyword describing the action that was performed.
42
94
  def install_app(app_or_ipa)
43
95
  abstract_method!
@@ -47,15 +99,18 @@ module RunLoop
47
99
  #
48
100
  # App data is never preserved. If you want to install a new version of
49
101
  # an app and preserve app data (upgrade testing), call
50
- # `ensure_app_installed`.
102
+ # `ensure_newest_installed`.
51
103
  #
52
104
  # Possible return values:
53
105
  #
54
106
  # * :nothing => app was not installed
55
107
  # * :uninstall => app was uninstalled
56
108
  #
109
+ # @param [String] bundle_id The CFBundleIdentifier of an app.
110
+ #
57
111
  # @raise [UninstallError] If the app cannot be uninstalled, usually
58
112
  # because it is a system app.
113
+ #
59
114
  # @return [Symbol] A keyword that describes what action was performed.
60
115
  def uninstall_app(bundle_id)
61
116
  abstract_method!
@@ -80,11 +135,15 @@ module RunLoop
80
135
  # but app data was not preserved.
81
136
  # * :installed => app was not installed.
82
137
  #
138
+ # @param [RunLoop::Ipa, RunLoop::App] app_or_ipa The ipa to install.
139
+ # The caller is responsible for validating the ipa for the device by
140
+ # checking that the codesign and instruction set is correct.
141
+ #
83
142
  # @raise [InstallError] If the app could not be installed.
84
143
  # @raise [UninstallError] If the app could not be uninstalled.
85
144
  #
86
145
  # @return [Symbol] A keyword that describes the action that was taken.
87
- def ensure_app_installed(app_or_ipa)
146
+ def ensure_newest_installed(app_or_ipa)
88
147
  abstract_method!
89
148
  end
90
149
 
@@ -94,18 +153,33 @@ module RunLoop
94
153
  # the CFBundleShortVersionString. If either are different, then this
95
154
  # method returns false.
96
155
  #
156
+ # @param [RunLoop::Ipa, RunLoop::App] app_or_ipa The ipa to install.
157
+ # The caller is responsible for validating the ipa for the device by
158
+ # checking that the codesign and instruction set is correct.
159
+ #
97
160
  # @raise [RuntimeError] If app is not already installed.
98
161
  def installed_app_same_as?(app_or_ipa)
99
162
  abstract_method!
100
163
  end
101
164
 
165
+ # Returns true if this tool can reset an app's sandbox without
166
+ # uninstalling the app.
167
+ def can_reset_app_sandbox?
168
+ abstract_method!
169
+ end
170
+
102
171
  # Clear the app sandbox.
103
172
  #
104
173
  # This method will never uninstall the app. If the concrete
105
174
  # implementation cannot reset the app data, this method should raise
106
- # an exception.
175
+ # a RunLoop::PhysicalDevice::NotImplementedError
107
176
  #
108
177
  # Does not clear Keychain. Use the Calabash iOS Keychain API.
178
+ #
179
+ # @param [String] bundle_id The CFBundleIdentifier of an app.
180
+ #
181
+ # @raise [RunLoop::PhysicalDevice::NotImplementedError] If this tool
182
+ # cannot reset the app sandbox without unintalling the app.
109
183
  def reset_app_sandbox(bundle_id)
110
184
  abstract_method!
111
185
  end
@@ -141,48 +215,21 @@ module RunLoop
141
215
  #
142
216
  # * sandbox/Documents
143
217
  # * sandbox/Library
144
- # * sandbox/Preferences
218
+ # * sandbox/Library/Preferences
145
219
  # * sandbox/tmp
146
220
  #
147
- # The data is a hash of arrays that define source/target file
148
- # path pairs.
149
- #
150
- # {
151
- # :documents => [
152
- # {
153
- # :source => "path/to/file/on/disk",
154
- # :target => "sub/dir/under/Documents"
155
- # },
156
- # {
157
- # :source => "path/to/other/file",
158
- # :target => "./"
159
- # }
160
- # ],
161
- #
162
- # :library => [ < ditto >],
163
- # :preferences => [ < ditto > ],
164
- # :tmp => [ < ditto >]
165
- # }
166
- #
167
- # * If a file exists at a target path, it will be replaced.
168
- # * Subdirectories will be created as necessary.
169
- # * :source files must exist.
221
+ # Behavior TBD.
170
222
  def sideload(data)
171
- abstract_method!
223
+ raise NotImplementedError,
224
+ "The behavior of the sideload method has not been determined"
172
225
  end
173
226
 
174
227
  # Removes a file or directory from the app sandbox.
175
228
  #
176
- # If the path does not exist, no error will be raised.
177
- #
178
- # Documents, Library, Preferences, and tmp directories will be
179
- # deleted, but then recreated. For example:
180
- #
181
- # remove_file_from_sandbox("Preferences")
182
- #
183
- # The Preferences directory will be deleted and then recreated.
229
+ # Behavior TBD.
184
230
  def remove_from_sandbox(path)
185
- abstract_method!
231
+ raise NotImplementedError,
232
+ "The behavior of the remove_from_sandbox method has not been determined"
186
233
  end
187
234
 
188
235
  # @!visibility private