calabash 1.2.1 → 1.9.9.pre1
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 +4 -4
- data/CONTRIBUTING.md +39 -0
- data/LICENSE +204 -21
- data/README.md +36 -6
- data/VERSIONING.md +16 -0
- data/bin/calabash +95 -0
- data/lib/calabash.rb +185 -1
- data/lib/calabash/android.rb +64 -0
- data/lib/calabash/android/adb.rb +277 -0
- data/lib/calabash/android/application.rb +110 -0
- data/lib/calabash/android/build.rb +12 -0
- data/lib/calabash/android/build/application.rb +13 -0
- data/lib/calabash/android/build/build_error.rb +11 -0
- data/lib/calabash/android/build/builder.rb +119 -0
- data/lib/calabash/android/build/java_keystore.rb +177 -0
- data/lib/calabash/android/build/resigner.rb +56 -0
- data/lib/calabash/android/build/test_server.rb +27 -0
- data/lib/calabash/android/console_helpers.rb +44 -0
- data/lib/calabash/android/cucumber.rb +3 -0
- data/lib/calabash/android/device.rb +965 -0
- data/lib/calabash/android/environment.rb +470 -0
- data/lib/calabash/android/gestures.rb +369 -0
- data/lib/calabash/android/interactions.rb +45 -0
- data/lib/calabash/android/lib/.irbrc +55 -0
- data/lib/calabash/android/lib/AndroidManifest.xml +51 -0
- data/lib/calabash/android/lib/TestServer.apk +0 -0
- data/lib/calabash/android/lib/calmd5/arm64-v8a/calmd5 +0 -0
- data/lib/calabash/android/lib/calmd5/arm64-v8a/calmd5-pie +0 -0
- data/lib/calabash/android/lib/calmd5/armeabi-v7a/calmd5 +0 -0
- data/lib/calabash/android/lib/calmd5/armeabi-v7a/calmd5-pie +0 -0
- data/lib/calabash/android/lib/calmd5/armeabi/calmd5 +0 -0
- data/lib/calabash/android/lib/calmd5/armeabi/calmd5-pie +0 -0
- data/lib/calabash/android/lib/calmd5/mips/calmd5 +0 -0
- data/lib/calabash/android/lib/calmd5/mips/calmd5-pie +0 -0
- data/lib/calabash/android/lib/calmd5/mips64/calmd5 +0 -0
- data/lib/calabash/android/lib/calmd5/mips64/calmd5-pie +0 -0
- data/lib/calabash/android/lib/calmd5/x86/calmd5 +0 -0
- data/lib/calabash/android/lib/calmd5/x86/calmd5-pie +0 -0
- data/lib/calabash/android/lib/calmd5/x86_64/calmd5 +0 -0
- data/lib/calabash/android/lib/calmd5/x86_64/calmd5-pie +0 -0
- data/lib/calabash/android/lib/screenshot_taker.jar +0 -0
- data/lib/calabash/android/life_cycle.rb +37 -0
- data/lib/calabash/android/orientation.rb +30 -0
- data/lib/calabash/android/physical_buttons.rb +39 -0
- data/lib/calabash/android/screenshot.rb +9 -0
- data/lib/calabash/android/scroll.rb +5 -0
- data/lib/calabash/android/server.rb +10 -0
- data/lib/calabash/android/text.rb +54 -0
- data/lib/calabash/application.rb +74 -0
- data/lib/calabash/cli.rb +12 -0
- data/lib/calabash/cli/build.rb +33 -0
- data/lib/calabash/cli/console.rb +90 -0
- data/lib/calabash/cli/generate.rb +110 -0
- data/lib/calabash/cli/helpers.rb +130 -0
- data/lib/calabash/cli/resign.rb +33 -0
- data/lib/calabash/cli/run.rb +99 -0
- data/lib/calabash/cli/setup_keystore.rb +39 -0
- data/lib/calabash/color.rb +32 -0
- data/lib/calabash/console_helpers.rb +90 -0
- data/lib/calabash/defaults.rb +56 -0
- data/lib/calabash/device.rb +401 -0
- data/lib/calabash/environment.rb +75 -0
- data/lib/calabash/gestures.rb +384 -0
- data/lib/calabash/http.rb +8 -0
- data/lib/calabash/http/error.rb +15 -0
- data/lib/calabash/http/request.rb +42 -0
- data/lib/calabash/http/retriable_client.rb +156 -0
- data/lib/calabash/interactions.rb +105 -0
- data/lib/calabash/ios.rb +37 -0
- data/lib/calabash/ios/application.rb +119 -0
- data/lib/calabash/ios/conditions.rb +79 -0
- data/lib/calabash/ios/console_helpers.rb +72 -0
- data/lib/calabash/ios/device.rb +24 -0
- data/lib/calabash/ios/device/device_implementation.rb +779 -0
- data/lib/calabash/ios/device/gestures_mixin.rb +167 -0
- data/lib/calabash/ios/device/keyboard_mixin.rb +133 -0
- data/lib/calabash/ios/device/physical_device_mixin.rb +266 -0
- data/lib/calabash/ios/device/rotation_mixin.rb +124 -0
- data/lib/calabash/ios/device/routes/backdoor_route_mixin.rb +86 -0
- data/lib/calabash/ios/device/routes/condition_route_mixin.rb +62 -0
- data/lib/calabash/ios/device/routes/error.rb +8 -0
- data/lib/calabash/ios/device/routes/handle_route_mixin.rb +102 -0
- data/lib/calabash/ios/device/routes/map_route_mixin.rb +38 -0
- data/lib/calabash/ios/device/routes/playback_route_mixin.rb +70 -0
- data/lib/calabash/ios/device/routes/response_parser.rb +48 -0
- data/lib/calabash/ios/device/routes/uia_route_mixin.rb +238 -0
- data/lib/calabash/ios/device/runtime_attributes.rb +184 -0
- data/lib/calabash/ios/device/status_bar_mixin.rb +17 -0
- data/lib/calabash/ios/device/text_mixin.rb +19 -0
- data/lib/calabash/ios/device/uia_keyboard_mixin.rb +188 -0
- data/lib/calabash/ios/device/uia_mixin.rb +12 -0
- data/lib/calabash/ios/environment.rb +41 -0
- data/lib/calabash/ios/interactions.rb +10 -0
- data/lib/calabash/ios/lib/.irbrc +55 -0
- data/lib/calabash/ios/lib/recordings/rotate_left_home_down_ipad.base64 +2 -0
- data/lib/calabash/ios/lib/recordings/rotate_left_home_down_iphone.base64 +2 -0
- data/lib/calabash/ios/lib/recordings/rotate_left_home_left_ipad.base64 +2 -0
- data/lib/calabash/ios/lib/recordings/rotate_left_home_left_iphone.base64 +2 -0
- data/lib/calabash/ios/lib/recordings/rotate_left_home_right_ipad.base64 +2 -0
- data/lib/calabash/ios/lib/recordings/rotate_left_home_right_iphone.base64 +2 -0
- data/lib/calabash/ios/lib/recordings/rotate_left_home_up_ipad.base64 +2 -0
- data/lib/calabash/ios/lib/recordings/rotate_left_home_up_iphone.base64 +2 -0
- data/lib/calabash/ios/lib/recordings/rotate_right_home_down_ipad.base64 +2 -0
- data/lib/calabash/ios/lib/recordings/rotate_right_home_down_iphone.base64 +2 -0
- data/lib/calabash/ios/lib/recordings/rotate_right_home_left_ipad.base64 +2 -0
- data/lib/calabash/ios/lib/recordings/rotate_right_home_left_iphone.base64 +2 -0
- data/lib/calabash/ios/lib/recordings/rotate_right_home_right_ipad.base64 +2 -0
- data/lib/calabash/ios/lib/recordings/rotate_right_home_right_iphone.base64 +2 -0
- data/lib/calabash/ios/lib/recordings/rotate_right_home_up_ipad.base64 +2 -0
- data/lib/calabash/ios/lib/recordings/rotate_right_home_up_iphone.base64 +2 -0
- data/lib/calabash/ios/orientation.rb +117 -0
- data/lib/calabash/ios/scroll.rb +504 -0
- data/lib/calabash/ios/server.rb +73 -0
- data/lib/calabash/ios/text.rb +248 -0
- data/lib/calabash/ios/uia.rb +24 -0
- data/lib/calabash/lib/skeleton/config/cucumber.yml +6 -0
- data/lib/calabash/lib/skeleton/features/sample.feature +5 -0
- data/lib/calabash/lib/skeleton/features/step_definitions/calabash_steps.rb +29 -0
- data/lib/calabash/lib/skeleton/features/support/env.rb +54 -0
- data/lib/calabash/lib/skeleton/features/support/hooks.rb +83 -0
- data/lib/calabash/life_cycle.rb +111 -0
- data/lib/calabash/location.rb +51 -0
- data/lib/calabash/logger.rb +87 -0
- data/lib/calabash/orientation.rb +84 -0
- data/lib/calabash/page.rb +35 -0
- data/lib/calabash/patch.rb +14 -0
- data/lib/calabash/patch/array.rb +16 -0
- data/lib/calabash/patch/run_loop.rb +90 -0
- data/lib/calabash/query.rb +160 -0
- data/lib/calabash/query_result.rb +85 -0
- data/lib/calabash/screenshot.rb +89 -0
- data/lib/calabash/server.rb +16 -0
- data/lib/calabash/text.rb +76 -0
- data/lib/calabash/utility.rb +58 -0
- data/lib/calabash/version.rb +3 -1
- data/lib/calabash/wait.rb +474 -0
- metadata +462 -24
@@ -0,0 +1,965 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Calabash
|
4
|
+
module Android
|
5
|
+
# A representation of a Calabash Android device.
|
6
|
+
class Device < ::Calabash::Device
|
7
|
+
attr_reader :adb
|
8
|
+
|
9
|
+
def initialize(identifier, server)
|
10
|
+
super
|
11
|
+
@adb = ADB.new(identifier)
|
12
|
+
|
13
|
+
http_client.on_error(Errno::ECONNREFUSED) do |server|
|
14
|
+
port_forward(server.endpoint.port, server.test_server_port)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.default_serial
|
19
|
+
serials = list_serials
|
20
|
+
|
21
|
+
if Environment::DEVICE_IDENTIFIER
|
22
|
+
index = serials.index(Environment::DEVICE_IDENTIFIER)
|
23
|
+
|
24
|
+
if index
|
25
|
+
serials[index]
|
26
|
+
else
|
27
|
+
raise "A device with the serial '#{Environment::DEVICE_IDENTIFIER}' is not visible on adb"
|
28
|
+
end
|
29
|
+
else
|
30
|
+
if serials.length == 0
|
31
|
+
raise 'No devices visible on adb. Ensure a device is visible in `adb devices`'
|
32
|
+
end
|
33
|
+
|
34
|
+
if serials.length > 1
|
35
|
+
raise 'More than one device connected. Use CAL_DEVICE_ID to select serial'
|
36
|
+
end
|
37
|
+
|
38
|
+
serials.first
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.list_serials
|
43
|
+
output = ADB.command('devices')
|
44
|
+
lines = output.lines
|
45
|
+
index = lines.index{|line| line.start_with?('List of devices attached')}
|
46
|
+
|
47
|
+
if index.nil?
|
48
|
+
raise "Could not parse adb output: '#{lines}'"
|
49
|
+
end
|
50
|
+
|
51
|
+
device_lines = lines[(index+1)..-1].select{|line| line.strip != ''}
|
52
|
+
|
53
|
+
device_lines.collect do |line|
|
54
|
+
line.match(/([^\s]+)/).captures.first
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def installed_packages
|
59
|
+
adb.shell('pm list packages').lines.map do |line|
|
60
|
+
line.sub('package:', '').chomp
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def installed_apps
|
65
|
+
adb.shell('pm list packages -f').lines.map do |line|
|
66
|
+
# line will be package:<path>=<package>
|
67
|
+
# e.g. "package:/system/app/GoogleEars.apk=com.google.android.ears"
|
68
|
+
info = line.sub("package:", "")
|
69
|
+
|
70
|
+
app_path, app_id = info.split('=').map(&:chomp)
|
71
|
+
|
72
|
+
{package: app_id, path: app_path}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_server_responding?
|
77
|
+
begin
|
78
|
+
http_client.get(HTTP::Request.new('ping'), retries: 1).body == 'pong'
|
79
|
+
rescue HTTP::Error => _
|
80
|
+
false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_server_ready?
|
85
|
+
begin
|
86
|
+
http_client.get(HTTP::Request.new('ready')).body == 'true'
|
87
|
+
rescue HTTP::Error => _
|
88
|
+
false
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def port_forward(host_port, test_server_port = nil)
|
93
|
+
if test_server_port.nil?
|
94
|
+
test_server_port = server.test_server_port
|
95
|
+
end
|
96
|
+
|
97
|
+
adb_forward_cmd = ['forward', "tcp:#{host_port}", "tcp:#{test_server_port}"]
|
98
|
+
adb.command(*adb_forward_cmd)
|
99
|
+
end
|
100
|
+
|
101
|
+
def make_map_parameters(query, map_method_name, *method_args)
|
102
|
+
converted_args = []
|
103
|
+
|
104
|
+
method_args.each do |arg|
|
105
|
+
if arg.is_a?(Hash)
|
106
|
+
if arg.keys.length > 1
|
107
|
+
raise "Cannot map '#{arg}'. More than one key (method name) is not allowed."
|
108
|
+
end
|
109
|
+
|
110
|
+
if arg.keys.length == 0
|
111
|
+
raise "Cannot map '#{arg}'. No key (method name) is given."
|
112
|
+
end
|
113
|
+
|
114
|
+
method_name = arg.keys.first.to_s
|
115
|
+
value = arg.values.first
|
116
|
+
|
117
|
+
if value.is_a?(Array)
|
118
|
+
arguments = value
|
119
|
+
else
|
120
|
+
arguments = [value]
|
121
|
+
end
|
122
|
+
|
123
|
+
converted =
|
124
|
+
{
|
125
|
+
method_name: method_name,
|
126
|
+
arguments: arguments
|
127
|
+
}
|
128
|
+
|
129
|
+
converted_args << converted
|
130
|
+
elsif arg.is_a?(Symbol)
|
131
|
+
method_name = arg.to_s
|
132
|
+
converted_args << method_name
|
133
|
+
else
|
134
|
+
raise "Invalid value for map: '#{arg}' (#{arg.class})"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
{
|
139
|
+
query: query,
|
140
|
+
operation: {
|
141
|
+
method_name: map_method_name,
|
142
|
+
arguments: converted_args
|
143
|
+
}
|
144
|
+
}
|
145
|
+
end
|
146
|
+
|
147
|
+
# @!visibility private
|
148
|
+
def map_route(query, method_name, *method_args)
|
149
|
+
parameters = make_map_parameters(query, method_name, *method_args)
|
150
|
+
|
151
|
+
request = HTTP::Request.new('map', params_for_request(parameters))
|
152
|
+
|
153
|
+
result = JSON.parse(http_client.get(request).body)
|
154
|
+
|
155
|
+
if result['outcome'] != 'SUCCESS'
|
156
|
+
raise "mapping \"#{query}\" with \"#{method_name}\" failed because: #{result['reason']}\n#{result['details']}"
|
157
|
+
end
|
158
|
+
|
159
|
+
result['results']
|
160
|
+
end
|
161
|
+
|
162
|
+
def perform_action(action, *arguments)
|
163
|
+
@logger.log "Action: #{action} - Arguments: #{arguments.join(', ')}"
|
164
|
+
|
165
|
+
parameters = {command: action, arguments: arguments}
|
166
|
+
request = HTTP::Request.new('/', params_for_request(parameters))
|
167
|
+
|
168
|
+
result = JSON.parse(http_client.get(request).body)
|
169
|
+
|
170
|
+
unless result['success']
|
171
|
+
message = result['message'] || result['bonusInformation']
|
172
|
+
|
173
|
+
if message.is_a?(Array)
|
174
|
+
message = message.join("\n")
|
175
|
+
end
|
176
|
+
|
177
|
+
if message.nil?
|
178
|
+
raise "Could not perform action '#{action}'"
|
179
|
+
else
|
180
|
+
raise message
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
result
|
185
|
+
end
|
186
|
+
|
187
|
+
def enter_text(text)
|
188
|
+
perform_action('keyboard_enter_text', text)
|
189
|
+
end
|
190
|
+
|
191
|
+
def md5_checksum(file_path)
|
192
|
+
result = adb.shell("#{md5_binary} '#{file_path}'")
|
193
|
+
captures = result.match(/(\w+)/).captures
|
194
|
+
|
195
|
+
if captures.length != 1
|
196
|
+
raise "Invalid MD5 result '#{result}' using #{md5_binary}"
|
197
|
+
end
|
198
|
+
|
199
|
+
captures[0]
|
200
|
+
end
|
201
|
+
|
202
|
+
# @!visibility private
|
203
|
+
def backdoor(method, *arguments)
|
204
|
+
parameters = {method_name: method, arguments: arguments}
|
205
|
+
json = parameters.to_json
|
206
|
+
request = HTTP::Request.new('/backdoor', json: json)
|
207
|
+
|
208
|
+
body = http_client.get(request).body
|
209
|
+
result = JSON.parse(body)
|
210
|
+
|
211
|
+
if result['outcome'] != 'SUCCESS'
|
212
|
+
details = if result['detail'].nil? || result['detail'].empty?
|
213
|
+
''
|
214
|
+
else
|
215
|
+
"\n#{result['detail']}"
|
216
|
+
end
|
217
|
+
|
218
|
+
raise "backdoor #{parameters} failed because: #{result['result']}#{details}"
|
219
|
+
end
|
220
|
+
|
221
|
+
result['result']
|
222
|
+
end
|
223
|
+
|
224
|
+
def go_home
|
225
|
+
adb.shell("input keyevent 3")
|
226
|
+
end
|
227
|
+
|
228
|
+
def set_location(location)
|
229
|
+
perform_action('set_gps_coordinates',
|
230
|
+
location[:latitude], location[:longitude])
|
231
|
+
end
|
232
|
+
|
233
|
+
def resume_app(path_or_application)
|
234
|
+
application = parse_path_or_app_parameters(path_or_application)
|
235
|
+
|
236
|
+
if app_running?(application)
|
237
|
+
main_activity = nil
|
238
|
+
|
239
|
+
begin
|
240
|
+
main_activity = application.main_activity
|
241
|
+
rescue
|
242
|
+
raise 'Could not detect a launchable activity. This is needed to resume the app'
|
243
|
+
end
|
244
|
+
|
245
|
+
resume_activity(application.identifier, main_activity)
|
246
|
+
else
|
247
|
+
raise "The app '#{application.identifier}' is not running"
|
248
|
+
end
|
249
|
+
|
250
|
+
true
|
251
|
+
end
|
252
|
+
|
253
|
+
def resume_activity(package, activity)
|
254
|
+
if package_running?(package)
|
255
|
+
if info[:sdk_version] >= 11
|
256
|
+
begin
|
257
|
+
perform_action('resume_application', package)
|
258
|
+
rescue EnsureInstrumentActionError => e
|
259
|
+
raise "Failed to resume app: #{e.message}"
|
260
|
+
end
|
261
|
+
else
|
262
|
+
adb.shell("am start -n '#{package}/#{activity}'")
|
263
|
+
end
|
264
|
+
else
|
265
|
+
raise "The app '#{package}' is not running"
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def app_running?(path_or_application)
|
270
|
+
application = parse_path_or_app_parameters(path_or_application)
|
271
|
+
|
272
|
+
package_running?(application.identifier)
|
273
|
+
end
|
274
|
+
|
275
|
+
def current_focus
|
276
|
+
# Example: mFocusedApp=AppWindowToken{42c52610 token=Token{42b5d048 ActivityRecord{42a7bcc8 u0 com.example/.MainActivity t3}}}
|
277
|
+
result = adb.shell('dumpsys window windows')
|
278
|
+
|
279
|
+
grep_words = ['mCurrentFocus', 'mFocusedApp']
|
280
|
+
|
281
|
+
grep_words.each do |grep_word|
|
282
|
+
result.lines.reverse.each do |line|
|
283
|
+
match = line.match(/#{grep_word}=.*\{[\w]+\s*([\w\.\:\!]+\s*)*\/*([\w\.]+)*/)
|
284
|
+
|
285
|
+
if match && match.captures.length == 2 && !match.captures.any?(&:nil?)
|
286
|
+
captures = match.captures
|
287
|
+
package = captures[0]
|
288
|
+
activity_simple_name = captures[1]
|
289
|
+
|
290
|
+
activity = if activity_simple_name.start_with?('.')
|
291
|
+
"#{package}#{activity_simple_name}"
|
292
|
+
else
|
293
|
+
activity_simple_name
|
294
|
+
end
|
295
|
+
|
296
|
+
return {activity: activity, package: package}
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
raise "Unexpected output from `dumpsys window windows`"
|
302
|
+
end
|
303
|
+
|
304
|
+
def evaluate_javascript_in(query, javascript)
|
305
|
+
parameters =
|
306
|
+
{
|
307
|
+
query: Query.new(query),
|
308
|
+
operation: {method_name: 'execute-javascript'},
|
309
|
+
javascript: javascript
|
310
|
+
}
|
311
|
+
|
312
|
+
json = parameters.to_json
|
313
|
+
request = HTTP::Request.new('/map', json: json)
|
314
|
+
|
315
|
+
body = http_client.get(request).body
|
316
|
+
result = JSON.parse(body)
|
317
|
+
|
318
|
+
if result['outcome'] != 'SUCCESS'
|
319
|
+
if result['results']
|
320
|
+
parsed_result = result['results'].map {|r| "\"#{r}\","}.join("\n")
|
321
|
+
|
322
|
+
raise "Could not evaluate javascript: \n#{parsed_result}"
|
323
|
+
else
|
324
|
+
raise "Could not evaluate javascript: \n#{result['detail']}"
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
Calabash::QueryResult.create(result['results'], query)
|
329
|
+
end
|
330
|
+
|
331
|
+
private
|
332
|
+
|
333
|
+
def package_running?(package)
|
334
|
+
running_packages.include?(package)
|
335
|
+
end
|
336
|
+
|
337
|
+
def running_packages
|
338
|
+
ps.lines.map(&:split).map(&:last)
|
339
|
+
end
|
340
|
+
|
341
|
+
def ps
|
342
|
+
adb.shell('ps')
|
343
|
+
end
|
344
|
+
|
345
|
+
def calabash_server_failure_file_path(application)
|
346
|
+
"/data/data/#{application.test_server.identifier}/files/calabash_failure.out"
|
347
|
+
end
|
348
|
+
|
349
|
+
def calabash_server_finished_file_path(application)
|
350
|
+
"/data/data/#{application.test_server.identifier}/files/calabash_finished.out"
|
351
|
+
end
|
352
|
+
|
353
|
+
def calabash_server_failure_exists?(application)
|
354
|
+
cmd = "ls #{calabash_server_failure_file_path(application)}"
|
355
|
+
|
356
|
+
adb.shell(cmd, no_exit_code_check: true).chomp ==
|
357
|
+
calabash_server_failure_file_path(application)
|
358
|
+
end
|
359
|
+
|
360
|
+
def calabash_server_finished_exists?(application)
|
361
|
+
cmd = "ls #{calabash_server_finished_file_path(application)}"
|
362
|
+
|
363
|
+
adb.shell(cmd, no_exit_code_check: true).chomp ==
|
364
|
+
calabash_server_finished_file_path(application)
|
365
|
+
end
|
366
|
+
|
367
|
+
def read_calabash_sever_failure(application)
|
368
|
+
adb.shell("cat #{calabash_server_failure_file_path(application)}")
|
369
|
+
end
|
370
|
+
|
371
|
+
def read_calabash_sever_finished(application)
|
372
|
+
adb.shell("cat #{calabash_server_finished_file_path(application)}")
|
373
|
+
end
|
374
|
+
|
375
|
+
def clear_calabash_server_report(application)
|
376
|
+
if installed_packages.include?(application.test_server.identifier)
|
377
|
+
adb.shell("am start -e method clear -n #{application.test_server.identifier}/sh.calaba.instrumentationbackend.StatusReporterActivity")
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
def _start_app(application, options={})
|
382
|
+
env_options = {}
|
383
|
+
|
384
|
+
options.fetch(:extras, {}).each do |k, v|
|
385
|
+
env_options[k] = v
|
386
|
+
end
|
387
|
+
|
388
|
+
env_options[:test_server_port] = server.test_server_port
|
389
|
+
env_options[:target_package] = application.identifier
|
390
|
+
|
391
|
+
env_options[:class] = options.fetch(:class, 'sh.calaba.instrumentationbackend.InstrumentationBackend')
|
392
|
+
|
393
|
+
if options[:activity]
|
394
|
+
env_options[:main_activity] = options[:activity]
|
395
|
+
end
|
396
|
+
|
397
|
+
if application.test_server.nil?
|
398
|
+
raise 'Invalid application. No test-server set.'
|
399
|
+
end
|
400
|
+
|
401
|
+
unless app_installed?(application.identifier)
|
402
|
+
raise "The application '#{application.identifier}' is not installed"
|
403
|
+
end
|
404
|
+
|
405
|
+
unless app_installed?(application.test_server.identifier)
|
406
|
+
raise "The test-server '#{application.test_server.identifier}' is not installed"
|
407
|
+
end
|
408
|
+
|
409
|
+
ensure_screen_on
|
410
|
+
|
411
|
+
# Clear any old error reports
|
412
|
+
clear_calabash_server_report(application)
|
413
|
+
|
414
|
+
# We have to forward the port ourselves, as an old test-server could be
|
415
|
+
# running on the old port. If the retriable client was able to
|
416
|
+
# determine if the port had been forwarded, we would not need this.
|
417
|
+
port_forward(server.endpoint.port, server.test_server_port)
|
418
|
+
|
419
|
+
# For now, the test-server cannot rebind an existing socket.
|
420
|
+
# So we have to stop any running Calabash servers from the client
|
421
|
+
# for now.
|
422
|
+
if test_server_responding?
|
423
|
+
@logger.log("A test-server is already running on port #{server.test_server_port}")
|
424
|
+
@logger.log("Trying to stop it")
|
425
|
+
|
426
|
+
begin
|
427
|
+
_stop_app
|
428
|
+
rescue => _
|
429
|
+
raise 'Failed to stop old running test-server'
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
extras = ''
|
434
|
+
|
435
|
+
env_options.each_pair do |key, val|
|
436
|
+
extras = "#{extras} -e \"#{key.to_s}\" \"#{val.to_s}\""
|
437
|
+
end
|
438
|
+
|
439
|
+
begin
|
440
|
+
instrument(application,
|
441
|
+
'sh.calaba.instrumentationbackend.CalabashInstrumentationTestRunner',
|
442
|
+
extras)
|
443
|
+
rescue ADB::ADBCallError => e
|
444
|
+
raise "Failed to start the application: '#{e.stderr.lines.first.chomp}'"
|
445
|
+
end
|
446
|
+
|
447
|
+
begin
|
448
|
+
Retriable.retriable(tries: 30, interval: 1, timeout: 30, on: RetryError) do
|
449
|
+
unless test_server_responding?
|
450
|
+
# Read any message the test-server might have
|
451
|
+
if calabash_server_failure_exists?(application)
|
452
|
+
failure_message = read_calabash_sever_failure(application)
|
453
|
+
|
454
|
+
raise "Failed to start the application: #{parse_failure_message(failure_message)}"
|
455
|
+
end
|
456
|
+
|
457
|
+
raise RetryError
|
458
|
+
end
|
459
|
+
end
|
460
|
+
rescue RetryError => _
|
461
|
+
@logger.log('Could not contact test-server', :error)
|
462
|
+
@logger.log('For information, see the adb logcat', :error)
|
463
|
+
raise 'Could not contact test-server'
|
464
|
+
end
|
465
|
+
|
466
|
+
begin
|
467
|
+
Retriable.retriable(tries: 10, interval: 1, timeout: 10) do
|
468
|
+
unless test_server_ready?
|
469
|
+
raise RetryError
|
470
|
+
end
|
471
|
+
end
|
472
|
+
rescue RetryError => _
|
473
|
+
@logger.log('Test-server was never ready', :error)
|
474
|
+
@logger.log('For information, see the adb logcat', :error)
|
475
|
+
raise 'Test-server was never ready'
|
476
|
+
end
|
477
|
+
|
478
|
+
# Return true to avoid cluttering the console
|
479
|
+
true
|
480
|
+
end
|
481
|
+
|
482
|
+
# @!visibility private
|
483
|
+
def _stop_app
|
484
|
+
Retriable.retriable(tries: 5, interval: 1) do
|
485
|
+
begin
|
486
|
+
http_client.get(HTTP::Request.new('kill'), retries: 1, interval: 0)
|
487
|
+
rescue HTTP::Error => _
|
488
|
+
# It's fine that we can't contact the test-server, as it might already have been shut down
|
489
|
+
if test_server_responding?
|
490
|
+
raise 'Could not kill the test-server'
|
491
|
+
end
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
# Return true to avoid cluttering the console
|
496
|
+
true
|
497
|
+
end
|
498
|
+
|
499
|
+
def parse_failure_message(message)
|
500
|
+
case message
|
501
|
+
when 'E_NO_LAUNCH_INTENT_FOR_PACKAGE'
|
502
|
+
'The application does not have an default launchable activity. Specify :activity in #start_app'
|
503
|
+
when 'E_COULD_NOT_DETECT_MAIN_ACTIVITY'
|
504
|
+
'Could not detect the main activity of the application. Specify :activity in #start_app'
|
505
|
+
when 'E_NO_INTERNET_PERMISSION'
|
506
|
+
'The application does not have internet permission. Add the internet permission to your manifest'
|
507
|
+
else
|
508
|
+
message
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
# @!visibility private
|
513
|
+
def ensure_screen_on
|
514
|
+
unless screen_on?
|
515
|
+
# Tap the 'lock' button
|
516
|
+
Logger.info "Screen is off, turning screen on."
|
517
|
+
adb.shell('input keyevent 26')
|
518
|
+
end
|
519
|
+
|
520
|
+
time_start = Time.now
|
521
|
+
|
522
|
+
while Time.now - time_start < 5
|
523
|
+
return true if screen_on?
|
524
|
+
end
|
525
|
+
|
526
|
+
raise 'Could not turn screen on'
|
527
|
+
end
|
528
|
+
|
529
|
+
# @!visibility private
|
530
|
+
def instrument(application, test_server_activity, extras = '')
|
531
|
+
unless application.is_a?(Android::Application)
|
532
|
+
raise ArgumentError, "Invalid application type '#{application.class}'"
|
533
|
+
end
|
534
|
+
|
535
|
+
if application.test_server.nil?
|
536
|
+
raise ArgumentError, "No test server set for '#{application}'"
|
537
|
+
end
|
538
|
+
|
539
|
+
unless app_installed?(application.test_server.identifier)
|
540
|
+
raise "The test-server '#{application.test_server.identifier}' is not installed"
|
541
|
+
end
|
542
|
+
|
543
|
+
cmd = "am instrument #{extras} #{application.test_server.identifier}/#{test_server_activity}"
|
544
|
+
|
545
|
+
@logger.log "Starting '#{test_server_activity}' using: '#{cmd}'"
|
546
|
+
|
547
|
+
adb.shell(cmd)
|
548
|
+
end
|
549
|
+
|
550
|
+
# @!visibility private
|
551
|
+
class EnsureInstrumentActionError < RuntimeError; end
|
552
|
+
|
553
|
+
# @!visibility private
|
554
|
+
def ensure_instrument_action(application, test_server_activity, extras = '')
|
555
|
+
clear_calabash_server_report(application)
|
556
|
+
|
557
|
+
begin
|
558
|
+
instrument(application, test_server_activity, extras)
|
559
|
+
rescue ADB::ADBCallError => e
|
560
|
+
raise EnsureInstrumentActionError, e
|
561
|
+
end
|
562
|
+
|
563
|
+
begin
|
564
|
+
Timeout.timeout(10) do
|
565
|
+
loop do
|
566
|
+
if calabash_server_failure_exists?(application)
|
567
|
+
failure_message = read_calabash_sever_failure(application)
|
568
|
+
|
569
|
+
raise EnsureInstrumentActionError, parse_failure_message(failure_message)
|
570
|
+
end
|
571
|
+
|
572
|
+
if calabash_server_finished_exists?(application)
|
573
|
+
output = read_calabash_sever_finished(application)
|
574
|
+
|
575
|
+
if output == 'SUCCESSFUL'
|
576
|
+
break
|
577
|
+
end
|
578
|
+
end
|
579
|
+
end
|
580
|
+
end
|
581
|
+
rescue Timeout::Error => _
|
582
|
+
raise EnsureInstrumentActionError, 'Timed out waiting for status'
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
# @!visibility private
|
587
|
+
def ts_clear_app_data(application)
|
588
|
+
begin
|
589
|
+
ensure_instrument_action(application, 'sh.calaba.instrumentationbackend.ClearAppData2')
|
590
|
+
rescue EnsureInstrumentActionError => e
|
591
|
+
raise "Failed to clear app data: #{e.message}"
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
# @!visibility private
|
596
|
+
def screen_on?
|
597
|
+
# Lollipop removed this output
|
598
|
+
if info[:sdk_version] < 20
|
599
|
+
results = adb.shell('dumpsys input_method')
|
600
|
+
output = results.lines.grep(/mScreenOn=(\w+)/)
|
601
|
+
|
602
|
+
if output.empty?
|
603
|
+
raise "Could not find 'mScreenOn'"
|
604
|
+
end
|
605
|
+
|
606
|
+
parsed_result = output.first.match(/mScreenOn=(\w+)/)
|
607
|
+
parsed_result.captures.first == 'true'
|
608
|
+
else
|
609
|
+
results = adb.shell('dumpsys power')
|
610
|
+
output = results.lines.grep(/mWakefulness=(\w+)/)
|
611
|
+
|
612
|
+
if output.empty?
|
613
|
+
raise "Could not find 'mWakefulness'"
|
614
|
+
end
|
615
|
+
|
616
|
+
parsed_result = output.first.match(/mWakefulness=(\w+)/)
|
617
|
+
parsed_result.captures.first == 'Awake'
|
618
|
+
end
|
619
|
+
end
|
620
|
+
|
621
|
+
# @!visibility private
|
622
|
+
def app_installed?(identifier)
|
623
|
+
installed_packages.include?(identifier)
|
624
|
+
end
|
625
|
+
|
626
|
+
# @!visibility private
|
627
|
+
def _screenshot(path)
|
628
|
+
cmd = "java -jar \"#{Screenshot::SCREENSHOT_JAR_PATH}\" #{identifier} \"#{File.expand_path(path)}\""
|
629
|
+
|
630
|
+
@logger.log "Taking screenshot using '#{cmd}'"
|
631
|
+
raise 'Could not take screenshot' unless system(cmd)
|
632
|
+
|
633
|
+
@logger.log("Saved screenshot as #{File.expand_path(path)}", :info)
|
634
|
+
path
|
635
|
+
end
|
636
|
+
|
637
|
+
# @!visibility private
|
638
|
+
def _clear_app_data(application)
|
639
|
+
ts_clear_app_data(application)
|
640
|
+
|
641
|
+
# Return true to avoid cluttering the console
|
642
|
+
true
|
643
|
+
end
|
644
|
+
|
645
|
+
# @!visibility private
|
646
|
+
def _install_app(application)
|
647
|
+
@logger.log "About to install #{application.path}"
|
648
|
+
|
649
|
+
if installed_packages.include?(application.identifier)
|
650
|
+
@logger.log 'Application is already installed. Uninstalling application.'
|
651
|
+
_uninstall_app(application)
|
652
|
+
end
|
653
|
+
|
654
|
+
adb_install_app(application)
|
655
|
+
|
656
|
+
if application.is_a?(Android::Application)
|
657
|
+
if application.test_server
|
658
|
+
@logger.log 'Installing the test-server as well'
|
659
|
+
install_app(application.test_server)
|
660
|
+
end
|
661
|
+
end
|
662
|
+
|
663
|
+
# Return true to avoid cluttering the console
|
664
|
+
true
|
665
|
+
end
|
666
|
+
|
667
|
+
# @!visibility private
|
668
|
+
def _ensure_app_installed(application)
|
669
|
+
@logger.log "Ensuring #{application.path} is installed"
|
670
|
+
|
671
|
+
if installed_packages.include?(application.identifier)
|
672
|
+
@logger.log 'Application is already installed. Ensuring right checksum'
|
673
|
+
|
674
|
+
installed_app = installed_apps.find{|app| app[:package] == application.identifier}
|
675
|
+
installed_app_md5_checksum = md5_checksum(installed_app[:path])
|
676
|
+
|
677
|
+
if application.md5_checksum != installed_app_md5_checksum
|
678
|
+
@logger.log("The md5 checksum has changed (#{application.md5_checksum} != #{installed_app_md5_checksum}.", :info)
|
679
|
+
_install_app(application)
|
680
|
+
end
|
681
|
+
else
|
682
|
+
adb_install_app(application)
|
683
|
+
end
|
684
|
+
|
685
|
+
if application.is_a?(Android::Application)
|
686
|
+
if application.test_server
|
687
|
+
@logger.log 'Ensuring the test-server is installed as well'
|
688
|
+
ensure_app_installed(application.test_server)
|
689
|
+
end
|
690
|
+
end
|
691
|
+
|
692
|
+
# Return true to avoid cluttering the console
|
693
|
+
true
|
694
|
+
end
|
695
|
+
|
696
|
+
# @!visibility private
|
697
|
+
def _uninstall_app(application)
|
698
|
+
if installed_packages.include?(application.identifier)
|
699
|
+
adb_uninstall_app(application.identifier)
|
700
|
+
end
|
701
|
+
|
702
|
+
if application.is_a?(Android::Application)
|
703
|
+
if application.test_server
|
704
|
+
@logger.log 'Uninstalling the test-server as well'
|
705
|
+
uninstall_app(application.test_server)
|
706
|
+
end
|
707
|
+
end
|
708
|
+
|
709
|
+
# Return true to avoid cluttering the console
|
710
|
+
true
|
711
|
+
end
|
712
|
+
|
713
|
+
# @!visibility private
|
714
|
+
def _tap(query, options={})
|
715
|
+
x = options[:at][:x]
|
716
|
+
y = options[:at][:y]
|
717
|
+
offset = options[:offset]
|
718
|
+
|
719
|
+
gesture_options =
|
720
|
+
{
|
721
|
+
x: x,
|
722
|
+
y: y,
|
723
|
+
offset: offset,
|
724
|
+
}
|
725
|
+
|
726
|
+
gesture = Gestures::Gesture.tap(gesture_options)
|
727
|
+
|
728
|
+
execute_gesture(Gestures::Gesture.with_parameters(gesture,
|
729
|
+
query: query,
|
730
|
+
timeout: options[:timeout]))
|
731
|
+
end
|
732
|
+
|
733
|
+
# @!visibility private
|
734
|
+
def _double_tap(query, options={})
|
735
|
+
x = options[:at][:x]
|
736
|
+
y = options[:at][:y]
|
737
|
+
offset = options[:offset]
|
738
|
+
|
739
|
+
gesture_options =
|
740
|
+
{
|
741
|
+
x: x,
|
742
|
+
y: y,
|
743
|
+
offset: offset,
|
744
|
+
}
|
745
|
+
|
746
|
+
gesture = Gestures::Gesture.double_tap(gesture_options)
|
747
|
+
|
748
|
+
execute_gesture(Gestures::Gesture.with_parameters(gesture,
|
749
|
+
query_string: query.to_s,
|
750
|
+
timeout: options[:timeout]))
|
751
|
+
end
|
752
|
+
|
753
|
+
# @!visibility private
|
754
|
+
def _long_press(query, options={})
|
755
|
+
x = options[:at][:x]
|
756
|
+
y = options[:at][:y]
|
757
|
+
offset = options[:offset]
|
758
|
+
duration = options[:duration]
|
759
|
+
|
760
|
+
gesture_options =
|
761
|
+
{
|
762
|
+
x: x,
|
763
|
+
y: y,
|
764
|
+
offset: offset,
|
765
|
+
time: duration
|
766
|
+
}
|
767
|
+
|
768
|
+
gesture = Gestures::Gesture.tap(gesture_options)
|
769
|
+
|
770
|
+
execute_gesture(Gestures::Gesture.with_parameters(gesture,
|
771
|
+
query: query,
|
772
|
+
timeout: options[:timeout]))
|
773
|
+
end
|
774
|
+
|
775
|
+
# @!visibility private
|
776
|
+
def _pan(query, from, to, options={})
|
777
|
+
from_x = from[:x]
|
778
|
+
from_y = from[:y]
|
779
|
+
from = {x: from_x, y: from_y}
|
780
|
+
to_x = to[:x]
|
781
|
+
to_y = to[:y]
|
782
|
+
to = {x: to_x, y: to_y}
|
783
|
+
duration = options[:duration]
|
784
|
+
|
785
|
+
gesture = Gestures::Gesture.generate_swipe(from, to, time: duration)
|
786
|
+
|
787
|
+
execute_gesture(Gestures::Gesture.with_parameters(gesture,
|
788
|
+
query_string: query.to_s,
|
789
|
+
timeout: options[:timeout]))
|
790
|
+
end
|
791
|
+
|
792
|
+
# @!visibility private
|
793
|
+
def _pan_between(query_from, query_to, options={})
|
794
|
+
gesture = Gestures::Gesture.generate_swipe({x: 50, y: 50}, {x: 50, y: 50}, time: options[:duration])
|
795
|
+
gesture.gestures.first.touches[0].query = query_from
|
796
|
+
gesture.gestures.first.touches[1].query = query_to
|
797
|
+
|
798
|
+
execute_gesture(Gestures::Gesture.with_parameters(gesture,
|
799
|
+
query_string: query_to,
|
800
|
+
timeout: options[:timeout]))
|
801
|
+
end
|
802
|
+
|
803
|
+
# @!visibility private
|
804
|
+
def _flick(query, from, to, options={})
|
805
|
+
from_x = from[:x]
|
806
|
+
from_y = from[:y]
|
807
|
+
from = {x: from_x, y: from_y}
|
808
|
+
to_x = to[:x]
|
809
|
+
to_y = to[:y]
|
810
|
+
to = {x: to_x, y: to_y}
|
811
|
+
duration = options[:duration]
|
812
|
+
|
813
|
+
gesture = Gestures::Gesture.generate_swipe(from, to, time: duration, flick: true)
|
814
|
+
|
815
|
+
execute_gesture(Gestures::Gesture.with_parameters(gesture,
|
816
|
+
query_string: query.to_s,
|
817
|
+
timeout: options[:timeout]))
|
818
|
+
end
|
819
|
+
|
820
|
+
# @!visibility private
|
821
|
+
def _pinch(direction, query, options={})
|
822
|
+
gesture = Gestures::Gesture.pinch(direction)
|
823
|
+
|
824
|
+
execute_gesture(Gestures::Gesture.with_parameters(gesture,
|
825
|
+
query_string: query.to_s,
|
826
|
+
timeout: options[:timeout]))
|
827
|
+
end
|
828
|
+
|
829
|
+
# @!visibility private
|
830
|
+
def adb_uninstall_app(package)
|
831
|
+
@logger.log "Uninstalling #{package}"
|
832
|
+
result = adb.command('uninstall', package, timeout: 60).lines.last
|
833
|
+
|
834
|
+
if result.downcase.chomp != 'success'
|
835
|
+
raise "Could not uninstall app '#{package}': #{result.chomp}"
|
836
|
+
end
|
837
|
+
|
838
|
+
if installed_packages.include?(package)
|
839
|
+
raise "App '#{package}' was not uninstalled"
|
840
|
+
end
|
841
|
+
end
|
842
|
+
|
843
|
+
# @!visibility private
|
844
|
+
def adb_install_app(application)
|
845
|
+
@logger.log "Installing #{application.path}"
|
846
|
+
result = adb.command('install' , '-r', application.path, timeout: 60).lines.last
|
847
|
+
|
848
|
+
if result.downcase.chomp != 'success'
|
849
|
+
raise "Could not install app '#{application.identifier}': #{result.chomp}"
|
850
|
+
end
|
851
|
+
|
852
|
+
unless installed_packages.include?(application.identifier)
|
853
|
+
raise "App '#{application.identifier}' was not installed"
|
854
|
+
end
|
855
|
+
end
|
856
|
+
|
857
|
+
# @!visibility private
|
858
|
+
def adb_clear_app_data(package)
|
859
|
+
@logger.log "Clearing #{package}"
|
860
|
+
|
861
|
+
unless installed_packages.include?(package)
|
862
|
+
raise "Cannot clear app. '#{package}' is not installed"
|
863
|
+
end
|
864
|
+
|
865
|
+
result = adb.shell("pm clear #{package}").lines.last
|
866
|
+
|
867
|
+
if result.downcase.chomp != 'success'
|
868
|
+
raise "Could not clear app '#{package}': #{result.chomp}"
|
869
|
+
end
|
870
|
+
end
|
871
|
+
|
872
|
+
# @!visibility private
|
873
|
+
def execute_gesture(multi_touch_gesture)
|
874
|
+
request = HTTP::Request.new('gesture', json: multi_touch_gesture.to_json)
|
875
|
+
|
876
|
+
body = http_client.get(request, timeout: multi_touch_gesture.timeout + 10).body
|
877
|
+
result = JSON.parse(body)
|
878
|
+
|
879
|
+
if result['outcome'] != 'SUCCESS'
|
880
|
+
raise "Failed to perform gesture. #{result['reason']}"
|
881
|
+
end
|
882
|
+
|
883
|
+
result = result['results'].first
|
884
|
+
|
885
|
+
results = []
|
886
|
+
queries = multi_touch_gesture.queries
|
887
|
+
|
888
|
+
result.each do |key, value|
|
889
|
+
query = queries.find{|query| query.to_s == key}
|
890
|
+
results << QueryResult.create([value], query)
|
891
|
+
end
|
892
|
+
|
893
|
+
if results.length == 1
|
894
|
+
results.first
|
895
|
+
else
|
896
|
+
results
|
897
|
+
end
|
898
|
+
end
|
899
|
+
|
900
|
+
# @!visibility private
|
901
|
+
def params_for_request(parameters)
|
902
|
+
{json: parameters.to_json}
|
903
|
+
end
|
904
|
+
|
905
|
+
# @!visibility private
|
906
|
+
def md5_binary
|
907
|
+
if @md5_binary
|
908
|
+
@md5_binary
|
909
|
+
else
|
910
|
+
if adb.shell('md5', no_exit_code_check: true).chomp == 'md5 file ...'
|
911
|
+
@md5_binary = 'md5'
|
912
|
+
else
|
913
|
+
# The device does not have 'md5'
|
914
|
+
calmd5 = Calabash::Android.binary_location('calmd5', info[:cpu_architecture], can_handle_pie_binaries?)
|
915
|
+
adb.command('push', calmd5, '/data/local/tmp/calmd5')
|
916
|
+
@md5_binary = '/data/local/tmp/calmd5'
|
917
|
+
end
|
918
|
+
end
|
919
|
+
end
|
920
|
+
|
921
|
+
# @!visibility private
|
922
|
+
def can_handle_pie_binaries?
|
923
|
+
# Newer Androids requires PIE enabled executables, older Androids break on them
|
924
|
+
info[:sdk_version] >= 16
|
925
|
+
end
|
926
|
+
|
927
|
+
# @!visibility private
|
928
|
+
def detect_abi
|
929
|
+
abi = adb.shell('getprop ro.product.cpu.abi').chomp
|
930
|
+
|
931
|
+
if abi == 'armeabi-v7a'
|
932
|
+
# armeabi-v7a does not necessarily support NEON vector instructions,
|
933
|
+
# our binaries for this arch requires that, so if CPU does not support
|
934
|
+
# NEON fall back to regular armeabi
|
935
|
+
cpuinfo = adb.shell('cat /proc/cpuinfo')
|
936
|
+
|
937
|
+
if cpuinfo.match /Features.*neon.*/
|
938
|
+
abi
|
939
|
+
else
|
940
|
+
'armeabi'
|
941
|
+
end
|
942
|
+
else
|
943
|
+
abi
|
944
|
+
end
|
945
|
+
end
|
946
|
+
|
947
|
+
# @!visibility private
|
948
|
+
def info
|
949
|
+
@info ||=
|
950
|
+
{
|
951
|
+
os_version: adb.shell('getprop ro.build.version.release').chomp,
|
952
|
+
sdk_version: adb.shell('getprop ro.build.version.sdk').to_i,
|
953
|
+
product_name: adb.shell('getprop ro.product.name').chomp,
|
954
|
+
model: adb.shell('getprop ro.product.model').chomp,
|
955
|
+
cpu_architecture: detect_abi,
|
956
|
+
manufacturer: adb.shell('getprop ro.product.manufacturer').chomp
|
957
|
+
}
|
958
|
+
end
|
959
|
+
|
960
|
+
def world_module
|
961
|
+
Calabash::Android
|
962
|
+
end
|
963
|
+
end
|
964
|
+
end
|
965
|
+
end
|