appium_instrumenter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,123 @@
1
+ module Calabash
2
+ module Android
3
+ # @!visibility private
4
+ class Environment
5
+ # @!visibility private
6
+ class InvalidEnvironmentError < RuntimeError; end
7
+ # @!visibility private
8
+ class InvalidJavaSDKHome < RuntimeError; end
9
+
10
+ # @!visibility private
11
+ # Returns true if running on Windows
12
+ def self.windows?
13
+ RbConfig::CONFIG['host_os'][/mswin|mingw|cygwin/, 0] != nil
14
+ end
15
+
16
+ # @!visibility private
17
+ # Returns the user home directory
18
+ def self.user_home_directory
19
+ if self.xtc?
20
+ home = File.join("./", "tmp", "home")
21
+ FileUtils.mkdir_p(home)
22
+ home
23
+ else
24
+ if self.windows?
25
+ # http://stackoverflow.com/questions/4190930/cross-platform-means-of-getting-users-home-directory-in-ruby
26
+ home = ENV["HOME"] || ENV["USERPROFILE"]
27
+ else
28
+ require "etc"
29
+ home = Etc.getpwuid.dir
30
+ end
31
+
32
+ unless File.exist?(home)
33
+ home = File.join("./", "tmp", "home")
34
+ FileUtils.mkdir_p(home)
35
+ end
36
+
37
+ home
38
+ end
39
+ end
40
+
41
+ # @!visibility private
42
+ # Returns true if debugging is enabled.
43
+ def self.debug?
44
+ ENV['DEBUG'] == '1' ||
45
+ ARGV.include?("-v") ||
46
+ ARGV.include?("--verbose")
47
+ end
48
+
49
+ # @!visibility private
50
+ # Returns true if we are running on the XTC
51
+ def self.xtc?
52
+ ENV['XAMARIN_TEST_CLOUD'] == '1'
53
+ end
54
+
55
+ # @!visibility private
56
+ # Returns true if running in Jenkins CI
57
+ #
58
+ # Checks the value of JENKINS_HOME
59
+ def self.jenkins?
60
+ value = ENV["JENKINS_HOME"]
61
+ !!value && value != ''
62
+ end
63
+
64
+ # @!visibility private
65
+ # Returns true if running in Travis CI
66
+ #
67
+ # Checks the value of TRAVIS
68
+ def self.travis?
69
+ value = ENV["TRAVIS"]
70
+ !!value && value != ''
71
+ end
72
+
73
+ # @!visibility private
74
+ # Returns true if running in Circle CI
75
+ #
76
+ # Checks the value of CIRCLECI
77
+ def self.circle_ci?
78
+ value = ENV["CIRCLECI"]
79
+ !!value && value != ''
80
+ end
81
+
82
+ # @!visibility private
83
+ # Returns true if running in Teamcity
84
+ #
85
+ # Checks the value of TEAMCITY_PROJECT_NAME
86
+ def self.teamcity?
87
+ value = ENV["TEAMCITY_PROJECT_NAME"]
88
+ !!value && value != ''
89
+ end
90
+
91
+ # @!visibility private
92
+ # Returns true if running in Teamcity
93
+ #
94
+ # Checks the value of GITLAB_CI
95
+ def self.gitlab?
96
+ value = ENV["GITLAB_CI"]
97
+ !!value && value != ''
98
+ end
99
+
100
+ # @!visibility private
101
+ # Returns true if running in a CI environment
102
+ def self.ci?
103
+ [
104
+ self.ci_var_defined?,
105
+ self.travis?,
106
+ self.jenkins?,
107
+ self.circle_ci?,
108
+ self.teamcity?,
109
+ self.gitlab?
110
+ ].any?
111
+ end
112
+
113
+ private
114
+
115
+ # !@visibility private
116
+ def self.ci_var_defined?
117
+ value = ENV["CI"]
118
+ !!value && value != ''
119
+ end
120
+ end
121
+ end
122
+ end
123
+
@@ -0,0 +1,220 @@
1
+ require "stringio"
2
+ require 'zip'
3
+ require 'tempfile'
4
+ require 'escape'
5
+ require 'rbconfig'
6
+ require_relative 'utils'
7
+ require_relative 'dependencies'
8
+ require_relative 'environment'
9
+
10
+ def package_name(app)
11
+ unless File.exist?(app)
12
+ raise "Application '#{app}' does not exist"
13
+ end
14
+
15
+ package_line = aapt_dump(app, "package").first
16
+ raise "'package' not found in aapt output" unless package_line
17
+ m = package_line.match(/name='([^']+)'/)
18
+ raise "Unexpected output from aapt: #{package_line}" unless m
19
+ m[1]
20
+ end
21
+
22
+ def main_activity(app)
23
+ unless File.exist?(app)
24
+ raise "Application '#{app}' does not exist"
25
+ end
26
+
27
+ begin
28
+ log("Trying to find launchable activity")
29
+ launchable_activity_line = aapt_dump(app, "launchable-activity").first
30
+ raise "'launchable-activity' not found in aapt output" unless launchable_activity_line
31
+ m = launchable_activity_line.match(/name='([^']+)'/)
32
+ raise "Unexpected output from aapt: #{launchable_activity_line}" unless m
33
+ log("Found launchable activity '#{m[1]}'")
34
+ m[1]
35
+ rescue => e
36
+ log("Could not find launchable activity, trying to parse raw AndroidManifest. #{e.message}")
37
+
38
+ manifest_data = `"#{Calabash::Android::Dependencies.aapt_path}" dump xmltree "#{app}" AndroidManifest.xml`
39
+ regex = /^\s*A:[\s*]android:name\(\w+\)\=\"android.intent.category.LAUNCHER\"/
40
+ lines = manifest_data.lines.collect(&:strip)
41
+ indicator_line = nil
42
+
43
+ lines.each_with_index do |line, index|
44
+ match = line.match(regex)
45
+
46
+ unless match.nil?
47
+ raise 'More than one launchable activity in AndroidManifest' unless indicator_line.nil?
48
+ indicator_line = index
49
+ end
50
+ end
51
+
52
+ raise 'No launchable activity found in AndroidManifest' unless indicator_line
53
+
54
+ intent_filter_found = false
55
+
56
+ (0..indicator_line).reverse_each do |index|
57
+ if intent_filter_found
58
+ match = lines[index].match(/\s*E:\s*activity-alias/)
59
+
60
+ raise 'Could not find target activity in activity alias' if match
61
+
62
+ match = lines[index].match(/^\s*A:\s*android:targetActivity\(\w*\)\=\"([^\"]+)/){$1}
63
+
64
+ if match
65
+ log("Found launchable activity '#{match}'")
66
+
67
+ return match
68
+ end
69
+ else
70
+ unless lines[index].match(/\s*E: intent-filter/).nil?
71
+ log("Read intent filter")
72
+ intent_filter_found = true
73
+ end
74
+ end
75
+ end
76
+
77
+ raise 'Could not find launchable activity'
78
+ end
79
+ end
80
+
81
+ def aapt_dump(app, key)
82
+ lines = `"#{Calabash::Android::Dependencies.aapt_path}" dump badging "#{app}"`.lines.collect(&:strip)
83
+ lines.select { |l| l.start_with?("#{key}:") }
84
+ end
85
+
86
+ def checksum(file_path)
87
+ require 'digest/md5'
88
+ Digest::MD5.file(file_path).hexdigest
89
+ end
90
+
91
+ def test_server_path(apk_file_path)
92
+ "test_servers/#{checksum(apk_file_path)}_#{Calabash::Android::VERSION}.apk"
93
+ end
94
+
95
+ def build_test_server_if_needed(app_path)
96
+ unless File.exist?(test_server_path(app_path))
97
+ if ARGV.include? "--no-build"
98
+ puts "No test server found for this combination of app and calabash version. Exiting!"
99
+ exit 1
100
+ else
101
+ puts "No test server found for this combination of app and calabash version. Recreating test server."
102
+ require 'calabash-android/operations'
103
+ require File.join(File.dirname(__FILE__), '..', '..', 'bin', 'calabash-android-build')
104
+ calabash_build(app_path)
105
+ end
106
+ end
107
+ end
108
+
109
+ def resign_apk(app_path)
110
+ Dir.mktmpdir do |tmp_dir|
111
+ log "Resign apk"
112
+ unsigned_path = File.join(tmp_dir, 'unsigned.apk')
113
+ unaligned_path = File.join(tmp_dir, 'unaligned.apk')
114
+ FileUtils.cp(app_path, unsigned_path)
115
+ unsign_apk(unsigned_path)
116
+ sign_apk(unsigned_path, unaligned_path)
117
+ zipalign_apk(unaligned_path, app_path)
118
+ end
119
+ end
120
+
121
+ def unsign_apk(path)
122
+ meta_files = `"#{Calabash::Android::Dependencies.aapt_path}" list "#{path}"`.lines.collect(&:strip).grep(/^META-INF\//)
123
+
124
+ signing_file_names = ['.mf', '.rsa', '.dsa', '.ec', '.sf']
125
+
126
+ files_to_remove = meta_files.select do |file|
127
+ # other will be:
128
+ # META-INF/foo/bar
129
+ # other #=> bar
130
+ directory, file_name, other = file.split('/')
131
+
132
+ if other != nil || file_name.nil?
133
+ false
134
+ else
135
+ if signing_file_names.include?(File.extname(file_name).downcase)
136
+ true
137
+ end
138
+ end
139
+ end
140
+
141
+ if files_to_remove.empty?
142
+ log "App wasn't signed. Will not try to unsign it."
143
+ else
144
+ system("\"#{Calabash::Android::Dependencies.aapt_path}\" remove \"#{path}\" #{files_to_remove.join(" ")}")
145
+ end
146
+ end
147
+
148
+ def zipalign_apk(inpath, outpath)
149
+ cmd = %Q("#{Calabash::Android::Dependencies.zipalign_path}" -f 4 "#{inpath}" "#{outpath}")
150
+ log "Zipaligning using: #{cmd}"
151
+ system(cmd)
152
+ end
153
+
154
+ def sign_apk(app_path, dest_path)
155
+ java_keystore = JavaKeystore.get_keystores.first
156
+
157
+ if java_keystore.nil?
158
+ raise 'No keystores found. You can specify the keystore location and credentials using calabash-android setup'
159
+ end
160
+
161
+ java_keystore.sign_apk(app_path, dest_path)
162
+ end
163
+
164
+ def fingerprint_from_apk(app_path)
165
+ app_path = File.expand_path(app_path)
166
+ Dir.mktmpdir do |tmp_dir|
167
+ Dir.chdir(tmp_dir) do
168
+ FileUtils.cp(app_path, "app.apk")
169
+ FileUtils.mkdir("META-INF")
170
+
171
+ Calabash::Utils.with_silent_zip do
172
+ Zip::File.foreach("app.apk") do |z|
173
+ z.extract if /^META-INF\/\w+.(rsa|dsa)/i =~ z.name
174
+ end
175
+ end
176
+
177
+ signature_files = Dir["#{tmp_dir}/META-INF/*"]
178
+
179
+ log 'Signature files:'
180
+
181
+ signature_files.each do |signature_file|
182
+ log signature_file
183
+ end
184
+
185
+ raise "No signature files found in META-INF. Cannot proceed." if signature_files.empty?
186
+ raise "More than one signature file (DSA or RSA) found in META-INF. Cannot proceed." if signature_files.length > 1
187
+
188
+ cmd = "\"#{Calabash::Android::Dependencies.keytool_path}\" -v -printcert -J\"-Dfile.encoding=utf-8\" -file \"#{signature_files.first}\""
189
+ log cmd
190
+ fingerprints = `#{cmd}`
191
+ md5_fingerprint = extract_sha1_fingerprint(fingerprints)
192
+ log "SHA1 fingerprint for signing cert (#{app_path}): #{md5_fingerprint}"
193
+ md5_fingerprint
194
+ end
195
+ end
196
+ end
197
+
198
+ def extract_md5_fingerprint(fingerprints)
199
+ m = fingerprints.scan(/MD5.*((?:[a-fA-F\d]{2}:){15}[a-fA-F\d]{2})/).flatten
200
+ raise "No MD5 fingerprint found:\n #{fingerprints}" if m.empty?
201
+ m.first
202
+ end
203
+
204
+ def extract_sha1_fingerprint(fingerprints)
205
+ m = fingerprints.scan(/SHA1.*((?:[a-fA-F\d]{2}:){15}[a-fA-F\d]{2})/).flatten
206
+ raise "No SHA1 fingerprint found:\n #{fingerprints}" if m.empty?
207
+ m.first
208
+ end
209
+
210
+ def extract_signature_algorithm_name(fingerprints)
211
+ m = fingerprints.scan(/Signature algorithm name: (.*)/).flatten
212
+ raise "No signature algorithm names found:\n #{fingerprints}" if m.empty?
213
+ m.first
214
+ end
215
+
216
+ def log(message, error = false)
217
+ if error or ARGV.include? "-v" or ARGV.include? "--verbose" or ENV["DEBUG"] == "1"
218
+ $stdout.puts "#{Time.now.strftime("%Y-%m-%d %H:%M:%S")} - #{message}"
219
+ end
220
+ end
@@ -0,0 +1,125 @@
1
+ require 'json'
2
+ class JavaKeystore
3
+ attr_reader :errors, :location, :keystore_alias, :password, :fingerprint
4
+ attr_reader :signature_algorithm_name
5
+
6
+ def initialize(location, keystore_alias, password)
7
+ raise "No such keystore file '#{location}'" unless File.exists?(File.expand_path(location))
8
+ log "Reading keystore data from keystore file '#{File.expand_path(location)}'"
9
+
10
+ keystore_data = system_with_stdout_on_success(Calabash::Android::Dependencies.keytool_path, '-list', '-v', '-alias', keystore_alias, '-keystore', location, '-storepass', password, '-J"-Dfile.encoding=utf-8"', '-J"-Duser.language=en-US"')
11
+
12
+ if keystore_data.nil?
13
+ if keystore_alias.empty?
14
+ log "Could not obtain keystore data. Will try to extract alias automatically"
15
+
16
+ keystore_data = system_with_stdout_on_success(Calabash::Android::Dependencies.keytool_path, '-list', '-v', '-keystore', location, '-storepass', password, '-J"-Dfile.encoding=utf-8"', '-J"-Duser.language=en-US"')
17
+ aliases = keystore_data.scan(/Alias name\:\s*(.*)/).flatten
18
+
19
+ if aliases.length == 0
20
+ raise 'Could not extract alias automatically. Please specify alias using calabash-android setup'
21
+ elsif aliases.length > 1
22
+ raise 'Multiple aliases found in keystore. Please specify alias using calabash-android setup'
23
+ else
24
+ keystore_alias = aliases.first
25
+ log "Extracted keystore alias '#{keystore_alias}'. Continuing"
26
+
27
+ return initialize(location, keystore_alias, password)
28
+ end
29
+ else
30
+ error = "Could not list certificates in keystore. Probably because the password was incorrect."
31
+ @errors = [{:message => error}]
32
+ log error
33
+ raise error
34
+ end
35
+ end
36
+
37
+ @location = location
38
+ @keystore_alias = keystore_alias
39
+ @password = password
40
+ log "Key store data:"
41
+ log keystore_data
42
+ @fingerprint = extract_sha1_fingerprint(keystore_data)
43
+ @signature_algorithm_name = extract_signature_algorithm_name(keystore_data)
44
+ log "Fingerprint: #{fingerprint}"
45
+ log "Signature algorithm name: #{signature_algorithm_name}"
46
+ end
47
+
48
+ def sign_apk(apk_path, dest_path)
49
+ raise "Cannot sign with a miss configured keystore" if errors
50
+ raise "No such file: #{apk_path}" unless File.exists?(apk_path)
51
+
52
+ # E.g. MD5withRSA or MD5withRSAandMGF1
53
+ encryption = signature_algorithm_name.split('with')[1].split('and')[0]
54
+ signing_algorithm = "SHA1with#{encryption}"
55
+ digest_algorithm = 'SHA1'
56
+
57
+ log "Signing using the signature algorithm: '#{signing_algorithm}'"
58
+ log "Signing using the digest algorithm: '#{digest_algorithm}'"
59
+
60
+ unless system_with_stdout_on_success(Calabash::Android::Dependencies.jarsigner_path, '-sigfile', 'CERT', '-sigalg', signing_algorithm, '-digestalg', digest_algorithm, '-signedjar', dest_path, '-storepass', password, '-keystore', location, apk_path, keystore_alias)
61
+ raise "Could not sign app: #{apk_path}"
62
+ end
63
+ end
64
+
65
+ def system_with_stdout_on_success(cmd, *args)
66
+ a = Escape.shell_command(args)
67
+ cmd = "\"#{cmd}\" #{a.gsub("'", '"')}"
68
+ log cmd
69
+ out = `#{cmd}`
70
+ if $?.exitstatus == 0
71
+ out
72
+ else
73
+ nil
74
+ end
75
+ end
76
+
77
+ def self.read_keystore_with_default_password_and_alias(path)
78
+ path = File.expand_path path
79
+
80
+ if File.exists? path
81
+ keystore = JavaKeystore.new(path, 'androiddebugkey', 'android')
82
+ if keystore.errors
83
+ log "Trying to "
84
+ nil
85
+ else
86
+ log "Unlocked keystore at #{path} - fingerprint: #{keystore.fingerprint}"
87
+ keystore
88
+ end
89
+ else
90
+ log "Trying to read keystore from: #{path} - no such file"
91
+ nil
92
+ end
93
+ end
94
+
95
+ def self.get_keystores
96
+ if keystore = keystore_from_settings
97
+ p "found calabash_settings file"
98
+ [ keystore ]
99
+ else
100
+ [
101
+ read_keystore_with_default_password_and_alias(File.join(ENV["HOME"], "/.android/debug.keystore")),
102
+ read_keystore_with_default_password_and_alias("debug.keystore"),
103
+ read_keystore_with_default_password_and_alias(File.join(ENV["HOME"], ".local/share/Xamarin/Mono\\ for\\ Android/debug.keystore")),
104
+ read_keystore_with_default_password_and_alias(File.join(ENV["HOME"], "AppData/Local/Xamarin/Mono for Android/debug.keystore")),
105
+ ].compact
106
+ end
107
+ end
108
+
109
+ def self.keystore_from_settings
110
+ keystore = JSON.parse(IO.read(".calabash_settings")) if File.exist? ".calabash_settings"
111
+ keystore = JSON.parse(IO.read("calabash_settings")) if File.exist? "calabash_settings"
112
+ return unless keystore
113
+ fail_if_key_missing(keystore, "keystore_location")
114
+ fail_if_key_missing(keystore, "keystore_password")
115
+ fail_if_key_missing(keystore, "keystore_alias")
116
+ keystore["keystore_location"] = File.expand_path(keystore["keystore_location"])
117
+ log("Keystore location specified in #{File.exist?(".calabash_settings") ? ".calabash_settings" : "calabash_settings"}.")
118
+ JavaKeystore.new(keystore["keystore_location"], keystore["keystore_alias"], keystore["keystore_password"])
119
+ end
120
+
121
+ def self.fail_if_key_missing(map, key)
122
+ raise "Found .calabash_settings but no #{key} defined." unless map[key]
123
+ end
124
+
125
+ end