lesspainful 0.9.14 → 0.10.0

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.
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: