juici 0.0.0.alpha1

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.
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