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