joshuapinter-cijoe 0.9.3
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/LICENSE +20 -0
- data/README.md +177 -0
- data/Rakefile +31 -0
- data/bin/cijoe +50 -0
- data/lib/cijoe/build.rb +67 -0
- data/lib/cijoe/campfire.rb +74 -0
- data/lib/cijoe/commit.rb +31 -0
- data/lib/cijoe/config.rb +43 -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/queue.rb +55 -0
- data/lib/cijoe/server.rb +114 -0
- data/lib/cijoe/version.rb +3 -0
- data/lib/cijoe/views/json.erb +16 -0
- data/lib/cijoe/views/template.erb +74 -0
- data/lib/cijoe.rb +225 -0
- data/test/fixtures/payload.json +52 -0
- data/test/helper.rb +47 -0
- data/test/test_campfire.rb +48 -0
- data/test/test_cijoe.rb +17 -0
- data/test/test_cijoe_queue.rb +28 -0
- data/test/test_cijoe_server.rb +128 -0
- data/test/test_hooks.rb +81 -0
- metadata +139 -0
data/lib/cijoe/queue.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
class CIJoe
|
2
|
+
# An in memory queue used for maintaining an order list of requested
|
3
|
+
# builds.
|
4
|
+
class Queue
|
5
|
+
# enabled - determines whether builds should be queued or not.
|
6
|
+
def initialize(enabled, verbose=false)
|
7
|
+
@enabled = enabled
|
8
|
+
@verbose = verbose
|
9
|
+
@queue = []
|
10
|
+
|
11
|
+
log("Build queueing enabled") if enabled
|
12
|
+
end
|
13
|
+
|
14
|
+
# Public: Appends a branch to be built, unless it already exists
|
15
|
+
# within the queue.
|
16
|
+
#
|
17
|
+
# branch - the name of the branch to build or nil if the default
|
18
|
+
# should be built.
|
19
|
+
#
|
20
|
+
# Returns nothing
|
21
|
+
def append_unless_already_exists(branch)
|
22
|
+
return unless enabled?
|
23
|
+
unless @queue.include? branch
|
24
|
+
@queue << branch
|
25
|
+
log "#{Time.now.to_i}: Queueing #{branch}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns a String of the next branch to build
|
30
|
+
def next_branch_to_build
|
31
|
+
branch = @queue.shift
|
32
|
+
log "#{Time.now.to_i}: De-queueing #{branch}"
|
33
|
+
branch
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns true if there are requested builds waiting and false
|
37
|
+
# otherwise.
|
38
|
+
def waiting?
|
39
|
+
if enabled?
|
40
|
+
not @queue.empty?
|
41
|
+
else
|
42
|
+
false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
def log(msg)
|
48
|
+
puts msg if @verbose
|
49
|
+
end
|
50
|
+
|
51
|
+
def enabled?
|
52
|
+
@enabled
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/cijoe/server.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'erb'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
class CIJoe
|
6
|
+
class Server < Sinatra::Base
|
7
|
+
attr_reader :joe
|
8
|
+
|
9
|
+
dir = File.dirname(File.expand_path(__FILE__))
|
10
|
+
|
11
|
+
set :views, "#{dir}/views"
|
12
|
+
set :public_folder, "#{dir}/public"
|
13
|
+
set :static, true
|
14
|
+
set :lock, true
|
15
|
+
|
16
|
+
before { joe.restore }
|
17
|
+
|
18
|
+
get '/ping' do
|
19
|
+
if joe.building? || !joe.last_build || !joe.last_build.worked?
|
20
|
+
halt 412, (joe.building? || joe.last_build.nil?) ? "building" : joe.last_build.sha
|
21
|
+
end
|
22
|
+
|
23
|
+
joe.last_build.sha
|
24
|
+
end
|
25
|
+
|
26
|
+
get '/?' do
|
27
|
+
erb(:template, {}, :joe => joe)
|
28
|
+
end
|
29
|
+
|
30
|
+
post '/?' do
|
31
|
+
unless params[:rebuild]
|
32
|
+
payload = JSON.parse(params[:payload])
|
33
|
+
pushed_branch = payload["ref"].split('/').last
|
34
|
+
end
|
35
|
+
|
36
|
+
# Only build if we were given an explicit branch via `?branch=blah`
|
37
|
+
# or the payload exists and the "ref" property matches our
|
38
|
+
# specified build branch.
|
39
|
+
if params[:branch] || params[:rebuild] || pushed_branch == joe.git_branch
|
40
|
+
joe.build(params[:branch])
|
41
|
+
end
|
42
|
+
|
43
|
+
redirect request.path
|
44
|
+
end
|
45
|
+
|
46
|
+
get '/api/json' do
|
47
|
+
response = [200, {'Content-Type' => 'application/json'}]
|
48
|
+
response_json = erb(:json, {}, :joe => joe)
|
49
|
+
if params[:jsonp]
|
50
|
+
response << params[:jsonp] + '(' + response_json + ')'
|
51
|
+
else
|
52
|
+
response << response_json
|
53
|
+
end
|
54
|
+
response
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
helpers do
|
59
|
+
include Rack::Utils
|
60
|
+
alias_method :h, :escape_html
|
61
|
+
|
62
|
+
# thanks integrity!
|
63
|
+
def ansi_color_codes(string)
|
64
|
+
string.gsub("\e[0m", '</span>').
|
65
|
+
gsub(/\e\[(\d+)m/, "<span class=\"color\\1\">")
|
66
|
+
end
|
67
|
+
|
68
|
+
def pretty_time(time)
|
69
|
+
time.strftime("%Y-%m-%d %H:%M")
|
70
|
+
end
|
71
|
+
|
72
|
+
def cijoe_root
|
73
|
+
root = request.path
|
74
|
+
root = "" if root == "/"
|
75
|
+
root
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def initialize(*args)
|
80
|
+
super
|
81
|
+
check_project
|
82
|
+
@joe = CIJoe.new(options.project_path)
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.start(host, port, project_path)
|
86
|
+
set :project_path, project_path
|
87
|
+
CIJoe::Server.run! :host => host, :port => port
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.rack_start(project_path)
|
91
|
+
set :project_path, project_path
|
92
|
+
self.new
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.project_path=(project_path)
|
96
|
+
user, pass = Config.cijoe(project_path).user.to_s, Config.cijoe(project_path).pass.to_s
|
97
|
+
if user != '' && pass != ''
|
98
|
+
use Rack::Auth::Basic do |username, password|
|
99
|
+
[ username, password ] == [ user, pass ]
|
100
|
+
end
|
101
|
+
puts "Using HTTP basic auth"
|
102
|
+
end
|
103
|
+
set :project_path, Proc.new{project_path}, true
|
104
|
+
end
|
105
|
+
|
106
|
+
def check_project
|
107
|
+
if options.project_path.nil? || !File.exists?(File.expand_path(options.project_path))
|
108
|
+
puts "Whoops! I need the path to a Git repo."
|
109
|
+
puts " $ git clone git@github.com:username/project.git project"
|
110
|
+
abort " $ cijoe project"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
{ "jobs": [
|
2
|
+
<% if joe.last_build %>
|
3
|
+
{"name":"<%= joe.project %>",
|
4
|
+
"url":"<%= joe.url %>",
|
5
|
+
"color":"<%= joe.last_build.status.to_s == "failed" ? 'red' : 'blue' %>",
|
6
|
+
"status":"<%= joe.last_build.status %>",
|
7
|
+
"started_at":"<%= pretty_time(joe.last_build.started_at) %>",
|
8
|
+
"finished_at":"<%= pretty_time(joe.last_build.finished_at) %>",
|
9
|
+
"duration":"<%= joe.last_build.duration if joe.last_build.duration %>",
|
10
|
+
"sha":"<%= joe.last_build.sha %>",
|
11
|
+
"short_sha":"<%= joe.last_build.short_sha %>",
|
12
|
+
"commit_url":"<%= joe.last_build.commit.url if joe.last_build.commit %>",
|
13
|
+
"branch":"<%= joe.last_build.branch %>"
|
14
|
+
}
|
15
|
+
<% end %>
|
16
|
+
]}
|
@@ -0,0 +1,74 @@
|
|
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 <%= joe.current_build.branch %> at <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="hidden", name="rebuild" value="true"><input type="submit" value="Build"/></form></li>
|
29
|
+
<% end %>
|
30
|
+
|
31
|
+
<% if joe.last_build %>
|
32
|
+
<li>
|
33
|
+
<span class="date"><%= pretty_time(joe.last_build.finished_at) %></span> »
|
34
|
+
<% if joe.last_build.sha %>
|
35
|
+
Built <%= joe.last_build.branch %> at <a href="<%= joe.last_build.commit.url %>"><%= joe.last_build.short_sha %></a>
|
36
|
+
<% end %>
|
37
|
+
<span class="<%= joe.last_build.status %>">(<%= joe.last_build.status %>)</span>
|
38
|
+
<% if joe.last_build.duration %>
|
39
|
+
in <span class="duration"><%= joe.last_build.duration %></span> seconds.
|
40
|
+
<% end %>
|
41
|
+
</li>
|
42
|
+
<% if joe.last_build.failed? %>
|
43
|
+
<li><pre class="terminal"><code><%=ansi_color_codes h(joe.last_build.output) %></code></pre></li>
|
44
|
+
<% end %>
|
45
|
+
<% end %>
|
46
|
+
</ul>
|
47
|
+
</div>
|
48
|
+
|
49
|
+
<div class="footer">
|
50
|
+
<div class="contact">
|
51
|
+
<p>
|
52
|
+
<a href="https://github.com/defunkt/cijoe#readme">Documentation</a><br/>
|
53
|
+
<a href="https://github.com/defunkt/cijoe">Source</a><br/>
|
54
|
+
<a href="https://github.com/defunkt/cijoe/issues">Issues</a><br/>
|
55
|
+
<a href="https://github.com/defunkt/cijoe/tree/v<%= CIJoe::VERSION %>">v<%= CIJoe::VERSION %></a>
|
56
|
+
</p>
|
57
|
+
</div>
|
58
|
+
<div class="contact">
|
59
|
+
<p>
|
60
|
+
Designed by <a href="http://tom.preston-werner.com/">Tom Preston-Werner</a><br/>
|
61
|
+
Influenced by <a href="http://integrityapp.com/">Integrity</a><br/>
|
62
|
+
Built with <a href="http://sinatrarb.com/">Sinatra</a><br/>
|
63
|
+
Keep it simple, Sam.
|
64
|
+
</p>
|
65
|
+
</div>
|
66
|
+
<div class="rss">
|
67
|
+
<a href="http://github.com/defunkt/cijoe">
|
68
|
+
<img src="<%= cijoe_root %>/octocat.png" alt="Octocat!" />
|
69
|
+
</a>
|
70
|
+
</div>
|
71
|
+
</div>
|
72
|
+
</div>
|
73
|
+
</body>
|
74
|
+
</html>
|
data/lib/cijoe.rb
ADDED
@@ -0,0 +1,225 @@
|
|
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
|
+
require 'cijoe/queue'
|
23
|
+
|
24
|
+
class CIJoe
|
25
|
+
attr_reader :user, :project, :url, :current_build, :last_build, :campfire
|
26
|
+
|
27
|
+
def initialize(project_path)
|
28
|
+
@project_path = File.expand_path(project_path)
|
29
|
+
|
30
|
+
@user, @project = git_user_and_project
|
31
|
+
@url = "http://github.com/#{@user}/#{@project}"
|
32
|
+
|
33
|
+
@campfire = CIJoe::Campfire.new(project_path)
|
34
|
+
|
35
|
+
@last_build = nil
|
36
|
+
@current_build = nil
|
37
|
+
@queue = Queue.new(!repo_config.buildqueue.to_s.empty?, true)
|
38
|
+
|
39
|
+
trap("INT") { stop }
|
40
|
+
end
|
41
|
+
|
42
|
+
# is a build running?
|
43
|
+
def building?
|
44
|
+
!!@current_build
|
45
|
+
end
|
46
|
+
|
47
|
+
# the pid of the running child process
|
48
|
+
def pid
|
49
|
+
building? and current_build.pid
|
50
|
+
end
|
51
|
+
|
52
|
+
# kill the child and exit
|
53
|
+
def stop
|
54
|
+
Process.kill(9, pid) if pid
|
55
|
+
exit!
|
56
|
+
end
|
57
|
+
|
58
|
+
# build callbacks
|
59
|
+
def build_failed(output, error)
|
60
|
+
finish_build :failed, "#{error}\n\n#{output}"
|
61
|
+
run_hook "build-failed"
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_worked(output)
|
65
|
+
finish_build :worked, output
|
66
|
+
run_hook "build-worked"
|
67
|
+
end
|
68
|
+
|
69
|
+
def finish_build(status, output)
|
70
|
+
@current_build.finished_at = Time.now
|
71
|
+
@current_build.status = status
|
72
|
+
@current_build.output = output
|
73
|
+
@last_build = @current_build
|
74
|
+
|
75
|
+
@current_build = nil
|
76
|
+
write_build 'current', @current_build
|
77
|
+
write_build 'last', @last_build
|
78
|
+
@campfire.notify(@last_build) if @campfire.valid?
|
79
|
+
|
80
|
+
build(@queue.next_branch_to_build) if @queue.waiting?
|
81
|
+
end
|
82
|
+
|
83
|
+
# run the build but make sure only one is running
|
84
|
+
# at a time (if new one comes in we will park it)
|
85
|
+
def build(branch=nil)
|
86
|
+
if building?
|
87
|
+
@queue.append_unless_already_exists(branch)
|
88
|
+
# leave anyway because a current build runs
|
89
|
+
return
|
90
|
+
end
|
91
|
+
@current_build = Build.new(@project_path, @user, @project)
|
92
|
+
write_build 'current', @current_build
|
93
|
+
Thread.new { build!(branch) }
|
94
|
+
end
|
95
|
+
|
96
|
+
def open_pipe(cmd)
|
97
|
+
read, write = IO.pipe
|
98
|
+
|
99
|
+
pid = fork do
|
100
|
+
read.close
|
101
|
+
$stdout.reopen write
|
102
|
+
exec cmd
|
103
|
+
end
|
104
|
+
|
105
|
+
write.close
|
106
|
+
|
107
|
+
yield read, pid
|
108
|
+
end
|
109
|
+
|
110
|
+
# update git then run the build
|
111
|
+
def build!(branch=nil)
|
112
|
+
@git_branch = branch
|
113
|
+
build = @current_build
|
114
|
+
output = ''
|
115
|
+
git_update
|
116
|
+
build.sha = git_sha
|
117
|
+
build.branch = git_branch
|
118
|
+
write_build 'current', build
|
119
|
+
|
120
|
+
open_pipe("cd \"#{@project_path}\" && #{runner_command} 2>&1") do |pipe, pid|
|
121
|
+
puts "#{Time.now.to_i}: Building #{build.branch} at #{build.short_sha}: pid=#{pid}"
|
122
|
+
|
123
|
+
build.pid = pid
|
124
|
+
write_build 'current', build
|
125
|
+
output = pipe.read
|
126
|
+
end
|
127
|
+
|
128
|
+
Process.waitpid(build.pid, 1)
|
129
|
+
status = $?.exitstatus.to_i
|
130
|
+
@current_build = build
|
131
|
+
puts "#{Time.now.to_i}: Built #{build.short_sha}: status=#{status}"
|
132
|
+
|
133
|
+
status == 0 ? build_worked(output) : build_failed('', output)
|
134
|
+
rescue Object => e
|
135
|
+
puts "Exception building: #{e.message} (#{e.class})"
|
136
|
+
build_failed('', e.to_s)
|
137
|
+
end
|
138
|
+
|
139
|
+
# shellin' out
|
140
|
+
def runner_command
|
141
|
+
runner = repo_config.runner.to_s
|
142
|
+
runner == '' ? "rake -s test:units" : runner
|
143
|
+
end
|
144
|
+
|
145
|
+
def git_sha
|
146
|
+
`cd \"#{@project_path}\" && git rev-parse origin/#{git_branch}`.chomp
|
147
|
+
end
|
148
|
+
|
149
|
+
def git_update
|
150
|
+
`cd \"#{@project_path}\" && git fetch origin && git reset --hard origin/#{git_branch}`
|
151
|
+
run_hook "after-reset"
|
152
|
+
end
|
153
|
+
|
154
|
+
def git_user_and_project
|
155
|
+
Config.remote(@project_path).origin.url.to_s.chomp('.git').split(':')[-1].split('/')[-2, 2]
|
156
|
+
end
|
157
|
+
|
158
|
+
def git_branch
|
159
|
+
return @git_branch if @git_branch
|
160
|
+
branch = repo_config.branch.to_s
|
161
|
+
@git_branch = branch == '' ? "master" : branch
|
162
|
+
end
|
163
|
+
|
164
|
+
# massage our repo
|
165
|
+
def run_hook(hook)
|
166
|
+
if File.exists?(file=path_in_project(".git/hooks/#{hook}")) && File.executable?(file)
|
167
|
+
data =
|
168
|
+
if @last_build && @last_build.commit
|
169
|
+
{
|
170
|
+
"MESSAGE" => @last_build.commit.message,
|
171
|
+
"AUTHOR" => @last_build.commit.author,
|
172
|
+
"SHA" => @last_build.commit.sha,
|
173
|
+
"OUTPUT" => @last_build.env_output
|
174
|
+
}
|
175
|
+
else
|
176
|
+
{}
|
177
|
+
end
|
178
|
+
|
179
|
+
orig_ENV = ENV.to_hash
|
180
|
+
ENV.clear
|
181
|
+
data.each{ |k, v| ENV[k] = v }
|
182
|
+
output = `cd \"#{@project_path}\" && sh #{file}`
|
183
|
+
|
184
|
+
ENV.clear
|
185
|
+
orig_ENV.to_hash.each{ |k, v| ENV[k] = v}
|
186
|
+
output
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# restore current / last build state from disk.
|
191
|
+
def restore
|
192
|
+
@last_build = read_build('last')
|
193
|
+
@current_build = read_build('current')
|
194
|
+
|
195
|
+
Process.kill(0, @current_build.pid) if @current_build && @current_build.pid
|
196
|
+
rescue Errno::ESRCH
|
197
|
+
# build pid isn't running anymore. assume previous
|
198
|
+
# server died and reset.
|
199
|
+
@current_build = nil
|
200
|
+
end
|
201
|
+
|
202
|
+
def path_in_project(path)
|
203
|
+
File.join(@project_path, path)
|
204
|
+
end
|
205
|
+
|
206
|
+
# write build info for build to file.
|
207
|
+
def write_build(name, build)
|
208
|
+
filename = path_in_project(".git/builds/#{name}")
|
209
|
+
Dir.mkdir path_in_project('.git/builds') unless File.directory?(path_in_project('.git/builds'))
|
210
|
+
if build
|
211
|
+
build.dump filename
|
212
|
+
elsif File.exist?(filename)
|
213
|
+
File.unlink filename
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def repo_config
|
218
|
+
Config.cijoe(@project_path)
|
219
|
+
end
|
220
|
+
|
221
|
+
# load build info from file.
|
222
|
+
def read_build(name)
|
223
|
+
Build.load(path_in_project(".git/builds/#{name}"), @project_path)
|
224
|
+
end
|
225
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
{
|
2
|
+
"after": "416cb2f7105e7f989bc223d1a975b93a6491b276",
|
3
|
+
"before": "0ae4da1eea2d191b33ebfbd5db48e3c1f91953ad",
|
4
|
+
"commits": [
|
5
|
+
{
|
6
|
+
"added": [
|
7
|
+
|
8
|
+
],
|
9
|
+
"author": {
|
10
|
+
"email": "joshua.owens@gmail.com",
|
11
|
+
"name": "Josh Owens",
|
12
|
+
"username": "queso"
|
13
|
+
},
|
14
|
+
"id": "09293a1703b3bdb36aba70d38abd5e44396c50a5",
|
15
|
+
"message": "Update README",
|
16
|
+
"modified": [
|
17
|
+
"README.textile"
|
18
|
+
],
|
19
|
+
"removed": [
|
20
|
+
|
21
|
+
],
|
22
|
+
"timestamp": "2011-02-07T15:27:35-08:00",
|
23
|
+
"url": "https:\/\/github.com\/fourbeansoup\/broth\/commit\/09293a1703b3bdb36aba70d38abd5e44396c50a5"
|
24
|
+
}
|
25
|
+
],
|
26
|
+
"compare": "https:\/\/github.com\/fourbeansoup\/broth\/compare\/0ae4da1...416cb2f",
|
27
|
+
"forced": false,
|
28
|
+
"ref": "refs\/heads\/master",
|
29
|
+
"repository": {
|
30
|
+
"created_at": "2009\/12\/05 10:44:57 -0800",
|
31
|
+
"description": "FourBeanSoup's Broth Application, a tasty starting point for every app",
|
32
|
+
"fork": true,
|
33
|
+
"forks": 4,
|
34
|
+
"has_downloads": true,
|
35
|
+
"has_issues": true,
|
36
|
+
"has_wiki": true,
|
37
|
+
"homepage": "",
|
38
|
+
"language": "JavaScript",
|
39
|
+
"name": "broth",
|
40
|
+
"open_issues": 2,
|
41
|
+
"organization": "fourbeansoup",
|
42
|
+
"owner": {
|
43
|
+
"email": "josh+scm@fourbeansoup.com",
|
44
|
+
"name": "fourbeansoup"
|
45
|
+
},
|
46
|
+
"private": false,
|
47
|
+
"pushed_at": "2011\/02\/07 17:17:00 -0800",
|
48
|
+
"size": 604,
|
49
|
+
"url": "https:\/\/github.com\/fourbeansoup\/broth",
|
50
|
+
"watchers": 10
|
51
|
+
}
|
52
|
+
}
|
data/test/helper.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'mocha'
|
4
|
+
|
5
|
+
ENV['RACK_ENV'] = 'test'
|
6
|
+
|
7
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
8
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
9
|
+
require 'cijoe'
|
10
|
+
|
11
|
+
CIJoe::Server.set :project_path, "."
|
12
|
+
CIJoe::Server.set :environment, "test"
|
13
|
+
|
14
|
+
TMP_DIR = '/tmp/cijoe_test'
|
15
|
+
|
16
|
+
def tmp_dir
|
17
|
+
TMP_DIR
|
18
|
+
end
|
19
|
+
|
20
|
+
def setup_git_info(options = {})
|
21
|
+
@tmp_dirs ||= []
|
22
|
+
@tmp_dirs += [options[:tmp_dir]]
|
23
|
+
create_tmpdir!(options[:tmp_dir])
|
24
|
+
dir = options[:tmp_dir] || tmp_dir
|
25
|
+
`cd #{dir} && git init`
|
26
|
+
options[:config].each do |key, value|
|
27
|
+
`cd #{dir} && git config --add #{key} "#{value}"`
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def teardown_git_info
|
32
|
+
remove_tmpdir!
|
33
|
+
@tmp_dirs.each do |dir|
|
34
|
+
remove_tmpdir!(dir)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def remove_tmpdir!(passed_dir = nil)
|
39
|
+
FileUtils.rm_rf(passed_dir || tmp_dir)
|
40
|
+
end
|
41
|
+
|
42
|
+
def create_tmpdir!(passed_dir = nil)
|
43
|
+
FileUtils.mkdir_p(passed_dir || tmp_dir)
|
44
|
+
end
|
45
|
+
|
46
|
+
class Test::Unit::TestCase
|
47
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require "helper"
|
2
|
+
require "cijoe"
|
3
|
+
require "fakefs/safe"
|
4
|
+
|
5
|
+
|
6
|
+
class TestCampfire < Test::Unit::TestCase
|
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
|
+
joe = @app.joe
|
17
|
+
|
18
|
+
# make Build#restore a no-op so we don't overwrite our current/last
|
19
|
+
# build attributes set from tests.
|
20
|
+
def joe.restore
|
21
|
+
end
|
22
|
+
|
23
|
+
# make CIJoe#build! and CIJoe#git_update a no-op so we don't overwrite our local changes
|
24
|
+
# or local commits nor should we run tests.
|
25
|
+
def joe.build!
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def teardown
|
30
|
+
teardown_git_info
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_campfire_pulls_campfire_config_from_git_config
|
34
|
+
setup_git_info(:config => {"campfire.subdomain" => "github", "remote.origin.url" => "https://github.com/defunkt/cijoe.git"})
|
35
|
+
cf = CIJoe::Campfire.new(tmp_dir)
|
36
|
+
assert_equal "github", cf.campfire_config[:subdomain]
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_campfire_pulls_campfire_config_from_its_own_git_config
|
40
|
+
setup_git_info(:config => {"campfire.subdomain" => "github"})
|
41
|
+
setup_git_info(:config => {"campfire.subdomain" => "37signals"}, :tmp_dir => "/tmp/cijoe_test_37signals")
|
42
|
+
cf1 = CIJoe::Campfire.new(tmp_dir)
|
43
|
+
cf2 = CIJoe::Campfire.new("/tmp/cijoe_test_37signals")
|
44
|
+
assert_equal "github", cf1.campfire_config[:subdomain]
|
45
|
+
assert_equal "37signals", cf2.campfire_config[:subdomain]
|
46
|
+
end
|
47
|
+
|
48
|
+
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,28 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestCIJoeQueue < Test::Unit::TestCase
|
4
|
+
def test_a_disabled_queue
|
5
|
+
subject = CIJoe::Queue.new(false)
|
6
|
+
subject.append_unless_already_exists("test")
|
7
|
+
assert_equal false, subject.waiting?
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_adding_two_items_to_a_queue
|
11
|
+
subject = CIJoe::Queue.new(true)
|
12
|
+
subject.append_unless_already_exists("test")
|
13
|
+
subject.append_unless_already_exists(nil)
|
14
|
+
assert_equal true, subject.waiting?
|
15
|
+
assert_equal "test", subject.next_branch_to_build
|
16
|
+
assert_equal nil, subject.next_branch_to_build
|
17
|
+
assert_equal false, subject.waiting?
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_adding_two_duplicate_items_to_a_queue
|
21
|
+
subject = CIJoe::Queue.new(true)
|
22
|
+
subject.append_unless_already_exists("test")
|
23
|
+
subject.append_unless_already_exists("test")
|
24
|
+
assert_equal true, subject.waiting?
|
25
|
+
assert_equal "test", subject.next_branch_to_build
|
26
|
+
assert_equal false, subject.waiting?
|
27
|
+
end
|
28
|
+
end
|