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,49 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ module Tddium
4
+ class TddiumCli < Thor
5
+ desc "account", "View account information"
6
+ def account
7
+ user_details = tddium_setup({:scm => false})
8
+
9
+ if user_details then
10
+ # User is already logged in, so just display the info
11
+ show_user_details(user_details)
12
+ else
13
+ exit_failure Text::Error::USE_ACTIVATE
14
+ end
15
+ end
16
+
17
+ desc "account:add [ROLE] [EMAIL]", "Authorize and invite a user to use your organization"
18
+ define_method "account:add" do |role, email|
19
+ tddium_setup({:scm => false})
20
+
21
+ r = Regexp.new(/\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+\z/)
22
+ if r !~ email then
23
+ exit_failure Text::Error::ADD_MEMBER_ERROR % [email, "Not a valid e-mail address: must be of the form user@host.domain"]
24
+ end
25
+
26
+ params = {:role=>role, :email=>email}
27
+ begin
28
+ say Text::Process::ADDING_MEMBER % [params[:email], params[:role]]
29
+ result = @tddium_api.set_memberships(params)
30
+ say Text::Process::ADDED_MEMBER % email
31
+ rescue TddiumClient::Error::API => e
32
+ exit_failure Text::Error::ADD_MEMBER_ERROR % [email, e.message]
33
+ end
34
+ end
35
+
36
+ desc "account:remove [EMAIL]", "Remove a user from an organization"
37
+ define_method "account:remove" do |email|
38
+ tddium_setup({:scm => false})
39
+
40
+ begin
41
+ say Text::Process::REMOVING_MEMBER % email
42
+ result = @tddium_api.delete_memberships(email)
43
+ say Text::Process::REMOVED_MEMBER % email
44
+ rescue TddiumClient::Error::API => e
45
+ exit_failure Text::Error::REMOVE_MEMBER_ERROR % [email, e.message]
46
+ end
47
+ end
48
+ end
49
+ 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 "activate", "Activate an account (deprecated)"
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 activate
10
+ say "To activate your account, please visit"
11
+ say "https://ci.solanolabs.com/"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,55 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ module Tddium
4
+ class TddiumCli < Thor
5
+ desc "config [suite | repo | org[:ACCOUNT]]", "Display config variables.
6
+ The scope argument can be 'suite', 'repo', 'org' (if you are a member of
7
+ one organization), or 'org:an_organization_name' (if you are a member of
8
+ multiple organizations). The default is 'suite'."
9
+ def config(scope="suite")
10
+ tddium_setup({:repo => true, :suite => true})
11
+
12
+ begin
13
+ config_details = @tddium_api.get_config_key(scope)
14
+ show_config_details(scope, config_details['env'])
15
+ rescue TddiumClient::Error::API => e
16
+ exit_failure Text::Error::LIST_CONFIG_ERROR
17
+ rescue Exception => e
18
+ exit_failure e.message
19
+ end
20
+ end
21
+
22
+ desc "config:add [SCOPE] [KEY] [VALUE]", "Set KEY=VALUE at SCOPE.
23
+ The scope argument can be 'suite', 'repo', 'org' (if you are a member of
24
+ one organization), or 'org:an_organization_name' (if you are a member of
25
+ multiple organizations)."
26
+ define_method "config:add" do |scope, key, value|
27
+ tddium_setup({:repo => true, :suite => true})
28
+
29
+ begin
30
+ say Text::Process::ADD_CONFIG % [key, value, scope]
31
+ result = @tddium_api.set_config_key(scope, key, value)
32
+ say Text::Process::ADD_CONFIG_DONE % [key, value, scope]
33
+ rescue TddiumClient::Error::API => e
34
+ exit_failure Text::Error::ADD_CONFIG_ERROR
35
+ rescue Exception => e
36
+ exit_failure e.message
37
+ end
38
+ end
39
+
40
+ desc "config:remove [SCOPE] [KEY]", "Remove config variable NAME from SCOPE."
41
+ define_method "config:remove" do |scope, key|
42
+ tddium_setup({:repo => true, :suite => true})
43
+
44
+ begin
45
+ say Text::Process::REMOVE_CONFIG % [key, scope]
46
+ result = @tddium_api.delete_config_key(scope, key)
47
+ say Text::Process::REMOVE_CONFIG_DONE % [key, scope]
48
+ rescue TddiumClient::Error::API => e
49
+ exit_failure Text::Error::REMOVE_CONFIG_ERROR
50
+ rescue Exception => e
51
+ exit_failure e.message
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,96 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ module Tddium
4
+ class TddiumCli < Thor
5
+ map "show" => :describe
6
+ desc "describe [SESSION]", "Describe the state of a session, if it
7
+ is provided; otherwise, the latest session on current branch."
8
+ method_option :account, :type => :string, :default => nil,
9
+ :aliases => %w(--org --organization)
10
+ method_option :all, :type=>:boolean, :default=>false
11
+ method_option :type, :type=>:string, :default=>nil
12
+ method_option :json, :type=>:boolean, :default=>false
13
+ method_option :names, :type=>:boolean, :default=>false
14
+ def describe(session_id=nil)
15
+ tddium_setup({:repo => false})
16
+
17
+ status_message = ''
18
+ if !session_id then
19
+ # params to get the most recent session id on current branch
20
+ suite_params = {
21
+ :suite_id => @tddium_api.current_suite_id,
22
+ :active => false,
23
+ :limit => 1
24
+ } if suite_for_current_branch?
25
+
26
+ sessions = suite_params ? @tddium_api.get_sessions(suite_params) : []
27
+ if sessions.empty? then
28
+ exit_failure Text::Status::NO_INACTIVE_SESSION
29
+ end
30
+
31
+ session_id = sessions[0]['id']
32
+
33
+ session_status = sessions[0]['status'].upcase
34
+ session_commit = sessions[0]['commit']
35
+ current_commit = @scm.current_commit
36
+ if session_commit == current_commit
37
+ commit_message = "equal to your current commit"
38
+ else
39
+ cnt_ahead = @scm.number_of_commits(session_commit, current_commit)
40
+ if cnt_ahead == 0
41
+ cnt_behind = @scm.number_of_commits(current_commit, session_commit)
42
+ commit_message = "your workspace is behind by #{cnt_behind} commits"
43
+ else
44
+ commit_message = "your workspace is ahead by #{cnt_ahead} commits"
45
+ end
46
+ end
47
+
48
+ duration = sessions[0]['duration']
49
+ start_timeago = "%s ago" % Tddium::TimeFormat.seconds_to_human_time(Time.now - Time.parse(sessions[0]["start_time"]))
50
+ if duration.nil?
51
+ finish_timeago = "no info about duration found, started #{start_timeago}"
52
+ elsif session_status == 'RUNNING'
53
+ finish_timeago = "in process, started #{start_timeago}"
54
+ else
55
+ finish_time = Time.parse(sessions[0]["start_time"]) + duration
56
+ finish_timeago = "%s ago" % Tddium::TimeFormat.seconds_to_human_time(Time.now - finish_time)
57
+ end
58
+
59
+ status_message = Text::Status::SESSION_STATUS % [session_commit, commit_message, session_status, finish_timeago]
60
+ end
61
+
62
+ result = @tddium_api.query_session(session_id)
63
+
64
+ filtered = result['session']['tests']
65
+ if !options[:all]
66
+ filtered = filtered.select{|x| x['status'] == 'failed'}
67
+ end
68
+
69
+ if options[:type]
70
+ filtered = filtered.select{|x| x['test_type'].downcase == options[:type].downcase}
71
+ end
72
+
73
+ if options[:json]
74
+ puts JSON.pretty_generate(result['session'])
75
+ elsif options[:names]
76
+ say filtered.map{|x| x['test_name']}.join(" ")
77
+ else
78
+ filtered.sort!{|a,b| [a['test_type'], a['test_name']] <=> [b['test_type'], b['test_name']]}
79
+
80
+ say Text::Process::DESCRIBE_SESSION % [session_id, status_message, options[:all] ? 'all' : 'failed']
81
+
82
+ table =
83
+ [["Test", "Status", "Duration"],
84
+ ["----", "------", "--------"]] +
85
+ filtered.map do |x|
86
+ [
87
+ x['test_name'],
88
+ x['status'],
89
+ x['elapsed_time'] ? "#{x['elapsed_time']}s" : "-"
90
+ ]
91
+ end
92
+ print_table table
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,62 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ module Tddium
4
+ class TddiumCli < Thor
5
+ desc "find_failing FILES", "Find failing ordering by binary searching a failing test run"
6
+ desc "find_failing files+ failing_file", "Find out which file causes pollution / makes the failing file fail"
7
+ def find_failing(*files)
8
+ failing = files.pop
9
+ if !files.include?(failing)
10
+ exit_failure "Files have to include the failing file, use the copy helper"
11
+ elsif files.size < 2
12
+ exit_failure "Files have to be more than 2, use the copy helper"
13
+ elsif !success?([failing])
14
+ exit_failure "#{failing} fails when run on it's own"
15
+ elsif success?(files)
16
+ exit_failure "tests pass locally"
17
+ else
18
+ loop do
19
+ a = remove_from(files, files.size / 2, :not => failing)
20
+ b = files - (a - [failing])
21
+ status, files = find_failing_set([a, b], failing)
22
+ if status == :finished
23
+ say "Fails when #{files.join(", ")} are run together"
24
+ break
25
+ elsif status == :continue
26
+ next
27
+ else
28
+ exit_failure "unable to isolate failure to 2 files"
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def find_failing_set(sets, failing)
37
+ sets.each do |set|
38
+ next if set == [failing]
39
+ if !success?(set)
40
+ if set.size == 2
41
+ return [:finished, set]
42
+ else
43
+ return [:continue, set]
44
+ end
45
+ end
46
+ end
47
+ return [:failure, []]
48
+ end
49
+
50
+ def remove_from(set, x, options)
51
+ set.dup.delete_if { |f| f != options[:not] && (x -= 1) >= 0 }
52
+ end
53
+
54
+ def success?(files)
55
+ command = "bundle exec ruby #{files.map { |f| "-r./#{f.sub(/\.rb$/, "")}" }.join(" ")} -e ''"
56
+ say "Running: #{command}"
57
+ status = system(command)
58
+ say "Status: #{status ? "Success" : "Failure"}"
59
+ status
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,53 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ module Tddium
4
+ class TddiumCli < Thor
5
+ desc "github:migrate_hooks", "Authorize and switch the repo to use the tddium webhook with the proper token"
6
+ define_method "github:migrate_hooks" do
7
+ suites = @tddium_api.get_suites
8
+ if suites.any?
9
+ say 'Please enter your github credentials; we do not store them anywhere'
10
+ username = HighLine.ask("username: ")
11
+ password = HighLine.ask("password: "){ |q| q.echo = "*" }
12
+ @github = Github.new(login: username, password: password)
13
+
14
+ suites.each do |suite|
15
+ login = suite['org_name'] || username
16
+ unless has_hook_token?(suite, login)
17
+ if confirm_for_repo?(suite['repo_name'])
18
+ set_hook_token(suite, login)
19
+ end
20
+ end
21
+ end
22
+ else
23
+ say 'You do not have any suites configured with tddium'
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def has_hook_token?(suite, login)
30
+ @github.repos.hooks.list(login, suite['repo_name']).any? do |hook|
31
+ hook["config"].try(:[], "token") == suite['repo_ci_hook_key'] && hook["active"]
32
+ end
33
+ rescue => e
34
+ say e.to_s
35
+ end
36
+
37
+ def confirm_for_repo?(name)
38
+ msg = "Do you want to switch the repo '#{name}' to use the tddium webhook with the proper token? (Yes/No/All)"
39
+ (@prev && @prev == 'All') ||
40
+ ((@prev = HighLine.ask(msg){ |q| q.validate = /Yes|No|All/ }) && ['Yes','All'].include?(@prev))
41
+ end
42
+
43
+ def set_hook_token(suite, login)
44
+ @github.repos.hooks.create(login, suite['repo_name'], {
45
+ active: true,
46
+ name: :tddium,
47
+ config: {
48
+ token: suite['repo_ci_hook_key']
49
+ }
50
+ })
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,15 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ module Tddium
4
+ class TddiumCli < Thor
5
+ desc "heroku", "Connect Heroku account with Solano CI (deprecated)"
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
+ method_option :app, :type => :string, :default => nil
10
+ def heroku
11
+ say "To activate your heroku account, please visit"
12
+ say "https://ci.solanolabs.com/"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,48 @@
1
+ # Copyright (c) 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ require 'fileutils'
4
+
5
+ module Tddium
6
+ class TddiumCli < Thor
7
+ desc "hg:mirror", "Construct local hg -> git mirror"
8
+ method_option :noop, :type => :boolean, :default => false
9
+ method_option :force, :type => :boolean, :default => false
10
+ define_method "hg:mirror" do |*args|
11
+ tddium_setup({:repo => true})
12
+
13
+ if @scm.scm_name != 'hg' then
14
+ exit_failure("Current repository does not appear to be using Mercurial")
15
+ end
16
+
17
+ if @scm.origin_url.nil? then
18
+ exit_failure("Missing default path; please set default path in hgrc")
19
+ end
20
+
21
+ if File.exists?(@scm.mirror_path) then
22
+ if !options[:force] then
23
+ exit_failure("Mirror already exists; use --force to recreate")
24
+ end
25
+ if options[:noop] then
26
+ exit_failure("Running in no-op mode; not removing existing mirror")
27
+ end
28
+ FileUtils.rm_rf(@scm.mirror_path)
29
+ end
30
+
31
+ FileUtils.mkdir_p(@scm.mirror_path)
32
+
33
+ Tddium::Scripts.prepend_script_path
34
+
35
+ clone_command = "git clone hg::#{@scm.root} #{@scm.mirror_path}"
36
+ # origin_command = "cd #{@scm.mirror_path} && git remote set-url origin #{@scm.origin_url}"
37
+
38
+ if options[:noop] then
39
+ puts "export PATH=#{ENV['PATH']}"
40
+ puts clone_command
41
+ # puts origin_command
42
+ else
43
+ Kernel.system(clone_command)
44
+ # Kernel.system(origin_command)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,83 @@
1
+ # Copyright (c) 2011, 2012, 2013, 2014 Solano Labs All Rights Reserved
2
+
3
+ module Tddium
4
+ class TddiumCli < Thor
5
+ desc "keys", "List SSH keys authorized for Solano CI"
6
+ def keys
7
+ user_details = tddium_setup({:scm => false})
8
+
9
+ begin
10
+ if user_details then
11
+ show_third_party_keys_details(user_details)
12
+ end
13
+
14
+ keys_details = @tddium_api.get_keys
15
+ show_keys_details(keys_details)
16
+ rescue TddiumClient::Error::API => e
17
+ exit_failure Text::Error::LIST_KEYS_ERROR
18
+ end
19
+ end
20
+
21
+ desc "keys:add [NAME] [PATH]", "Authorize an existing keypair for Solano CI"
22
+ method_option :dir, :type=>:string, :default=>nil
23
+ define_method "keys:add" do |name, path|
24
+ tddium_setup({:scm => false})
25
+
26
+ path = File.expand_path(path)
27
+
28
+ output_dir = options[:dir] || ENV['TDDIUM_GEM_KEY_DIR']
29
+ output_dir ||= Default::SSH_OUTPUT_DIR
30
+
31
+ begin
32
+ keydata = Tddium::Ssh.validate_keys name, path, @tddium_api
33
+ say Text::Process::ADD_KEYS_ADD % name
34
+ result = @tddium_api.set_keys({:keys => [keydata]})
35
+
36
+ priv_path = path.sub(/[.]pub$/, '')
37
+ say Text::Process::ADD_KEYS_ADD_DONE % [name, priv_path, result["git_server"] || Default::GIT_SERVER, priv_path]
38
+
39
+ rescue TddiumClient::Error::API => e
40
+ exit_failure Text::Error::ADD_KEYS_ERROR % name
41
+ rescue TddiumError => e
42
+ exit_failure e.message
43
+ end
44
+ end
45
+
46
+ map "generate" => :gen
47
+ desc "keys:gen [NAME]", "Generate and authorize a keypair for Solano CI"
48
+ method_option :dir, :type=>:string, :default=>nil
49
+ define_method "keys:gen" do |name|
50
+ tddium_setup({:scm => false})
51
+
52
+ output_dir = options[:dir] || ENV['TDDIUM_GEM_KEY_DIR']
53
+ output_dir ||= Default::SSH_OUTPUT_DIR
54
+
55
+ begin
56
+ keydata = Tddium::Ssh.validate_keys name, output_dir, @tddium_api, true
57
+ say Text::Process::ADD_KEYS_GENERATE % name
58
+
59
+ result = @tddium_api.set_keys({:keys => [keydata]})
60
+ outfile = File.expand_path(File.join(output_dir, "identity.tddium.#{name}"))
61
+ say Text::Process::ADD_KEYS_GENERATE_DONE % [name, result["git_server"] || Default::GIT_SERVER, outfile]
62
+
63
+ rescue TddiumClient::Error::API => e
64
+ exit_failure Text::Error::ADD_KEYS_ERROR % name
65
+ rescue TddiumError => e
66
+ exit_failure e.message
67
+ end
68
+ end
69
+
70
+ desc "keys:remove [NAME]", "Remove a key that was authorized for Solano CI"
71
+ define_method "keys:remove" do |name|
72
+ tddium_setup({:scm => false})
73
+
74
+ begin
75
+ say Text::Process::REMOVE_KEYS % name
76
+ result = @tddium_api.delete_keys(name)
77
+ say Text::Process::REMOVE_KEYS_DONE % name
78
+ rescue TddiumClient::Error::API => e
79
+ exit_failure Text::Error::REMOVE_KEYS_ERROR % name
80
+ end
81
+ end
82
+ end
83
+ end