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