juici 0.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +5 -0
  3. data/Gemfile.lock +74 -0
  4. data/Procfile +1 -0
  5. data/README.md +138 -0
  6. data/Rakefile +33 -0
  7. data/TODO.md +28 -0
  8. data/bin/juici +7 -0
  9. data/bin/juicic +54 -0
  10. data/config/mongoid.yml.sample +25 -0
  11. data/juici-interface.gemspec +19 -0
  12. data/juici.gemspec +32 -0
  13. data/lib/juici.rb +24 -0
  14. data/lib/juici/app.rb +67 -0
  15. data/lib/juici/build_environment.rb +38 -0
  16. data/lib/juici/build_logic.rb +37 -0
  17. data/lib/juici/build_queue.rb +111 -0
  18. data/lib/juici/callback.rb +26 -0
  19. data/lib/juici/config.rb +11 -0
  20. data/lib/juici/controllers.rb +6 -0
  21. data/lib/juici/controllers/base.rb +26 -0
  22. data/lib/juici/controllers/build_queue.rb +14 -0
  23. data/lib/juici/controllers/builds.rb +74 -0
  24. data/lib/juici/controllers/index.rb +20 -0
  25. data/lib/juici/controllers/trigger.rb +85 -0
  26. data/lib/juici/database.rb +25 -0
  27. data/lib/juici/exceptions.rb +2 -0
  28. data/lib/juici/find_logic.rb +11 -0
  29. data/lib/juici/helpers/form_helpers.rb +11 -0
  30. data/lib/juici/helpers/html_helpers.rb +4 -0
  31. data/lib/juici/helpers/url_helpers.rb +38 -0
  32. data/lib/juici/interface.rb +13 -0
  33. data/lib/juici/models/build.rb +190 -0
  34. data/lib/juici/models/build_process.rb +7 -0
  35. data/lib/juici/models/project.rb +9 -0
  36. data/lib/juici/server.rb +172 -0
  37. data/lib/juici/version.rb +8 -0
  38. data/lib/juici/views/README.markdown +138 -0
  39. data/lib/juici/views/about.erb +16 -0
  40. data/lib/juici/views/builds.erb +7 -0
  41. data/lib/juici/views/builds/edit.erb +23 -0
  42. data/lib/juici/views/builds/list.erb +27 -0
  43. data/lib/juici/views/builds/new.erb +43 -0
  44. data/lib/juici/views/builds/show.erb +4 -0
  45. data/lib/juici/views/index.erb +30 -0
  46. data/lib/juici/views/layout.erb +44 -0
  47. data/lib/juici/views/not_found.erb +3 -0
  48. data/lib/juici/views/partials/builds/debug.erb +22 -0
  49. data/lib/juici/views/partials/builds/output.erb +1 -0
  50. data/lib/juici/views/partials/builds/show.erb +19 -0
  51. data/lib/juici/views/partials/builds/sidebar.erb +13 -0
  52. data/lib/juici/views/partials/index/recently_built.erb +19 -0
  53. data/lib/juici/views/queue/list.erb +6 -0
  54. data/lib/juici/views/redirect.erb +0 -0
  55. data/lib/juici/views/support.erb +6 -0
  56. data/lib/juici/watcher.rb +48 -0
  57. data/public/favicon.ico +0 -0
  58. data/public/images/black_denim.png +0 -0
  59. data/public/styles/builds.css +62 -0
  60. data/public/styles/juici.css +226 -0
  61. data/public/vendor/bootstrap.css +6004 -0
  62. data/public/vendor/bootstrap.js +2036 -0
  63. data/public/vendor/img/glyphicons-halflings-white.png +0 -0
  64. data/public/vendor/jquery.js +9440 -0
  65. data/sample/mongod.conf +5 -0
  66. data/script/cibuild +10 -0
  67. data/spec/build_callback_spec.rb +54 -0
  68. data/spec/build_environment_spec.rb +53 -0
  69. data/spec/build_process_spec.rb +96 -0
  70. data/spec/build_queue_spec.rb +63 -0
  71. data/spec/controllers/builds_spec.rb +68 -0
  72. data/spec/controllers/index_spec.rb +28 -0
  73. data/spec/juici_app_spec.rb +8 -0
  74. data/spec/models/build_spec.rb +54 -0
  75. data/spec/spec_helper.rb +26 -0
  76. metadata +290 -0
@@ -0,0 +1,5 @@
1
+ # Store data in /usr/local/var/mongodb instead of the default /data/db
2
+ dbpath = /usr/local/var/mongodb
3
+
4
+ # Only accept local connections
5
+ bind_ip = 127.0.0.1
@@ -0,0 +1,10 @@
1
+ #!/bin/sh
2
+
3
+ be="bundle exec"
4
+
5
+ export RACK_ENV="test"
6
+
7
+ bundle install --path .bundle
8
+
9
+ $be rake db:destroy
10
+ $be rake spec
@@ -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,8 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Juici::App do
4
+ it "instanciates cleanly and exits cleanly" do
5
+ app = Juici::App.new
6
+ Juici::App.shutdown
7
+ end
8
+ end
@@ -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