duckworth 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,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,8 @@
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
+
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