joshuapinter-cijoe 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|