juici 0.0.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +74 -0
- data/Procfile +1 -0
- data/README.md +138 -0
- data/Rakefile +33 -0
- data/TODO.md +28 -0
- data/bin/juici +7 -0
- data/bin/juicic +54 -0
- data/config/mongoid.yml.sample +25 -0
- data/juici-interface.gemspec +19 -0
- data/juici.gemspec +32 -0
- data/lib/juici.rb +24 -0
- data/lib/juici/app.rb +67 -0
- data/lib/juici/build_environment.rb +38 -0
- data/lib/juici/build_logic.rb +37 -0
- data/lib/juici/build_queue.rb +111 -0
- data/lib/juici/callback.rb +26 -0
- data/lib/juici/config.rb +11 -0
- data/lib/juici/controllers.rb +6 -0
- data/lib/juici/controllers/base.rb +26 -0
- data/lib/juici/controllers/build_queue.rb +14 -0
- data/lib/juici/controllers/builds.rb +74 -0
- data/lib/juici/controllers/index.rb +20 -0
- data/lib/juici/controllers/trigger.rb +85 -0
- data/lib/juici/database.rb +25 -0
- data/lib/juici/exceptions.rb +2 -0
- data/lib/juici/find_logic.rb +11 -0
- data/lib/juici/helpers/form_helpers.rb +11 -0
- data/lib/juici/helpers/html_helpers.rb +4 -0
- data/lib/juici/helpers/url_helpers.rb +38 -0
- data/lib/juici/interface.rb +13 -0
- data/lib/juici/models/build.rb +190 -0
- data/lib/juici/models/build_process.rb +7 -0
- data/lib/juici/models/project.rb +9 -0
- data/lib/juici/server.rb +172 -0
- data/lib/juici/version.rb +8 -0
- data/lib/juici/views/README.markdown +138 -0
- data/lib/juici/views/about.erb +16 -0
- data/lib/juici/views/builds.erb +7 -0
- data/lib/juici/views/builds/edit.erb +23 -0
- data/lib/juici/views/builds/list.erb +27 -0
- data/lib/juici/views/builds/new.erb +43 -0
- data/lib/juici/views/builds/show.erb +4 -0
- data/lib/juici/views/index.erb +30 -0
- data/lib/juici/views/layout.erb +44 -0
- data/lib/juici/views/not_found.erb +3 -0
- data/lib/juici/views/partials/builds/debug.erb +22 -0
- data/lib/juici/views/partials/builds/output.erb +1 -0
- data/lib/juici/views/partials/builds/show.erb +19 -0
- data/lib/juici/views/partials/builds/sidebar.erb +13 -0
- data/lib/juici/views/partials/index/recently_built.erb +19 -0
- data/lib/juici/views/queue/list.erb +6 -0
- data/lib/juici/views/redirect.erb +0 -0
- data/lib/juici/views/support.erb +6 -0
- data/lib/juici/watcher.rb +48 -0
- data/public/favicon.ico +0 -0
- data/public/images/black_denim.png +0 -0
- data/public/styles/builds.css +62 -0
- data/public/styles/juici.css +226 -0
- data/public/vendor/bootstrap.css +6004 -0
- data/public/vendor/bootstrap.js +2036 -0
- data/public/vendor/img/glyphicons-halflings-white.png +0 -0
- data/public/vendor/jquery.js +9440 -0
- data/sample/mongod.conf +5 -0
- data/script/cibuild +10 -0
- data/spec/build_callback_spec.rb +54 -0
- data/spec/build_environment_spec.rb +53 -0
- data/spec/build_process_spec.rb +96 -0
- data/spec/build_queue_spec.rb +63 -0
- data/spec/controllers/builds_spec.rb +68 -0
- data/spec/controllers/index_spec.rb +28 -0
- data/spec/juici_app_spec.rb +8 -0
- data/spec/models/build_spec.rb +54 -0
- data/spec/spec_helper.rb +26 -0
- metadata +290 -0
data/sample/mongod.conf
ADDED
data/script/cibuild
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Juicy::Build::Callback" do
|
4
|
+
|
5
|
+
it "should call #process! on each of it's callbacks" do
|
6
|
+
def mock_callback
|
7
|
+
mock(:payload= => nil).tap do |callback|
|
8
|
+
callback.expects(:process!).once
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
callbacks = ["rawr", "test"]
|
13
|
+
|
14
|
+
callbacks.each do |callback|
|
15
|
+
Juici::Callback.stubs(:new).with(callback).
|
16
|
+
returns(mock_callback)
|
17
|
+
end
|
18
|
+
|
19
|
+
build = Juici::Build.new(:callbacks => callbacks)
|
20
|
+
build.start!
|
21
|
+
build.success!
|
22
|
+
build.destroy
|
23
|
+
end
|
24
|
+
|
25
|
+
# TODO DRY This right up
|
26
|
+
it "should have status success on success" do
|
27
|
+
cb = Juici::Callback.new("test")
|
28
|
+
cb.stubs(:process!)
|
29
|
+
|
30
|
+
Juici::Callback.stubs(:new).with("test").returns(cb)
|
31
|
+
|
32
|
+
build = Juici::Build.new(:callbacks => ["test"])
|
33
|
+
build.start!
|
34
|
+
build.success!
|
35
|
+
|
36
|
+
JSON.load(cb.payload)["status"].should == Juici::BuildStatus::PASS
|
37
|
+
build.destroy
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should have status failed on failure" do
|
41
|
+
cb = Juici::Callback.new("test")
|
42
|
+
cb.stubs(:process!)
|
43
|
+
|
44
|
+
Juici::Callback.stubs(:new).with("test").returns(cb)
|
45
|
+
|
46
|
+
build = Juici::Build.new(:callbacks => ["test"])
|
47
|
+
build.start!
|
48
|
+
build.failure!
|
49
|
+
|
50
|
+
JSON.load(cb.payload)["status"].should == Juici::BuildStatus::FAIL
|
51
|
+
build.destroy
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
# Stub the shit out of ENV.to_hash
|
4
|
+
describe "Juici build environment" do
|
5
|
+
|
6
|
+
it "Should hose sensitive environment variables" do
|
7
|
+
new_env = ENV.to_hash
|
8
|
+
::Juici::BUILD_SENSITIVE_VARIABLES.each do |var|
|
9
|
+
new_env[var] = "Some values!"
|
10
|
+
end
|
11
|
+
ENV.stubs(:to_hash).returns(new_env)
|
12
|
+
|
13
|
+
env = ::Juici::BuildEnvironment.new
|
14
|
+
::Juici::BUILD_SENSITIVE_VARIABLES.each do |var|
|
15
|
+
env[var].should be_nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "Should merge json hashes" do
|
20
|
+
env = ::Juici::BuildEnvironment.new
|
21
|
+
json = %[{"my_spec_key": "my_spec_value"}]
|
22
|
+
|
23
|
+
env.load_json!(json).should == true
|
24
|
+
env["my_spec_key"].should == "my_spec_value"
|
25
|
+
end
|
26
|
+
|
27
|
+
it "Should fail on valid json that is a string" do
|
28
|
+
env = ::Juici::BuildEnvironment.new
|
29
|
+
json = %["rawr!"]
|
30
|
+
|
31
|
+
env.load_json!(json).should == false
|
32
|
+
end
|
33
|
+
|
34
|
+
it "Should fail on valid json that is an integer" do
|
35
|
+
env = ::Juici::BuildEnvironment.new
|
36
|
+
json = %[4]
|
37
|
+
|
38
|
+
env.load_json!(json).should == false
|
39
|
+
end
|
40
|
+
|
41
|
+
it "Should fail on invalid json" do
|
42
|
+
env = ::Juici::BuildEnvironment.new
|
43
|
+
json = %[{ butts lol]
|
44
|
+
|
45
|
+
env.load_json!(json).should == false
|
46
|
+
end
|
47
|
+
|
48
|
+
it "Should regard an empty string as valid" do
|
49
|
+
env = ::Juici::BuildEnvironment.new
|
50
|
+
json = ""
|
51
|
+
env.load_json!(json).should == true
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Juici build abstraction" do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
@app = Juici::App.new
|
7
|
+
end
|
8
|
+
|
9
|
+
after(:all) do
|
10
|
+
Juici::App.shutdown
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
it "Should run a given command in a subshell" do
|
15
|
+
watcher = Juici::Watcher.instance.start
|
16
|
+
build = Juici::Build.new(parent: "test project",
|
17
|
+
environment: {},
|
18
|
+
command: "/bin/echo 'test build succeeded'")
|
19
|
+
$build_queue << build
|
20
|
+
|
21
|
+
# Wait a reasonable time for build to finish
|
22
|
+
# TODO: This can leverage the hooks system
|
23
|
+
# TODO: Easer will be to have worker.block or something
|
24
|
+
Timeout::timeout(2) do
|
25
|
+
poll_build(build)
|
26
|
+
|
27
|
+
build.reload
|
28
|
+
build.status.should == Juici::BuildStatus::PASS
|
29
|
+
build[:output].chomp.should == "test build succeeded"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it "Should catch failed builds" do
|
34
|
+
watcher = Juici::Watcher.instance.start
|
35
|
+
build = Juici::Build.new(parent: "test project",
|
36
|
+
environment: {},
|
37
|
+
command: "exit 3")
|
38
|
+
$build_queue << build
|
39
|
+
|
40
|
+
# Wait a reasonable time for build to finish
|
41
|
+
# TODO: This can leverage the hooks system
|
42
|
+
# TODO: Easer will be to have worker.block or something
|
43
|
+
Timeout::timeout(2) do
|
44
|
+
poll_build(build)
|
45
|
+
|
46
|
+
build.reload
|
47
|
+
build.status.should == Juici::BuildStatus::FAIL
|
48
|
+
build[:output].chomp.should == ""
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it "Should kill builds" do
|
53
|
+
build = Juici::Build.new(parent: "test",
|
54
|
+
command: "sleep 30")
|
55
|
+
$build_queue << build
|
56
|
+
sleep 1
|
57
|
+
build.kill!
|
58
|
+
|
59
|
+
build.reload
|
60
|
+
|
61
|
+
build.status.should == Juici::BuildStatus::FAIL
|
62
|
+
build.warnings.should include("Killed!")
|
63
|
+
end
|
64
|
+
|
65
|
+
it "Can create and fetch new bundles" do
|
66
|
+
watcher = Juici::Watcher.instance.start
|
67
|
+
build = Juici::Build.new(parent: "test project",
|
68
|
+
environment: ::Juici::BuildEnvironment.new.to_hash,
|
69
|
+
command: <<-EOS)
|
70
|
+
#!/bin/sh
|
71
|
+
set -e
|
72
|
+
|
73
|
+
cat > Gemfile <<EOF
|
74
|
+
source :rubygems
|
75
|
+
gem "m2a"
|
76
|
+
EOF
|
77
|
+
|
78
|
+
env
|
79
|
+
|
80
|
+
bundle install
|
81
|
+
bundle list
|
82
|
+
bundle config
|
83
|
+
EOS
|
84
|
+
|
85
|
+
build.save!
|
86
|
+
$build_queue << build
|
87
|
+
|
88
|
+
Timeout::timeout(10) do
|
89
|
+
poll_build(build)
|
90
|
+
|
91
|
+
build.status.should == Juici::BuildStatus::PASS
|
92
|
+
build[:output].chomp.should match /m2a/
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Juici::BuildQueue do
|
4
|
+
|
5
|
+
subject { Juici::BuildQueue.new.tap { |q| q.__builds = @builds } }
|
6
|
+
|
7
|
+
it "Should return 1 as min priority when empty" do
|
8
|
+
@builds = []
|
9
|
+
subject.current_min_priority.should == 1
|
10
|
+
end
|
11
|
+
|
12
|
+
it "Should return the min priority when set" do
|
13
|
+
@builds = builds_with(priority: [ 5, 3, 7 ])
|
14
|
+
subject.current_min_priority.should == 3
|
15
|
+
end
|
16
|
+
|
17
|
+
it "Should deal gracefully with nil" do
|
18
|
+
@builds = builds_with(priority: [ 5, 4, nil, 7 ])
|
19
|
+
subject.current_min_priority.should == 4
|
20
|
+
end
|
21
|
+
|
22
|
+
it "Should remove a given build by pid by pid" do
|
23
|
+
# Build an array of
|
24
|
+
@builds = builds_with(pid: [1, 2, 3, 4, 5, 6])
|
25
|
+
subject.purge(:pid, stub(:pid => 3))
|
26
|
+
@builds.collect(&:pid).should == [1, 2, 4, 5, 6]
|
27
|
+
end
|
28
|
+
|
29
|
+
it "Should silently fail to remove nonexistant pids by pid" do
|
30
|
+
@builds = builds_with(pid: [1, 2, 3, 4, 5, 6])
|
31
|
+
subject.purge(:pid, stub(:pid => 9))
|
32
|
+
@builds.collect(&:pid).should == [1, 2, 3, 4, 5, 6]
|
33
|
+
end
|
34
|
+
|
35
|
+
it "Should remove a given build by id" do
|
36
|
+
@builds = builds_with(_id: [1, 2, 3, 4, 5, 6])
|
37
|
+
subject.delete(3)
|
38
|
+
@builds.collect(&:_id).should == [1, 2, 4, 5, 6]
|
39
|
+
end
|
40
|
+
|
41
|
+
it "Should return a low priority job from #next_child" do
|
42
|
+
@builds = builds_with(priority: [1, 2, 3, 4, 5, 6])
|
43
|
+
subject.next_child.priority.should == 1
|
44
|
+
@builds = builds_with(priority: [6, 5, 4, 3, 2, 1])
|
45
|
+
subject.next_child.priority.should == 1
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
class Juici::BuildQueue #{{{ Test injection
|
51
|
+
def __builds=(builds)
|
52
|
+
@builds = builds
|
53
|
+
end
|
54
|
+
end #}}}
|
55
|
+
|
56
|
+
def builds_with(args)
|
57
|
+
args.map do |k, v|
|
58
|
+
v.map do |i|
|
59
|
+
stub(k => i)
|
60
|
+
end
|
61
|
+
end.flatten
|
62
|
+
end
|
63
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Juici::Controllers::Builds do
|
4
|
+
|
5
|
+
describe "list" do
|
6
|
+
it "should throw not_found for invalid projects" do
|
7
|
+
params = { :project => "__LolIDon'tExist" }
|
8
|
+
lambda {
|
9
|
+
Juici::Controllers::Builds.new(params).list
|
10
|
+
}.should raise_error(Sinatra::NotFound)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "show" do
|
15
|
+
it "should throw not_found for invalid projects" do
|
16
|
+
params = { :project => "__LolIDon'tExist" }
|
17
|
+
lambda {
|
18
|
+
Juici::Controllers::Builds.new(params).show
|
19
|
+
}.should raise_error(Sinatra::NotFound)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "new" do
|
24
|
+
it "should return the new template" do
|
25
|
+
Juici::Controllers::Builds.new({}).new do |opts, template|
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "edit" do
|
31
|
+
it "should throw not_found for invalid projects" do
|
32
|
+
params = { :project => "__LolIDon'tExist" }
|
33
|
+
lambda {
|
34
|
+
Juici::Controllers::Builds.new(params).show
|
35
|
+
}.should raise_error(Sinatra::NotFound)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "Should update build objects when given data" do
|
39
|
+
# FIXME This is a kludge to work around #38
|
40
|
+
::Juici::Project.find_or_create_by(name: "test project")
|
41
|
+
build = Juici::Build.new(parent: "test project", priority: 1, title: "test build")
|
42
|
+
build.save!
|
43
|
+
|
44
|
+
Juici::Controllers::Builds.new({:priority => 15, :title => "butts lol", :id => build[:_id], :project => "test project"}).update!
|
45
|
+
build.reload
|
46
|
+
|
47
|
+
build[:title].should == "butts lol"
|
48
|
+
build[:priority].should == 15
|
49
|
+
end
|
50
|
+
|
51
|
+
it "Should not let you update a build's ID" do
|
52
|
+
# FIXME This is a kludge to work around #38
|
53
|
+
::Juici::Project.find_or_create_by(name: "test project")
|
54
|
+
build = Juici::Build.new(parent: "test project")
|
55
|
+
build.save!
|
56
|
+
|
57
|
+
updated_build = Juici::Controllers::Builds.new({:_id => "New id lol", :id => build[:_id], :project => "test project"}).update!
|
58
|
+
|
59
|
+
updated_build[:_id].should == build[:_id]
|
60
|
+
end
|
61
|
+
|
62
|
+
it "Should not touch values if given invalid values" do
|
63
|
+
pending("Needs more research on mongoid")
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Juici::Controllers::Index do
|
4
|
+
|
5
|
+
describe "index" do
|
6
|
+
it "should render index" do
|
7
|
+
Juici::Controllers::Index.new().index do |template, opts|
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "about" do
|
13
|
+
it "should add block-header to all h1 tags" do
|
14
|
+
Juici::Controllers::Index.new().about do |template, opts|
|
15
|
+
opts[:content].should match(/class="block-header"/)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "support" do
|
21
|
+
it "should render support" do
|
22
|
+
Juici::Controllers::Index.new().support do |template, opts|
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Juici::Build do
|
4
|
+
|
5
|
+
describe "Time elapsed" do
|
6
|
+
it "Should have the total duration if complete" do
|
7
|
+
|
8
|
+
build = Juici::Build.new
|
9
|
+
Time.stubs(:now).returns(Time.at(5000)).then.returns(Time.at(5005))
|
10
|
+
build.start!
|
11
|
+
build.success!
|
12
|
+
|
13
|
+
build.time_elapsed.to_i.should == 5
|
14
|
+
end
|
15
|
+
|
16
|
+
it "Should have the time running if started" do
|
17
|
+
build = Juici::Build.new
|
18
|
+
Time.stubs(:now).returns(Time.at(5000)).then.returns(Time.at(5006))
|
19
|
+
build.start!
|
20
|
+
|
21
|
+
build.time_elapsed.to_i.should == 6
|
22
|
+
end
|
23
|
+
|
24
|
+
it "Should be nil if not started" do
|
25
|
+
build = Juici::Build.new
|
26
|
+
build.time_elapsed.should be_nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it "Should clone itself with #new_from" do
|
31
|
+
values = {}
|
32
|
+
Juici::Build::CLONABLE_FIELDS.each do |k|
|
33
|
+
case k
|
34
|
+
when :environment
|
35
|
+
values[k] = {:something => "#{k}_value"}
|
36
|
+
else
|
37
|
+
values[k] = "#{k}_value"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
build = Juici::Build.new(values)
|
42
|
+
build[:output] = "Lol, I has an output"
|
43
|
+
build[:buffer] = "/tmp/buffer/lol"
|
44
|
+
new_build = Juici::Build.new_from(build)
|
45
|
+
|
46
|
+
Juici::Build::CLONABLE_FIELDS.each do |k|
|
47
|
+
build[k].should == new_build[k]
|
48
|
+
end
|
49
|
+
|
50
|
+
build[:_id].should_not == new_build[:_id]
|
51
|
+
new_build[:output].should be_nil
|
52
|
+
new_build[:buffer].should be_nil
|
53
|
+
end
|
54
|
+
end
|