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,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MWRjMjRmMDgxYWRhOWY0YzZmMWM2ZDY4YmQzOTkxNDhmZmQwMDQzYQ==
5
+ data.tar.gz: !binary |-
6
+ Y2I3OTRkYTIyNjU2NGRlNjBjNjY5M2VlOThhZjRhYTFiMDlmZTM3Mg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ YzJhZmQwYzA0NTI4M2NmNmI4YTczZTc0ZjQ0NDJhNTc5MzljZTViM2UzMmU3
10
+ OWM5ZTYzNWFjNTQwM2UzYWI3MDY2NmNlNjIxN2ExYzVhNzJmNmZkZTQzYmQy
11
+ NjhkN2I0OTYwZjFlMDE1NWM2YjZmNmE5Y2M4OTg2ZDkyYWU2YWQ=
12
+ data.tar.gz: !binary |-
13
+ YzAyYmEzODdkMTI5NzMzOTVmNzk0MWNjM2E2MzE2NDY4NmEzMWRiNmFkOWZl
14
+ YjhiODU0OWI3MmM0NGZkNGRiNGY2NWM2ZjU5NTExYjM5OTY1NmNlNzZjNDU2
15
+ OWUzMTk2MjYzMTJjMDU4YTQzNDk4NDFiMmE4MDgwMmViYTEyZGE=
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
4
+ #
5
+
6
+ require 'yaml'
7
+
8
+ if RUBY_VERSION < "2.1.0" && defined?(YAML::ENGINE) then
9
+ YAML::ENGINE.yamler = 'syck'
10
+ end
11
+
12
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
13
+
14
+ if ENV['COVERAGE']
15
+ begin
16
+ require 'rubygems'
17
+ require 'simplecov'
18
+ SimpleCov.root(ENV['COVERAGE_ROOT'])
19
+ SimpleCov.start do
20
+ add_group "Commands", "lib/cli/tddium/commands"
21
+ end
22
+ rescue => e
23
+ STDERR.puts "Can't load simplecov: #{e.inspect} #{e.backtrace}"
24
+ end
25
+ end
26
+
27
+ require "tddium"
28
+ require "tddium/cli"
29
+ Tddium::TddiumCli.start
@@ -0,0 +1,19 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ require "tddium/constant"
4
+ require "tddium/version"
5
+
6
+ require "tddium/util"
7
+
8
+ require "tddium/scm"
9
+ require "tddium/ssh"
10
+
11
+ module Tddium
12
+ class TddiumError < Exception
13
+ attr_reader :message
14
+
15
+ def initialize(message)
16
+ @message = message
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ require 'tddium/agent/tddium'
@@ -0,0 +1,122 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ require 'json'
4
+ require 'fileutils'
5
+ require 'securerandom'
6
+ require 'tddium_client'
7
+
8
+ module Tddium
9
+ class BuildAgent
10
+ MAXIMUM_ATTACHMENT_SIZE = 16*1024*1024
11
+
12
+ def initialize
13
+ end
14
+
15
+ # @return Boolean indicating whether or not we are running inside Tddium
16
+ def tddium?
17
+ return ENV.member?('TDDIUM')
18
+ end
19
+
20
+ # @return The current worker thread ID
21
+ # @note Id is not unique across workers; there is no accessible GUID
22
+ def thread_id
23
+ return fetch_id('TDDIUM_TID')
24
+ end
25
+
26
+ # @return Current session ID
27
+ def session_id
28
+ return fetch_id('TDDIUM_SESSION_ID')
29
+ end
30
+
31
+ # @return Per-execution unique ID of currently running test
32
+ def test_exec_id
33
+ return fetch_id('TDDIUM_TEST_EXEC_ID')
34
+ end
35
+
36
+ # @return Tddium environment (batch, interactive, etc.)
37
+ def environment
38
+ env = ENV['TDDIUM_MODE'] || 'none'
39
+ return env
40
+ end
41
+
42
+ # Status of build
43
+ # @param which :current or :last
44
+ # @return 'passed', 'failed', 'error', or 'unknown'
45
+ def build_status(which=:current)
46
+ status = 'unknown'
47
+ case which
48
+ when :current
49
+ status = ENV['TDDIUM_BUILD_STATUS']
50
+ when :last
51
+ status = ENV['TDDIUM_LAST_BUILD_STATUS']
52
+ end
53
+ status ||= 'unknown'
54
+ return status
55
+ end
56
+
57
+ # BOTCH: must be SCM agnostic
58
+ def current_branch
59
+ cmd = "cd #{ENV['TDDIUM_REPO_ROOT']} && git symbolic-ref HEAD"
60
+ `#{cmd}`.gsub("\n", "").split("/")[2..-1].join("/")
61
+ end
62
+
63
+ # Attach a blob to the session -- excessive storage use is billable
64
+ # @param data blob that is convertible into a string
65
+ # @param metadata hash of metadata options
66
+ # @note See attach_file for description of options
67
+ def attach(data, metadata)
68
+ if data.size > MAXIMUM_ATTACHMENT_SIZE then
69
+ raise TddiumError.new("Data are too large to attach to session")
70
+ end
71
+
72
+ if !metadata.member?(:name) then
73
+ guid = SecureRandom.hex(4)
74
+ metadata[:name] = "user.#{guid}.dat"
75
+ end
76
+
77
+ guid = SecureRandom.hex(8)
78
+ temp_path = File.join(ENV['HOME'], 'tmp', "attach-#{guid}.dat")
79
+ File.open(temp_path, File::CREAT|File::TRUNC|File::RDWR, 0600) do |file|
80
+ file.write(data)
81
+ attach_file(temp_path, metadata)
82
+ end
83
+ end
84
+
85
+ # Attach a blob to the session -- excessive storage use is billable
86
+ # @param data blob that is convertible into a string
87
+ # @param [Hash] metadata hash of metadata options
88
+ # @option metadata [String] :name Override name of attachment
89
+ # @option metadata [String] :exec_id Attach to named test execution
90
+ def attach_file(path, metadata={})
91
+ if !File.exists?(path) then
92
+ raise Errno::ENOENT.new(path)
93
+ end
94
+ if File.size(path) > MAXIMUM_ATTACHMENT_SIZE then
95
+ raise TddiumError.new("Data are too large to attach to session")
96
+ end
97
+ name = metadata[:name] || File.basename(path)
98
+ attach_path = attachment_path(name, metadata[:exec_id])
99
+ FileUtils.cp(path, attach_path)
100
+ end
101
+
102
+ # FUTURE: convert to call to internal agent API server
103
+ # Unregistered and authenticated files will be ignored
104
+ def attachment_path(name, exec_id=nil)
105
+ path = File.join(ENV['HOME'], 'results', session_id.to_s)
106
+ if exec_id.nil? then
107
+ path = File.join(path, 'session', name)
108
+ else
109
+ path = File.join(path, exec_id.to_s, name)
110
+ end
111
+ return path
112
+ end
113
+
114
+ protected
115
+
116
+ def fetch_id(name)
117
+ return nil unless tddium? && ENV.member?(name)
118
+ id = ENV[name]
119
+ return id.to_i
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,26 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ require 'rubygems'
4
+ require 'thor'
5
+ require 'highline/import'
6
+ require 'json'
7
+ require 'launchy'
8
+ require 'tddium_client'
9
+ require 'shellwords'
10
+ require 'base64'
11
+ require 'msgpack_pure'
12
+ require 'erb'
13
+ require 'github_api'
14
+
15
+ require 'tddium/script'
16
+ require 'tddium/cli/params_helper'
17
+ require 'tddium/cli/tddium'
18
+
19
+ require 'tddium/cli/api'
20
+ require 'tddium/cli/config'
21
+ require 'tddium/cli/suite'
22
+ require 'tddium/cli/prompt'
23
+ require 'tddium/cli/show'
24
+ require 'tddium/cli/util'
25
+ require 'tddium/cli/text_helper'
26
+ require 'tddium/cli/timeformat'
@@ -0,0 +1,319 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ module Tddium
4
+ class TddiumAPI
5
+ include TddiumConstant
6
+
7
+ attr_reader :scm # rspec
8
+
9
+ def initialize(api_config, tddium_client, scm)
10
+ @scm = scm
11
+ @api_config = api_config
12
+ @tddium_client = tddium_client
13
+ end
14
+
15
+ def call_api(method, api_path, params = {}, api_key = nil, show_error = true)
16
+ api_key ||= @api_config.get_api_key unless api_key == false
17
+
18
+ begin
19
+ result = @tddium_client.call_api(method, api_path, params, api_key)
20
+ rescue TddiumClient::Error::UpgradeRequired => e
21
+ abort e.message
22
+ rescue TddiumClient::Error::Base => e
23
+ say e.message.dup if show_error
24
+ raise e
25
+ end
26
+ result
27
+ end
28
+
29
+ def get_single_account_id
30
+ user_details = user_logged_in?(true, false)
31
+ return nil unless user_details
32
+ accounts = user_details["participating_accounts"]
33
+ unless accounts.length == 1
34
+ msg = "You are a member of more than one organization.\n"
35
+ msg << "Please specify the organization you want to operate on with "
36
+ msg << "'org:an_organization_name'.\n"
37
+ accounts.each do |acct|
38
+ msg << " #{acct["account"]}\n"
39
+ end
40
+ raise msg
41
+ end
42
+ accounts.first["account_id"]
43
+ end
44
+
45
+ def get_account_id(acct_name)
46
+ user_details = user_logged_in?(true, false)
47
+ return nil unless user_details
48
+ accts = user_details["participating_accounts"]
49
+ acct = accts.select{|acct| acct["account"] == acct_name}.first
50
+ if acct.nil?
51
+ raise "You aren't a member of organization '%s'." % acct_name
52
+ end
53
+ acct["account_id"]
54
+ end
55
+
56
+ def env_path(scope, key=nil)
57
+ path = ['']
58
+
59
+ case scope
60
+ when "suite"
61
+ path << 'suites'
62
+ path << current_suite_id
63
+ when "repo"
64
+ path << 'repos'
65
+ path << current_repo_id
66
+ when "account", "org"
67
+ path << 'accounts'
68
+ path << get_single_account_id
69
+ when /\Aaccount:/
70
+ path << 'accounts'
71
+ path << get_account_id(scope.sub(/\Aaccount:/, ''))
72
+ when /\Aorg:/
73
+ path << 'accounts'
74
+ path << get_account_id(scope.sub(/\Aorg:/, ''))
75
+ else
76
+ raise "Unrecognized scope. Use 'suite', 'repo', 'org', or 'org:an_organization_name'."
77
+ end
78
+
79
+ path << 'env'
80
+ path << key if key
81
+ path.join('/')
82
+ end
83
+
84
+ def get_config_key(scope, key=nil)
85
+ path = env_path(scope, key)
86
+ call_api(:get, path)
87
+ end
88
+
89
+ def set_config_key(scope, key, value)
90
+ path = env_path(scope)
91
+ call_api(:post, path, :env=>{key=>value})
92
+ end
93
+
94
+ def delete_config_key(scope, key)
95
+ path = env_path(scope, key)
96
+ call_api(:delete, path)
97
+ end
98
+
99
+ def get_user(api_key=nil)
100
+ result = call_api(:get, Api::Path::USERS, {}, api_key, false) rescue nil
101
+ result && result['user']
102
+ end
103
+
104
+ def set_user(params)
105
+ call_api(:post, Api::Path::USERS, {:user => params}, false, false)
106
+ end
107
+
108
+ def update_user(user_id, params, api_key=nil)
109
+ call_api(:put, "#{Api::Path::USERS}/#{user_id}/", params, api_key, false)
110
+ end
111
+
112
+ def get_user_credentials(options = {})
113
+ params = {}
114
+
115
+ if options[:cli_token]
116
+ params[:cli_token] = options[:cli_token]
117
+ elsif options[:invited]
118
+ # prompt for email/invitation and password
119
+ token = options[:invitation_token] || ask(Text::Prompt::INVITATION_TOKEN)
120
+ params[:invitation_token] = token.strip
121
+ params[:password] = options[:password] || HighLine.ask(Text::Prompt::NEW_PASSWORD) { |q| q.echo = "*" }
122
+ else
123
+ say Text::Warning::USE_PASSWORD_TOKEN
124
+ params[:email] = options[:email] || HighLine.ask(Text::Prompt::EMAIL)
125
+ params[:password] = options[:password] || HighLine.ask(Text::Prompt::PASSWORD) { |q| q.echo = "*" }
126
+ end
127
+ params
128
+ end
129
+
130
+ def login_user(options = {})
131
+ # POST (email, password) to /users/sign_in to retrieve an API key
132
+ begin
133
+ user = options[:params]
134
+ login_result = call_api(:post, Api::Path::SIGN_IN, {:user => user}, false, options[:show_error])
135
+ @api_config.set_api_key(login_result["api_key"], user[:email])
136
+ rescue TddiumClient::Error::Base => e
137
+ end
138
+ login_result
139
+ end
140
+
141
+ def user_logged_in?(active = true, message = false)
142
+ global_api_key = @api_config.get_api_key(:global => true)
143
+ repo_api_key = @api_config.get_api_key(:repo => true)
144
+
145
+ if (global_api_key && repo_api_key && global_api_key != repo_api_key)
146
+ say Text::Error::INVALID_CREDENTIALS if message
147
+ return
148
+ end
149
+
150
+ result = repo_api_key || global_api_key
151
+
152
+ if message && result.nil? then
153
+ say Text::Error::NOT_INITIALIZED
154
+ end
155
+
156
+ if result && active
157
+ u = get_user
158
+ if message && u.nil?
159
+ say Text::Error::INVALID_CREDENTIALS
160
+ end
161
+ u
162
+ else
163
+ result
164
+ end
165
+ end
166
+
167
+ def get_memberships(params={})
168
+ result = call_api(:get, Api::Path::MEMBERSHIPS)
169
+ result['account_roles'] || []
170
+ end
171
+
172
+ def set_memberships(params={})
173
+ result = call_api(:post, Api::Path::MEMBERSHIPS, params)
174
+ result['memberships'] || []
175
+ end
176
+
177
+ def delete_memberships(email, params={})
178
+ call_api(:delete, "#{Api::Path::MEMBERSHIPS}/#{email}", params)
179
+ end
180
+
181
+ def get_usage(params={})
182
+ result = call_api(:get, Api::Path::ACCOUNT_USAGE_BY_ACCOUNT)
183
+ result['usage'] || []
184
+ end
185
+
186
+ def get_keys(params={})
187
+ result = call_api(:get, Api::Path::KEYS)
188
+ result['keys']|| []
189
+ end
190
+
191
+ def set_keys(params)
192
+ call_api(:post, Api::Path::KEYS, params)
193
+ end
194
+
195
+ def delete_keys(name, params={})
196
+ call_api(:delete, "#{Api::Path::KEYS}/#{name}", params)
197
+ end
198
+
199
+ def default_branch
200
+ @default_branch ||= @scm.default_branch
201
+ end
202
+
203
+ def current_branch
204
+ @current_branch ||= @scm.current_branch
205
+ end
206
+
207
+ def current_repo_id(options={})
208
+ # api_config.get_branch will query the server if there is no locally cached data
209
+ @api_config.get_branch(current_branch, 'repo_id', options)
210
+ end
211
+
212
+ def current_suite_id(options={})
213
+ # api_config.get_branch will query the server if there is no locally cached data
214
+ @api_config.get_branch(current_branch, 'id', options)
215
+ end
216
+
217
+ def current_suite_options(options={})
218
+ @api_config.get_branch(current_branch, 'options', options)
219
+ end
220
+
221
+ def default_suite_id(options={})
222
+ # api_config.get_branch will query the server if there is no locally cached data
223
+ @api_config.get_branch(default_branch, 'id', options)
224
+ end
225
+
226
+ def default_suite_options(options={})
227
+ @api_config.get_branch(default_branch, 'options', options)
228
+ end
229
+
230
+ # suites/user_suites returns:
231
+ # [
232
+ # 'account',
233
+ # 'account_id',
234
+ # 'branch',
235
+ # 'ci_ssh_pubkey',
236
+ # 'git_repo_uri',
237
+ # 'id',
238
+ # 'org_name',
239
+ # 'repo_name',
240
+ # 'repo_url'
241
+ # ]
242
+ def get_suites(params={})
243
+ current_suites = call_api(:get, "#{Api::Path::SUITES}/user_suites", params)
244
+ current_suites ||= {}
245
+ current_suites['suites'] || []
246
+ end
247
+
248
+ def get_suite_by_id(id, params={})
249
+ current_suites = call_api(:get, "#{Api::Path::SUITES}/#{id}", params)
250
+ current_suites ||= {}
251
+ current_suites['suite']
252
+ end
253
+
254
+ def create_suite(params)
255
+ account_id = params.delete(:account_id)
256
+ new_suite = call_api(:post, Api::Path::SUITES, {:suite => params, :account_id => account_id})
257
+ new_suite["suite"]
258
+ end
259
+
260
+ def update_suite(id, params={})
261
+ call_api(:put, "#{Api::Path::SUITES}/#{id}", params)
262
+ end
263
+
264
+ def permanent_destroy_suite(id, params={})
265
+ call_api(:delete, "#{Api::Path::SUITES}/#{id}/permanent_destroy", params)
266
+ end
267
+
268
+ def demand_repoman_account(id, params={})
269
+ call_api(:post, "#{Api::Path::ACCOUNTS}/#{id}/demand_repoman", params)
270
+ end
271
+
272
+ def get_sessions(params={})
273
+ begin
274
+ call_api(:get, Api::Path::SESSIONS, params)['sessions']
275
+ rescue TddiumClient::Error::Base
276
+ []
277
+ end
278
+ end
279
+
280
+ def create_session(suite_id, params = {})
281
+ new_session = call_api(:post, Api::Path::SESSIONS, params.merge(:suite_id=>suite_id))
282
+ new_session['session']
283
+ end
284
+
285
+ def update_session(session_id, params={})
286
+ result = call_api(:put, "#{Api::Path::SESSIONS}/#{session_id}", params)
287
+ result['session']
288
+ end
289
+
290
+ def register_session(session_id, suite_id, test_pattern, test_exclude_pattern=nil)
291
+ args = {:suite_id => suite_id, :test_pattern => test_pattern}
292
+ if test_exclude_pattern
293
+ args[:test_exclude_pattern] = test_exclude_pattern
294
+ end
295
+
296
+ call_api(:post, "#{Api::Path::SESSIONS}/#{session_id}/#{Api::Path::REGISTER_TEST_EXECUTIONS}", args)
297
+ end
298
+
299
+ def start_session(session_id, params)
300
+ call_api(:post, "#{Api::Path::SESSIONS}/#{session_id}/#{Api::Path::START_TEST_EXECUTIONS}", params)
301
+ end
302
+
303
+ def stop_session(ls_id, params = {})
304
+ call_api(:post, "#{Api::Path::SESSIONS}/#{ls_id}/stop", params)
305
+ end
306
+
307
+ def poll_session(session_id, params={})
308
+ call_api(:get, "#{Api::Path::SESSIONS}/#{session_id}/#{Api::Path::TEST_EXECUTIONS}")
309
+ end
310
+
311
+ def query_session(session_id, params={})
312
+ call_api(:get, "#{Api::Path::SESSIONS}/#{session_id}/#{Api::Path::QUERY_TEST_EXECUTIONS}")
313
+ end
314
+
315
+ def check_session_done(session_id)
316
+ call_api(:get, "#{Api::Path::SESSIONS}/#{session_id}/check_done")
317
+ end
318
+ end
319
+ end