duckworth 0.0.1
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.
- data/config/email.yml +15 -0
- data/lib/duckworth.rb +161 -0
- data/lib/toolbag.rb +162 -0
- data/spec/duckworth_spec.rb +162 -0
- data/spec/fixtures/jobs/assign_pending_tasks/builds/9/log +11 -0
- data/spec/fixtures/jobs/assign_pending_tasks/nextBuildNumber +1 -0
- data/spec/fixtures/jobs/assign_pending_tasks/workspace/spec/git_manager_spec.rb +174 -0
- data/spec/fixtures/jobs/build_in_progress/builds/3/log +11 -0
- data/spec/fixtures/jobs/build_in_progress/builds/4/log +8 -0
- data/spec/fixtures/jobs/build_in_progress/nextBuildNumber +1 -0
- data/spec/fixtures/jobs/build_missing_job_directory/builds/1 +0 -0
- metadata +82 -0
data/config/email.yml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# example:
|
2
|
+
# authentication:
|
3
|
+
# from: you@yourdomain.com
|
4
|
+
# password: your_password_here
|
5
|
+
# users:
|
6
|
+
# "User 1 Full Name as found in git blame":"associated_email1@domain.com@"
|
7
|
+
# "User 2 Full Name as found in git blame":"associated_email2@domain.com@"
|
8
|
+
authentication:
|
9
|
+
from: 'you@domain.com'
|
10
|
+
password: 'password'
|
11
|
+
|
12
|
+
users:
|
13
|
+
'Joe Schmoe': 'joeschmoe@domain.com'
|
14
|
+
'Jane Doe': 'janedoe@domain.com'
|
15
|
+
'John Jimenez': 'fake-email0@domain.com'
|
data/lib/duckworth.rb
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'net/smtp'
|
2
|
+
require 'yaml'
|
3
|
+
require 'lib/toolbag'
|
4
|
+
|
5
|
+
class Duckworth
|
6
|
+
include Toolbag::Parses
|
7
|
+
include Toolbag::Searches
|
8
|
+
include Toolbag::Organizes
|
9
|
+
include Toolbag::Writes
|
10
|
+
include Toolbag::Communicates
|
11
|
+
|
12
|
+
attr_accessor :build_number, :tasks
|
13
|
+
|
14
|
+
def initialize( guidlines = {} )
|
15
|
+
# call prepare
|
16
|
+
prepare( guidlines )
|
17
|
+
find and organize
|
18
|
+
end
|
19
|
+
|
20
|
+
def prepare guidlines
|
21
|
+
verify_provided_guidlines( guidlines )
|
22
|
+
end
|
23
|
+
|
24
|
+
def find
|
25
|
+
# Find the pending specs
|
26
|
+
find_pending_specs
|
27
|
+
end
|
28
|
+
|
29
|
+
def organize
|
30
|
+
# Organize the pending specs
|
31
|
+
find_contributors
|
32
|
+
end
|
33
|
+
|
34
|
+
def mail
|
35
|
+
# Mail the pending specs
|
36
|
+
end
|
37
|
+
|
38
|
+
# protected
|
39
|
+
|
40
|
+
def workspace
|
41
|
+
"#{@job_root}/workspace"
|
42
|
+
end
|
43
|
+
|
44
|
+
def log
|
45
|
+
"#{@builds}/#{@build_number}/log"
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def verify_provided_guidlines guidlines
|
51
|
+
root = guidlines[:jenkins_root]
|
52
|
+
name = guidlines[:job_name]
|
53
|
+
@builds = "#{root}/jobs/#{name}/builds"
|
54
|
+
@job_root = "#{root}/jobs/#{name}"
|
55
|
+
|
56
|
+
raise errors( :no_jenkins_root ) if root.nil?
|
57
|
+
unless File.exist?(root) and File.directory?( root )
|
58
|
+
raise errors( :dne_jenkins_root )
|
59
|
+
end
|
60
|
+
|
61
|
+
raise errors( :no_job_name ) if name.nil?
|
62
|
+
unless directory? "#{root}/jobs/#{name}"
|
63
|
+
raise errors( :dne_job_name )
|
64
|
+
end
|
65
|
+
|
66
|
+
next_build = "#{root}/jobs/#{name}/nextBuildNumber"
|
67
|
+
|
68
|
+
unless file? next_build
|
69
|
+
raise errors( :no_build_number )
|
70
|
+
end
|
71
|
+
|
72
|
+
unless directory? "#{@builds}/#{last_build_number( next_build )}"
|
73
|
+
raise errors(:dne_build_number)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def errors why_i_refuse_to_work
|
78
|
+
general_help = "" +
|
79
|
+
"Create new instances like Duckworth.new( " +
|
80
|
+
":jenkins_root => <directory_containing_jobs_directory>, " +
|
81
|
+
":job_name => <name_of_job_duckworth_will_work_on>)"
|
82
|
+
|
83
|
+
{ :no_jenkins_root => "Duckworth cannot work without a :jenkins_root. ",
|
84
|
+
:no_job_name => "Duckworth cannot work without a :job_name. ",
|
85
|
+
:dne_jenkins_root => "Duckworth cannot find path for :jenkins_root.",
|
86
|
+
:dne_job_name => "Duckworth cannot find path for :job_name.",
|
87
|
+
:no_build_number => "Duckworth cannot find nextBuildNumber file.",
|
88
|
+
:dne_build_number => "Duckworth cannot find log file for nextBuildNumber."
|
89
|
+
}[why_i_refuse_to_work] +
|
90
|
+
( why_i_refuse_to_work.to_s.match( /dne|build_number/ ) ? '' : general_help )
|
91
|
+
end
|
92
|
+
|
93
|
+
def file? file
|
94
|
+
File.exist?( file ) and File.file?( file )
|
95
|
+
end
|
96
|
+
|
97
|
+
def directory? directory
|
98
|
+
File.exist?( directory ) and File.directory?( directory )
|
99
|
+
end
|
100
|
+
|
101
|
+
def last_build_number( next_build_number_file )
|
102
|
+
build_number = File.open(
|
103
|
+
next_build_number_file, 'r'
|
104
|
+
).readlines.each { |line| line }.first.to_i - 1
|
105
|
+
|
106
|
+
unless File.exist?( "#{@builds}/#{build_number}/log" )
|
107
|
+
raise errors(:dne_build_number)
|
108
|
+
end
|
109
|
+
|
110
|
+
job_in_progress = File.open(
|
111
|
+
"#{@builds}/#{build_number}/log", 'r'
|
112
|
+
).readlines.select { |line| line.match /Finished: (SUCCESS|FAILURE)/ }.empty?
|
113
|
+
|
114
|
+
build_number -= 1 if job_in_progress
|
115
|
+
raise 'First Build Still in progress' if build_number == 0
|
116
|
+
@build_number = build_number
|
117
|
+
build_number
|
118
|
+
end
|
119
|
+
|
120
|
+
def found_pending_marker line
|
121
|
+
line.match( /^[\s\t]*Pending:/ )
|
122
|
+
end
|
123
|
+
|
124
|
+
def found_new_spec_marker line, status
|
125
|
+
( status == 'start' || status == 'updating spec data' ) && !line.match( /^[\s\t]*#/ )
|
126
|
+
end
|
127
|
+
|
128
|
+
def found_spec_details_marker line, status
|
129
|
+
( status == 'found new pending spec' || status == 'updating spec data' ) && line.match( /^[\s\t]*\#/ )
|
130
|
+
end
|
131
|
+
|
132
|
+
def find_pending_specs
|
133
|
+
logfile = File.new( log )
|
134
|
+
status = ''
|
135
|
+
@tasks = {}
|
136
|
+
task = {}
|
137
|
+
|
138
|
+
logfile.readlines.each do |line|
|
139
|
+
|
140
|
+
if found_pending_marker line
|
141
|
+
status = 'start'
|
142
|
+
|
143
|
+
elsif found_new_spec_marker line, status
|
144
|
+
status = 'found new pending spec'
|
145
|
+
task = {}
|
146
|
+
task[:name] = line.chomp
|
147
|
+
task[:details] = []
|
148
|
+
|
149
|
+
elsif found_spec_details_marker line, status
|
150
|
+
status = 'updating spec data'
|
151
|
+
if line.match /(spec.*)[:](\d+)/
|
152
|
+
task[:spec_file] = $1
|
153
|
+
task[:line_number] = $2
|
154
|
+
else
|
155
|
+
task[:details].push line.chomp
|
156
|
+
end
|
157
|
+
@tasks[task[:name]] = task
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
data/lib/toolbag.rb
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
module Toolbag
|
2
|
+
|
3
|
+
module Parses
|
4
|
+
def found_pending_marker line
|
5
|
+
line.match( /^[\s\t]*Pending:/ )
|
6
|
+
end
|
7
|
+
|
8
|
+
def found_new_spec_marker line, status
|
9
|
+
( status == 'start' || status == 'updating spec data' ) && !line.match( /^[\s\t]*#/ )
|
10
|
+
end
|
11
|
+
|
12
|
+
def found_spec_details_marker line, status
|
13
|
+
( status == 'found new pending spec' || status == 'updating spec data' ) && line.match( /^[\s\t]*\#/ )
|
14
|
+
end
|
15
|
+
|
16
|
+
def find_pending_specs
|
17
|
+
logfile = File.new( @log_file_name )
|
18
|
+
status = ''
|
19
|
+
@tasks = {}
|
20
|
+
task = {}
|
21
|
+
|
22
|
+
logfile.readlines.each do |line|
|
23
|
+
|
24
|
+
if found_pending_marker line
|
25
|
+
status = 'start'
|
26
|
+
|
27
|
+
elsif found_new_spec_marker line, status
|
28
|
+
status = 'found new pending spec'
|
29
|
+
task = {}
|
30
|
+
task[:name] = line.chomp
|
31
|
+
task[:details] = []
|
32
|
+
|
33
|
+
elsif found_spec_details_marker line, status
|
34
|
+
status = 'updating spec data'
|
35
|
+
if line.match /(spec.*)[:](\d+)/
|
36
|
+
task[:spec_file] = $1
|
37
|
+
task[:line_number] = $2
|
38
|
+
else
|
39
|
+
task[:details].push line.chomp
|
40
|
+
end
|
41
|
+
@tasks[task[:name]] = task
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module Searches
|
48
|
+
def search
|
49
|
+
@tasks.each_pair do |key, attributes|
|
50
|
+
contributors = []
|
51
|
+
`git blame "#{attributes[:spec_file]}"`.each do |blame|
|
52
|
+
contributor = blame.split(")")[0].split("(")[1]
|
53
|
+
contributor = contributor.split(/\s+\d/)[0]
|
54
|
+
contributors.push( contributor )
|
55
|
+
end
|
56
|
+
@tasks[key][:contributors] = contributors.uniq
|
57
|
+
@tasks[key][:contributors].reject!{ |contributor| contributor == 'Not Committed Yet'}
|
58
|
+
end
|
59
|
+
@tasks
|
60
|
+
end
|
61
|
+
|
62
|
+
def find_contributors
|
63
|
+
Dir.chdir( workspace ) { search }
|
64
|
+
@tasks
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
module Organizes
|
69
|
+
def tasks_by_contributor
|
70
|
+
open_addressbook
|
71
|
+
consolidated_emails = {}
|
72
|
+
@email_mappings.each_key do |contributor|
|
73
|
+
consolidated_emails[contributor] = []
|
74
|
+
@tasks.each do |object|
|
75
|
+
key = object[0]
|
76
|
+
values = object[1]
|
77
|
+
if values[:contributors].include?( contributor )
|
78
|
+
message = "\n" +
|
79
|
+
" Spec: #{values[:spec_file]}:#{values[:line_number]}\n" +
|
80
|
+
" Collaborators: #{values[:contributors].join(',')}\n" +
|
81
|
+
" Title: #{values[:name]}\n" +
|
82
|
+
" Details: #{values[:details].join('\n')}"
|
83
|
+
consolidated_emails[contributor].push( message )
|
84
|
+
end
|
85
|
+
end
|
86
|
+
consolidated_emails[contributor] = consolidated_emails[contributor].join(
|
87
|
+
"\n\n"
|
88
|
+
)
|
89
|
+
end
|
90
|
+
consolidated_emails
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
module Writes
|
95
|
+
def stdout key, attributes
|
96
|
+
puts "\nPending Spec Information:"
|
97
|
+
puts " Description: #{key}"
|
98
|
+
puts " Contributors: #{attributes[:contributors].join(', ')}"
|
99
|
+
puts " File: #{attributes[:spec_file]}"
|
100
|
+
puts " Line: #{attributes[:line_number]}\n"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
module Communicates
|
105
|
+
|
106
|
+
def open_addressbook
|
107
|
+
config = YAML::load_file( './config/email.yml' )
|
108
|
+
@email_from = config['authentication']['from']
|
109
|
+
@email_password = config['authentication']['password']
|
110
|
+
@email_mappings = config['users']
|
111
|
+
end
|
112
|
+
|
113
|
+
def blame(options = nil)
|
114
|
+
if !options[:spam]
|
115
|
+
self.tasks_by_contributor.each_pair do |recipient, message|
|
116
|
+
if message == ''
|
117
|
+
subject = 'Congratulations, you have no pending specs! :)'
|
118
|
+
messy = 'Woot!'
|
119
|
+
else
|
120
|
+
subject = "Please collaborate to fix consolidated list of specs: "
|
121
|
+
messy = message
|
122
|
+
end
|
123
|
+
|
124
|
+
send_gmail(
|
125
|
+
:recipients => [recipient],
|
126
|
+
:subject => subject,
|
127
|
+
:message => messy
|
128
|
+
)
|
129
|
+
end
|
130
|
+
else
|
131
|
+
self.tasks.each_pair do |key, attributes|
|
132
|
+
if options.nil? || !options[:email]
|
133
|
+
stdout key, attributes
|
134
|
+
else
|
135
|
+
send_gmail :recipients => attributes[:contributors],
|
136
|
+
:subject => "Please collaborate to fix spec: " +
|
137
|
+
"#{attributes[:spec_file]} : " +
|
138
|
+
"#{attributes[:line_number]}",
|
139
|
+
:message => "Spec Details:\n " +
|
140
|
+
"Details\n #{attributes[:details].join('\n')}\n" +
|
141
|
+
"Collaborators:\n #{attributes[:contributors].join(', ')}\n"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def send_gmail options
|
148
|
+
open_addressbook
|
149
|
+
recipients = options[:recipients]
|
150
|
+
msg = "Subject: #{options[:subject]}\n\n#{options[:message]}"
|
151
|
+
smtp = Net::SMTP.new 'smtp.gmail.com', 587
|
152
|
+
smtp.enable_starttls
|
153
|
+
puts "Sending e-mail to #{recipients.join(',')}"
|
154
|
+
smtp.start( '', @email_from, @email_password, :login ) do
|
155
|
+
smtp.send_message( msg, @email_from, recipients.map{ |recipient|
|
156
|
+
# @email_mappings["#{recipient.gsub(' ', '_') }" ]
|
157
|
+
@email_mappings[ recipient ]
|
158
|
+
})
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rspec'
|
3
|
+
require 'lib/duckworth'
|
4
|
+
|
5
|
+
describe Duckworth do
|
6
|
+
describe "Preparation" do
|
7
|
+
before :each do
|
8
|
+
@errors = {
|
9
|
+
:no_jenkins_root => "" +
|
10
|
+
"Duckworth cannot work without a :jenkins_root. " +
|
11
|
+
"Create new instances like Duckworth.new( " +
|
12
|
+
":jenkins_root => <directory_containing_jobs_directory>, " +
|
13
|
+
":job_name => <name_of_job_duckworth_will_work_on>)",
|
14
|
+
|
15
|
+
:no_job_name => "" +
|
16
|
+
"Duckworth cannot work without a :job_name. " +
|
17
|
+
"Create new instances like Duckworth.new( " +
|
18
|
+
":jenkins_root => <directory_containing_jobs_directory>, " +
|
19
|
+
":job_name => <name_of_job_duckworth_will_work_on>)",
|
20
|
+
|
21
|
+
:dne_jenkins_root => "Duckworth cannot find path for :jenkins_root.",
|
22
|
+
:dne_job_name => "Duckworth cannot find path for :job_name.",
|
23
|
+
:no_build_number => "Duckworth cannot find nextBuildNumber file.",
|
24
|
+
:dne_build_number => "Duckworth cannot find log file for nextBuildNumber."
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
it "refuses to work without proper instructions" do
|
29
|
+
lambda{ Duckworth.new }.should raise_error @errors[:no_jenkins_root]
|
30
|
+
lambda{
|
31
|
+
Duckworth.new( :jenkins_root => 'asdf')
|
32
|
+
}.should raise_error @errors[:dne_jenkins_root]
|
33
|
+
|
34
|
+
lambda{
|
35
|
+
Duckworth.new(:jenkins_root => '/')
|
36
|
+
}.should raise_error @errors[:no_job_name]
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
it "refuses to work on wild goose chases" do
|
41
|
+
lambda{
|
42
|
+
Duckworth.new(:jenkins_root => '/', :job_name => 'asdf')
|
43
|
+
}.should raise_error @errors[:dne_job_name]
|
44
|
+
|
45
|
+
lambda{
|
46
|
+
Duckworth.new(
|
47
|
+
:jenkins_root => './spec/fixtures',
|
48
|
+
:job_name => 'malformed_job_directory'
|
49
|
+
)
|
50
|
+
}.should raise_error @errors[:no_build_number]
|
51
|
+
|
52
|
+
lambda{
|
53
|
+
Duckworth.new(
|
54
|
+
:jenkins_root => './spec/fixtures',
|
55
|
+
:job_name => 'build_missing_job_directory'
|
56
|
+
)
|
57
|
+
}.should raise_error @errors[:dne_build_number]
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
it "knows when build is in progress, and will revert previous build" do
|
62
|
+
Duckworth.new(
|
63
|
+
:jenkins_root => './spec/fixtures',
|
64
|
+
:job_name => 'build_in_progress'
|
65
|
+
).build_number.should == 3
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
it "prepares for work when initialized with proper instructions" do
|
71
|
+
lambda{
|
72
|
+
@duckworth = Duckworth.new(
|
73
|
+
:jenkins_root => './spec/fixtures',
|
74
|
+
:job_name => 'assign_pending_tasks'
|
75
|
+
)
|
76
|
+
}.should_not raise_error
|
77
|
+
|
78
|
+
@duckworth.class.should == Duckworth
|
79
|
+
|
80
|
+
@duckworth.workspace.should ==
|
81
|
+
'./spec/fixtures/jobs/assign_pending_tasks/workspace'
|
82
|
+
|
83
|
+
@duckworth.log.should ==
|
84
|
+
'./spec/fixtures/jobs/assign_pending_tasks/builds/9/log'
|
85
|
+
|
86
|
+
@duckworth.build_number.should == 9
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe Duckworth do
|
91
|
+
before :each do
|
92
|
+
@test_01 = ' Fake test 1 spec/git_manager_spec.rb'
|
93
|
+
@test_02 = ' Fake test 2 spec/git_manager_spec.rb'
|
94
|
+
|
95
|
+
@duckworth = Duckworth.new(
|
96
|
+
:jenkins_root => './spec/fixtures',
|
97
|
+
:job_name => 'assign_pending_tasks'
|
98
|
+
)
|
99
|
+
|
100
|
+
@duckworth.blame( :spam => false )
|
101
|
+
|
102
|
+
@expected_tasks = {
|
103
|
+
@test_01 => {
|
104
|
+
:spec_file => 'spec/git_manager_spec.rb',
|
105
|
+
:details=>[" # Test 123"],
|
106
|
+
:line_number => '13',
|
107
|
+
:contributors => ['John Jimenez'],
|
108
|
+
:name => " Fake test 1 spec/git_manager_spec.rb"
|
109
|
+
},
|
110
|
+
@test_02 => {
|
111
|
+
:spec_file => 'spec/git_manager_spec.rb',
|
112
|
+
:details=>[" # Test 123"],
|
113
|
+
:line_number => '11',
|
114
|
+
:contributors => ['John Jimenez'],
|
115
|
+
:name => " Fake test 2 spec/git_manager_spec.rb"
|
116
|
+
}
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
it "collects pending specs as expected" do
|
121
|
+
@duckworth.tasks[@test_01][:spec_file].should == @expected_tasks[@test_01][:spec_file]
|
122
|
+
@duckworth.tasks[@test_02][:spec_file].should == @expected_tasks[@test_02][:spec_file]
|
123
|
+
end
|
124
|
+
|
125
|
+
it "collects pending spec line number as expected" do
|
126
|
+
@duckworth.tasks[@test_01][:line_number].should == @expected_tasks[@test_01][:line_number]
|
127
|
+
@duckworth.tasks[@test_02][:line_number].should == @expected_tasks[@test_02][:line_number]
|
128
|
+
end
|
129
|
+
|
130
|
+
it "collects pending spec name as expected" do
|
131
|
+
@duckworth.tasks[@test_01][:spec_name].should == @expected_tasks[@test_01][:spec_name]
|
132
|
+
@duckworth.tasks[@test_02][:spec_name].should == @expected_tasks[@test_02][:spec_name]
|
133
|
+
end
|
134
|
+
|
135
|
+
it "collects pending spec contributors as expected" do
|
136
|
+
@duckworth.tasks[@test_01][:contributors].should == @expected_tasks[@test_01][:contributors]
|
137
|
+
@duckworth.tasks[@test_02][:contributors].should == @expected_tasks[@test_02][:contributors]
|
138
|
+
end
|
139
|
+
|
140
|
+
it "collects pending spec details as expected" do
|
141
|
+
@duckworth.tasks[@test_01][:details].should == @expected_tasks[@test_01][:details]
|
142
|
+
@duckworth.tasks[@test_02][:details].should == @expected_tasks[@test_02][:details]
|
143
|
+
end
|
144
|
+
|
145
|
+
it "consolidates blames into structure for e-mailing each developer once" do
|
146
|
+
expected_text = "\n" +
|
147
|
+
" Spec: spec/git_manager_spec.rb:11\n" +
|
148
|
+
" Collaborators: John Jimenez\n" +
|
149
|
+
" Title: Fake test 2 spec/git_manager_spec.rb\n" +
|
150
|
+
" Details: # Test 123\n\n\n" +
|
151
|
+
" Spec: spec/git_manager_spec.rb:13\n" +
|
152
|
+
" Collaborators: John Jimenez\n" +
|
153
|
+
" Title: Fake test 1 spec/git_manager_spec.rb\n" +
|
154
|
+
" Details: # Test 123"
|
155
|
+
|
156
|
+
@duckworth.tasks_by_contributor.should == {
|
157
|
+
"John Jimenez" => expected_text,
|
158
|
+
"Joe Schmoe" => '',
|
159
|
+
"Jane Doe" => '',
|
160
|
+
}
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Pending:
|
2
|
+
Fake test 1 spec/git_manager_spec.rb
|
3
|
+
# Test 123
|
4
|
+
# ./spec/git_manager_spec.rb:13
|
5
|
+
Fake test 2 spec/git_manager_spec.rb
|
6
|
+
# Test 123
|
7
|
+
# ./spec/git_manager_spec.rb:11
|
8
|
+
|
9
|
+
Finished in x minutes y seconds
|
10
|
+
z examples, 0 failures, 2 pending
|
11
|
+
Finished: SUCCESS
|
@@ -0,0 +1 @@
|
|
1
|
+
10
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rspec'
|
3
|
+
require 'lib/duckworth'
|
4
|
+
|
5
|
+
describe Duckworth do
|
6
|
+
describe "Preparation" do
|
7
|
+
before :each do
|
8
|
+
@errors = {
|
9
|
+
:no_jenkins_root => "" +
|
10
|
+
"Duckworth cannot work without a :jenkins_root. " +
|
11
|
+
"Create new instances like Duckworth.new( " +
|
12
|
+
":jenkins_root => <directory_containing_jobs_directory>, " +
|
13
|
+
":job_name => <name_of_job_duckworth_will_work_on>)",
|
14
|
+
|
15
|
+
:no_job_name => "" +
|
16
|
+
"Duckworth cannot work without a :job_name. " +
|
17
|
+
"Create new instances like Duckworth.new( " +
|
18
|
+
":jenkins_root => <directory_containing_jobs_directory>, " +
|
19
|
+
":job_name => <name_of_job_duckworth_will_work_on>)",
|
20
|
+
|
21
|
+
:dne_jenkins_root => "Duckworth cannot find path for :jenkins_root.",
|
22
|
+
:dne_job_name => "Duckworth cannot find path for :job_name.",
|
23
|
+
:no_build_number => "Duckworth cannot find nextBuildNumber file.",
|
24
|
+
:dne_build_number => "Duckworth cannot find log file for nextBuildNumber."
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
it "refuses to work without proper instructions" do
|
29
|
+
lambda{ Duckworth.new }.should raise_error @errors[:no_jenkins_root]
|
30
|
+
lambda{
|
31
|
+
Duckworth.new( :jenkins_root => 'asdf')
|
32
|
+
}.should raise_error @errors[:dne_jenkins_root]
|
33
|
+
|
34
|
+
lambda{
|
35
|
+
Duckworth.new(:jenkins_root => '/')
|
36
|
+
}.should raise_error @errors[:no_job_name]
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
it "refuses to work on wild goose chases" do
|
41
|
+
lambda{
|
42
|
+
Duckworth.new(:jenkins_root => '/', :job_name => 'asdf')
|
43
|
+
}.should raise_error @errors[:dne_job_name]
|
44
|
+
|
45
|
+
lambda{
|
46
|
+
Duckworth.new(
|
47
|
+
:jenkins_root => './spec/fixtures',
|
48
|
+
:job_name => 'malformed_job_directory'
|
49
|
+
)
|
50
|
+
}.should raise_error @errors[:no_build_number]
|
51
|
+
|
52
|
+
lambda{
|
53
|
+
Duckworth.new(
|
54
|
+
:jenkins_root => './spec/fixtures',
|
55
|
+
:job_name => 'build_missing_job_directory'
|
56
|
+
)
|
57
|
+
}.should raise_error @errors[:dne_build_number]
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
it "knows when build is in progress, and will revert previous build" do
|
62
|
+
Duckworth.new(
|
63
|
+
:jenkins_root => './spec/fixtures',
|
64
|
+
:job_name => 'build_in_progress'
|
65
|
+
).build_number.should == 3
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
it "prepares for work when initialized with proper instructions" do
|
71
|
+
lambda{
|
72
|
+
@duckworth = Duckworth.new(
|
73
|
+
:jenkins_root => './spec/fixtures',
|
74
|
+
:job_name => 'assign_pending_tasks'
|
75
|
+
)
|
76
|
+
}.should_not raise_error
|
77
|
+
|
78
|
+
@duckworth.class.should == Duckworth
|
79
|
+
|
80
|
+
@duckworth.workspace.should ==
|
81
|
+
'./spec/fixtures/jobs/assign_pending_tasks/workspace'
|
82
|
+
|
83
|
+
@duckworth.log.should ==
|
84
|
+
'./spec/fixtures/jobs/assign_pending_tasks/builds/9/log'
|
85
|
+
|
86
|
+
@duckworth.build_number.should == 9
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe Duckworth do
|
91
|
+
before :each do
|
92
|
+
@test_01 = ' Fake test 1 spec/git_manager_spec.rb'
|
93
|
+
@test_02 = ' Fake test 2 spec/git_manager_spec.rb'
|
94
|
+
|
95
|
+
@duckworth = Duckworth.new(
|
96
|
+
:jenkins_root => './spec/fixtures',
|
97
|
+
:job_name => 'assign_pending_tasks'
|
98
|
+
).blame( :email => false )
|
99
|
+
|
100
|
+
@expected_tasks = {
|
101
|
+
@test_01 => {
|
102
|
+
:spec_file => 'spec/git_manager_spec.rb',
|
103
|
+
:details=>[" # Test 123"],
|
104
|
+
:line_number => '13',
|
105
|
+
:contributors => ['John Jimenez'],
|
106
|
+
:name => " Fake test 1 spec/git_manager_spec.rb"
|
107
|
+
},
|
108
|
+
@test_02 => {
|
109
|
+
:spec_file => 'spec/git_manager_spec.rb',
|
110
|
+
:details=>[" # Test 123"],
|
111
|
+
:line_number => '11',
|
112
|
+
:contributors => ['John Jimenez'],
|
113
|
+
:name => " Fake test 2 spec/git_manager_spec.rb"
|
114
|
+
}
|
115
|
+
}
|
116
|
+
end
|
117
|
+
|
118
|
+
it "collects pending specs as expected" do
|
119
|
+
@duckworth.tasks[@test_01][:spec_file].should == @expected_tasks[@test_01][:spec_file]
|
120
|
+
@duckworth.tasks[@test_02][:spec_file].should == @expected_tasks[@test_02][:spec_file]
|
121
|
+
end
|
122
|
+
|
123
|
+
it "collects pending spec line number as expected" do
|
124
|
+
@duckworth.tasks[@test_01][:line_number].should == @expected_tasks[@test_01][:line_number]
|
125
|
+
@duckworth.tasks[@test_02][:line_number].should == @expected_tasks[@test_02][:line_number]
|
126
|
+
end
|
127
|
+
|
128
|
+
it "collects pending spec name as expected" do
|
129
|
+
@duckworth.tasks[@test_01][:spec_name].should == @expected_tasks[@test_01][:spec_name]
|
130
|
+
@duckworth.tasks[@test_02][:spec_name].should == @expected_tasks[@test_02][:spec_name]
|
131
|
+
end
|
132
|
+
|
133
|
+
it "collects pending spec contributors as expected" do
|
134
|
+
@duckworth.tasks[@test_01][:contributors].should == @expected_tasks[@test_01][:contributors]
|
135
|
+
@duckworth.tasks[@test_02][:contributors].should == @expected_tasks[@test_02][:contributors]
|
136
|
+
end
|
137
|
+
|
138
|
+
it "collects pending spec details as expected" do
|
139
|
+
@duckworth.tasks[@test_01][:details].should == @expected_tasks[@test_01][:details]
|
140
|
+
@duckworth.tasks[@test_02][:details].should == @expected_tasks[@test_02][:details]
|
141
|
+
end
|
142
|
+
|
143
|
+
it "consolidates blames into structure for e-mailing each developer once" do
|
144
|
+
expected_text = "\n" +
|
145
|
+
" Spec: spec/git_manager_spec.rb:11\n" +
|
146
|
+
" Collaborators: John Jimenez\n" +
|
147
|
+
" Title: Fake test 2 spec/git_manager_spec.rb\n" +
|
148
|
+
" Details: # Test 123\n\n\n" +
|
149
|
+
" Spec: spec/git_manager_spec.rb:13\n" +
|
150
|
+
" Collaborators: John Jimenez\n" +
|
151
|
+
" Title: Fake test 1 spec/git_manager_spec.rb\n" +
|
152
|
+
" Details: # Test 123"
|
153
|
+
|
154
|
+
@duckworth.tasks_by_contributor.should == {
|
155
|
+
"John Jimenez" => expected_text,
|
156
|
+
"Joe Schmoe" => '',
|
157
|
+
"Jane Doe" => '',
|
158
|
+
}
|
159
|
+
# { " Fake test 2 spec/git_manager_spec.rb"=>{
|
160
|
+
# :spec_file=>"spec/git_manager_spec.rb",
|
161
|
+
# :details=>[" # Test 123"],
|
162
|
+
# :contributors=>["John Jimenez"],
|
163
|
+
# :name=>" Fake test 2 spec/git_manager_spec.rb",
|
164
|
+
# :line_number=>"11"
|
165
|
+
# },
|
166
|
+
# " Fake test 1 spec/git_manager_spec.rb"=>{
|
167
|
+
# :spec_file=>"spec/git_manager_spec.rb",
|
168
|
+
# :details=>[" # Test 123"],
|
169
|
+
# :contributors=>["John Jimenez"],
|
170
|
+
# :name=>" Fake test 1 spec/git_manager_spec.rb",
|
171
|
+
# :line_number=>"13"
|
172
|
+
# }
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Pending:
|
2
|
+
Fake test 1 spec/git_manager_spec.rb
|
3
|
+
# Test 123
|
4
|
+
# ./spec/git_manager_spec.rb:13
|
5
|
+
Fake test 2 spec/git_manager_spec.rb
|
6
|
+
# Test 123
|
7
|
+
# ./spec/git_manager_spec.rb:11
|
8
|
+
|
9
|
+
Finished in x minutes y seconds
|
10
|
+
z examples, 0 failures, 2 pending
|
11
|
+
Finished: SUCCESS
|
@@ -0,0 +1 @@
|
|
1
|
+
5
|
File without changes
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: duckworth
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- kikuchiyo
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2013-01-12 00:00:00 Z
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: QA Automation Engineer Assistant, for Jenkins and Git.
|
22
|
+
email: jimenez.john0@gmail.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
|
+
files:
|
30
|
+
- lib/toolbag.rb
|
31
|
+
- lib/duckworth.rb
|
32
|
+
- config/email.yml
|
33
|
+
- spec/duckworth_spec.rb
|
34
|
+
- spec/fixtures/jobs/assign_pending_tasks/builds/9/log
|
35
|
+
- spec/fixtures/jobs/assign_pending_tasks/nextBuildNumber
|
36
|
+
- spec/fixtures/jobs/assign_pending_tasks/workspace/spec/git_manager_spec.rb
|
37
|
+
- spec/fixtures/jobs/build_in_progress/builds/3/log
|
38
|
+
- spec/fixtures/jobs/build_in_progress/builds/4/log
|
39
|
+
- spec/fixtures/jobs/build_in_progress/nextBuildNumber
|
40
|
+
- spec/fixtures/jobs/build_missing_job_directory/builds/1
|
41
|
+
homepage: http://rubygems.org/gems/duckworth
|
42
|
+
licenses: []
|
43
|
+
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
hash: 3
|
55
|
+
segments:
|
56
|
+
- 0
|
57
|
+
version: "0"
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
hash: 3
|
64
|
+
segments:
|
65
|
+
- 0
|
66
|
+
version: "0"
|
67
|
+
requirements: []
|
68
|
+
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.8.10
|
71
|
+
signing_key:
|
72
|
+
specification_version: 3
|
73
|
+
summary: QA Automation Engineer Assistant
|
74
|
+
test_files:
|
75
|
+
- spec/duckworth_spec.rb
|
76
|
+
- spec/fixtures/jobs/assign_pending_tasks/builds/9/log
|
77
|
+
- spec/fixtures/jobs/assign_pending_tasks/nextBuildNumber
|
78
|
+
- spec/fixtures/jobs/assign_pending_tasks/workspace/spec/git_manager_spec.rb
|
79
|
+
- spec/fixtures/jobs/build_in_progress/builds/3/log
|
80
|
+
- spec/fixtures/jobs/build_in_progress/builds/4/log
|
81
|
+
- spec/fixtures/jobs/build_in_progress/nextBuildNumber
|
82
|
+
- spec/fixtures/jobs/build_missing_job_directory/builds/1
|