Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

rewrote all of hobson CI

  • Loading branch information...
commit 4cabbb5b5a5ed1ba50316f480df7becacfcb350c 1 parent d1cb99e
@deadlyicon authored
View
3  lib/hobson.rb
@@ -79,8 +79,7 @@ def files
end
end
- def projects project_name=nil
- return Project.find(project_name) unless project_name.nil?
+ def projects
redis.smembers(:projects).map{|p| Project.find(p) }
end
View
10 lib/hobson/ci.rb
@@ -1,5 +1,15 @@
module Hobson::CI
+ extend self
+
autoload :ProjectRef, 'hobson/ci/project_ref'
+ def redis
+ @redis ||= Redis::Namespace.new(:ci, :redis => Hobson.redis)
+ end
+
+ def project_refs
+ redis.smembers(:project_refs).map{|p| ProjectRef.find(p) }
+ end
+
end
View
163 lib/hobson/ci/project_ref.rb
@@ -1,86 +1,155 @@
+# a hobson ci project is a Hobson::Project and a git ref
class Hobson::CI::ProjectRef
- def self.redis_hash
- Hobson::RedisHash.new(Hobson.redis, "TestRun:CI")
- end
+ HISTORY_LENGTH = 10
+
+ class << self
+
+ def create project_name, ref
+ Hobson::Project.find(project_name) or raise "unknown project #{project_name.inspect}"
+ ref.present? or raise "invalid gir ref #{ref.inspect}"
+ new(project_name, ref).save
+ end
+
+ def find id
+ project_name, ref = id.scan(/^(.+?):(.+)$/).first
+ project_ref = new(project_name, ref)
+ project_ref.new_record? ? nil : project_ref
+ end
- def self.all
- redis_hash.keys.map{|key| self.new *key.split('::') }
end
- def data
- @data ||= self.class.redis_hash
+ attr_reader :project_name, :ref
+
+ def initialize project_name, ref
+ @project_name, @ref = project_name, ref
end
- attr_reader :origin_url, :ref
+ def id
+ "#{project_name}:#{ref}"
+ end
- def initialize origin_url, ref=nil
- @origin_url, @ref = origin_url, ref || 'master'
+ def project
+ @project ||= Hobson::Project.find(project_name)
end
- def key
- "#{origin_url}::#{ref}"
+ def check_for_new_sha!
+ create_test_run! if current_sha_untested?
end
- def save
- data[key] ||= current_sha
- self
+ def current_sha_untested?
+ shas.exclude? current_sha
end
- def delete
- data.delete key
+ def create_test_run! sha=current_sha
+ test_run = project.run_tests!(sha, "CI:#{id}")
+ redis.pipelined{ # single request
+ redis.lpush(:shas, sha)
+ redis.lpush(:test_run_ids, test_run.id)
+ redis.ltrim(:shas, 0, HISTORY_LENGTH)
+ redis.ltrim(:test_run_ids, 0, HISTORY_LENGTH)
+ }
+ test_run
end
- def project
- @project ||= Hobson::Project.from_origin_url(origin_url)
+ %w{shas test_run_ids}.each{|list|
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{list}
+ @#{list} ||= redis.lrange(:#{list}, 0, HISTORY_LENGTH-1)
+ end
+ RUBY
+ }
+
+ def history
+ @history ||= shas.zip(test_run_ids, test_run_statuses)
end
- def last_test_run
- @last_test_run ||= project \
- .test_runs \
- .find_all{|test_run| test_run.sha == last_sha} \
- .sort_by(&:enqueued_build_at) \
- .last
+ def test_runs
+ @test_runs ||= test_run_ids.map{|id| project.test_runs(id) }
end
- def last_sha
- data[key]
+ def test_run_statuses
+ @test_run_statuses ||= test_runs.map(&:status)
end
+ # def status
+ # @status ||= begin
+ # # find the most recently complete test run's status
+ # test_run = test_run_statuses.find(&:complete)
+ # # if we dont have one we dont have a status, otherwise if its passed its green otherwise red
+ # test_run.nil? ? nil : test_run.status == 'passed' ? 'green' : 'red'
+ # end
+ # end
+
+ # # returns a hash of sha => test_run_id
+ # def test_run_index
+ # @test_run_index ||= redis.hgetall(:test_run_index)
+ # end
+
+ # def shas
+ # test_run_index.keys
+ # end
+
+ # def test_run_ids
+ # test_run_index.values
+ # end
+
+ # # a redis hash of sha => test_run_result
+ # def test_run_results
+ # @test_run_results ||= redis.hgetall(:test_run_results)
+ # end
+
+ # def test_runs
+ # @test_runs ||= redis.hgetall(:test_runs).each{|sha,test_run_id|
+ # project.test_runs(test_run_id)
+ # }
+ # end
+
+ # def test_run_results
+ # @test_run_results ||= redis.hgetall(:test_runs).each{|sha,test_run_id|
+ # @test_runs ||= redis.hgetall(:test_runs).each{|sha,test_run_id|
+ # project.test_runs(itest_run_id)
+ # }
+ # end
+
def current_sha
- @current_sha ||= begin
- cmd = %(git ls-remote #{origin_url.inspect} #{ref.inspect})
- result = `#{cmd}`
- raise "failed getting remote sha for #{origin_url.inspect} #{ref.inspect}\n#{cmd.inspect} failed" unless $?.success?
- result.scan(/^(\w+)/).try(:first).try(:first)
+ @current_sha or begin
+ cmd = %(cd #{Hobson.root.to_s.inspect} && git ls-remote #{project.origin.inspect} #{ref.inspect})
+ result = `#{cmd}`
+ @current_sha = result.scan(/(\b[0-9a-f]{5,40}\b)/).try(:first).try(:first)
+ if !$?.success? || @current_sha.blank?
+ raise "failed getting remote sha for #{origin_url.inspect} #{ref.inspect}\n#{cmd.inspect} failed"
+ end
end
+ @current_sha
end
- def needs_test_run?
- last_sha != current_sha && project.test_runs.map(&:sha).exclude?(current_sha)
+ def new_record?
+ !Hobson::CI.redis.sismember(:project_refs, id)
end
- def run_tests!
- @last_test_run = project.run_tests!(current_sha, 'CI') and data[key] = current_sha
+ def save
+ Hobson::CI.redis.sadd(:project_refs, id)
+ self
end
- def running?
- last_test_run && !last_test_run.complete?
+ def delete
+ redis.keys.each{|key| redis.del key}
+ Hobson::CI.redis.srem(:project_refs, id)
+ self
end
- def passed?
- last_test_run && last_test_run.passed?
+ def inspect
+ "#<#{self.class} #{id}>"
end
+ alias_method :to_s, :inspect
- def failed?
- last_test_run && !passed?
+ def == other
+ other.is_a?(self.class) && self.id == other.id
end
- def status
- running? ? 'running' :
- passed? ? 'passed' :
- failed? ? 'failed' :
- 'new'
+ def redis
+ @redis ||= Redis::Namespace.new("Project:#{id}", :redis => Hobson::CI.redis)
end
end
View
6 lib/hobson/server.rb
@@ -52,8 +52,7 @@ def self.start! options={}
end
get '/ci' do
- @project_refs = Hobson::CI::ProjectRef.all
- if @project_refs.present?
+ if project_refs.present?
haml :ci
else
redirect '/ci/new'
@@ -68,8 +67,7 @@ def self.start! options={}
response = {:success => true, :seconds_since_last_check => seconds_since_last_check, :checked => false}
if seconds_since_last_check >= MAX_CHECK_FOR_CHANGES_INTERVAL
@@last_time_we_checked_for_changes = now
- project_refs = Hobson::CI::ProjectRef.all.find_all(&:needs_test_run?)
- project_refs.each(&:run_tests!)
+ project_refs.each(&:check_for_new_sha!)
response[:checked] = true
end
response.to_json
View
24 lib/hobson/server/helpers.rb
@@ -13,11 +13,8 @@ def projects
end
def project
- @project or begin
- @project = Hobson::Project[params["project_name"]]
- raise "project #{params["project_name"]} not found" if @project.new_record?
- end
- @project
+ @project ||= Hobson::Project[params["project_name"]] or
+ raise "project #{params["project_name"]} not found"
end
def test_runs
@@ -28,6 +25,10 @@ def test_run
@test_run ||= project.test_runs(params["test_run_id"]) or raise Sinatra::NotFound
end
+ def project_refs
+ @project_refs ||= Hobson::CI.project_refs
+ end
+
# URL Helpers
def projects_path
@@ -172,6 +173,19 @@ def sort_tests tests
}
end
+ def test_run_status_color test_run_status
+ case test_run_status
+ when 'errored','aborted','failed'
+ 'red'
+ when 'passed'
+ 'green'
+ when 'complete','running tests','waiting to be run','building','waiting to be built','waiting...'
+ 'blue'
+ else
+ 'blue'
+ end
+ end
+
# Sam Elliott’s partials.rb
# https://gist.github.com/119874
View
50 lib/hobson/server/views/ci.haml
@@ -1,41 +1,25 @@
- page_title "CI"
-- auto_refresh!
+-# auto_refresh!
-%a{:href => '/ci/new'} new
-%ul.project_refs
- - @project_refs.sort_by(&:key).each do |project_ref|
- %li.project_ref{:class => project_ref.status}
- %h1= project_ref.project.name
- %h2= project_ref.ref
- %dl
- %dt Status
- %dd
- - if project_ref.last_test_run.present?
- %a{:href => test_run_path(project_ref.last_test_run)}= project_ref.status
- - else
- = project_ref.status
+%secion.ci
- %dt Current Test Run
- %dd
- - if project_ref.last_test_run.present?
- %a{:href => test_run_path(project_ref.last_test_run)}= project_ref.last_test_run.id
- - else
- n/a
+ %a{:href => '/ci/new'} new
- %dt Repo Url
- %dd
- %a{:href => repo_url(project_ref.origin_url)}= project_ref.origin_url
+ %ul.project_refs
+ - project_refs.each do |project_ref|
+ - history = Hobson::CI::ProjectRef::HISTORY_LENGTH.times.to_a.zip(project_ref.test_run_statuses).reverse
+ - current = history.shift.last
+ %li.project_ref{:class => test_run_status_color(current)}
- %dt Git Ref
- %dd
- %a{:href => ref_url(project_ref.origin_url, project_ref.ref)}= project_ref.ref
+ .project= project_ref.project.name.gsub(/-|_/,' ')
- %dt Last Sha
- %dd
- %a{:href => sha_url(project_ref.origin_url, project_ref.last_sha)}= project_ref.last_sha
+ .ref= project_ref.ref
- %dt Total Test Runs
- %dd= project_ref.project.test_runs.size
+ %ol.history
+ - history.each do |n, test_run_status|
+ %li.test_run{:class => test_run_status_color(test_run_status)}
-:javascript
- Hobson.CI.pollForChanges();
+ .icon
+ .red !
+ .green
+ .blue ?
View
122 lib/hobson/server/views/screen.sass
@@ -338,36 +338,104 @@ form.new-test-run
&.logs > div
width: 10em
+@mixin glass($dark)
+ $light: lighten($dark, 5%)
+ $white: rgba(255,255,255,0.5)
+ $clear: rgba(255,255,255,0)
+ box-shadow: 6px 6px 31px lightgrey, inset 0px 0px 30px 0px $white
+ background: -webkit-linear-gradient(top,$white 0%,$white 2%,$clear 20%,$clear 80%,$white 98%,$white 100%), -webkit-repeating-linear-gradient(-45deg,$light 0%,$light 2%,$dark 3%,$dark 5%,$light 6%)
+ border: 3px solid $dark
+
.project_refs
> .project_ref
- float: left
- margin: 10px
- padding: 10px
- border-radius: 10px
- box-shadow: 3px 3px 10px black
- > a
- text-decoration: none
- color: black
- h1
- font-size: 150%
- h2
- font-size: 130%
- h1, h2
- text-align: center
- &.running
- background-color: lightblue
- &.failed
- background-color: lighten(red, 10%)
- &.passed
- background-color: green
-
- dl
- dt
+ $red: #EC3E2D
+ $green: #B8EA88
+ $blue: #7EC4ED
+
+ display: inline-block
+ height: 18em
+ width: 20em
+
+ margin: 1em
+ padding: 2em
+ border-radius: 2em
+
+ &.green
+ +glass($green)
+ .icon > .green
+ display: block
+ &.red
+ +glass($red)
+ .icon > .red
+ display: block
+ &.blue
+ +glass($blue)
+ .icon > .blue
+ display: block
+
+ >
+ .project, .ref
+ display: block
+ text-align: center
font-weight: bold
- &:after
- content: ':'
- dd
- margin-bottom: 1em
+ margin-bottom: 0.5em
+
+ .project
+ font-size: 200%
+ .ref
+ font-size: 150%
+
+ .history > *
+ margin: 1.5em 0
+ display: inline-block
+ height: 1.5em
+ width: 8%
+ border-radius: 2em
+ box-shadow: 0 0 10px black
+ &.green
+ background-color: $green
+ &.red
+ background-color: $red
+ &.blue
+ background-color: $blue
+
+ .icon
+ font-size: 700%
+ text-align: center
+ text-shadow: 0px 0px 10px black
+ > *
+ display: none
+ &.red
+ color: darken($red,10%)
+ &.green
+ color: darken($green,10%)
+ &.blue
+ color: darken($blue,10%)
+
+ .DISABLED
+ > a
+ text-decoration: none
+ color: black
+ h1
+ font-size: 180%
+ h2
+ font-size: 130%
+ h1, h2
+ text-align: center
+ &.running
+ background-color: lightblue
+ &.failed
+ background-color: lighten(red, 10%)
+ &.passed
+ background-color: green
+
+ dl
+ dt
+ font-weight: bold
+ &:after
+ content: ':'
+ dd
+ margin-bottom: 1em
#new_project_ref
label
View
67 spec/hobson/ci/project_ref_spec.rb
@@ -0,0 +1,67 @@
+require 'spec_helper'
+
+describe Hobson::CI::ProjectRef do
+
+ # let(:project){ Factory.project }
+
+ worker_context do
+
+ describe "create" do
+ before{
+ @project = Factory.project('git://github.com/rails/rails.git')
+ }
+ context "when given a known project name" do
+ it "should return a saved ProjectRef instance" do
+ project_ref = Hobson::CI::ProjectRef.create('rails', 'master')
+ project_ref.new_record?.should be_false
+ Hobson::CI.project_refs.should == [project_ref]
+ end
+ end
+ context "when given an unknown project name" do
+ it "should raise and error" do
+ lambda{ Hobson::CI::ProjectRef.create('fails', 'master') }.should raise_error
+ end
+ end
+ context "when given invalid git ref" do
+ it "should raise and error" do
+ lambda{ Hobson::CI::ProjectRef.create('rails', nil) }.should raise_error
+ lambda{ Hobson::CI::ProjectRef.create('rails', '') }.should raise_error
+ end
+ end
+ end
+
+ describe "find" do
+ before{
+ Factory.project('git://github.com/magic/donkies.git')
+ @project_ref = Hobson::CI::ProjectRef.create('donkies', 'development')
+ }
+ context "when given a bad id" do
+ it "should return nil" do
+ Hobson::CI::ProjectRef.find('bad:id').should be_nil
+ end
+ end
+ context "when given a good id" do
+ it "should return a ProjectRef instance" do
+ Hobson::CI::ProjectRef.find(@project_ref.id).should == @project_ref
+ end
+ end
+ end
+
+ describe "#test_runs" do
+ let(:project_ref){ Factory.project_ref }
+ context "when this project ref has no previous test_runs" do
+ it "should return an empty array" do
+ project_ref.test_runs.should == []
+ end
+ end
+ context "when this project ref has previous test_runs" do
+ it "should return an empty array" do
+ project_ref.test_runs.should == []
+ end
+ end
+ end
+
+ end
+
+
+end
View
12 spec/hobson/ci_spec.rb
@@ -0,0 +1,12 @@
+require 'spec_helper'
+
+describe Hobson::Project do
+
+ subject { Factory.project }
+ alias_method :project, :subject
+
+ worker_context do
+
+ end
+
+end
View
4 spec/support/factory.rb
@@ -33,4 +33,8 @@ def job test_run=self.test_run, index=0
job
end
+ def project_ref project_name=self.project.name, ref=:master
+ Hobson::CI::ProjectRef.create(project_name, ref)
+ end
+
end
Please sign in to comment.
Something went wrong with that request. Please try again.