ssp-cijoe 0.4.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/.gitignore +6 -0
- data/LICENSE +20 -0
- data/README.markdown +169 -0
- data/Rakefile +67 -0
- data/bin/cijoe +52 -0
- data/deps.rip +4 -0
- data/examples/build-failed +22 -0
- data/examples/build-worked +22 -0
- data/examples/cijoe.ru +15 -0
- data/examples/cijoed +53 -0
- data/lib/cijoe/build.rb +74 -0
- data/lib/cijoe/campfire.rb +72 -0
- data/lib/cijoe/commit.rb +27 -0
- data/lib/cijoe/config.rb +43 -0
- data/lib/cijoe/partials.rb +16 -0
- data/lib/cijoe/public/favicon.ico +0 -0
- data/lib/cijoe/public/octocat.png +0 -0
- data/lib/cijoe/public/screen.css +213 -0
- data/lib/cijoe/server.rb +90 -0
- data/lib/cijoe/version.rb +3 -0
- data/lib/cijoe/views/_build.erb +12 -0
- data/lib/cijoe/views/template.erb +75 -0
- data/lib/cijoe.rb +237 -0
- data/test/helper.rb +12 -0
- data/test/test_cijoe.rb +17 -0
- data/test/test_cijoe_server.rb +50 -0
- metadata +138 -0
@@ -0,0 +1,213 @@
|
|
1
|
+
/*****************************************************************************/
|
2
|
+
/*
|
3
|
+
/* Common
|
4
|
+
/*
|
5
|
+
/*****************************************************************************/
|
6
|
+
|
7
|
+
/* Global Reset */
|
8
|
+
|
9
|
+
* {
|
10
|
+
margin: 0;
|
11
|
+
padding: 0;
|
12
|
+
}
|
13
|
+
|
14
|
+
html, body {
|
15
|
+
height: 100%;
|
16
|
+
}
|
17
|
+
|
18
|
+
body {
|
19
|
+
background-color: white;
|
20
|
+
font: 13.34px helvetica, arial, clean, sans-serif;
|
21
|
+
*font-size: small;
|
22
|
+
text-align: center;
|
23
|
+
}
|
24
|
+
|
25
|
+
h1, h2, h3, h4, h5, h6 {
|
26
|
+
font-size: 100%;
|
27
|
+
}
|
28
|
+
|
29
|
+
h1 {
|
30
|
+
margin-bottom: 1em;
|
31
|
+
}
|
32
|
+
|
33
|
+
h1 a {
|
34
|
+
text-decoration: none;
|
35
|
+
color: #000;
|
36
|
+
}
|
37
|
+
|
38
|
+
.failed, .color31 {
|
39
|
+
color: red !important;
|
40
|
+
}
|
41
|
+
|
42
|
+
.worked, .color32 {
|
43
|
+
color: green !important;
|
44
|
+
}
|
45
|
+
|
46
|
+
.errored, .color33 {
|
47
|
+
color: yellow !important;
|
48
|
+
}
|
49
|
+
|
50
|
+
p {
|
51
|
+
margin: 1em 0;
|
52
|
+
}
|
53
|
+
|
54
|
+
a {
|
55
|
+
color: #00a;
|
56
|
+
}
|
57
|
+
|
58
|
+
a:hover {
|
59
|
+
color: black;
|
60
|
+
}
|
61
|
+
|
62
|
+
a:visited {
|
63
|
+
color: #a0a;
|
64
|
+
}
|
65
|
+
|
66
|
+
table {
|
67
|
+
font-size: inherit;
|
68
|
+
font: 100%;
|
69
|
+
}
|
70
|
+
|
71
|
+
/*****************************************************************************/
|
72
|
+
/*
|
73
|
+
/* Home
|
74
|
+
/*
|
75
|
+
/*****************************************************************************/
|
76
|
+
|
77
|
+
ul.posts {
|
78
|
+
list-style-type: none;
|
79
|
+
margin-bottom: 2em;
|
80
|
+
}
|
81
|
+
|
82
|
+
ul.posts li {
|
83
|
+
line-height: 1.75em;
|
84
|
+
}
|
85
|
+
|
86
|
+
ul.posts .date,
|
87
|
+
ul.posts .duration {
|
88
|
+
color: #aaa;
|
89
|
+
font-family: Monaco, "Courier New", monospace;
|
90
|
+
font-size: 80%;
|
91
|
+
}
|
92
|
+
|
93
|
+
/*****************************************************************************/
|
94
|
+
/*
|
95
|
+
/* Site
|
96
|
+
/*
|
97
|
+
/*****************************************************************************/
|
98
|
+
|
99
|
+
.site {
|
100
|
+
font-size: 110%;
|
101
|
+
text-align: justify;
|
102
|
+
width: 80%;
|
103
|
+
margin: 3em auto 2em auto;
|
104
|
+
line-height: 1.5em;
|
105
|
+
}
|
106
|
+
|
107
|
+
.title {
|
108
|
+
color: #a00;
|
109
|
+
font-weight: bold;
|
110
|
+
margin-bottom: 2em;
|
111
|
+
}
|
112
|
+
|
113
|
+
.site .title a {
|
114
|
+
color: #a00;
|
115
|
+
text-decoration: none;
|
116
|
+
}
|
117
|
+
|
118
|
+
.site .title a:hover {
|
119
|
+
color: black;
|
120
|
+
}
|
121
|
+
|
122
|
+
.site .title .extra {
|
123
|
+
color: #aaa;
|
124
|
+
text-decoration: none;
|
125
|
+
margin-left: 1em;
|
126
|
+
font-size: 0.9em;
|
127
|
+
}
|
128
|
+
|
129
|
+
.site .title a.extra:hover {
|
130
|
+
color: black;
|
131
|
+
}
|
132
|
+
|
133
|
+
.site .meta {
|
134
|
+
color: #aaa;
|
135
|
+
}
|
136
|
+
|
137
|
+
.site .footer {
|
138
|
+
font-size: 80%;
|
139
|
+
color: #666;
|
140
|
+
border-top: 4px solid #eee;
|
141
|
+
margin-top: 2em;
|
142
|
+
overflow: hidden;
|
143
|
+
}
|
144
|
+
|
145
|
+
.site .footer .contact {
|
146
|
+
float: left;
|
147
|
+
margin-right: 3em;
|
148
|
+
}
|
149
|
+
|
150
|
+
.site .footer .contact a {
|
151
|
+
color: #8085C1;
|
152
|
+
}
|
153
|
+
|
154
|
+
.site .footer .rss {
|
155
|
+
margin-top: 1.1em;
|
156
|
+
margin-right: -.2em;
|
157
|
+
float: right;
|
158
|
+
}
|
159
|
+
|
160
|
+
.site .footer .rss img {
|
161
|
+
border: 0;
|
162
|
+
}
|
163
|
+
|
164
|
+
/*****************************************************************************/
|
165
|
+
/*
|
166
|
+
/* Posts
|
167
|
+
/*
|
168
|
+
/*****************************************************************************/
|
169
|
+
|
170
|
+
#post {
|
171
|
+
|
172
|
+
}
|
173
|
+
|
174
|
+
/* standard */
|
175
|
+
|
176
|
+
#post pre {
|
177
|
+
border: 1px solid #ddd;
|
178
|
+
background-color: #eef;
|
179
|
+
padding: 0 .4em;
|
180
|
+
}
|
181
|
+
|
182
|
+
#post ul,
|
183
|
+
#post ol {
|
184
|
+
margin-left: 1.25em;
|
185
|
+
}
|
186
|
+
|
187
|
+
#post code {
|
188
|
+
border: 1px solid #ddd;
|
189
|
+
background-color: #eef;
|
190
|
+
font-size: 95%;
|
191
|
+
padding: 0 .2em;
|
192
|
+
}
|
193
|
+
|
194
|
+
#post pre code {
|
195
|
+
border: none;
|
196
|
+
}
|
197
|
+
|
198
|
+
/* terminal */
|
199
|
+
|
200
|
+
pre.terminal {
|
201
|
+
border: 1px solid black;
|
202
|
+
background-color: #333;
|
203
|
+
color: white;
|
204
|
+
padding: 5px;
|
205
|
+
overflow: auto;
|
206
|
+
word-wrap: break-word;
|
207
|
+
}
|
208
|
+
|
209
|
+
pre.terminal code {
|
210
|
+
font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace;
|
211
|
+
background-color: #333;
|
212
|
+
}
|
213
|
+
|
data/lib/cijoe/server.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'erb'
|
3
|
+
require 'cijoe/partials'
|
4
|
+
|
5
|
+
class CIJoe
|
6
|
+
class Server < Sinatra::Base
|
7
|
+
attr_reader :joe
|
8
|
+
|
9
|
+
helpers Sinatra::Partials
|
10
|
+
|
11
|
+
dir = File.dirname(File.expand_path(__FILE__))
|
12
|
+
|
13
|
+
set :views, "#{dir}/views"
|
14
|
+
set :public, "#{dir}/public"
|
15
|
+
set :static, true
|
16
|
+
set :lock, true
|
17
|
+
|
18
|
+
before { joe.restore }
|
19
|
+
|
20
|
+
get '/ping' do
|
21
|
+
if joe.building? || !joe.last_build || !joe.last_build.worked?
|
22
|
+
halt 412, joe.last_build ? joe.last_build.sha : "building"
|
23
|
+
end
|
24
|
+
|
25
|
+
joe.last_build.sha
|
26
|
+
end
|
27
|
+
|
28
|
+
get '/?' do
|
29
|
+
erb(:template, {}, :joe => joe)
|
30
|
+
end
|
31
|
+
|
32
|
+
post '/?' do
|
33
|
+
payload = params[:payload].to_s
|
34
|
+
if payload.empty? || payload.include?(joe.git_branch)
|
35
|
+
joe.build
|
36
|
+
end
|
37
|
+
redirect request.path
|
38
|
+
end
|
39
|
+
|
40
|
+
user, pass = Config.cijoe.user.to_s, Config.cijoe.pass.to_s
|
41
|
+
if user != '' && pass != ''
|
42
|
+
use Rack::Auth::Basic do |username, password|
|
43
|
+
[ username, password ] == [ user, pass ]
|
44
|
+
end
|
45
|
+
puts "Using HTTP basic auth"
|
46
|
+
end
|
47
|
+
|
48
|
+
helpers do
|
49
|
+
include Rack::Utils
|
50
|
+
alias_method :h, :escape_html
|
51
|
+
|
52
|
+
# thanks integrity!
|
53
|
+
def ansi_color_codes(string)
|
54
|
+
string.gsub("\e[0m", '</span>').
|
55
|
+
gsub(/\e\[(\d+)m/, "<span class=\"color\\1\">")
|
56
|
+
end
|
57
|
+
|
58
|
+
def pretty_time(time)
|
59
|
+
time.strftime("%Y-%m-%d %H:%M")
|
60
|
+
end
|
61
|
+
|
62
|
+
def cijoe_root
|
63
|
+
root = request.path
|
64
|
+
root = "" if root == "/"
|
65
|
+
root
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def initialize(*args)
|
70
|
+
super
|
71
|
+
check_project
|
72
|
+
@joe = CIJoe.new(options.project_path)
|
73
|
+
|
74
|
+
CIJoe::Campfire.activate
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.start(host, port, project_path)
|
78
|
+
set :project_path, project_path
|
79
|
+
CIJoe::Server.run! :host => host, :port => port
|
80
|
+
end
|
81
|
+
|
82
|
+
def check_project
|
83
|
+
if options.project_path.nil? || !File.exists?(File.expand_path(options.project_path))
|
84
|
+
puts "Whoops! I need the path to a Git repo."
|
85
|
+
puts " $ git clone git@github.com:username/project.git project"
|
86
|
+
abort " $ cijoe project"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<li>
|
2
|
+
<span class="date"><%= pretty_time(build.finished_at) %></span> » Built <a href="<%= build.commit.url %>"><%= build.short_sha %></a> <span class="<%= build.status %>">(<%= build.status %>)</span>
|
3
|
+
<span class="metrics">
|
4
|
+
[ <a href="<%= build.metrics_url %>">Metrics</a> ]
|
5
|
+
</span>
|
6
|
+
<span class="coverage">
|
7
|
+
[ <a href="<%= build.coverage_url %>">Coverage</a> ]
|
8
|
+
</span>
|
9
|
+
<% if build.duration %>
|
10
|
+
in <span class="duration"><%= build.duration %></span> seconds.
|
11
|
+
<% end %>
|
12
|
+
</li>
|
@@ -0,0 +1,75 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<link href="<%= cijoe_root %>/screen.css" media="screen" rel="stylesheet" type="text/css" />
|
5
|
+
<link rel="shortcut icon" href="<%= cijoe_root %>/favicon.ico" type="image/x-icon" />
|
6
|
+
<title><%= h(joe.project) %>: CI Joe</title>
|
7
|
+
</head>
|
8
|
+
<body>
|
9
|
+
<div class="site">
|
10
|
+
<div class="title">
|
11
|
+
<a href="<%= cijoe_root %>/">CI Joe</a>
|
12
|
+
<span class="extra">because knowing is half the battle</span>
|
13
|
+
</div>
|
14
|
+
|
15
|
+
<div id="home">
|
16
|
+
<h1><a href="<%= joe.url %>"><%= joe.project %></a></h1>
|
17
|
+
<ul class="posts">
|
18
|
+
<% if joe.current_build %>
|
19
|
+
<li>
|
20
|
+
<span class="date"><%= pretty_time(joe.current_build.started_at) if joe.current_build %></span> »
|
21
|
+
<% if joe.current_build.sha %>
|
22
|
+
Building <a href="<%= joe.current_build.commit.url %>"><%= joe.current_build.short_sha %></a> <small>(pid: <%= joe.pid %>)</small>
|
23
|
+
<% else %>
|
24
|
+
Build starting...
|
25
|
+
<% end %>
|
26
|
+
</li>
|
27
|
+
<% else %>
|
28
|
+
<li><form method="POST"><input type="submit" value="Build"/></form></li>
|
29
|
+
<% end %>
|
30
|
+
|
31
|
+
<% if joe.last_build %>
|
32
|
+
<%= partial :build, :locals => {:build => joe.last_build} %>
|
33
|
+
<% if joe.last_build.failed? %>
|
34
|
+
<li><pre class="terminal"><code><%=ansi_color_codes h(joe.last_build.output) %></code></pre></li>
|
35
|
+
<% end %>
|
36
|
+
<% end %>
|
37
|
+
</ul>
|
38
|
+
|
39
|
+
<% unless joe.build_history.empty? %>
|
40
|
+
<h1><a href="<%= joe.url %>"><%= joe.project %></a> <span style="color: #999;">build history</span></h1>
|
41
|
+
<ul class="posts">
|
42
|
+
<% joe.build_history.each do |current_build| %>
|
43
|
+
<%= partial :build, :locals => {:build => current_build} %>
|
44
|
+
<% end %>
|
45
|
+
</ul>
|
46
|
+
<% end %>
|
47
|
+
|
48
|
+
</div>
|
49
|
+
|
50
|
+
<div class="footer">
|
51
|
+
<div class="contact">
|
52
|
+
<p>
|
53
|
+
<a href="http://github.com/defunkt/cijoe/tree/master#readme">Documentation</a><br/>
|
54
|
+
<a href="http://github.com/defunkt/cijoe">Source</a><br/>
|
55
|
+
<a href="http://github.com/defunkt/cijoe/issues">Issues</a><br/>
|
56
|
+
<a href="http://twitter.com/defunkt">Twitter</a>
|
57
|
+
</p>
|
58
|
+
</div>
|
59
|
+
<div class="contact">
|
60
|
+
<p>
|
61
|
+
Designed by <a href="http://tom.preston-werner.com/">Tom Preston-Werner</a><br/>
|
62
|
+
Influenced by <a href="http://integrityapp.com/">Integrity</a><br/>
|
63
|
+
Built with <a href="http://sinatrarb.com/">Sinatra</a><br/>
|
64
|
+
Keep it simple, Sam.
|
65
|
+
</p>
|
66
|
+
</div>
|
67
|
+
<div class="rss">
|
68
|
+
<a href="http://github.com/defunkt/cijoe">
|
69
|
+
<img src="<%= cijoe_root %>/octocat.png" alt="Octocat!" />
|
70
|
+
</a>
|
71
|
+
</div>
|
72
|
+
</div>
|
73
|
+
</div>
|
74
|
+
</body>
|
75
|
+
</html>
|
data/lib/cijoe.rb
ADDED
@@ -0,0 +1,237 @@
|
|
1
|
+
##
|
2
|
+
# CI Joe.
|
3
|
+
# Because knowing is half the battle.
|
4
|
+
#
|
5
|
+
# This is a stupid simple CI server. It can build one (1)
|
6
|
+
# git-based project only.
|
7
|
+
#
|
8
|
+
# It only remembers the last build.
|
9
|
+
#
|
10
|
+
# It only notifies to Campfire.
|
11
|
+
#
|
12
|
+
# It's a RAH (Real American Hero).
|
13
|
+
#
|
14
|
+
# Seriously, I'm gonna be nuts about keeping this simple.
|
15
|
+
|
16
|
+
require 'cijoe/version'
|
17
|
+
require 'cijoe/config'
|
18
|
+
require 'cijoe/commit'
|
19
|
+
require 'cijoe/build'
|
20
|
+
require 'cijoe/campfire'
|
21
|
+
require 'cijoe/server'
|
22
|
+
|
23
|
+
class CIJoe
|
24
|
+
|
25
|
+
attr_reader :user, :project, :url, :current_build, :last_build
|
26
|
+
|
27
|
+
def initialize(project_path)
|
28
|
+
project_path = File.expand_path(project_path)
|
29
|
+
Dir.chdir(project_path)
|
30
|
+
|
31
|
+
@user, @project = git_user_and_project
|
32
|
+
@url = "http://github.com/#{@user}/#{@project}"
|
33
|
+
|
34
|
+
@last_build = nil
|
35
|
+
@current_build = nil
|
36
|
+
|
37
|
+
trap("INT") { stop }
|
38
|
+
end
|
39
|
+
|
40
|
+
# is a build running?
|
41
|
+
def building?
|
42
|
+
!!@current_build
|
43
|
+
end
|
44
|
+
|
45
|
+
# the pid of the running child process
|
46
|
+
def pid
|
47
|
+
building? and current_build.pid
|
48
|
+
end
|
49
|
+
|
50
|
+
# kill the child and exit
|
51
|
+
def stop
|
52
|
+
# another build waits
|
53
|
+
if Config.cijoe.buildallfile && File.exist?(Config.cijoe.buildallfile.to_s)
|
54
|
+
# clean out on stop
|
55
|
+
FileUtils.rm(Config.cijoe.buildallfile.to_s)
|
56
|
+
end
|
57
|
+
|
58
|
+
Process.kill(9, pid) if pid
|
59
|
+
exit!
|
60
|
+
end
|
61
|
+
|
62
|
+
# build callbacks
|
63
|
+
def build_failed(output, error)
|
64
|
+
finish_build :failed, "#{error}\n\n#{output}"
|
65
|
+
run_hook "build-failed"
|
66
|
+
end
|
67
|
+
|
68
|
+
def build_worked(output)
|
69
|
+
finish_build :worked, output
|
70
|
+
run_hook "build-worked"
|
71
|
+
end
|
72
|
+
|
73
|
+
def finish_build(status, output)
|
74
|
+
@current_build.finished_at = Time.now
|
75
|
+
@current_build.status = status
|
76
|
+
@current_build.output = output
|
77
|
+
@last_build = @current_build
|
78
|
+
|
79
|
+
@current_build = nil
|
80
|
+
|
81
|
+
actual_build_id = "#{@last_build.commit.sha[0..7]}.#{Time.now.to_i}.build"
|
82
|
+
write_build actual_build_id, @last_build
|
83
|
+
write_build 'current', @current_build
|
84
|
+
write_build 'last', @last_build
|
85
|
+
@last_build.notify if @last_build.respond_to? :notify
|
86
|
+
|
87
|
+
# another build waits
|
88
|
+
if Config.cijoe.buildallfile && File.exist?(Config.cijoe.buildallfile.to_s)
|
89
|
+
# clean out before new build
|
90
|
+
FileUtils.rm(Config.cijoe.buildallfile.to_s)
|
91
|
+
build
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# run the build but make sure only one is running
|
96
|
+
# at a time (if new one comes in we will park it)
|
97
|
+
def build
|
98
|
+
if building?
|
99
|
+
# only if switched on to build all incoming requests
|
100
|
+
if Config.cijoe.buildallfile
|
101
|
+
# and there is no previous request
|
102
|
+
return if File.exist?(Config.cijoe.buildallfile.to_s)
|
103
|
+
# we will mark awaiting builds
|
104
|
+
FileUtils.touch(Config.cijoe.buildallfile.to_s)
|
105
|
+
end
|
106
|
+
# leave anyway because a current build runs
|
107
|
+
return
|
108
|
+
end
|
109
|
+
@current_build = Build.new(@user, @project)
|
110
|
+
write_build 'current', @current_build
|
111
|
+
Thread.new { build! }
|
112
|
+
end
|
113
|
+
|
114
|
+
def open_pipe(cmd)
|
115
|
+
read, write = IO.pipe
|
116
|
+
|
117
|
+
pid = fork do
|
118
|
+
read.close
|
119
|
+
$stdout.reopen write
|
120
|
+
exec cmd
|
121
|
+
end
|
122
|
+
|
123
|
+
write.close
|
124
|
+
|
125
|
+
yield read, pid
|
126
|
+
end
|
127
|
+
|
128
|
+
# update git then run the build
|
129
|
+
def build!
|
130
|
+
build = @current_build
|
131
|
+
output = ''
|
132
|
+
git_update
|
133
|
+
build.sha = git_sha
|
134
|
+
write_build 'current', build
|
135
|
+
|
136
|
+
open_pipe("#{runner_command} 2>&1") do |pipe, pid|
|
137
|
+
puts "#{Time.now.to_i}: Building #{build.short_sha}: pid=#{pid}"
|
138
|
+
|
139
|
+
build.pid = pid
|
140
|
+
write_build 'current', build
|
141
|
+
output = pipe.read
|
142
|
+
end
|
143
|
+
|
144
|
+
Process.waitpid(build.pid)
|
145
|
+
status = $?.exitstatus.to_i
|
146
|
+
puts "#{Time.now.to_i}: Built #{build.short_sha}: status=#{status}"
|
147
|
+
|
148
|
+
status == 0 ? build_worked(output) : build_failed('', output)
|
149
|
+
rescue Object => e
|
150
|
+
puts "Exception building: #{e.message} (#{e.class})"
|
151
|
+
build_failed('', e.to_s)
|
152
|
+
end
|
153
|
+
|
154
|
+
# shellin' out
|
155
|
+
def runner_command
|
156
|
+
runner = Config.cijoe.runner.to_s
|
157
|
+
runner == '' ? "rake -s test:units" : runner
|
158
|
+
end
|
159
|
+
|
160
|
+
def git_sha
|
161
|
+
`git rev-parse origin/#{git_branch}`.chomp
|
162
|
+
end
|
163
|
+
|
164
|
+
def git_update
|
165
|
+
`git fetch origin && git reset --hard origin/#{git_branch}`
|
166
|
+
run_hook "after-reset"
|
167
|
+
end
|
168
|
+
|
169
|
+
def git_user_and_project
|
170
|
+
Config.remote.origin.url.to_s.chomp('.git').split(':')[-1].split('/')[-2, 2]
|
171
|
+
end
|
172
|
+
|
173
|
+
def git_branch
|
174
|
+
branch = Config.cijoe.branch.to_s
|
175
|
+
branch == '' ? "master" : branch
|
176
|
+
end
|
177
|
+
|
178
|
+
# massage our repo
|
179
|
+
def run_hook(hook)
|
180
|
+
if File.exists?(file=".git/hooks/#{hook}") && File.executable?(file)
|
181
|
+
data =
|
182
|
+
if @last_build && @last_build.commit
|
183
|
+
{
|
184
|
+
"MESSAGE" => @last_build.commit.message,
|
185
|
+
"AUTHOR" => @last_build.commit.author,
|
186
|
+
"SHA" => @last_build.commit.sha,
|
187
|
+
"OUTPUT" => @last_build.clean_output
|
188
|
+
}
|
189
|
+
else
|
190
|
+
{}
|
191
|
+
end
|
192
|
+
data.each{ |k, v| ENV[k] = v }
|
193
|
+
`sh #{file}`
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# restore current / last build state from disk.
|
198
|
+
def restore
|
199
|
+
unless @last_build
|
200
|
+
@last_build = read_build('last')
|
201
|
+
end
|
202
|
+
|
203
|
+
unless @current_build
|
204
|
+
@current_build = read_build('current')
|
205
|
+
end
|
206
|
+
|
207
|
+
Process.kill(0, @current_build.pid) if @current_build && @current_build.pid
|
208
|
+
rescue Errno::ESRCH
|
209
|
+
# build pid isn't running anymore. assume previous
|
210
|
+
# server died and reset.
|
211
|
+
@current_build = nil
|
212
|
+
end
|
213
|
+
|
214
|
+
# write build info for build to file.
|
215
|
+
def write_build(name, build)
|
216
|
+
filename = ".git/builds/#{name}"
|
217
|
+
Dir.mkdir '.git/builds' unless File.directory?('.git/builds')
|
218
|
+
if build
|
219
|
+
build.dump filename
|
220
|
+
elsif File.exist?(filename)
|
221
|
+
File.unlink filename
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# load build info from file.
|
226
|
+
def read_build(name)
|
227
|
+
Build.load(".git/builds/#{name}")
|
228
|
+
end
|
229
|
+
|
230
|
+
def build_history
|
231
|
+
build_entries = Dir.open(".git/builds").entries.select { |dir| dir.match /^([a-z0-9]{8})\.([\d]{10})\.build$/ }
|
232
|
+
history = build_entries.inject([]) do |builds, entry|
|
233
|
+
builds << read_build(entry)
|
234
|
+
end
|
235
|
+
history
|
236
|
+
end
|
237
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
5
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
6
|
+
require 'cijoe'
|
7
|
+
|
8
|
+
CIJoe::Server.set :project_path, "."
|
9
|
+
CIJoe::Server.set :environment, "test"
|
10
|
+
|
11
|
+
class Test::Unit::TestCase
|
12
|
+
end
|
data/test/test_cijoe.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestCIJoe < Test::Unit::TestCase
|
4
|
+
def test_raise_error_on_invalid_command
|
5
|
+
assert_raise RuntimeError, LoadError do
|
6
|
+
CIJoe::Config.new('--invalid').to_s
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_return_value_of_config
|
11
|
+
assert_equal `git config blame`.chomp, CIJoe::Config.new('blame').to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_return_empty_string_when_config_does_not_exist
|
15
|
+
assert_equal '', CIJoe::Config.new('invalid').to_s
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "helper"
|
2
|
+
require "rack/test"
|
3
|
+
require "cijoe/server"
|
4
|
+
|
5
|
+
class TestCIJoeServer < Test::Unit::TestCase
|
6
|
+
include Rack::Test::Methods
|
7
|
+
|
8
|
+
class ::CIJoe
|
9
|
+
attr_writer :current_build, :last_build
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_accessor :app
|
13
|
+
|
14
|
+
def setup
|
15
|
+
@app = CIJoe::Server.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_ping
|
19
|
+
app.joe.last_build = build :worked
|
20
|
+
assert !app.joe.building?, "have a last build, but not a current"
|
21
|
+
|
22
|
+
get "/ping"
|
23
|
+
assert_equal 200, last_response.status
|
24
|
+
assert_equal app.joe.last_build.sha, last_response.body
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_ping_building
|
28
|
+
app.joe.current_build = build :building
|
29
|
+
assert app.joe.building?, "buildin' a awsum project"
|
30
|
+
|
31
|
+
get "/ping"
|
32
|
+
assert_equal 412, last_response.status
|
33
|
+
assert_equal "building", last_response.body
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_ping_failed
|
37
|
+
app.joe.last_build = build :failed
|
38
|
+
|
39
|
+
get "/ping"
|
40
|
+
assert_equal 412, last_response.status
|
41
|
+
assert_equal app.joe.last_build.sha, last_response.body
|
42
|
+
end
|
43
|
+
|
44
|
+
# Create a new, fake build. All we care about is status.
|
45
|
+
|
46
|
+
def build status
|
47
|
+
CIJoe::Build.new "user", "project", Time.now, Time.now,
|
48
|
+
"deadbeef", status, "output", 1337
|
49
|
+
end
|
50
|
+
end
|