tddium 1.25.5

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