lesspainful 0.9.14 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,11 +1,12 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lesspainful (0.9.13)
4
+ lesspainful (0.10.0)
5
5
  bundler (~> 1.2)
6
6
  json
7
- rest-client
8
- rubyzip
7
+ rest-client (= 1.6.7)
8
+ rubyzip (= 0.9.9)
9
+ thor (= 0.17.0)
9
10
 
10
11
  GEM
11
12
  remote: http://rubygems.org/
@@ -15,6 +16,7 @@ GEM
15
16
  rest-client (1.6.7)
16
17
  mime-types (>= 1.16)
17
18
  rubyzip (0.9.9)
19
+ thor (0.17.0)
18
20
 
19
21
  PLATFORMS
20
22
  ruby
data/Rakefile CHANGED
@@ -1,2 +1,11 @@
1
1
  require 'bundler'
2
2
  Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << 'test'
8
+ end
9
+
10
+ desc "Run tests"
11
+ task :default => :test
data/bin/lesspainful CHANGED
@@ -1,339 +1,26 @@
1
1
  #!/usr/bin/env ruby
2
- require 'rubygems'
3
- require 'zip/zip'
4
- require 'digest'
5
- require 'rest_client'
6
- require 'json'
7
- require 'rbconfig'
8
- require 'tmpdir'
9
-
10
- def host
11
- ENV["LP_HOST"] || "https://www.lesspainful.com"
12
- end
13
-
14
- def digest(file)
15
- Digest::SHA256.file(file).hexdigest
16
- end
17
-
18
- def unzip_file (file, destination)
19
- Zip::ZipFile.open(file) { |zip_file|
20
- zip_file.each { |f|
21
- f_path=File.join(destination, f.name)
22
- FileUtils.mkdir_p(File.dirname(f_path))
23
- zip_file.extract(f, f_path) unless File.exist?(f_path)
24
- }
25
- }
26
- end
27
-
28
- def workspace
29
- if ARGV[2]
30
- abort_unless(File.exist?(ARGV[2])) do
31
- puts "Provided workspace: #{ARGV[2]} does not exist."
32
- end
33
- File.join(File.expand_path(ARGV[2]),File::Separator)
34
- else
35
- ""
36
- end
37
- end
38
-
39
- def features_zip
40
- if ARGV[3] and ARGV[3].end_with?".zip"
41
- abort_unless(File.exist?(ARGV[3])) do
42
- puts "No file found #{ARGV[3]}"
43
- end
44
- File.expand_path(ARGV[3])
45
- end
46
- end
47
-
48
- def app
49
- ARGV[0]
50
- end
51
-
52
- def api_key
53
- ARGV[1]
54
- end
55
-
56
- def is_android?
57
- app.end_with? ".apk"
58
- end
59
-
60
- def calabash_android_version
61
- `bundle exec calabash-android version`.strip
62
- end
63
-
64
- def is_ios?
65
- app.end_with? ".ipa"
66
- end
67
-
68
- def test_server_path
69
- require 'digest/md5'
70
- digest = Digest::MD5.file(app).hexdigest
71
- File.join("test_servers","#{digest}_#{calabash_android_version}.apk")
72
- end
73
-
74
- def all_files
75
- dir = workspace
76
- if features_zip
77
- dir = Dir.mktmpdir
78
- unzip_file(features_zip, dir)
79
- dir = File.join(dir,File::Separator)
80
- end
81
-
82
-
83
- files = Dir.glob(File.join("#{dir}features","**","*"))
84
-
85
- if File.directory?("#{dir}playback")
86
- files += Dir.glob(File.join("#{dir}playback","*"))
87
- end
88
-
89
-
90
- files += Dir.glob(File.join("#{workspace}vendor","cache","*"))
91
-
92
- if workspace and workspace.strip != ""
93
- files += Dir.glob("#{workspace}Gemfile")
94
- files += Dir.glob("#{workspace}Gemfile.lock")
95
- end
96
-
97
- if is_android?
98
- files << test_server_path
99
- end
100
- p files
101
- {:feature_prefix => dir, :workspace_prefix => workspace, :files => files.find_all { |file_or_dir| File.file? file_or_dir }}
102
- end
103
-
104
- def http_post(address, args, &block)
105
- if block_given?
106
- response = RestClient.post "#{host}/#{address}", args, {:content_type => "multipart/form-data"} do |response, request, result, &other_block|
107
- block.call(response,request,result, &other_block)
108
- end
109
- else
110
- response = RestClient.post "#{host}/#{address}", args
111
- end
112
-
113
- response.body
114
- end
115
-
116
- def is_windows?
117
- (RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/)
118
- end
119
-
120
- def is_macosx?
121
- (RbConfig::CONFIG['host_os'] =~ /darwin/)
122
- end
123
-
124
- def validate_ipa(ipa)
125
- result = false
126
- dir = Dir.mktmpdir #do |dir|
127
-
128
- unzip_file(ipa,dir)
129
- unless File.directory?("#{dir}/Payload") #macos only
130
- abort do
131
- puts "Unzipping #{ipa} to #{dir} failed: Did not find a Payload directory (invalid .ipa)."
132
- end
133
- end
134
- app_dir = Dir.foreach("#{dir}/Payload").find {|d| /\.app$/.match(d)}
135
- app = app_dir.split(".")[0]
136
- res = `otool "#{File.expand_path(dir)}/Payload/#{app_dir}/#{app}" -o 2> /dev/null | grep CalabashServer`
137
-
138
- if /CalabashServer/.match(res)
139
- puts "ipa: #{ipa} *contains* calabash.framework"
140
- result = :calabash
141
- end
142
-
143
- unless result
144
- res = `otool "#{File.expand_path(dir)}/Payload/#{app_dir}/#{app}" -o 2> /dev/null | grep FrankServer`
145
- if /FrankServer/.match(res)
146
- puts "ipa: #{ipa} *contains* FrankServer"
147
- result = :frank
148
- else
149
- puts "ipa: #{ipa} *does not contain* calabash.framework"
150
- result = false
151
- end
152
- end
153
- #end
154
- result
155
- end
156
-
157
-
158
- def self.log(message)
159
- puts "#{Time.now } #{message}"
160
- $stdout.flush
161
- end
162
-
163
- def self.log_header(message)
164
- if is_windows?
165
- puts "\n### #{message} ###"
166
- else
167
- puts "\n\e[#{35}m### #{message} ###\e[0m"
168
- end
169
- end
170
-
171
- def usage
172
- "Usage: lesspainful <app> <api_key> <opt workspace>? <opt feature.zip>?"
173
- end
174
-
175
- def verify_arguments
176
- server = nil
177
- log_and_abort(usage) unless app
178
- log_and_abort(usage) unless api_key
179
- abort_unless(File.exist?(app)) do
180
- puts usage
181
- puts "No such file: #{app}"
182
- end
183
- abort_unless(/\.(apk|ipa)$/ =~ app) do
184
- puts usage
185
- puts "<app> should be either an ipa or apk file"
186
- end
187
- if is_ios? and is_macosx? and not ENV['CHECK_IPA'] == '0'
188
- log_header("Checking ipa for linking with Calabash or Frank")
189
- server = validate_ipa(app)
190
- abort_unless(server) do
191
- puts "The .ipa file does not seem to be linked with Calabash."
192
- puts "Verify that your app is linked correctly."
193
- puts "To disable this check run with Environment Variable CHECK_IPA=0"
194
- end
195
- end
196
- server
197
- end
198
-
199
- def abort(&block)
200
- yield block
201
- exit 1
202
- end
203
-
204
- def abort_unless(condition, &block)
205
- unless condition
206
- yield block
207
- exit 1
208
- end
209
- end
210
-
211
- def log_and_abort(message)
212
- abort do
213
- puts message
214
- end
215
- end
216
-
217
-
218
- def self.verify_files
219
- if is_android?
220
- abort_unless(File.exist?(test_server_path)) do
221
- puts "No test server found. Please run:"
222
- puts " calabash-android build #{app}"
223
- end
224
-
225
- calabash_gem = Dir.glob("vendor/cache/calabash-android-*").first
226
- abort_unless(calabash_gem) do
227
- puts "calabash-android was not packaged correct."
228
- puts "Please tell contact@lesspainful.com about this bug."
229
- end
230
- end
231
- end
232
-
233
2
 
3
+ require 'lesspainful/cli'
234
4
 
5
+ begin
235
6
 
236
- start_at = Time.now
237
-
238
- server = verify_arguments
239
-
240
- log_header("Checking for Gemfile")
241
- unless File.exist?("Gemfile")
242
- log("Gemfile missing.")
243
- if is_android?
244
- log("Creating Gemfile for Android")
245
- tgt = File.join(File.dirname(__FILE__),"..","lib","GemfileAndroid")
246
- elsif is_ios?
247
- log("Creating Gemfile for iOS")
248
- gemfile = "GemfileIOS"
249
- if server == :frank
250
- gemfile = "GemfileIOSFrank"
251
- end
252
- tgt = File.join(File.dirname(__FILE__),"..","lib",gemfile)
253
- else
254
- abort do
255
- puts usage
256
- puts "Your app (second argument) must be an ipa or apk file."
257
- end
258
-
259
- end
260
- FileUtils.cp(File.expand_path(tgt), "Gemfile")
261
- end
262
-
263
- log_header("Packaging")
264
- log_and_abort "Bundler failed. Please check command: bundle package" unless system("bundle package --all")
265
-
266
-
267
- log_header("Collecting files")
268
- collected_files = all_files
269
- file_paths = collected_files[:files]
270
- feature_prefix = collected_files[:feature_prefix]
271
- workspace_prefix = collected_files[:workspace_prefix]
272
-
273
- hashes = file_paths.collect { |f| digest(f) }
274
- hashes << digest(app)
275
-
276
- log_header("Verifying files")
277
-
278
- verify_files
279
-
280
-
281
-
282
-
283
- log_header("Negotiating upload")
284
-
285
- response = http_post("check_hash", {"hashes" => hashes})
286
-
287
- cache_status = JSON.parse(response)
288
-
289
- curl_args = []
290
- files = []
291
- paths = []
292
- file_paths.each do |file|
293
- if cache_status[digest(file)]
294
- #Server already knows about this file. No need to upload it.
295
- files << digest(file)
7
+ script = LessPainful::CLI.new
8
+ if ARGV.count == 0
9
+ script.invoke(:help)
10
+ elsif LessPainful::CLI.all_tasks.keys.include?(ARGV[0])
11
+ LessPainful::CLI.start
12
+ elsif ARGV.count >= 2
13
+ #For backwards compat allow "old-style"
14
+ script.invoke(:submit, ARGV)
296
15
  else
297
- #Upload file
298
- files << File.new(file)
16
+ script.invoke(:help)
299
17
  end
300
18
 
301
- if file.start_with?(feature_prefix)
302
- prefix = feature_prefix
303
- else
304
- prefix = workspace_prefix
19
+ rescue Exception=>e
20
+ if ENV['DEBUG']
21
+ puts e.backtrace.join("\n") if e.backtrace
22
+ puts "Message:"
305
23
  end
306
- paths << file.sub(prefix, "")
24
+ puts e.message
25
+ exit(false)
307
26
  end
308
-
309
- app_file = cache_status[digest(app)] ? digest(app) : File.new(app)
310
-
311
- log_header("Uploading negotiated files")
312
- response = http_post("upload", {"files" => files,
313
- "paths" => paths,
314
- "app" => app_file,
315
- "api_key" => api_key,
316
- "app_filename" => File.basename(app)}) do |response, request, result, &block|
317
- case response.code
318
- when 200
319
- response
320
- when 403
321
- abort do
322
- puts "Invalid API key"
323
- end
324
- when 413
325
- abort do
326
- puts "Files too large"
327
- end
328
- else
329
- abort do
330
- log "Unexpected Error. Please contact contact@lesspainful.com and provide the timestamp on your left."
331
- end
332
- end
333
- end
334
-
335
- end_at = Time.now
336
-
337
- log_header("Done (took %.1fs)" % (end_at - start_at))
338
- log response
339
-
data/lesspainful.gemspec CHANGED
@@ -12,11 +12,13 @@ Gem::Specification.new do |s|
12
12
  s.summary = %q{Client for uploading calabash test to www.lesspainful.com}
13
13
  s.description = %q{www.lesspainful.com lets you run calabash tests for native and hybrid apps on physical devices}
14
14
  s.files = `git ls-files`.split("\n")
15
+ s.test_files = Dir.glob("test/**/*.rb")
15
16
  s.executables = "lesspainful"
16
17
  s.require_paths = ["lib"]
17
18
 
19
+ s.add_dependency( "thor", "0.17.0")
18
20
  s.add_dependency( "bundler","~> 1.2")
19
21
  s.add_dependency( "json")
20
- s.add_dependency( "rubyzip")
21
- s.add_dependency( "rest-client")
22
+ s.add_dependency( "rubyzip","0.9.9")
23
+ s.add_dependency( "rest-client","1.6.7")
22
24
  end
@@ -0,0 +1,547 @@
1
+ require 'thor'
2
+ require 'yaml'
3
+ require 'rubygems'
4
+ require 'zip/zip'
5
+ require 'digest'
6
+ require 'rest_client'
7
+ require 'json'
8
+ require 'rbconfig'
9
+ require 'tmpdir'
10
+ require 'fileutils'
11
+
12
+ module LessPainful
13
+ class CLI < Thor
14
+ include Thor::Actions
15
+
16
+ attr_accessor :host, :app, :api_key, :workspace, :config, :profile, :features_zip, :skip_check, :reset_between_scenarios, :dry_run
17
+
18
+ attr_accessor :endpoint_path
19
+
20
+ def self.source_root
21
+ File.join(File.dirname(__FILE__), '..')
22
+ end
23
+
24
+ desc "submit <APP> <API_KEY>", "Submits your app and test suite to LessPainful's device labs"
25
+
26
+ method_option :host,
27
+ :desc => "Device Lab Host to submit to.",
28
+ :aliases => '-h', :type => :string,
29
+ :default => (ENV["LP_HOST"] || 'https://www.lesspainful.com')
30
+
31
+ method_option :workspace,
32
+ :desc => "Workspace containing Gemfile and features.",
33
+ :aliases => '-w',
34
+ :type => :string,
35
+ :default => File.expand_path(".")
36
+
37
+ method_option :features,
38
+ :desc => "Zip file with features, step definitions, etc.",
39
+ :aliases => '-f',
40
+ :type => :string
41
+
42
+ method_option :config,
43
+ :desc => "Cucumber configuration file (cucumber.yml).",
44
+ :aliases => '-c',
45
+ :type => :string
46
+
47
+ method_option :profile,
48
+ :desc => "Profile to run (profile from cucumber.yml).",
49
+ :aliases => '-p',
50
+ :type => :string
51
+
52
+ method_option "skip-check",
53
+ :desc => "Skip checking for ipa linked with Calabash (iOS only).",
54
+ :type => :boolean
55
+
56
+ method_option "reset-between-scenarios",
57
+ :desc => "Reinstall app between each scenario (iOS only).",
58
+ :type => :string,
59
+ :default => ENV['RESET_BETWEEN_SCENARIOS']=='1' ? '1': "0"
60
+
61
+ method_option "dry-run",
62
+ :desc => "Sanity check only, don't upload.",
63
+ :aliases => '-d',
64
+ :type => :boolean,
65
+ :default => false #do upload by default
66
+
67
+ def submit(app, api_key, *args)
68
+
69
+ self.host = options[:host]
70
+
71
+ unless self.host
72
+ self.endpoint_path = "upload" #invoked old-style
73
+ self.host = ENV["LP_HOST"] || 'https://www.lesspainful.com'
74
+ else
75
+ self.endpoint_path = "upload2" #nginx receives upload
76
+ end
77
+
78
+ app_path = File.expand_path(app)
79
+ unless File.exist?(app_path)
80
+ raise "App is not a file: #{app_path}"
81
+ end
82
+
83
+ self.app = app_path
84
+
85
+ self.dry_run = options["dry-run"]
86
+
87
+ self.api_key = api_key
88
+
89
+ self.skip_check = ENV['CHECK_IPA'] == '0'
90
+ self.skip_check = options["skip-check"] unless options["skip-check"].nil?
91
+
92
+ self.reset_between_scenarios = options["reset-between-scenarios"]
93
+
94
+ parse_and_set_config_and_profile
95
+
96
+
97
+
98
+ workspace_path = options[:workspace] || File.expand_path(".")
99
+
100
+ if args[0]
101
+ workspace_path = args[0]
102
+ end
103
+
104
+ unless File.directory?(workspace_path)
105
+ raise "Provided workspace: #{workspace_path} is not a directory."
106
+ end
107
+ self.workspace = File.join(File.expand_path(workspace_path), File::Separator)
108
+
109
+
110
+ features_path = options[:features]
111
+ features_path = args[1] if args[1]
112
+
113
+ unless features_path.nil?
114
+ if File.exist?(features_path)
115
+ self.features_zip = File.expand_path(features_path)
116
+ else
117
+ raise "Provided features file does not exist #{features_path}"
118
+ end
119
+ end
120
+
121
+ if ENV['DEBUG']
122
+ puts "Host = #{self.host}"
123
+ puts "App = #{self.app}"
124
+ puts "API Key = #{self.api_key}"
125
+ puts "Workspace = #{self.workspace}"
126
+ puts "Features Zip = #{self.features_zip}"
127
+ puts "Config = #{self.config}"
128
+ puts "Profile = #{self.profile}"
129
+ puts "Skip Check = #{self.skip_check}"
130
+ puts "Reset Between Scenarios = #{self.reset_between_scenarios}"
131
+ end
132
+
133
+ #Argument parsing done
134
+ json = submit_test_job
135
+
136
+ unless dry_run
137
+ if JSON.respond_to?(:pretty_generate)
138
+ puts JSON.pretty_generate(json)
139
+ else
140
+ puts json
141
+ end
142
+ end
143
+
144
+ end
145
+
146
+ default_task :submit
147
+
148
+ no_tasks do
149
+
150
+ def submit_test_job
151
+ start_at = Time.now
152
+
153
+ server = verify_app_and_extract_test_server
154
+
155
+
156
+ log_header("Checking for Gemfile")
157
+ gemfile_path = File.join(self.workspace, "Gemfile")
158
+ unless File.exist?(gemfile_path)
159
+ copy_default_gemfile(gemfile_path, server)
160
+ end
161
+
162
+ log_header("Packaging")
163
+ FileUtils.cd(self.workspace) do
164
+ unless system("bundle package --all")
165
+ log_and_abort "Bundler failed. Please check command: bundle package"
166
+ end
167
+ end
168
+
169
+
170
+ log_header("Verifying dependencies")
171
+ verify_dependencies
172
+
173
+
174
+ if dry_run
175
+ log_header("Dry run only")
176
+ log("Dry run completed OK")
177
+ return
178
+ end
179
+
180
+ app_file, files, paths = gather_files_and_paths_to_upload(all_files)
181
+
182
+
183
+ log_header("Uploading negotiated files")
184
+
185
+ upload_data = {"files" => files,
186
+ "paths" => paths,
187
+ "reset_between_scenarios" => reset_between_scenarios,
188
+ "app" => app_file,
189
+ "api_key" => api_key,
190
+ "app_filename" => File.basename(app)}
191
+
192
+ if profile #only if config and profile
193
+ upload_data["profile"] = profile
194
+ end
195
+
196
+ if ENV['DEBUG']
197
+ puts JSON.pretty_generate(upload_data)
198
+ end
199
+
200
+
201
+ response = http_post(endpoint_path,upload_data) do |response, request, result, &block|
202
+ case response.code
203
+ when 200..202
204
+ response
205
+ when 403
206
+ abort do
207
+ puts "Invalid API key"
208
+ end
209
+ when 413
210
+ abort do
211
+ puts "Files too large"
212
+ end
213
+ else
214
+ abort do
215
+ log "Unexpected Error. Please contact contact@lesspainful.com and provide the timestamp on your left."
216
+ end
217
+ end
218
+ end
219
+
220
+ end_at = Time.now
221
+
222
+ log_header("Done (took %.1fs)" % (end_at - start_at))
223
+
224
+ return :status => response.code, :body => JSON.parse(response)
225
+ end
226
+
227
+
228
+ def copy_default_gemfile(gemfile_path, server)
229
+ log("")
230
+ log("Gemfile missing.")
231
+ log("You must provide a Gemfile in your workspace.")
232
+ log("A Gemfile must describe your dependencies and their versions")
233
+ log("See: http://gembundler.com/v1.2/gemfile.html")
234
+ log("")
235
+ log("Warning proceeding with default Gemfile.")
236
+ log("It is strongly recommended that you create a custom Gemfile.")
237
+ tgt = nil
238
+ if is_android?
239
+ log("Creating Gemfile for Android")
240
+ tgt = File.join(CLI.source_root, "GemfileAndroid")
241
+ elsif is_ios?
242
+ log("Creating Gemfile for iOS")
243
+ gemfile = "GemfileIOS"
244
+ if server == :frank
245
+ gemfile = "GemfileIOSFrank"
246
+ end
247
+ tgt = File.join(CLI.source_root, gemfile)
248
+ else
249
+ raise "Your app must be an ipa or apk file."
250
+ end
251
+ log("Proceeding with Gemfile: #{gemfile_path}")
252
+
253
+
254
+ FileUtils.cp(File.expand_path(tgt), gemfile_path)
255
+
256
+ puts(File.read(gemfile_path))
257
+
258
+ log("")
259
+
260
+ end
261
+
262
+ def gather_files_and_paths_to_upload(collected_files)
263
+
264
+ log_header("Calculating digests")
265
+
266
+ file_paths = collected_files[:files]
267
+ feature_prefix = collected_files[:feature_prefix]
268
+ workspace_prefix = collected_files[:workspace_prefix]
269
+
270
+ hashes = file_paths.collect { |f| digest(f) }
271
+ hashes << digest(app)
272
+
273
+
274
+ log_header("Negotiating upload")
275
+
276
+ response = http_post("check_hash", {"hashes" => hashes})
277
+
278
+ cache_status = JSON.parse(response)
279
+
280
+ log_header("Gathering files based on negotiation")
281
+
282
+ files = []
283
+ paths = []
284
+ file_paths.each do |file|
285
+ if cache_status[digest(file)]
286
+ #Server already knows about this file. No need to upload it.
287
+ files << digest(file)
288
+ else
289
+ #Upload file
290
+ files << File.new(file)
291
+ end
292
+
293
+ if file.start_with?(feature_prefix)
294
+ prefix = feature_prefix
295
+ else
296
+ prefix = workspace_prefix
297
+ end
298
+ paths << file.sub(prefix, "")
299
+ end
300
+
301
+ if config
302
+ files << File.new(config)
303
+ paths << "config/cucumber.yml"
304
+ end
305
+
306
+ app_file = cache_status[digest(app)] ? digest(app) : File.new(app)
307
+
308
+ return app_file, files, paths
309
+ end
310
+
311
+ def digest(file)
312
+ Digest::SHA256.file(file).hexdigest
313
+ end
314
+
315
+ def unzip_file (file, destination)
316
+ Zip::ZipFile.open(file) { |zip_file|
317
+ zip_file.each { |f|
318
+ f_path=File.join(destination, f.name)
319
+ FileUtils.mkdir_p(File.dirname(f_path))
320
+ zip_file.extract(f, f_path) unless File.exist?(f_path)
321
+ }
322
+ }
323
+ end
324
+
325
+
326
+ def is_android?
327
+ app.end_with? ".apk"
328
+ end
329
+
330
+ def calabash_android_version
331
+ `bundle exec calabash-android version`.strip
332
+ end
333
+
334
+ def is_ios?
335
+ app.end_with? ".ipa"
336
+ end
337
+
338
+ def test_server_path
339
+ require 'digest/md5'
340
+ digest = Digest::MD5.file(app).hexdigest
341
+ File.join("test_servers", "#{digest}_#{calabash_android_version}.apk")
342
+ end
343
+
344
+ def all_files
345
+ dir = workspace
346
+ if features_zip
347
+ dir = Dir.mktmpdir
348
+ unzip_file(features_zip, dir)
349
+ dir = File.join(dir, File::Separator)
350
+ end
351
+
352
+
353
+ files = Dir.glob(File.join("#{dir}features", "**", "*"))
354
+
355
+ if File.directory?("#{dir}playback")
356
+ files += Dir.glob(File.join("#{dir}playback", "*"))
357
+ end
358
+
359
+ if config
360
+ files << config
361
+ end
362
+
363
+
364
+ files += Dir.glob(File.join("#{workspace}vendor", "cache", "*"))
365
+
366
+ if workspace and workspace.strip != ""
367
+ files += Dir.glob("#{workspace}Gemfile")
368
+ files += Dir.glob("#{workspace}Gemfile.lock")
369
+ end
370
+
371
+ if is_android?
372
+ files << test_server_path
373
+ end
374
+ p files
375
+ {:feature_prefix => dir, :workspace_prefix => workspace, :files => files.find_all { |file_or_dir| File.file? file_or_dir }}
376
+ end
377
+
378
+ def http_post(address, args, &block)
379
+ if block_given?
380
+ response = RestClient.post "#{host}/#{address}", args, {:content_type => "multipart/form-data"} do |response, request, result, &other_block|
381
+ block.call(response, request, result, &other_block)
382
+ end
383
+ else
384
+ response = RestClient.post "#{host}/#{address}", args
385
+ end
386
+
387
+ response.body
388
+ end
389
+
390
+ def is_windows?
391
+ (RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/)
392
+ end
393
+
394
+ def is_macosx?
395
+ (RbConfig::CONFIG['host_os'] =~ /darwin/)
396
+ end
397
+
398
+ def validate_ipa(ipa)
399
+ result = false
400
+ dir = Dir.mktmpdir #do |dir|
401
+
402
+ unzip_file(ipa, dir)
403
+ unless File.directory?("#{dir}/Payload") #macos only
404
+ raise "Unzipping #{ipa} to #{dir} failed: Did not find a Payload directory (invalid .ipa)."
405
+ end
406
+ app_dir = Dir.foreach("#{dir}/Payload").find { |d| /\.app$/.match(d) }
407
+ app = app_dir.split(".")[0]
408
+ res = `otool "#{File.expand_path(dir)}/Payload/#{app_dir}/#{app}" -o 2> /dev/null | grep CalabashServer`
409
+
410
+ if /CalabashServer/.match(res)
411
+ puts "ipa: #{ipa} *contains* calabash.framework"
412
+ result = :calabash
413
+ end
414
+
415
+ unless result
416
+ res = `otool "#{File.expand_path(dir)}/Payload/#{app_dir}/#{app}" -o 2> /dev/null | grep FrankServer`
417
+ if /FrankServer/.match(res)
418
+ puts "ipa: #{ipa} *contains* FrankServer"
419
+ result = :frank
420
+ else
421
+ puts "ipa: #{ipa} *does not contain* calabash.framework"
422
+ result = false
423
+ end
424
+ end
425
+
426
+ result
427
+ end
428
+
429
+
430
+ def log(message)
431
+ puts "#{Time.now } #{message}"
432
+ $stdout.flush
433
+ end
434
+
435
+ def log_header(message)
436
+ if is_windows?
437
+ puts "\n### #{message} ###"
438
+ else
439
+ puts "\n\e[#{35}m### #{message} ###\e[0m"
440
+ end
441
+ end
442
+
443
+
444
+ def verify_app_and_extract_test_server
445
+ server = nil
446
+
447
+ unless File.exist?(app)
448
+ raise "No such file: #{app}"
449
+ end
450
+ unless (/\.(apk|ipa)$/ =~ app)
451
+ raise "<APP> should be either an ipa or apk file."
452
+ end
453
+ if is_ios? and is_macosx? and not skip_check
454
+ log_header("Checking ipa for linking with Calabash or Frank")
455
+ server = validate_ipa(app)
456
+ abort_unless(server) do
457
+ puts "The .ipa file does not seem to be linked with Calabash."
458
+ puts "Verify that your app is linked correctly."
459
+ puts "To disable this check run with --skip-check or set Environment Variable CHECK_IPA=0"
460
+ end
461
+ end
462
+ server
463
+ end
464
+
465
+ def abort(&block)
466
+ yield block
467
+ exit 1
468
+ end
469
+
470
+ def abort_unless(condition, &block)
471
+ unless condition
472
+ yield block
473
+ exit 1
474
+ end
475
+ end
476
+
477
+ def log_and_abort(message)
478
+ abort do
479
+ puts message
480
+ end
481
+ end
482
+
483
+
484
+ def verify_dependencies
485
+ if is_android?
486
+ abort_unless(File.exist?(test_server_path)) do
487
+ puts "No test server found. Please run:"
488
+ puts " calabash-android build #{app}"
489
+ end
490
+
491
+ calabash_gem = Dir.glob("vendor/cache/calabash-android-*").first
492
+ abort_unless(calabash_gem) do
493
+ puts "calabash-android was not packaged correct."
494
+ puts "Please tell contact@lesspainful.com about this bug."
495
+ end
496
+ end
497
+ end
498
+
499
+
500
+ def parse_and_set_config_and_profile
501
+ config_path = options[:config]
502
+ if config_path
503
+ config_path = File.expand_path(config_path)
504
+ unless File.exist?(config_path)
505
+ raise "Config file does not exist #{config_path}"
506
+ end
507
+
508
+
509
+ begin
510
+ config_yml = YAML.load_file(config_path)
511
+ rescue Exception => e
512
+ puts "Unable to parse #{config_path} as yml. Is this your Cucumber.yml file?"
513
+ raise e
514
+ end
515
+
516
+ if ENV['DEBUG']
517
+ puts "Parsed Cucumber config as:"
518
+ puts config_yml.inspect
519
+ end
520
+
521
+ profile = options[:profile]
522
+ unless profile
523
+ raise "Config file provided but no profile selected."
524
+ else
525
+ unless config_yml[profile]
526
+ raise "Config file provided did not contain profile #{profile}."
527
+ else
528
+ puts "Using profile #{profile}..."
529
+ self.profile = profile
530
+ end
531
+ end
532
+ else
533
+ if options[:profile]
534
+ raise "Profile selected but no config file provided."
535
+ end
536
+ end
537
+
538
+ self.config = config_path
539
+ end
540
+
541
+ end
542
+
543
+
544
+ end
545
+ end
546
+
547
+
data/lib/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module LessPainful
2
- VERSION = "0.9.14"
2
+ VERSION = "0.10.0"
3
3
  end
@@ -0,0 +1 @@
1
+ a: 42
@@ -0,0 +1,62 @@
1
+ require 'test/unit'
2
+ require 'thor'
3
+ require 'fileutils'
4
+ require 'lesspainful/cli'
5
+
6
+ class TestParser < Test::Unit::TestCase
7
+ def test_should_raise_if_no_app_or_api_key_is_given
8
+ script = LessPainful::CLI.new([])
9
+ assert_raise Thor::InvocationError do
10
+ script.invoke(:submit)
11
+ end
12
+
13
+ script = LessPainful::CLI.new(["test/ipa/2012 Olympics_cal.ipa"])
14
+ assert_raise Thor::InvocationError do
15
+ script.invoke(:submit)
16
+ end
17
+
18
+
19
+ end
20
+
21
+ def test_should_raise_if_app_is_not_file_ipa_or_apk
22
+ script = LessPainful::CLI.new(["test/ipa/NONE_EXIST_2012 Olympics_cal.ipa","JIFZCTPZJJXJLEKMMYRY","."])
23
+ assert_raise RuntimeError do
24
+ script.invoke(:submit)
25
+ end
26
+
27
+ script = LessPainful::CLI.new(["test/ipa/features.zip","JIFZCTPZJJXJLEKMMYRY","."])
28
+
29
+ assert_raise RuntimeError do
30
+ script.invoke(:submit)
31
+ end
32
+ end
33
+
34
+ def test_should_parse_all_configuration_options
35
+ FileUtils.rm_f(File.join("test","vendor"))
36
+ FileUtils.rm_f(File.join("test","Gemfile"))
37
+ FileUtils.rm_f(File.join("test","Gemfile.lock"))
38
+
39
+ config_options = {
40
+ :host => "http://localhost:8080",
41
+ :workspace => "test",
42
+ :features => "test/ipa/features.zip",
43
+ :config => "test/config/cucumber.yml",
44
+ :profile => "a",
45
+ "skip-check" => false,
46
+ "reset-between-scenarios" => false,
47
+ "dry-run" => true
48
+ }
49
+ script = LessPainful::CLI.new(["test/ipa/2012 Olympics_cal.ipa","JIFZCTPZJJXJLEKMMYRY"],config_options)
50
+
51
+
52
+ script.invoke(:submit)
53
+
54
+ FileUtils.rm_f(File.join("test","vendor","cache"))
55
+ FileUtils.rm_f(File.join("test","Gemfile"))
56
+ FileUtils.rm_f(File.join("test","Gemfile.lock"))
57
+
58
+
59
+
60
+ end
61
+
62
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lesspainful
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.14
4
+ version: 0.10.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-17 00:00:00.000000000 Z
12
+ date: 2013-03-10 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: thor
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - '='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.17.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - '='
28
+ - !ruby/object:Gem::Version
29
+ version: 0.17.0
14
30
  - !ruby/object:Gem::Dependency
15
31
  name: bundler
16
32
  requirement: !ruby/object:Gem::Requirement
@@ -48,33 +64,33 @@ dependencies:
48
64
  requirement: !ruby/object:Gem::Requirement
49
65
  none: false
50
66
  requirements:
51
- - - ! '>='
67
+ - - '='
52
68
  - !ruby/object:Gem::Version
53
- version: '0'
69
+ version: 0.9.9
54
70
  type: :runtime
55
71
  prerelease: false
56
72
  version_requirements: !ruby/object:Gem::Requirement
57
73
  none: false
58
74
  requirements:
59
- - - ! '>='
75
+ - - '='
60
76
  - !ruby/object:Gem::Version
61
- version: '0'
77
+ version: 0.9.9
62
78
  - !ruby/object:Gem::Dependency
63
79
  name: rest-client
64
80
  requirement: !ruby/object:Gem::Requirement
65
81
  none: false
66
82
  requirements:
67
- - - ! '>='
83
+ - - '='
68
84
  - !ruby/object:Gem::Version
69
- version: '0'
85
+ version: 1.6.7
70
86
  type: :runtime
71
87
  prerelease: false
72
88
  version_requirements: !ruby/object:Gem::Requirement
73
89
  none: false
74
90
  requirements:
75
- - - ! '>='
91
+ - - '='
76
92
  - !ruby/object:Gem::Version
77
- version: '0'
93
+ version: 1.6.7
78
94
  description: www.lesspainful.com lets you run calabash tests for native and hybrid
79
95
  apps on physical devices
80
96
  email:
@@ -93,7 +109,9 @@ files:
93
109
  - lib/GemfileAndroid
94
110
  - lib/GemfileIOS
95
111
  - lib/GemfileIOSFrank
112
+ - lib/lesspainful/cli.rb
96
113
  - lib/version.rb
114
+ - test/config/cucumber.yml
97
115
  - test/ipa/.irbrc
98
116
  - test/ipa/2012 Olympics_cal.ipa
99
117
  - test/ipa/2012 Olympics_no_cal.ipa
@@ -108,6 +126,7 @@ files:
108
126
  - test/ipa/features/support/launch.rb
109
127
  - test/ipa/irb_ios4.sh
110
128
  - test/ipa/irb_ios5.sh
129
+ - test/test_parser.rb
111
130
  homepage: http://www.lesspainful.com
112
131
  licenses: []
113
132
  post_install_message:
@@ -132,5 +151,11 @@ rubygems_version: 1.8.23
132
151
  signing_key:
133
152
  specification_version: 3
134
153
  summary: Client for uploading calabash test to www.lesspainful.com
135
- test_files: []
154
+ test_files:
155
+ - test/ipa/features/step_definitions/calabash_steps.rb
156
+ - test/ipa/features/step_definitions/my_first_steps.rb
157
+ - test/ipa/features/support/env.rb
158
+ - test/ipa/features/support/hooks.rb
159
+ - test/ipa/features/support/launch.rb
160
+ - test/test_parser.rb
136
161
  has_rdoc: