tddium 1.25.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +15 -0
  2. data/bin/tddium +29 -0
  3. data/lib/tddium.rb +19 -0
  4. data/lib/tddium/agent.rb +3 -0
  5. data/lib/tddium/agent/tddium.rb +122 -0
  6. data/lib/tddium/cli.rb +26 -0
  7. data/lib/tddium/cli/api.rb +319 -0
  8. data/lib/tddium/cli/commands/account.rb +49 -0
  9. data/lib/tddium/cli/commands/activate.rb +14 -0
  10. data/lib/tddium/cli/commands/config.rb +55 -0
  11. data/lib/tddium/cli/commands/describe.rb +96 -0
  12. data/lib/tddium/cli/commands/find_failing.rb +62 -0
  13. data/lib/tddium/cli/commands/github.rb +53 -0
  14. data/lib/tddium/cli/commands/heroku.rb +15 -0
  15. data/lib/tddium/cli/commands/hg.rb +48 -0
  16. data/lib/tddium/cli/commands/keys.rb +83 -0
  17. data/lib/tddium/cli/commands/login.rb +37 -0
  18. data/lib/tddium/cli/commands/logout.rb +14 -0
  19. data/lib/tddium/cli/commands/password.rb +26 -0
  20. data/lib/tddium/cli/commands/rerun.rb +50 -0
  21. data/lib/tddium/cli/commands/server.rb +22 -0
  22. data/lib/tddium/cli/commands/spec.rb +306 -0
  23. data/lib/tddium/cli/commands/status.rb +107 -0
  24. data/lib/tddium/cli/commands/stop.rb +19 -0
  25. data/lib/tddium/cli/commands/suite.rb +110 -0
  26. data/lib/tddium/cli/commands/web.rb +22 -0
  27. data/lib/tddium/cli/config.rb +245 -0
  28. data/lib/tddium/cli/params_helper.rb +36 -0
  29. data/lib/tddium/cli/prompt.rb +128 -0
  30. data/lib/tddium/cli/show.rb +122 -0
  31. data/lib/tddium/cli/suite.rb +179 -0
  32. data/lib/tddium/cli/tddium.rb +153 -0
  33. data/lib/tddium/cli/text_helper.rb +16 -0
  34. data/lib/tddium/cli/timeformat.rb +21 -0
  35. data/lib/tddium/cli/util.rb +132 -0
  36. data/lib/tddium/constant.rb +509 -0
  37. data/lib/tddium/scm.rb +8 -0
  38. data/lib/tddium/scm/git.rb +188 -0
  39. data/lib/tddium/scm/git_log_parser.rb +67 -0
  40. data/lib/tddium/scm/hg.rb +160 -0
  41. data/lib/tddium/scm/hg_log_parser.rb +66 -0
  42. data/lib/tddium/scm/scm.rb +20 -0
  43. data/lib/tddium/script.rb +12 -0
  44. data/lib/tddium/script/git-remote-hg +1258 -0
  45. data/lib/tddium/ssh.rb +66 -0
  46. data/lib/tddium/util.rb +35 -0
  47. data/lib/tddium/version.rb +5 -0
  48. metadata +394 -0
@@ -0,0 +1,37 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ module Tddium
4
+ class TddiumCli < Thor
5
+ desc "login [[TOKEN]]", "Log in using your email address or token (see: https://ci.solanolabs.com/user_settings/token)"
6
+ method_option :email, :type => :string, :default => nil
7
+ method_option :password, :type => :string, :default => nil
8
+ method_option :ssh_key_file, :type => :string, :default => nil
9
+ def login(*args)
10
+ user_details = tddium_setup({:login => false, :scm => false})
11
+
12
+ login_options = options.dup
13
+
14
+ if args.first && args.first =~ /@/
15
+ login_options[:email] ||= args.first
16
+ elsif args.first
17
+ # assume cli token
18
+ login_options[:cli_token] = args.first
19
+ end
20
+
21
+ if user_details then
22
+ say Text::Process::ALREADY_LOGGED_IN
23
+ elsif user = @tddium_api.login_user(:params => @tddium_api.get_user_credentials(login_options), :show_error => true)
24
+ say Text::Process::LOGGED_IN_SUCCESSFULLY
25
+ if @scm.repo? then
26
+ @api_config.populate_branches(@tddium_api.current_branch)
27
+ end
28
+ @api_config.write_config
29
+ else
30
+ exit_failure
31
+ end
32
+ if prompt_missing_ssh_key then
33
+ say Text::Process::NEXT_STEPS
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,14 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ module Tddium
4
+ class TddiumCli < Thor
5
+ desc "logout", "Log out of tddium"
6
+ def logout
7
+ tddium_setup({:login => false, :scm => false})
8
+
9
+ @api_config.logout
10
+
11
+ say Text::Process::LOGGED_OUT_SUCCESSFULLY
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,26 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ module Tddium
4
+ class TddiumCli < Thor
5
+ desc "password", "Change password"
6
+ map "passwd" => :password
7
+ def password
8
+ user_details = tddium_setup({:scm => false})
9
+
10
+ params = {}
11
+ params[:current_password] = HighLine.ask(Text::Prompt::CURRENT_PASSWORD) { |q| q.echo = "*" }
12
+ params[:password] = HighLine.ask(Text::Prompt::NEW_PASSWORD) { |q| q.echo = "*" }
13
+ params[:password_confirmation] = HighLine.ask(Text::Prompt::PASSWORD_CONFIRMATION) { |q| q.echo = "*" }
14
+
15
+ begin
16
+ user_id = user_details["id"]
17
+ @tddium_api.update_user(user_id, {:user => params})
18
+ say Text::Process::PASSWORD_CHANGED
19
+ rescue TddiumClient::Error::API => e
20
+ exit_failure Text::Error::PASSWORD_ERROR % e.explanation
21
+ rescue TddiumClient::Error::Base => e
22
+ exit_failure e.message
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,50 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ module Tddium
4
+ class TddiumCli < Thor
5
+ desc "rerun SESSION", "Rerun failing tests from a session"
6
+ method_option :account, :type => :string, :default => nil,
7
+ :aliases => %w(--org --organization)
8
+ method_option :max_parallelism, :type => :numeric, :default => nil
9
+ method_option :no_op, :type=>:boolean, :default => false, :aliases => ["-n"]
10
+ method_option :force, :type=>:boolean, :default => false
11
+ def rerun(session_id=nil)
12
+ tddium_setup({:repo => false})
13
+
14
+ session_id ||= session_id_for_current_suite
15
+
16
+ begin
17
+ result = @tddium_api.query_session(session_id)
18
+ rescue TddiumClient::Error::API => e
19
+ exit_failure Text::Error::NO_SESSION_EXISTS
20
+ end
21
+
22
+ tests = result['session']['tests']
23
+ tests = tests.select{ |t| ['failed', 'error'].include?(t['status']) }
24
+ tests = tests.map{ |t| t['test_name'] }
25
+
26
+ cmd = "tddium run"
27
+ cmd += " --max-parallelism=#{options[:max_parallelism]}" if options[:max_parallelism]
28
+ cmd += " --org=#{options[:account]}" if options[:account]
29
+ cmd += " --force" if options[:force]
30
+ cmd += " #{tests.join(" ")}"
31
+
32
+ say cmd
33
+ Kernel.exec(cmd) if !options[:no_op]
34
+ end
35
+
36
+ private
37
+
38
+ def session_id_for_current_suite
39
+ return unless suite_for_current_branch?
40
+ suite_params = {
41
+ :suite_id => @tddium_api.current_suite_id,
42
+ :active => false,
43
+ :limit => 1,
44
+ :origin => %w(ci cli)
45
+ }
46
+ session = @tddium_api.get_sessions(suite_params)
47
+ session[0]["id"]
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,22 @@
1
+ # Copyright (c) 2014 Solano Labs All Rights Reserved
2
+
3
+ module Tddium
4
+ class TddiumCli < Thor
5
+ desc 'server', "displays the saved connection info"
6
+
7
+ def server
8
+ self.class.display
9
+ end
10
+
11
+ desc 'server:set --host HOST [--port PORT] [--proto PROTO] [--insecure]', "saves connection info"
12
+
13
+ method_option :host, type: :string, required: true
14
+ method_option :port, type: :numeric, default: 443
15
+ method_option :proto, type: :string, default: 'https'
16
+ method_option :insecure, type: :boolean, default: false
17
+
18
+ define_method 'server:set' do
19
+ self.class.write_params options
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,306 @@
1
+ # Copyright (c) 2011-2015 Solano Labs All Rights Reserved
2
+ require 'digest'
3
+
4
+ module Tddium
5
+ class TddiumCli < Thor
6
+ map "cucumber" => :spec
7
+ map "test" => :spec
8
+ map "run" => :spec
9
+ desc "run [PATTERN]", "Run the test suite, or tests that match PATTERN"
10
+ method_option :account, :type => :string, :default => nil,
11
+ :aliases => %w(--org --organization)
12
+ method_option :tag, :type => :string, :default => nil
13
+ method_option :user_data_file, :type => :string, :default => nil
14
+ method_option :max_parallelism, :type => :numeric, :default => nil
15
+ method_option :test_pattern, :type => :string, :default => nil
16
+ method_option :test_exclude_pattern, :type => :string, :default => nil
17
+ method_option :force, :type => :boolean, :default => false
18
+ method_option :quiet, :type => :boolean, :default => false
19
+ method_option :machine, :type => :boolean, :default => false
20
+ method_option :session_id, :type => :numeric, :default => nil
21
+ method_option :tool, :type => :hash, :default => {}
22
+ def spec(*pattern)
23
+ machine_data = {}
24
+
25
+ tddium_setup({:repo => true})
26
+
27
+ suite_auto_configure unless options[:machine]
28
+
29
+ exit_failure unless suite_for_current_branch?
30
+
31
+ if !options[:machine] && @tddium_api.get_keys.empty? then
32
+ warn(Text::Warning::NO_SSH_KEY)
33
+ end
34
+
35
+ if @scm.changes?(options) then
36
+ exit_failure(Text::Error::SCM_CHANGES_NOT_COMMITTED) if !options[:force]
37
+ warn(Text::Warning::SCM_CHANGES_NOT_COMMITTED)
38
+ end
39
+
40
+ test_execution_params = {}
41
+
42
+ if user_data_file_path = options[:user_data_file] then
43
+ if File.exists?(user_data_file_path) then
44
+ user_data = File.open(user_data_file_path) { |file| file.read }
45
+ test_execution_params[:user_data_text] = Base64.encode64(user_data)
46
+ test_execution_params[:user_data_filename] = File.basename(user_data_file_path)
47
+ say Text::Process::USING_SPEC_OPTION[:user_data_file] % user_data_file_path
48
+ else
49
+ exit_failure Text::Error::NO_USER_DATA_FILE % user_data_file_path
50
+ end
51
+ end
52
+
53
+ if max_parallelism = options[:max_parallelism] then
54
+ test_execution_params[:max_parallelism] = max_parallelism
55
+ say Text::Process::USING_SPEC_OPTION[:max_parallelism] % max_parallelism
56
+ end
57
+
58
+ test_execution_params[:tag] = options[:tag] if options[:tag]
59
+ test_pattern = nil
60
+
61
+ if pattern.is_a?(Array) && pattern.size > 0 then
62
+ test_pattern = pattern.join(",")
63
+ end
64
+
65
+ test_pattern ||= options[:test_pattern]
66
+ if test_pattern then
67
+ say Text::Process::USING_SPEC_OPTION[:test_pattern] % test_pattern
68
+ end
69
+
70
+ test_exclude_pattern ||= options[:test_exclude_pattern]
71
+ if test_exclude_pattern then
72
+ say Text::Process::USING_SPEC_OPTION[:test_exclude_pattern] % test_exclude_pattern
73
+ end
74
+
75
+ tries = 0
76
+ while tries < Default::SCM_READY_TRIES do
77
+ # Call the API to get the suite and its tests
78
+ suite_details = @tddium_api.get_suite_by_id(@tddium_api.current_suite_id,
79
+ :session_id => options[:session_id])
80
+
81
+ if suite_details["repoman_current"] == true
82
+ break
83
+ else
84
+ @tddium_api.demand_repoman_account(suite_details["account_id"])
85
+
86
+ say Text::Process::SCM_REPO_WAIT
87
+ sleep @api_config.scm_ready_sleep
88
+ end
89
+
90
+ tries += 1
91
+ end
92
+ exit_failure Text::Error::SCM_REPO_NOT_READY unless suite_details["repoman_current"]
93
+
94
+ update_suite_parameters!(suite_details, options[:session_id])
95
+
96
+ start_time = Time.now
97
+
98
+ new_session_params = {
99
+ :commits_encoded => read_and_encode_latest_commits,
100
+ :cache_control_encoded => read_and_encode_cache_control,
101
+ :cache_save_paths_encoded => read_and_encode_cache_save_paths,
102
+ :raw_config_file => read_and_encode_config_file
103
+ }
104
+
105
+ # Create a session
106
+ # or use an already-created session
107
+ #
108
+ session_id = options[:session_id]
109
+ session_data = if session_id && session_id > 0
110
+ @tddium_api.update_session(session_id, new_session_params)
111
+ else
112
+ @tddium_api.create_session(@tddium_api.current_suite_id, new_session_params)
113
+ end
114
+
115
+ session_data ||= {}
116
+ session_id ||= session_data["id"]
117
+
118
+ push_options = {}
119
+ if options[:machine]
120
+ push_options[:use_private_uri] = true
121
+ end
122
+
123
+ if !@scm.push_latest(session_data, suite_details, push_options) then
124
+ exit_failure Text::Error::SCM_PUSH_FAILED
125
+ end
126
+
127
+ machine_data[:session_id] = session_id
128
+
129
+ # Register the tests
130
+ @tddium_api.register_session(session_id, @tddium_api.current_suite_id, test_pattern, test_exclude_pattern)
131
+
132
+ # Start the tests
133
+ start_test_executions = @tddium_api.start_session(session_id, test_execution_params)
134
+ num_tests_started = start_test_executions["started"].to_i
135
+
136
+ say Text::Process::STARTING_TEST % num_tests_started.to_s
137
+
138
+ tests_finished = false
139
+ finished_tests = {}
140
+ latest_message = -100000
141
+ test_statuses = Hash.new(0)
142
+ session_status = nil
143
+ messages = nil
144
+ last_finish_timestamp = nil
145
+
146
+ report = start_test_executions["report"]
147
+
148
+ # In CI mode, just hang up here. The session will continue running.
149
+ if options[:machine] then
150
+ say Text::Process::BUILD_CONTINUES
151
+ return
152
+ end
153
+
154
+ say ""
155
+ say Text::Process::CHECK_TEST_REPORT % report
156
+ say Text::Process::TERMINATE_INSTRUCTION
157
+ say ""
158
+
159
+ # Catch Ctrl-C to interrupt the test
160
+ Signal.trap(:INT) do
161
+ say Text::Process::INTERRUPT
162
+ say Text::Process::CHECK_TEST_STATUS
163
+ tests_finished = true
164
+ session_status = "interrupted"
165
+ end
166
+
167
+ while !tests_finished do
168
+ current_test_executions = @tddium_api.poll_session(session_id)
169
+ session_status = current_test_executions['session_status']
170
+
171
+ messages, latest_message = update_messages(latest_message,
172
+ finished_tests,
173
+ messages,
174
+ current_test_executions["messages"])
175
+
176
+ # Print out the progress of running tests
177
+ current_test_executions["tests"].each do |test_name, result_params|
178
+ if finished_tests.size == 0 && result_params["finished"] then
179
+ say ""
180
+ say Text::Process::CHECK_TEST_REPORT % report
181
+ say Text::Process::TERMINATE_INSTRUCTION
182
+ say ""
183
+ end
184
+ if result_params["finished"] && !finished_tests[test_name]
185
+ test_status = result_params["status"]
186
+ message = case test_status
187
+ when "passed" then [".", :green, false]
188
+ when "failed" then ["F", :red, false]
189
+ when "error" then ["E", nil, false]
190
+ when "pending" then ["*", :yellow, false]
191
+ when "skipped" then [".", :yellow, false]
192
+ else [".", nil, false]
193
+ end
194
+ finished_tests[test_name] = test_status
195
+ last_finish_timestamp = Time.now
196
+ test_statuses[test_status] += 1
197
+ say *message
198
+ end
199
+ end
200
+
201
+ # XXX time out if all tests are done and the session isn't done.
202
+ if current_test_executions['session_done'] ||
203
+ (finished_tests.size >= num_tests_started && (Time.now - last_finish_timestamp) > Default::TEST_FINISH_TIMEOUT)
204
+ tests_finished = true
205
+ end
206
+
207
+ sleep(Default::SLEEP_TIME_BETWEEN_POLLS) if !tests_finished
208
+ end
209
+
210
+ display_alerts(messages, 'error', Text::Status::SPEC_ERRORS)
211
+
212
+ # Print out the result
213
+ say ""
214
+ say Text::Process::RUN_TDDIUM_WEB
215
+ say ""
216
+ say Text::Process::FINISHED_TEST % (Time.now - start_time)
217
+ say "#{finished_tests.size} tests, #{test_statuses["failed"]} failures, #{test_statuses["error"]} errors, #{test_statuses["pending"]} pending, #{test_statuses["skipped"]} skipped"
218
+
219
+ if test_statuses['failed'] > 0
220
+ say ""
221
+ say Text::Process::FAILED_TESTS
222
+ finished_tests.each do |name, status|
223
+ next if status != 'failed'
224
+ say " - #{name}"
225
+ end
226
+ say ""
227
+ end
228
+
229
+ say Text::Process::SUMMARY_STATUS % session_status
230
+ say ""
231
+
232
+ suite = suite_details.merge({"id" => @tddium_api.current_suite_id})
233
+ @api_config.set_suite(suite)
234
+ @api_config.write_config
235
+
236
+ exit_failure if session_status != 'passed'
237
+ rescue TddiumClient::Error::API => e
238
+ exit_failure "Failed due to error: #{e.explanation}"
239
+ rescue TddiumClient::Error::Base => e
240
+ exit_failure "Failed due to error: #{e.message}"
241
+ rescue RuntimeError => e
242
+ exit_failure "Failed due to internal error: #{e.inspect} #{e.backtrace}"
243
+ end
244
+
245
+ private
246
+
247
+ def update_messages(latest_message, finished_tests, messages, current, display=true)
248
+ messages = current
249
+ if !options[:machine] && finished_tests.size == 0 && messages
250
+ messages.each do |m|
251
+ seqno = m["seqno"].to_i
252
+ if seqno > latest_message
253
+ if !options[:quiet] || m["level"] == 'error' then
254
+ display_message(m)
255
+ end
256
+ latest_message = seqno
257
+ end
258
+ end
259
+ end
260
+ [messages, latest_message]
261
+ end
262
+
263
+ def read_and_encode_latest_commits
264
+ commits = @scm.commits
265
+ commits_packed = Tddium.message_pack(commits)
266
+ commits_encoded = Base64.encode64(commits_packed)
267
+ commits_encoded
268
+ end
269
+
270
+ def cache_control_config
271
+ @repo_config['cache'] || {}
272
+ end
273
+
274
+ def read_and_encode_cache_control
275
+ cache_key_paths = cache_control_config['key_paths'] || cache_control_config[:key_paths]
276
+ cache_key_paths ||= ["Gemfile", "Gemfile.lock", "requirements.txt", "packages.json", "package.json"]
277
+ cache_key_paths.reject!{|x| x =~ /(solano|tddium).yml$/}
278
+ cache_control_data = {}
279
+ cache_key_paths.each do |p|
280
+ if File.exists?(p) then
281
+ cache_control_data[p] = Digest::SHA1.file(p).to_s
282
+ end
283
+ end
284
+
285
+ msgpack = Tddium.message_pack(cache_control_data)
286
+ cache_control_encoded = Base64.encode64(msgpack)
287
+ end
288
+
289
+ def read_and_encode_cache_save_paths
290
+ cache_save_paths = cache_control_config['save_paths'] || cache_control_config[:save_paths]
291
+ msgpack = Tddium.message_pack(cache_save_paths)
292
+ cache_save_paths_encoded = Base64.encode64(msgpack)
293
+ end
294
+
295
+ def read_and_encode_config_file
296
+ fn = @repo_config.config_filename
297
+ root = @scm.root
298
+
299
+ if fn && root && File.exists?(File.join(root, fn)) then
300
+ Base64.encode64(File.read(File.join(root, fn)))
301
+ else
302
+ nil
303
+ end
304
+ end
305
+ end
306
+ end