From 7721421daf7d2ca4e1b52da66c12d41bb09538b9 Mon Sep 17 00:00:00 2001 From: Nicolas Sanguinetti Date: Sat, 17 Jan 2009 23:16:22 -0200 Subject: [PATCH] Refactor Build into Commit and Build [#61 state:resolved] * Migrations are fixed, and should work out of the box. Just run `integrity create_db config.yml` and your database should be migrated (backup the database just in case before, and let us know if something goes wrong). * Build manually is broken if you don't have commits in your project already. If there's a build already, it will just rebuild that commit This ties into [#16] but breaks a few things. Mainly building for the first time to test the config is working (before we get sent a push). * Posting to /push requires more information. All the information is already sent by Github, so no harm done if you're using that. If not, you need to provide, for each commit, a timestamp, the author name and email (in 'name ' format), and the commit message. We will publish a custom ruby script that you can run as your post-receive hook and that supplies all this information. * Building is still done in the foreground, but all the "dirty" work is done and making it build in background should be easy now. * Some styles and views need work. need work (at least, to handle commits that haven't been built yet), but maybe something else I'm missing. --- app.rb | 4 +- lib/integrity.rb | 3 +- lib/integrity/build.rb | 87 +++++--- lib/integrity/commit.rb | 72 ++++++ lib/integrity/core_ext/object.rb | 12 +- lib/integrity/helpers.rb | 4 +- lib/integrity/installer.rb | 2 +- lib/integrity/migrations.rb | 137 ++++++++++-- lib/integrity/notifier.rb | 2 +- lib/integrity/project.rb | 75 ++++--- lib/integrity/project_builder.rb | 36 +-- lib/integrity/scm/git.rb | 9 - test/acceptance/api_test.rb | 23 +- test/acceptance/browse_project_builds_test.rb | 28 +-- test/acceptance/browse_project_test.rb | 10 +- test/acceptance/build_notifications_test.rb | 2 + test/acceptance/delete_project_test.rb | 2 +- test/acceptance/manual_build_project_test.rb | 6 + test/acceptance/project_syndication_test.rb | 4 +- test/helpers/acceptance/git_helper.rb | 2 +- test/helpers/fixtures.rb | 59 +++-- test/unit/build_test.rb | 48 ++-- test/unit/commit_test.rb | 84 +++++++ test/unit/project_builder_test.rb | 93 ++++---- test/unit/project_test.rb | 208 +++++++----------- views/_build_info.haml | 18 -- views/_commit_info.haml | 18 ++ views/build.haml | 4 +- views/home.haml | 8 +- views/project.builder | 18 +- views/project.haml | 20 +- 31 files changed, 677 insertions(+), 421 deletions(-) create mode 100644 lib/integrity/commit.rb create mode 100644 test/unit/commit_test.rb delete mode 100644 views/_build_info.haml create mode 100644 views/_commit_info.haml diff --git a/app.rb b/app.rb index d78750c8..99897946 100644 --- a/app.rb +++ b/app.rb @@ -123,9 +123,9 @@ redirect project_url(@project) end -get "/:project/builds/:build" do +get "/:project/builds/:commit" do login_required unless current_project.public? - show :build, :title => ["projects", current_project.permalink, current_build.short_commit_identifier] + show :build, :title => ["projects", current_project.permalink, current_commit.short_commit_identifier] end get "/integrity.css" do diff --git a/lib/integrity.rb b/lib/integrity.rb index b147cbb5..b0614ba1 100644 --- a/lib/integrity.rb +++ b/lib/integrity.rb @@ -20,6 +20,7 @@ require "core_ext/string" require "project" +require "commit" require "build" require "project_builder" require "scm" @@ -78,4 +79,4 @@ def call(severity, time, progname, msg) time.strftime("[%H:%M:%S] ") + msg2str(msg) + "\n" end end -end +end \ No newline at end of file diff --git a/lib/integrity/build.rb b/lib/integrity/build.rb index 9b68aac7..01b77503 100644 --- a/lib/integrity/build.rb +++ b/lib/integrity/build.rb @@ -2,60 +2,75 @@ module Integrity class Build include DataMapper::Resource - property :id, Serial - property :output, Text, :nullable => false, :default => "" - property :successful, Boolean, :nullable => false, :default => false - property :commit_identifier, String, :nullable => false - property :commit_metadata, Yaml, :nullable => false, :lazy => false - property :created_at, DateTime - property :updated_at, DateTime + property :id, Integer, :serial => true + property :output, Text, :default => "", :lazy => false + property :successful, Boolean, :default => false + property :commit_id, Integer, :nullable => false + property :created_at, DateTime + property :updated_at, DateTime + property :started_at, DateTime + property :completed_at, DateTime - belongs_to :project, :class_name => "Integrity::Project" + belongs_to :commit, :class_name => "Integrity::Commit" + + def self.pending + all(:started_at => nil) + end + + def pending? + started_at.nil? + end def failed? !successful? end def status - successful? ? :success : :failed - end - - def human_readable_status - successful? ? "Build Successful" : "Build Failed" + case + when pending? then :pending + when successful? then :success + when failed? then :failed + end end - + + # + # Deprecated methods + # def short_commit_identifier - sha1?(commit_identifier) ? commit_identifier[0..6] : commit_identifier + warn "Build#short_commit_identifier is deprecated, use Commit#short_identifier" + commit.short_identifier end - def commit_metadata - case data = attribute_get(:commit_metadata) - when String; YAML.load(data) - else data - end + def commit_identifier + warn "Build#commit_identifier is deprecated, use Commit#identifier" + commit.identifier end - + def commit_author - @author ||= begin - commit_metadata[:author] =~ /^(.*) <(.*)>$/ - OpenStruct.new(:name => $1.strip, :email => $2.strip, :full => commit_metadata[:author]) - end + warn "Build#commit_author is deprecated, use Commit#author" + commit.author end def commit_message - commit_metadata[:message] + warn "Build#commit_message is deprecated, use Commit#message" + commit.message end def commited_at - case commit_metadata[:date] - when String then Time.parse(commit_metadata[:date]) - else commit_metadata[:date] - end + warn "Build#commited_at is deprecated, use Commit#committed_at" + commit.committed_at + end + + def project_id + warn "Build#project_id is deprecated, use Commit#project_id" + commit.project_id + end + + def commit_metadata + warn "Build#commit_metadata is deprecated, use the different methods in Commit instead" + { :message => commit.message, + :author => commit.author, + :date => commit.committed_at } end - - private - def sha1?(string) - string =~ /^[a-f0-9]{40}$/ - end end -end +end \ No newline at end of file diff --git a/lib/integrity/commit.rb b/lib/integrity/commit.rb new file mode 100644 index 00000000..a2c162de --- /dev/null +++ b/lib/integrity/commit.rb @@ -0,0 +1,72 @@ +module Integrity + class Commit + include DataMapper::Resource + + property :id, Integer, :serial => true + property :identifier, String, :nullable => false + property :message, String, :nullable => false, :length => 255 + property :author, String, :nullable => false, :length => 255 + property :committed_at, DateTime, :nullable => false + property :created_at, DateTime + property :updated_at, DateTime + + belongs_to :project, :class_name => "Integrity::Project" + has 1, :build, :class_name => "Integrity::Build" + + def short_identifier + identifier.to_s[0..6] + end + + def author + @structured_author ||= attribute_get(:author).tap do |author_string| + author_string =~ /^(.*) <(.*)>$/ + author_string.singleton_def(:name) { $1.strip } + author_string.singleton_def(:email) { $2.strip } + author_string.singleton_def(:full) { author_string } + end + end + + def status + build.nil? ? :pending : build.status + end + + def successful? + status == :success + end + + def failed? + status == :failed + end + + def pending? + status == :pending + end + + def human_readable_status + case status + when :success; "Built #{short_identifier} successfully" + when :failed; "Built #{short_identifier} and failed" + when :pending; "#{short_identifier} hasn't been built yet" + end + end + + def output + build && build.output + end + + def queue_build + self.build = Build.create(:commit_id => id) + self.save + + # Build on foreground (this will move away, I promise) + ProjectBuilder.new(project).build(self) + end + + # Deprecation layer + alias :short_commit_identifier :short_identifier + alias :commit_identifier :identifier + alias :commit_author :author + alias :commit_message :message + alias :commited_at :committed_at + end +end \ No newline at end of file diff --git a/lib/integrity/core_ext/object.rb b/lib/integrity/core_ext/object.rb index 5f592882..abe4166c 100644 --- a/lib/integrity/core_ext/object.rb +++ b/lib/integrity/core_ext/object.rb @@ -3,4 +3,14 @@ def tap yield self self end -end + + def singleton_class + class << self; self; end + end + + def singleton_def(name, &block) + singleton_class.instance_eval do + define_method(name, &block) + end + end +end \ No newline at end of file diff --git a/lib/integrity/helpers.rb b/lib/integrity/helpers.rb index b9353f71..e7d75c47 100644 --- a/lib/integrity/helpers.rb +++ b/lib/integrity/helpers.rb @@ -38,8 +38,8 @@ def current_project @project ||= Project.first(:permalink => params[:project]) or raise Sinatra::NotFound end - def current_build - @build ||= current_project.builds.first(:commit_identifier => params[:build]) or raise Sinatra::NotFound + def current_commit + @commit ||= current_project.commits.first(:identifier => params[:commit]) or raise Sinatra::NotFound end def show(view, options={}) diff --git a/lib/integrity/installer.rb b/lib/integrity/installer.rb index 8ec3d33a..0ec957a3 100644 --- a/lib/integrity/installer.rb +++ b/lib/integrity/installer.rb @@ -64,7 +64,7 @@ def edit_template_files edit_integrity_configuration edit_thin_configuration end - + def edit_integrity_configuration config = File.read(root / "config.yml") config.gsub! %r(sqlite3:///var/integrity.db), "sqlite3://#{root}/integrity.db" diff --git a/lib/integrity/migrations.rb b/lib/integrity/migrations.rb index 7066358b..5ebea519 100644 --- a/lib/integrity/migrations.rb +++ b/lib/integrity/migrations.rb @@ -2,27 +2,28 @@ require "migration_runner" include DataMapper::Types +include Integrity migration 1, :initial do up do create_table :integrity_projects do - column :id, Serial - column :name, String, :nullable => false - column :permalink, String - column :uri, URI, :nullable => false - column :branch, String, :nullable => false, :default => "master" - column :command, String, :nullable => false, :default => "rake" - column :public, Boolean, :default => true - column :building, Boolean, :default => false - column :created_at, DateTime - column :updated_at, DateTime + column :id, Integer, :serial => true + column :name, String, :nullable => false + column :permalink, String + column :uri, URI, :nullable => false + column :branch, String, :nullable => false, :default => "master" + column :command, String, :nullable => false, :default => "rake" + column :public, Boolean, :default => true + column :building, Boolean, :default => false + column :created_at, DateTime + column :updated_at, DateTime - column :build_id, Serial - column :notifier_id,Serial + column :build_id, Integer + column :notifier_id, Integer end create_table :integrity_builds do - column :id, Serial + column :id, Integer, :serial => true column :output, Text, :nullable => false, :default => "" column :successful, Boolean, :nullable => false, :default => false column :commit_identifier, String, :nullable => false @@ -30,15 +31,15 @@ column :created_at, DateTime column :updated_at, DateTime - column :project_id, Serial + column :project_id, Integer end create_table :integrity_notifiers do - column :id, Serial - column :name, String, :nullable => false - column :config, Yaml, :nullable => false + column :id, Integer, :serial => true + column :name, String, :nullable => false + column :config, Yaml, :nullable => false - column :project_id, Serial + column :project_id, Integer end end @@ -48,3 +49,103 @@ drop_table :integrity_builds end end + +migration 2, :add_commits do + up do + class Build + property :commit_identifier, String + property :commit_metadata, Yaml, :lazy => false + property :project_id, Integer + end + + create_table :integrity_commits do + column :id, Integer, :serial => true + column :identifier, String, :nullable => false + column :message, String, :nullable => false, :length => 255 + column :author, String, :nullable => false, :length => 255 + column :committed_at, DateTime, :nullable => false + column :created_at, DateTime + column :updated_at, DateTime + + column :project_id, Integer + end + + modify_table :integrity_builds do + add_column :commit_id, Integer + add_column :started_at, DateTime + add_column :completed_at, DateTime + end + + # Die, orphans, die + Build.all(:project_id => nil).destroy! + + # sqlite hodgepockery + all_builds = Build.all.each {|b| b.freeze } + drop_table :integrity_builds + create_table :integrity_builds do + column :id, Integer, :serial => true + column :started_at, DateTime + column :completed_at, DateTime + column :successful, Boolean + column :output, Text, :nullable => false, :default => "" + column :created_at, DateTime + column :updated_at, DateTime + + column :commit_id, Integer + end + + all_builds.each do |build| + commit = Commit.first(:identifier => build.commit_identifier) + + if commit.nil? + commit = Commit.create(:identifier => build.commit_identifier, + :message => build.commit_metadata[:message], + :author => build.commit_metadata[:author], + :committed_at => build.commit_metadata[:date], + :project_id => build.project_id) + end + + Build.create(:commit_id => commit.id, + :started_at => build.created_at, + :completed_at => build.updated_at, + :successful => build.successful, + :output => build.output) + end + end + + down do + modify_table :integrity_builds do + add_column :commit_identifier, String, :nullable => false + add_column :commit_metadata, Yaml, :nullable => false + add_column :project_id, Integer + end + + # sqlite hodgepockery + all_builds = Build.all.map {|b| b.freeze } + drop_table :integrity_builds + create_table :integrity_builds do + column :id, Integer, :serial => true + column :output, Text, :nullable => false, :default => "" + column :successful, Boolean, :nullable => false, :default => false + column :commit_identifier, String, :nullable => false + column :commit_metadata, Yaml, :nullable => false + column :created_at, DateTime + column :updated_at, DateTime + column :project_id, Integer + end + + all_builds.each do |build| + Build.create(:project_id => build.commit.project_id, + :output => build.output, + :successful => build.successful, + :commit_identifier => build.commit.identifier, + :commit_metadata => { + :message => build.commit.message, + :author => build.commit.author.full, + :date => commit.committed_at + }.to_yaml) + end + + drop_table :commits + end +end \ No newline at end of file diff --git a/lib/integrity/notifier.rb b/lib/integrity/notifier.rb index 09f2a5c4..8c11dd9e 100644 --- a/lib/integrity/notifier.rb +++ b/lib/integrity/notifier.rb @@ -2,7 +2,7 @@ module Integrity class Notifier include DataMapper::Resource - property :id, Serial + property :id, Integer, :serial => true property :name, String, :nullable => false property :config, Yaml, :nullable => false, :lazy => false diff --git a/lib/integrity/project.rb b/lib/integrity/project.rb index 66fbfdfb..b9aee84a 100644 --- a/lib/integrity/project.rb +++ b/lib/integrity/project.rb @@ -2,7 +2,7 @@ module Integrity class Project include DataMapper::Resource - property :id, Serial + property :id, Integer, :serial => true property :name, String, :nullable => false property :permalink, String property :uri, URI, :nullable => false, :length => 255 @@ -13,7 +13,7 @@ class Project property :created_at, DateTime property :updated_at, DateTime - has n, :builds, :class_name => "Integrity::Build" + has n, :commits, :class_name => "Integrity::Commit" has n, :notifiers, :class_name => "Integrity::Notifier" before :save, :set_permalink @@ -28,38 +28,50 @@ def self.only_public_unless(condition) all(:public => true) end end - + def build(commit_identifier="HEAD") - return if building? - update_attributes(:building => true) - ProjectBuilder.new(self).build(commit_identifier) - ensure - update_attributes(:building => false) - send_notifications + commit = commits.first(:identifier => commit_identifier, :project_id => id) || last_commit + commit.queue_build end def push(payload) payload = JSON.parse(payload || "") - - if Integrity.config[:build_all_commits] - payload["commits"].sort_by { |commit| Time.parse(commit["timestamp"]) }.each do |commit| - build(commit["id"]) if payload["ref"] =~ /#{branch}/ - end + return unless payload["ref"] =~ /#{branch}/ + return if payload["commits"].nil? + return if payload["commits"].empty? + + commits = if Integrity.config[:build_all_commits] + payload["commits"] else - build(payload["after"]) if payload["ref"] =~ /#{branch}/ + [payload["commits"].first] + end + + commits.each do |commit_data| + create_commit_from(commit_data) + build(commit_data['id']) end end + def last_commit + commits.first(:project_id => id, :order => [:committed_at.desc]) + end + def last_build - all_builds.first + warn "Project#last_build is deprecated, use Project#last_commit" + last_commit end + def previous_commits + commits.all(:project_id => id, :order => [:committed_at.desc]).tap {|commits| commits.shift } + end + def previous_builds - all_builds.tap {|builds| builds.shift } + warn "Project#previous_builds is deprecated, use Project#previous_commits" + previous_commits end def status - last_build && last_build.status + last_commit && last_commit.status end def public=(flag) @@ -83,6 +95,13 @@ def enable_notifiers(*args) end private + def create_commit_from(data) + commits.create(:identifier => data["id"], + :author => data["author"], + :message => data["message"], + :committed_at => data["timestamp"]) + end + def set_permalink self.permalink = (name || "").downcase. gsub(/'s/, "s"). @@ -92,26 +111,10 @@ def set_permalink end def delete_code - builds.destroy! + commits.all(:project_id => id).destroy! ProjectBuilder.new(self).delete_code rescue SCM::SCMUnknownError => error Integrity.log "Problem while trying to deleting code: #{error}" end - - def send_notifications - notifiers.each do |notifier| - begin - Integrity.log "Notifying of build #{last_build.short_commit_identifier} using the #{notifier.name} notifier" - notifier.notify_of_build last_build - rescue Timeout::Error - Integrity.log "#{notifier.name} notifier timed out" - next - end - end - end - - def all_builds - builds.all.sort_by {|b| b.commited_at }.reverse - end end -end +end \ No newline at end of file diff --git a/lib/integrity/project_builder.rb b/lib/integrity/project_builder.rb index fe6979f2..ca26eafa 100644 --- a/lib/integrity/project_builder.rb +++ b/lib/integrity/project_builder.rb @@ -1,23 +1,23 @@ module Integrity class ProjectBuilder - attr_reader :build_script - def initialize(project) + @project = project @uri = project.uri @build_script = project.command @branch = project.branch @scm = SCM.new(@uri, @branch, export_directory) - @build = Build.new(:project => project) end def build(commit) - Integrity.log "Building #{commit} (#{@branch}) of #{@build.project.name} in #{export_directory} using #{scm_name}" - @scm.with_revision(commit) { run_build_script } + Integrity.log "Building #{commit.identifier} (#{@branch}) of #{@project.name} in #{export_directory} using #{@scm.name}" + @commit = commit + @build = commit.build + @build.update_attributes(:started_at => Time.now) + @scm.with_revision(commit.identifier) { run_build_script } @build ensure - @build.commit_identifier = @scm.commit_identifier(commit) - @build.commit_metadata = @scm.commit_metadata(commit) - @build.save + @build.update_attributes(:commit_id => commit.id, :completed_at => Time.now) + send_notifications end def delete_code @@ -27,18 +27,26 @@ def delete_code end private - def export_directory - Integrity.config[:export_directory] / "#{SCM.working_tree_path(@uri)}-#{@branch}" + def send_notifications + @project.notifiers.each do |notifier| + begin + Integrity.log "Notifying of build #{@commit.short_identifier} using the #{notifier.name} notifier" + notifier.notify_of_build @commit + rescue Timeout::Error + Integrity.log "#{notifier.name} notifier timed out" + next + end + end end - def scm_name - @scm.name + def export_directory + Integrity.config[:export_directory] / "#{SCM.working_tree_path(@uri)}-#{@branch}" end def run_build_script - Integrity.log "Running `#{build_script}` in #{@scm.working_directory}" + Integrity.log "Running `#{@build_script}` in #{@scm.working_directory}" - IO.popen "(cd #{@scm.working_directory} && #{build_script}) 2>&1", "r" do |pipe| + IO.popen "(cd #{@scm.working_directory} && #{@build_script}) 2>&1", "r" do |pipe| @build.output = pipe.read end @build.successful = $?.success? diff --git a/lib/integrity/scm/git.rb b/lib/integrity/scm/git.rb index 5b8616fe..c76c52d4 100644 --- a/lib/integrity/scm/git.rb +++ b/lib/integrity/scm/git.rb @@ -21,15 +21,6 @@ def with_revision(revision) yield end - def commit_identifier(sha1) - `cd #{working_directory} && git show -s --pretty=format:%H #{sha1}`.chomp - end - - def commit_metadata(sha1) - format = %Q(---%n:author: %an <%ae>%n:message: >-%n %s%n:date: %ci%n) - YAML.load(`cd #{working_directory} && git show -s --pretty=format:"#{format}" #{sha1}`) - end - def name self.class.name.split("::").last end diff --git a/test/acceptance/api_test.rb b/test/acceptance/api_test.rb index ba84f380..bf4aef0a 100644 --- a/test/acceptance/api_test.rb +++ b/test/acceptance/api_test.rb @@ -35,16 +35,17 @@ class ApiTest < Test::Unit::AcceptanceTestCase Project.gen(:my_test_project, :uri => repo.path) - lambda do - basic_auth "admin", "test" - post "/my-test-project/push", :payload => payload(repo.head, "master", repo.commits) - end.should change(Build, :count).by(5) + basic_auth "admin", "test" + post "/my-test-project/push", :payload => payload(repo.head, "master", repo.commits) visit "/my-test-project" response_body.should have_tag("h1", /Built #{git_repo(:my_test_project).short_head} successfully/) + + previous_builds = Hpricot(response_body).search("#previous_builds li") + previous_builds.should have(4).elements end - scenario "receiving a build request with build_all_commits *disabled* only builds HEAD" do + scenario "receiving a build request with build_all_commits *disabled* only builds the last commit passed" do Integrity.config[:build_all_commits] = false Project.gen(:my_test_project, :uri => git_repo(:my_test_project).path) @@ -53,18 +54,18 @@ class ApiTest < Test::Unit::AcceptanceTestCase git_repo(:my_test_project).add_successful_commit head = git_repo(:my_test_project).head - lambda do - basic_auth "admin", "test" - post "/my-test-project/push", :payload => payload(head, "master") - end.should change(Build, :count).by(1) + basic_auth "admin", "test" + post "/my-test-project/push", :payload => payload(head, "master", git_repo(:my_test_project).commits) response_body.should == "Thanks, build started." response_code.should == 200 visit "/my-test-project" - response_body.should =~ /#{git_repo(:my_test_project).short_head} successfully/ - response_body.should =~ /This commit will work/ + response_body.should have_tag("h1", /Built #{git_repo(:my_test_project).short_head} successfully/) + + previous_builds = Hpricot(response_body).search("#previous_builds li") + previous_builds.should be_empty end scenario "an unauthenticated request returns a 401" do diff --git a/test/acceptance/browse_project_builds_test.rb b/test/acceptance/browse_project_builds_test.rb index 89b29c75..21421153 100644 --- a/test/acceptance/browse_project_builds_test.rb +++ b/test/acceptance/browse_project_builds_test.rb @@ -8,7 +8,7 @@ class BrowseProjectBuildsTest < Test::Unit::AcceptanceTestCase EOS scenario "a project with no builds should say so in a friendly manner" do - Project.gen(:integrity, :public => true, :builds => []) + Project.gen(:integrity, :public => true, :commits => []) visit "/integrity" @@ -18,39 +18,39 @@ class BrowseProjectBuildsTest < Test::Unit::AcceptanceTestCase end scenario "a user can see the last build and the list of previous builds on a project page" do - Project.gen(:integrity, :public => true, :builds => 2.of { Build.gen(:successful => true) } + 2.of { Build.gen(:successful => false) }) + Project.gen(:integrity, :public => true, :commits => 2.of { Commit.gen(:successful) } + 2.of { Commit.gen(:failed) } + 2.of { Commit.gen(:pending) }) visit "/integrity" response_body.should have_tag("#last_build") response_body.should have_tag("#previous_builds") do |builds| + builds.should have_exactly(1).search("li.pending") + builds.should have_exactly(2).search("li.failed") builds.should have_exactly(2).search("li.success") - builds.should have_exactly(1).search("li.failed") end end scenario "a user can see details about the last build on the project page" do - build = Build.gen(:successful => true, - :commit_identifier => "7fee3f0014b529e2b76d591a8085d76eab0ff923", - :commit_metadata => { :author => "Nicolas Sanguinetti ", - :message => "No more pending tests :)", - :date => Time.mktime(2008, 12, 15, 18) }.to_yaml, - :output => "This is the build output") - Project.gen(:integrity, :public => true, :builds => [build]) + commit = Commit.gen(:successful, :identifier => "7fee3f0014b529e2b76d591a8085d76eab0ff923", + :author => "Nicolas Sanguinetti ", + :message => "No more pending tests :)", + :committed_at => Time.mktime(2008, 12, 15, 18)) + commit.build.update_attributes(:output => "This is the build output") + Project.gen(:integrity, :public => true, :commits => [commit]) visit "/integrity" response_body.should have_tag("h1", /Built 7fee3f0 successfully/) response_body.should have_tag("blockquote p", /No more pending tests/) - response_body.should have_tag("span.who", /by:\s+Nicolas Sanguinetti/) + response_body.should have_tag("span.who", /by: Nicolas Sanguinetti/) response_body.should have_tag("span.when", /Dec 15th/) response_body.should have_tag("pre.output", /This is the build output/) end scenario "a user can browse to individual build pages" do - Project.gen(:integrity, :public => true, :builds => [ - Build.gen(:commit_identifier => "7fee3f0014b529e2b76d591a8085d76eab0ff923"), - Build.gen(:commit_identifier => "87e673a83d273ecde121624a3fcfae57a04f2b76") + Project.gen(:integrity, :public => true, :commits => [ + Commit.gen(:successful, :identifier => "7fee3f0014b529e2b76d591a8085d76eab0ff923"), + Commit.gen(:successful, :identifier => "87e673a83d273ecde121624a3fcfae57a04f2b76") ]) visit "/integrity" diff --git a/test/acceptance/browse_project_test.rb b/test/acceptance/browse_project_test.rb index 8cca8c2f..8aae8217 100644 --- a/test/acceptance/browse_project_test.rb +++ b/test/acceptance/browse_project_test.rb @@ -28,15 +28,15 @@ class BrowsePublicProjectsTest < Test::Unit::AcceptanceTestCase end scenario "a user can see the projects status on the home page" do - integrity = Project.gen(:integrity, :builds => 3.of { Build.gen(:successful => true) }) - test = Project.gen(:my_test_project, :builds => 2.of { Build.gen(:successful => false) }) + integrity = Project.gen(:integrity, :commits => 3.of { Commit.gen(:successful) }) + test = Project.gen(:my_test_project, :commits => 2.of { Commit.gen(:failed) }) no_build = Project.gen(:public => true, :building => false) building = Project.gen(:public => true, :building => true) visit "/" - - response_body.should =~ /Built #{integrity.last_build.short_commit_identifier}\s*on Dec 15th\s*successfully/m - response_body.should =~ /Built #{test.last_build.short_commit_identifier}\s*on Dec 15th\s*and failed/m + + response_body.should =~ /Built #{integrity.last_commit.short_identifier} on Dec 15th successfully/m + response_body.should =~ /Built #{test.last_commit.short_identifier} on Dec 15th and failed/m response_body.should =~ /Never built yet/ response_body.should =~ /Building!/ end diff --git a/test/acceptance/build_notifications_test.rb b/test/acceptance/build_notifications_test.rb index 2b86b4f9..b1d5417e 100644 --- a/test/acceptance/build_notifications_test.rb +++ b/test/acceptance/build_notifications_test.rb @@ -13,6 +13,8 @@ class BuildNotificationsTest < Test::Unit::AcceptanceTestCase end scenario "an admin sets up a notifier for a project that didn't have any" do + return pending "Manual building is broken" + git_repo(:my_test_project).add_successful_commit Project.gen(:my_test_project, :notifiers => [], :uri => git_repo(:my_test_project).path) rm_f "/tmp/textfile_notifications.txt" diff --git a/test/acceptance/delete_project_test.rb b/test/acceptance/delete_project_test.rb index b4b09dc0..d9d2bcd4 100644 --- a/test/acceptance/delete_project_test.rb +++ b/test/acceptance/delete_project_test.rb @@ -8,7 +8,7 @@ class DeleteProjectTest < Test::Unit::AcceptanceTestCase EOS scenario "an admin can delete a project from the 'Edit Project' screen" do - Project.generate(:integrity, :builds => 4.of { Build.gen }) + Project.generate(:integrity, :commits => 4.of { Commit.gen }) login_as "admin", "test" diff --git a/test/acceptance/manual_build_project_test.rb b/test/acceptance/manual_build_project_test.rb index cc815d2f..d650c970 100644 --- a/test/acceptance/manual_build_project_test.rb +++ b/test/acceptance/manual_build_project_test.rb @@ -8,6 +8,8 @@ class ManualBuildProjectTest < Test::Unit::AcceptanceTestCase EOS scenario "clicking on 'Manual Build' triggers a successful build" do + return pending "Manual building is broken" + git_repo(:my_test_project).add_successful_commit Project.gen(:my_test_project, :uri => git_repo(:my_test_project).path) login_as "admin", "test" @@ -23,6 +25,8 @@ class ManualBuildProjectTest < Test::Unit::AcceptanceTestCase end scenario "clicking on 'Manual Build' triggers a failed build" do + return pending "Manual building is broken" + git_repo(:my_test_project).add_failing_commit Project.gen(:my_test_project, :uri => git_repo(:my_test_project).path) login_as "admin", "test" @@ -35,6 +39,8 @@ class ManualBuildProjectTest < Test::Unit::AcceptanceTestCase end scenario "fixing the build command and then rebuilding result in a successful build" do + return pending "Manual building is broken" + git_repo(:my_test_project).add_successful_commit project = Project.gen(:my_test_project, :uri => git_repo(:my_test_project).path, :command => "ruby not-found.rb") diff --git a/test/acceptance/project_syndication_test.rb b/test/acceptance/project_syndication_test.rb index f8411fdd..082b0545 100644 --- a/test/acceptance/project_syndication_test.rb +++ b/test/acceptance/project_syndication_test.rb @@ -14,8 +14,8 @@ class ProjectSyndicationTest < Test::Unit::AcceptanceTestCase end scenario "a public project's feed should include the latest builds" do - builds = 10.of { Build.gen(:successful => true) } + 1.of { Build.gen(:successful => false) } - Project.gen(:integrity, :public => true, :builds => builds) + commits = 10.of { Commit.gen(:successful) } + 1.of { Commit.gen(:failed) } + Project.gen(:integrity, :public => true, :commits => commits) visit "/integrity.atom" diff --git a/test/helpers/acceptance/git_helper.rb b/test/helpers/acceptance/git_helper.rb index 35e5d935..0f18ec5f 100644 --- a/test/helpers/acceptance/git_helper.rb +++ b/test/helpers/acceptance/git_helper.rb @@ -43,7 +43,7 @@ def commits Dir.chdir(@path) do commits = `git log --pretty=oneline`.collect { |line| line.split(" ").first } commits.inject([]) do |commits, sha1| - format = %Q(---%n:message: >-%n %s%n:timestamp: %ci%n:id: #{sha1}) + format = %Q(---%n:message: >-%n %s%n:timestamp: %ci%n:id: %H%n:author: %an <%ae>) commits << YAML.load(`git show -s --pretty=format:"#{format}" #{sha1}`) end end diff --git a/test/helpers/fixtures.rb b/test/helpers/fixtures.rb index f8b92547..c787f7cd 100644 --- a/test/helpers/fixtures.rb +++ b/test/helpers/fixtures.rb @@ -9,16 +9,6 @@ def pick end end -def commit_metadata - meta_data = <<-EOS ---- -:author: #{/\w+ \w+ <\w+@example.org>/.gen} -:message: >- - #{/\w+/.gen} -:date: #{unique {|i| Time.mktime(2008, 12, 15, 18, (59 - i) % 60) }} -EOS -end - def create_notifier!(name) klass = Class.new(Integrity::Notifier::Base) do def self.to_haml; ""; end @@ -58,12 +48,49 @@ def deliver!; nil; end :building => false } end +Integrity::Commit.fixture do + project = Integrity::Project.first || Integrity::Project.gen + + { :identifier => Digest::SHA1.hexdigest(/[:paragraph:]/.gen), + :message => /[:sentence:]/.gen, + :author => /\w+ \w+ <\w+@example.org>/.gen, + :committed_at => unique {|i| Time.mktime(2009, 12, 15, 18, (59 - i) % 60) }, + :project_id => project.id } +end + +Integrity::Commit.fixture(:successful) do + Integrity::Commit.generate_attributes.update(:build => Integrity::Build.gen(:successful)) +end + +Integrity::Commit.fixture(:failed) do + Integrity::Commit.generate_attributes.update(:build => Integrity::Build.gen(:failed)) +end + +Integrity::Commit.fixture(:pending) do + Integrity::Commit.generate_attributes.update(:build => Integrity::Build.gen(:pending)) +end + Integrity::Build.fixture do - { :output => /[:paragraph:]/.gen, - :successful => true, - :created_at => unique {|i| Time.mktime(2008, 12, 15, 18, (59 - i) % 60) }, - :commit_identifier => Digest::SHA1.hexdigest(/[:paragraph:]/.gen), - :commit_metadata => commit_metadata } + commit = Integrity::Commit.first || Integrity::Commit.gen + + { :output => /[:paragraph:]/.gen, + :successful => true, + :started_at => unique {|i| Time.mktime(2008, 12, 15, 18, i % 60) }, + :created_at => unique {|i| Time.mktime(2008, 12, 15, 18, i % 60) }, + :completed_at => unique {|i| Time.mktime(2008, 12, 15, 18, i % 60) }, + :commit_id => commit.id } +end + +Integrity::Build.fixture(:successful) do + Integrity::Build.generate_attributes.update(:successful => true) +end + +Integrity::Build.fixture(:failed) do + Integrity::Build.generate_attributes.update(:successful => false) +end + +Integrity::Build.fixture(:pending) do + Integrity::Build.generate_attributes.update(:successful => nil, :started_at => nil, :completed_at => nil) end Integrity::Notifier.fixture(:irc) do @@ -80,4 +107,4 @@ def deliver!; nil; end { :project => Integrity::Project.generate, :name => "Twitter", :config => { :email => "foo@example.org", :pass => "secret" }} -end +end \ No newline at end of file diff --git a/test/unit/build_test.rb b/test/unit/build_test.rb index b98c1209..c2a8b9dc 100644 --- a/test/unit/build_test.rb +++ b/test/unit/build_test.rb @@ -8,16 +8,16 @@ class BuildTest < Test::Unit::TestCase specify "fixture is valid and can be saved" do lambda do - Build.generate.tap do |build| - build.should be_valid - build.save - end + build = Build.gen + build.save + + build.should be_valid end.should change(Build, :count).by(1) end describe "Properties" do before(:each) do - @build = Build.generate(:commit_identifier => "658ba96cb0235e82ee720510c049883955200fa9") + @build = Build.gen end it "captures the build's STDOUT/STDERR" do @@ -37,36 +37,16 @@ class BuildTest < Test::Unit::TestCase @build.successful = false @build.status.should be(:failed) end - - it "has an human readable status" do - Build.gen(:successful => true).human_readable_status.should == "Build Successful" - Build.gen(:successful => false).human_readable_status.should == "Build Failed" - end - - it "has a commit identifier" do - @build.commit_identifier.should be("658ba96cb0235e82ee720510c049883955200fa9") - end - - it "has a short commit identifier" do - @build.short_commit_identifier.should == "658ba96" - Build.gen(:commit_identifier => "402").short_commit_identifier.should == "402" - end - - it "has a commit author" do - build = Build.gen(:commit_metadata => { :author => "Nicolás Sanguinetti " }) - build.commit_author.name.should == "Nicolás Sanguinetti" - build.commit_author.email.should == "contacto@nicolassanguinetti.info" - build.commit_author.full.should == "Nicolás Sanguinetti " - end - - it "has a commit message" do - build = Build.gen(:commit_metadata => { :message => "This commit rocks" }) - build.commit_message.should == "This commit rocks" + end + + describe "Pending builds" do + before(:each) do + 3.of { Build.gen(:started_at => nil) } + 2.of { Build.gen(:started_at => Time.mktime(2009, 1, 17, 23, 18)) } end - it "has a commit date" do - build = Build.gen(:commit_metadata => { :date => Time.utc(2008, 10, 12, 14, 18, 20) }) - build.commited_at.to_s.should == "Sun Oct 12 14:18:20 UTC 2008" + it "finds builds that need to be built" do + Build.should have(3).pending end end -end +end \ No newline at end of file diff --git a/test/unit/commit_test.rb b/test/unit/commit_test.rb new file mode 100644 index 00000000..e4feab40 --- /dev/null +++ b/test/unit/commit_test.rb @@ -0,0 +1,84 @@ +require File.dirname(__FILE__) + '/../helpers' + +class CommitTest < Test::Unit::TestCase + before(:each) do + RR.reset + setup_and_reset_database! + end + + specify "fixture is valid and can be saved" do + lambda do + commit = Commit.gen + commit.save + + commit.should be_valid + end.should change(Commit, :count).by(1) + end + + describe "Properties" do + before(:each) do + @commit = Commit.generate(:identifier => "658ba96cb0235e82ee720510c049883955200fa9", + :author => "Nicolás Sanguinetti ") + end + + it "has a commit identifier" do + @commit.identifier.should be("658ba96cb0235e82ee720510c049883955200fa9") + end + + it "has a short commit identifier" do + @commit.short_identifier.should == "658ba96" + + @commit.identifier = "402" + @commit.short_identifier.should == "402" + end + + it "has a commit author" do + commit = Commit.gen(:author => "Nicolás Sanguinetti ") + commit.author.name.should == "Nicolás Sanguinetti" + commit.author.email.should == "contacto@nicolassanguinetti.info" + commit.author.full.should == "Nicolás Sanguinetti " + end + + it "has a commit message" do + commit = Commit.gen(:message => "This commit rocks") + commit.message.should == "This commit rocks" + end + + it "has a commit date" do + commit = Commit.gen(:committed_at => Time.utc(2008, 10, 12, 14, 18, 20)) + commit.committed_at.to_s.should == "2008-10-12T14:18:20+00:00" + end + + it "has a human readable status" do + commit = Commit.gen(:successful, :identifier => "658ba96cb0235e82ee720510c049883955200fa9") + commit.human_readable_status.should be("Built 658ba96 successfully") + + commit = Commit.gen(:failed, :identifier => "658ba96cb0235e82ee720510c049883955200fa9") + commit.human_readable_status.should be("Built 658ba96 and failed") + + commit = Commit.gen(:pending, :identifier => "658ba96cb0235e82ee720510c049883955200fa9") + commit.human_readable_status.should be("658ba96 hasn't been built yet") + end + end + + describe "Queueing a build" do + before(:each) do + @commit = Commit.gen + stub.instance_of(ProjectBuilder).build(@commit) + end + + it "creates an empty Build" do + @commit.build.should be_nil + @commit.queue_build + @commit.build.should_not be_nil + end + + it "ensures the build is saved" do + @commit.build.should be_nil + @commit.queue_build + + commit = Commit.first(:identifier => @commit.identifier) + commit.build.should_not be_nil + end + end +end \ No newline at end of file diff --git a/test/unit/project_builder_test.rb b/test/unit/project_builder_test.rb index 6d9ce64c..6d2347eb 100644 --- a/test/unit/project_builder_test.rb +++ b/test/unit/project_builder_test.rb @@ -6,90 +6,93 @@ class ProjectBuilderTest < Test::Unit::TestCase @directory = Integrity.config[:export_directory] + "/foca-integrity-master" FileUtils.mkdir(@directory) end - + after(:all) do FileUtils.rm_rf(@directory) end - + before(:each) do setup_and_reset_database! @project = Integrity::Project.generate(:integrity, :command => "echo 'output!'") ignore_logs! end - + it "creates a new SCM with given project's uri, branch and export_directory" do SCM::Git.expects(:new).with(@project.uri, @project.branch, @directory) ProjectBuilder.new(@project) end - + describe "When building" do - it "creates a new build" do - lambda do - SCM::Git.any_instance.expects(:with_revision).with("commit").yields - SCM::Git.any_instance.expects(:commit_identifier).with("commit").returns("commit identifier") - SCM::Git.any_instance.expects(:commit_metadata).with("commit").returns(:meta => "data") - - build = ProjectBuilder.new(@project).build("commit") - build.commit_identifier.should == "commit identifier" - build.commit_metadata.should == {:meta => "data"} - build.output.should == "output!\n" - build.should be_successful - end.should change(@project.builds, :count).by(1) + before(:each) do + @commit = @project.commits.gen(:pending) end - - it "creates a new build even if something horrible happens" do - lambda do - lambda do - SCM::Git.any_instance.expects(:with_revision).with("commit").raises - SCM::Git.any_instance.expects(:commit_identifier).with("commit").returns("commit identifier") - SCM::Git.any_instance.expects(:commit_metadata).with("commit").returns(:meta => "data") - - build = ProjectBuilder.new(@project).build("commit") - build.commit_identifier.should == "commit identifier" - build.commit_metadata.should == {:meta => "data"} - end.should change(@project.builds, :count).by(1) - end.should raise_error + + it "sets the started and completed timestamps" do + SCM::Git.any_instance.expects(:with_revision).with(@commit.identifier).yields + + build = ProjectBuilder.new(@project).build(@commit) + build.output.should == "output!\n" + build.started_at.should_not be_nil + build.completed_at.should_not be_nil + build.should be_successful end + + it "ensures completed_at is set, even if something horrible happens" do + lambda { + SCM::Git.any_instance.expects(:with_revision).with(@commit.identifier).raises + + build = ProjectBuilder.new(@project).build(@commit) + build.started_at.should_not be_nil + build.completed_at.should_not be_nil + build.should be_failed + }.should raise_error + end + it "sets the build status to failure when the build command exits with a non-zero status" do @project.update_attributes(:command => "exit 1") - SCM::Git.any_instance.expects(:with_revision).with("HEAD").yields - build = ProjectBuilder.new(@project).build("HEAD") + SCM::Git.any_instance.expects(:with_revision).with(@commit.identifier).yields + build = ProjectBuilder.new(@project).build(@commit) build.should be_failed end - - it "sets the build status to failure when the build command exits with a zero status" do + + it "sets the build status to success when the build command exits with a zero status" do @project.update_attributes(:command => "exit 0") - SCM::Git.any_instance.expects(:with_revision).with("HEAD").yields - build = ProjectBuilder.new(@project).build("HEAD") + SCM::Git.any_instance.expects(:with_revision).with(@commit.identifier).yields + build = ProjectBuilder.new(@project).build(@commit) build.should be_successful end - + it "runs the command in the export directory" do @project.update_attributes(:command => "cat foo.txt") File.open(@directory + "/foo.txt", "w") { |file| file << "bar!" } - SCM::Git.any_instance.expects(:with_revision).with("HEAD").yields - - build = ProjectBuilder.new(@project).build("HEAD") + SCM::Git.any_instance.expects(:with_revision).with(@commit.identifier).yields + + build = ProjectBuilder.new(@project).build(@commit) build.output.should == "bar!" end - + it "captures both stdout and stderr" do @project.update_attributes(:command => "echo foo through out && echo bar through err 1>&2") - SCM::Git.any_instance.expects(:with_revision).with("HEAD").yields - - build = ProjectBuilder.new(@project).build("HEAD") + SCM::Git.any_instance.expects(:with_revision).with(@commit.identifier).yields + + build = ProjectBuilder.new(@project).build(@commit) build.output.should == "foo through out\nbar through err\n" end + + it "raises SCMUnknownError if it can't figure the scm from the uri" do + @project.update_attributes(:uri => "scm://example.org") + lambda { ProjectBuilder.new(@project) }.should raise_error(SCM::SCMUnknownError) + end end - + describe "When deleting the code from disk" do it "destroys the directory" do lambda do ProjectBuilder.new(@project).delete_code end.should change(Pathname.new(@directory), :directory?).from(true).to(false) end - + it "don't complains if the directory doesn't exists" do Pathname.new(@directory).should_not be_directory lambda { ProjectBuilder.new(@project).delete_code }.should_not raise_error diff --git a/test/unit/project_test.rb b/test/unit/project_test.rb index 82ca35b1..bbdaa0e7 100644 --- a/test/unit/project_test.rb +++ b/test/unit/project_test.rb @@ -15,7 +15,7 @@ class ProjectTest < Test::Unit::TestCase end end.should change(Project, :count).by(1) end - + specify "integrity fixture is valid and can be saved" do lambda do Project.generate(:integrity).tap do |project| @@ -24,118 +24,120 @@ class ProjectTest < Test::Unit::TestCase end end.should change(Project, :count).by(1) end - + describe "Properties" do before(:each) do @project = Project.generate(:integrity) end - + it "has a name" do @project.name.should == "Integrity" end - + it "has a permalink" do @project.permalink.should == "integrity" - + @project.tap do |project| project.name = "foo's bar/baz and BACON?!" project.save end.permalink.should == "foos-bar-baz-and-bacon" end - + it "has an URI" do @project.uri.should == Addressable::URI.parse("git://github.com/foca/integrity.git") end - + it "has a branch" do @project.branch.should == "master" end - + specify "branch defaults to master" do Project.new.branch.should == "master" end - + it "has a command" do # TODO: rename to build_command @project.command.should == "rake" end - + specify "command defaults to 'rake'" do Project.new.command.should == "rake" end - + it "has a building flag" do @project.should_not be_building end - + specify "building flag default to false" do Project.new.should_not be_building end - + it "knows it's visibility" do # TODO: rename Project#public property to visibility # TODO: and have utility method to query its state instead - + Project.new.should be_public - + @project.should be_public @project.tap { |p| p.public = "1" }.should be_public @project.tap { |p| p.public = "0" }.should_not be_public - + Project.gen(:public => "false").should be_public Project.gen(:public => "true").should be_public Project.gen(:public => false).should_not be_public Project.gen(:public => nil).should_not be_public end - + it "has a created_at" do @project.created_at.should be_a(DateTime) end - + it "has an updated_at" do @project.updated_at.should be_a(DateTime) end - + it "knows it's status" do - Project.gen(:builds => 1.of{Build.make(:successful => true )}).status.should == :success - Project.gen(:builds => 2.of{Build.make(:successful => true )}).status.should == :success - Project.gen(:builds => 2.of{Build.make(:successful => false)}).status.should == :failed - Project.gen(:builds => []).status.should be_nil + Project.gen(:commits => 1.of{ Commit.gen(:successful) }).status.should == :success + Project.gen(:commits => 2.of{ Commit.gen(:successful) }).status.should == :success + Project.gen(:commits => 2.of{ Commit.gen(:failed) }).status.should == :failed + Project.gen(:commits => 1.of{ Commit.gen(:pending) }).status.should == :pending + Project.gen(:commits => []).status.should be_nil end - + it "knows it's last build" do - Project.gen(:builds => []).last_build.should be_nil - - project = Project.gen(:builds => (builds = 5.of{Build.make(:successful => true)})) - project.last_build.should == builds.sort_by {|build| build.created_at }.last + Project.gen(:commits => []).last_commit.should be_nil + + commits = 5.of { Commit.gen(:successful) } + project = Project.gen(:commits => commits) + project.last_commit.should == commits.sort_by {|c| c.committed_at }.last end end - + describe "Validation" do it "requires a name" do lambda do Project.gen(:name => nil).should_not be_valid end.should_not change(Project, :count) end - + it "requires an URI" do lambda do Project.gen(:uri => nil).should_not be_valid end.should_not change(Project, :count) end - + it "requires a branch" do lambda do Project.gen(:branch => nil).should_not be_valid end.should_not change(Project, :count) end - + it "requires a command" do lambda do Project.gen(:command => nil).should_not be_valid end.should_not change(Project, :count) end - + it "ensures its name is unique" do Project.gen(:name => "Integrity") lambda do @@ -162,80 +164,79 @@ class ProjectTest < Test::Unit::TestCase projects.should include(@public_project) end end - + describe "When finding its previous builds" do before(:each) do - @builds = 5.of { Build.gen } - @project = Project.generate(:builds => @builds) + @project = Project.generate(:commits => 5.of { Commit.gen }) + @commits = @project.commits.sort_by {|c| c.committed_at }.reverse end - + it "has 4 previous builds" do - @project.should have(4).previous_builds + @project.should have(4).previous_commits end - + it "returns the builds ordered chronogicaly (desc) by creation date" do - builds_sorted_by_creation_date = @builds.sort_by {|build| build.created_at }.reverse - @project.previous_builds.should == builds_sorted_by_creation_date[1..-1] + @project.previous_commits.should == @commits[1..-1] end - + it "excludes the last build" do - @project.previous_builds.should_not include(@project.last_build) + @project.previous_commits.should_not include(@project.last_commit) end - + it "returns an empty array if it has only one build" do - project = Project.gen(:builds => 1.of { Integrity::Build.make }) - project.should have(:no).previous_builds + project = Project.gen(:commits => 1.of { Integrity::Commit.gen }) + project.should have(:no).previous_commits end - + it "returns an empty array if there are no builds" do - project = Project.gen(:builds => []) - project.should have(:no).previous_builds + project = Project.gen(:commits => []) + project.should have(:no).previous_commits end end - + describe "When getting destroyed" do before(:each) do - @builds = (1..7).of { Build.make } - @project = Project.generate(:builds => @builds) + @commits = 7.of { Commit.gen } + @project = Project.generate(:commits => @commits) end - + it "destroys itself and tell ProjectBuilder to delete the code from disk" do lambda do stub.instance_of(ProjectBuilder).delete_code @project.destroy end.should change(Project, :count).by(-1) end - + it "destroys its builds" do lambda do @project.destroy - end.should change(Build, :count).by(-@builds.length) + end.should change(Commit, :count).by(-7) end end - + describe "When retrieving state about its notifier" do before(:each) do @project = Project.generate @irc = Notifier.generate(:irc) end - + specify "#config_for returns given notifier's configuration" do @project.update_attributes(:notifiers => [@irc]) @project.config_for("IRC").should == {:uri => "irc://irc.freenode.net/integrity"} end - + specify "#config_for returns an empty hash if no such notifier" do @project.config_for("IRC").should == {} end - + specify "#notifies? is true if it uses the given notifier" do @project.update_attributes(:notifiers => [@irc]) @project.notifies?("IRC").should == true end - + specify "#notifies? is false if it doesnt use the given notifier" do @project.update_attributes(:notifiers => []) - + @project.notifies?("IRC").should == false @project.notifies?("UndefinedNotifier").should == false end @@ -243,79 +244,32 @@ class ProjectTest < Test::Unit::TestCase describe "When building a build" do before(:each) do - @builds = (1..7).of { Build.make } - @project = Project.generate(:integrity, :builds => @builds) + @commits = 2.of { Commit.gen } + @project = Project.gen(:integrity, :commits => @commits) stub.instance_of(ProjectBuilder).build { nil } end + + it "gets the specified commit and creates a pending build for it" do + commit = @commits.last - it "builds the given commit identifier and handle its building state" do - @project.should_not be_building - lambda do - mock.instance_of(Integrity::ProjectBuilder).build("foo") { @project.should be_building } - @project.build("foo") - end.should_not change(@project, :building) - end - - it "don't build if it is already building" do - @project.update_attributes(:building => true) - do_not_call(ProjectBuilder).build - @project.build - end - - it "builds HEAD by default" do - mock.instance_of(Integrity::ProjectBuilder).build("HEAD") - @project.build - end - - it "sends notifications with all registered notifiers" do - irc = Integrity::Notifier.make(:irc) - twitter = Integrity::Notifier.make(:twitter) - @project.update_attributes(:notifiers => [irc, twitter]) - - mock.proxy(Integrity::Notifier::IRC).notify_of_build(@project.last_build, :uri => "irc://irc.freenode.net/integrity") - mock.proxy(Integrity::Notifier::Twitter).notify_of_build(@project.last_build, :email => "foo@example.org", :pass => "secret") + lambda { + @project.build(commit.identifier) + }.should change(Build, :count).by(1) - @project.build + build = Build.all.last + build.commit.should be(commit) + build.should be_pending + + commit.should be_pending end - - it "still sends notification even if one of the notifier timeout" do - irc = Integrity::Notifier.make(:irc) - twitter = Integrity::Notifier.make(:twitter) - @project.update_attributes(:notifiers => [irc, twitter]) - - mock.proxy(Integrity::Notifier::Twitter).notify_of_build(@project.last_build, anything) do - raise Timeout::Error - end - mock.proxy(Integrity::Notifier::IRC).notify_of_build(@project.last_build, anything) - + + it "gets the project's last commit if passed a wrong commit identifier" do @project.build - end - end - - it "doesn't have weird, wtf-ish bugs" do - success = Project.gen(:builds => 10.of{Build.make(:successful => true)}) - failure = Project.gen(:builds => 10.of{Build.make(:successful => false)}) - unbuild = Project.gen - - Build.count.should == 20 - success.should have(10).builds - failure.should have(10).builds - unbuild.should have(0).builds - success.last_build.should be_successful - failure.last_build.should be_failed - unbuild.last_build.should be_nil - - Project.all.each do |project| - case project - when success - project.last_build.should be_successful - when failure - project.id.should == failure.id - project.last_build.should be_failed - when unbuild - project.last_build.should be_nil - end + build = Build.all.last + build.commit.should be(@project.last_commit) + + @project.last_commit.should be_pending end end end diff --git a/views/_build_info.haml b/views/_build_info.haml deleted file mode 100644 index 1857e3d9..00000000 --- a/views/_build_info.haml +++ /dev/null @@ -1,18 +0,0 @@ -%h1< - &== Built #{build.short_commit_identifier} #{build.successful? ? "successfully" : "and failed"} -%blockquote - %p&= build.commit_message - %p.meta< - %span.who< - &== by: #{build.commit_author.name} - | - %span.when{ :title => build.commited_at.iso8601 }< - &= pretty_date build.commited_at - | - %span.what< - &== commit: #{build.commit_identifier} - -%h2 Build Output: -%pre.output - :preserve - #{bash_color_codes h(build.output)} diff --git a/views/_commit_info.haml b/views/_commit_info.haml new file mode 100644 index 00000000..38c2d504 --- /dev/null +++ b/views/_commit_info.haml @@ -0,0 +1,18 @@ +%h1< + &== Built #{commit.short_identifier} #{commit.successful? ? "successfully" : "and failed"} +%blockquote + %p&= commit.message + %p.meta< + %span.who< + &== by: #{commit.author.name} + | + %span.when{ :title => commit.commited_at }< + &= pretty_date commit.committed_at + | + %span.what< + &== commit: #{commit.identifier} + +%h2 Build Output: +%pre.output + :preserve + #{bash_color_codes h(commit.output)} diff --git a/views/build.haml b/views/build.haml index 529ed07e..7fd2a5d4 100644 --- a/views/build.haml +++ b/views/build.haml @@ -1,2 +1,2 @@ -#build{ :class => @build.status } - = partial(:build_info, :build => @build) +#build{ :class => @commit.status } + = partial(:commit_info, :commit => @commit) diff --git a/views/home.haml b/views/home.haml index b85604af..c31b15d3 100644 --- a/views/home.haml +++ b/views/home.haml @@ -8,16 +8,14 @@ - else %ul#projects - @projects.each do |project| - %li{ :class => cycle("even", "odd") + (project.building? ? ' building' : '') + (project.last_build ? (project.last_build.successful? ? ' success' : ' failed') : '') } + %li{ :class => cycle("even", "odd") + ' ' + project.status.to_s } %a{ :href => project_path(project) }&= project.name .meta - if project.building? Building! - - elsif project.last_build.nil? + - elsif project.last_commit.nil? Never built yet - else - == Built #{project.last_build.short_commit_identifier} - = pretty_date(project.last_build.created_at) - = project.last_build.successful? ? "successfully" : "and failed" + == Built #{project.last_commit.short_identifier} #{pretty_date(project.last_commit.committed_at)} #{project.last_commit.successful? ? "successfully" : "and failed"} %p#new %a{ :href => "/new" } Add a new project diff --git a/views/project.builder b/views/project.builder index d6a3651c..1fc6c449 100644 --- a/views/project.builder +++ b/views/project.builder @@ -2,20 +2,20 @@ xml.instruct! xml.feed :xmlns => "http://www.w3.org/2005/Atom" do xml.title "Build history for #{@project.name}" xml.subtitle @project.uri - xml.updated @project.builds.first.created_at + xml.updated @project.last_commit.updated_at xml.link :href => "#{project_url(@project)}.atom", :rel => "self" xml.id "#{project_url(@project)}.atom" - @project.builds.each do |build| + @project.commits.each do |commit| xml.entry do - xml.id build_url(build) - xml.link :href => build_url(build), :rel => "alternate", :type => "text/html" - xml.updated build.created_at - xml.published build.created_at + xml.id build_url(commit) + xml.link :href => build_url(commit), :rel => "alternate", :type => "text/html" + xml.updated commit.created_at + xml.published commit.created_at - xml.title "Built #{build.short_commit_identifier} #{build.successful? ? "successfully" : "and failed"}" - xml.author { xml.name(build.commit_author.name) } - xml.content("
#{partial(:build_info, :build => build)}
", :type => "html") + xml.title commit.human_readable_status + xml.author { xml.name(commit.author.name) } + xml.content("
#{partial(:commit_info, :commit => commit)}
", :type => "html") end end end diff --git a/views/project.haml b/views/project.haml index cb051a5e..8febb881 100644 --- a/views/project.haml +++ b/views/project.haml @@ -1,28 +1,28 @@ #administrative %a{ :href => project_path(@project, :edit) } Edit Project -- if @project.builds.empty? +- if @project.commits.empty? %form.blank_slate{ :action => project_path(@project, :builds), :method => :post } %p No builds for this project, buddy %h1 You can request a %button{ :type => :submit } manual build - else - - @build = @project.last_build - #last_build{ :class => @build.status } - = partial(:build_info, :build => @build) + - @commit = @project.last_commit + #last_build{ :class => @commit.status } + = partial(:commit_info, :commit => @commit) %form{ :action => project_path(@project, :builds), :method => :post } %p.submit %button{ :type => :submit } Request Manual Build - - unless @project.previous_builds.empty? + - unless @project.previous_commits.empty? %h2 Previous builds %ul#previous_builds - - @project.previous_builds.each do |build| - %li{ :class => build.status } - %a{ :href => build_path(build) } + - @project.previous_commits.each do |commit| + %li{ :class => commit.status } + %a{ :href => build_path(commit) } %strong.build< - &== Build #{build.short_commit_identifier} + &== Build #{commit.short_identifier} %span.attribution< - == by #{build.commit_author.name}, #{pretty_date build.commited_at} + == by #{commit.author.name}, #{pretty_date commit.committed_at}