jeroenvandijk-bob 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ (The MIT License)
2
+
3
+ Copyright (c) 2008-2009 Nicolas Sanguinetti, entp.com
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ 'Software'), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,30 @@
1
+ = Bob the Builder
2
+
3
+ Given a Buildable object with a determined API (described in it's documentation),
4
+ Bob will, when called like:
5
+
6
+ Bob.build(buildable, commit_id) # or Bob.build(buildable, [commit_id, commit_id, ...])
7
+
8
+ or from your buildable (if you are using the Bob::Buildable mixin provided) as:
9
+
10
+ buildable.build(commit_id) # or buildable.build([commit_id, commit_id, ...])
11
+
12
+ 1. Checkout the buildable on the specified commit
13
+ 2. Call <tt>Buildable#start_building</tt>
14
+ 3. Run the script provided in <tt>Buildable#build_script</tt> in the buildable.
15
+ 4. When the build process finishes, it will call <tt>Buildable#finish_building</tt> with
16
+ the commit_id, the build status (true if the script returns a status code
17
+ of 0, false otherwise), and a string with the build output (both STDOUT and STDERR).
18
+
19
+ If you pass an array of commits, the steps 1-4 will be repeated for each commit provided,
20
+ in order.
21
+
22
+ == Do I need this?
23
+
24
+ Probably not. Check out integrity[http://integrityapp.com] for a full fledged
25
+ automated CI server, which is what most people need.
26
+
27
+ == Credits
28
+
29
+ Authors:: Nicolas Sanguinetti (foca[http://github.com/foca]) and Simon Rozet (sr[http://github.com/sr])
30
+ License:: MIT (Check LICENSE for details)
data/Rakefile ADDED
@@ -0,0 +1,70 @@
1
+ require "rake/testtask"
2
+
3
+ rdoc_sources = %w(hanna/rdoctask rdoc/task rake/rdoctask)
4
+ begin
5
+ require rdoc_sources.shift
6
+ rescue LoadError
7
+ retry
8
+ end
9
+
10
+ begin
11
+ require 'jeweler'
12
+ Jeweler::Tasks.new do |s|
13
+ s.name = "bob"
14
+ s.rubyforge_project = "integrity"
15
+ s.summary = "Bob builds!"
16
+ s.email = "info@integrityapp.com"
17
+ s.homepage = "http://integrityapp.com"
18
+ s.description = "Bob the Builder will build your code. Simple."
19
+ s.authors = ["Nicolás Sanguinetti", "Simon Rozet"]
20
+ s.files = FileList["[A-Z]*", "{lib}/**/*"]
21
+
22
+ s.add_dependency "addressable"
23
+ s.add_dependency "ninja"
24
+ s.has_rdoc = true
25
+ if s.respond_to?(:add_development_dependency)
26
+ s.add_development_dependency "sr-mg"
27
+ s.add_development_dependency "contest"
28
+ s.add_development_dependency "redgreen"
29
+ s.add_development_dependency "ruby-debug"
30
+ end
31
+ end
32
+ rescue LoadError
33
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
34
+ end
35
+
36
+ begin
37
+ require "metric_fu" if RUBY_VERSION < "1.9"
38
+ rescue LoadError
39
+ end
40
+
41
+ begin
42
+ require "mg"
43
+ MG.new("bob.gemspec")
44
+ rescue LoadError
45
+ end
46
+
47
+ desc "Default: run all tests"
48
+ task :default => :test
49
+
50
+ SCMs = %w[git svn]
51
+
52
+ desc "Run unit tests"
53
+ task :test => SCMs.map { |scm| "test:#{scm}" } do
54
+ ruby "test/bob_test.rb"
55
+ ruby "test/test_test.rb"
56
+ end
57
+
58
+ SCMs.each { |scm|
59
+ desc "Run unit tests with #{scm}"
60
+ task "test:#{scm}" do
61
+ ruby "test/scm/#{scm}_test.rb"
62
+ end
63
+ }
64
+
65
+ (defined?(RDoc::Task) ? RDoc::Task : Rake::RDocTask).new do |rd|
66
+ rd.main = "README.rdoc"
67
+ rd.title = "Documentation for Bob the Builder"
68
+ rd.rdoc_files.include("README.rdoc", "LICENSE", "lib/**/*.rb")
69
+ rd.rdoc_dir = "doc"
70
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.1
data/lib/bob.rb ADDED
@@ -0,0 +1,35 @@
1
+ require "fileutils"
2
+ require "pathname"
3
+ require "yaml"
4
+ require "logger"
5
+ require "time"
6
+ require "ninja"
7
+ require "addressable/uri"
8
+
9
+ require "bob/buildable"
10
+ require "bob/builder"
11
+ require "bob/scm"
12
+
13
+ module Bob
14
+ # Builds the specified <tt>buildable</tt>. This object must understand
15
+ # the API described in the README.
16
+ def self.build(buildable)
17
+ Builder.new(buildable).build
18
+ end
19
+
20
+ # Directory where the code for the different buildables will be checked out.
21
+ # Make sure the user running Bob is allowed to write to this directory.
22
+ def self.directory
23
+ Pathname(@directory || "/tmp")
24
+ end
25
+
26
+ # What to log with (must implement ruby's Logger interface). Logs to STDOUT
27
+ # by default.
28
+ def self.logger
29
+ @logger || Logger.new(STDOUT)
30
+ end
31
+
32
+ class << self
33
+ attr_writer :directory, :logger
34
+ end
35
+ end
@@ -0,0 +1,74 @@
1
+ module Bob
2
+ # Mixin to add to your classes.
3
+ module Buildable
4
+ # Build itself.
5
+ def build
6
+ Bob.build(self)
7
+ end
8
+
9
+ # What kind of repository this buildable represents. Must
10
+ # return a String ("git", "svn", etc.)
11
+ #
12
+ # <b>You must implement this in the classes where you mixin this module</b>
13
+ def scm
14
+ raise NotImplementedError
15
+ end
16
+
17
+ # Full URI to the repository to clone/checkout.
18
+ #
19
+ # <b>You must implement this in the classes where you mixin this module</b>
20
+ def uri
21
+ raise NotImplementedError
22
+ end
23
+
24
+ # Branch of the code you want to watch in order to build.
25
+ #
26
+ # <b>You must implement this in the classes where you mixin this module</b>
27
+ def branch
28
+ raise NotImplementedError
29
+ end
30
+
31
+ # Indentifier of the commit to build.
32
+ #
33
+ # The special identifier <tt>:head</tt> will be resolved to the head
34
+ # commit of the current branch (for example, "HEAD" under git or the
35
+ # latest revision under svn)
36
+ #
37
+ # <b>You must implement this in the classes where you mixin this module</b>
38
+ def commit
39
+ raise NotImplementedError
40
+ end
41
+
42
+ # Script that will be run in the buildable's checked out code,
43
+ # if it returns a status code of 0 it will be considered a
44
+ # successfull build. Else it will be considered a failed build.
45
+ #
46
+ # <b>You must implement this in the classes where you mixin this module</b>
47
+ def build_script
48
+ raise NotImplementedError
49
+ end
50
+
51
+ # Optional callback sent when the build starts. It can be used to calculate
52
+ # the build duration for example.
53
+ def start_building
54
+ end
55
+
56
+ # Callback sent after a build finishes. The first argument is a hash with
57
+ # information about the commit.
58
+ #
59
+ # <tt>identifier</tt>:: A string with the commit identifier of the
60
+ # commit that was built
61
+ # <tt>author</tt>:: A string with the name/email of the committer
62
+ # <tt>message</tt>:: The commit message
63
+ # <tt>committed_at</tt>:: A Time object with the timestamp of the commit
64
+ #
65
+ # The second argument is a boolean indicating whether the build was
66
+ # successful. Finally, the last one is a string with the full output
67
+ # returned by the build process (STDOUT and STDERR interleaved)
68
+ #
69
+ # <b>You must implement this in the classes where you mixin this module</b>
70
+ def finish_building(commit_info, build_status, build_output)
71
+ raise NotImplementedError
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,53 @@
1
+ module Bob
2
+ # A Builder will take care of building a buildable (wow, you didn't see
3
+ # that coming, right?).
4
+ class Builder
5
+ attr_reader :buildable
6
+
7
+ include Ninja
8
+
9
+ # Instantiate the Builder, passing an object that understands
10
+ # the <tt>Buildable</tt> interface.
11
+ def initialize(buildable)
12
+ @buildable = buildable
13
+ end
14
+
15
+ # This is where the magic happens:
16
+ #
17
+ # 1. Notify the buildable that the build is starting.
18
+ # 2. Check out the repo to the appropriate commit.
19
+ # 3. Run the build script on it.
20
+ # 4. Reports the build back to the buildable.
21
+ def build
22
+ Bob.logger.info "Building #{buildable.commit} of the #{buildable.scm} repo at #{buildable.uri}"
23
+
24
+ in_background do
25
+ buildable.start_building if buildable.respond_to?(:start_building)
26
+
27
+ scm.with_commit(buildable.commit) { |commit_info|
28
+ buildable.finish_building(commit_info, *run_build_script)
29
+ }
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def run_build_script
36
+ build_output = nil
37
+
38
+ Bob.logger.debug("Running the build script for #{buildable.uri}")
39
+ IO.popen(build_script, "r") { |output| build_output = output.read }
40
+ Bob.logger.debug("Ran build script `#{build_script}` and got:\n#{build_output}")
41
+
42
+ [$?.success?, build_output]
43
+ end
44
+
45
+ def build_script
46
+ "(cd #{scm.dir_for(buildable.commit)} && #{buildable.build_script} 2>&1)"
47
+ end
48
+
49
+ def scm
50
+ @scm ||= SCM.new(buildable.scm, buildable.uri, buildable.branch)
51
+ end
52
+ end
53
+ end
data/lib/bob/scm.rb ADDED
@@ -0,0 +1,25 @@
1
+ require "bob/scm/abstract"
2
+
3
+ module Bob
4
+ module SCM
5
+ autoload :Git, "bob/scm/git"
6
+ autoload :Svn, "bob/scm/svn"
7
+
8
+ class Error < StandardError; end
9
+
10
+ # Factory to return appropriate SCM instances (according to repository kind)
11
+ def self.new(scm, uri, branch)
12
+ class_for(scm).new(uri, branch)
13
+ end
14
+
15
+ # A copy of Inflector.camelize, from ActiveSupport. It will convert
16
+ # string to UpperCamelCase.
17
+ def self.class_for(string)
18
+ class_name = string.to_s.
19
+ gsub(/\/(.?)/) { "::#{$1.upcase}" }.
20
+ gsub(/(?:^|_)(.)/) { $1.upcase }
21
+ const_get(class_name)
22
+ end
23
+ private_class_method :class_for
24
+ end
25
+ end
@@ -0,0 +1,65 @@
1
+ module Bob
2
+ module SCM
3
+ class Abstract
4
+ attr_reader :uri, :branch
5
+
6
+ def initialize(uri, branch)
7
+ @uri = Addressable::URI.parse(uri)
8
+ @branch = branch
9
+ end
10
+
11
+ # Checkout the code at the specified <tt>commit</tt> and call the
12
+ # passed block.
13
+ def with_commit(commit)
14
+ commit = resolve(commit)
15
+ checkout(commit)
16
+ yield info(commit)
17
+ end
18
+
19
+ # Directory where the code will be checked out for the given
20
+ # <tt>commit</tt>.
21
+ def dir_for(commit)
22
+ commit = resolve(commit)
23
+ Bob.directory.join(path, "-", commit)
24
+ end
25
+
26
+ protected
27
+
28
+ # Get some information about the specified <tt>commit</tt>.
29
+ # Returns a hash with:
30
+ #
31
+ # [<tt>identifier</tt>] Commit identifier
32
+ # [<tt>author</tt>] Commit author's name and email
33
+ # [<tt>message</tt>] Commit message
34
+ # [<tt>committed_at</tt>] Commit date (as a <tt>Time</tt> object)
35
+ def info(commit)
36
+ raise NotImplementedError
37
+ end
38
+
39
+ # Return the identifier for the last commit in this branch of the
40
+ # repository.
41
+ def head
42
+ raise NotImplementedError
43
+ end
44
+
45
+ def run(command, directory=nil)
46
+ command = "(#{directory ? "cd #{directory} && " : ""}#{command} &>/dev/null)"
47
+ Bob.logger.debug(command)
48
+ system(command) || raise(Error, "Couldn't run SCM command `#{command}`")
49
+ end
50
+
51
+ def path
52
+ @path ||= "#{uri}-#{branch}".
53
+ gsub(/[^\w_ \-]+/i, '-'). # Remove unwanted chars.
54
+ gsub(/[ \-]+/i, '-'). # No more than one of the separator in a row.
55
+ gsub(/^\-|\-$/i, '') # Remove leading/trailing separator.
56
+ end
57
+
58
+ private
59
+
60
+ def resolve(commit)
61
+ commit == :head ? head : commit
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,34 @@
1
+ module Bob
2
+ module SCM
3
+ class Git < Abstract
4
+ protected
5
+
6
+ def info(commit)
7
+ format = "---%nidentifier: %H%nauthor: %an \
8
+ <%ae>%nmessage: >-%n %s%ncommitted_at: %ci%n"
9
+
10
+ dump = YAML.load(`cd #{dir_for(commit)} && git show -s \
11
+ --pretty=format:"#{format}" #{commit}`)
12
+
13
+ dump.update("committed_at" => Time.parse(dump["committed_at"]))
14
+ end
15
+
16
+ def head
17
+ `git ls-remote --heads #{uri} #{branch} | cut -f1`.chomp
18
+ end
19
+
20
+ private
21
+
22
+ def checkout(commit)
23
+ run "git clone #{uri} #{dir_for(commit)}" unless cloned?(commit)
24
+ run "git fetch origin", dir_for(commit)
25
+ run "git checkout origin/#{branch}", dir_for(commit)
26
+ run "git reset --hard #{commit}", dir_for(commit)
27
+ end
28
+
29
+ def cloned?(commit)
30
+ dir_for(commit).join(".git").directory?
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,37 @@
1
+ module Bob
2
+ module SCM
3
+ class Svn < Abstract
4
+ protected
5
+
6
+ def info(revision)
7
+ dump = `svn log --non-interactive --revision #{revision} #{uri}`.split("\n")
8
+ meta = dump[1].split(" | ")
9
+
10
+ { "identifier" => revision,
11
+ "message" => dump[3],
12
+ "author" => meta[1],
13
+ "committed_at" => Time.parse(meta[2]) }
14
+ end
15
+
16
+
17
+ def head
18
+ `svn info #{uri}`.split("\n").detect { |l| l =~ /^Revision: (\d+)/ }
19
+ $1.to_s
20
+ end
21
+
22
+ private
23
+
24
+ def checkout(revision)
25
+ unless checked_out?(revision)
26
+ run "svn co -q #{uri} #{dir_for(revision)}"
27
+ end
28
+
29
+ run "svn up -q -r#{revision}", dir_for(revision)
30
+ end
31
+
32
+ def checked_out?(commit)
33
+ dir_for(commit).join(".svn").directory?
34
+ end
35
+ end
36
+ end
37
+ end
data/lib/bob/test.rb ADDED
@@ -0,0 +1,4 @@
1
+ require "bob"
2
+ require "bob/test/buildable_stub"
3
+ require "bob/test/scm/git"
4
+ require "bob/test/scm/svn"
@@ -0,0 +1,43 @@
1
+ module Bob
2
+ module Test
3
+ class BuildableStub
4
+ include Bob::Buildable
5
+
6
+ def self.for(repo, commit)
7
+ scm = (Bob::Test::GitRepo === repo) ? "git" : "svn"
8
+ uri =
9
+ if scm == "git"
10
+ repo.path
11
+ else
12
+ "file://#{SvnRepo.server_root}/#{repo.name}"
13
+ end
14
+ # TODO: move onto repo object?
15
+ branch = (scm == "git") ? "master" : ""
16
+ build_script = "./test"
17
+
18
+ new(scm, uri, branch, commit, build_script)
19
+ end
20
+
21
+ attr_reader :scm, :uri, :branch, :commit, :build_script,
22
+ :repo, :status, :output, :commit_info
23
+
24
+ def initialize(scm, uri, branch, commit, build_script)
25
+ @scm = scm.to_s
26
+ @uri = uri.to_s
27
+ @branch = branch
28
+ @commit = commit
29
+ @build_script = build_script
30
+
31
+ @status = nil
32
+ @output = ""
33
+ @commit_info = {}
34
+ end
35
+
36
+ def finish_building(commit_info, status, output)
37
+ @commit_info = commit_info
38
+ @status = status ? :successful : :failed
39
+ @output = output
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,62 @@
1
+ module Bob::Test
2
+ class AbstractSCMRepo
3
+ attr_reader :path, :name
4
+
5
+ def initialize(name, base_dir=Bob.directory)
6
+ @name = name
7
+ @path = File.join(base_dir, @name.to_s)
8
+ end
9
+
10
+ def add_commit(message, &action)
11
+ Dir.chdir(path) do
12
+ yield action
13
+ commit(message)
14
+ end
15
+ end
16
+
17
+ def add_failing_commit
18
+ add_commit "This commit will fail" do
19
+ system "echo '#{build_script(false)}' > test"
20
+ system "chmod +x test"
21
+ add "test"
22
+ end
23
+ end
24
+
25
+ def add_successful_commit
26
+ add_commit "This commit will work" do
27
+ system "echo '#{build_script(true)}' > test"
28
+ system "chmod +x test"
29
+ add "test"
30
+ end
31
+ end
32
+
33
+ def commits
34
+ raise NotImplementedError
35
+ end
36
+
37
+ def head
38
+ commits.last["identifier"]
39
+ end
40
+
41
+ protected
42
+ def add(file)
43
+ raise NotImplementedError
44
+ end
45
+
46
+ def commit(message)
47
+ raise NotImplementedError
48
+ end
49
+
50
+ def run(command)
51
+ system "#{command} &>/dev/null"
52
+ end
53
+
54
+ def build_script(successful=true)
55
+ <<-script
56
+ #!/bin/sh
57
+ echo "Running tests..."
58
+ exit #{successful ? 0 : 1}
59
+ script
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,48 @@
1
+ require File.dirname(__FILE__) + "/abstract"
2
+
3
+ module Bob::Test
4
+ class GitRepo < AbstractSCMRepo
5
+ def create
6
+ FileUtils.mkdir(path)
7
+
8
+ Dir.chdir(path) do
9
+ run "git init"
10
+ run "git config user.name 'John Doe'"
11
+ run "git config user.email 'johndoe@example.org'"
12
+ run "echo 'just a test repo' >> README"
13
+ add "README"
14
+ commit "First commit"
15
+ end
16
+ end
17
+
18
+ def commits
19
+ Dir.chdir(path) do
20
+ commits = `git log --pretty=oneline`.collect { |l| l.split(" ").first }
21
+ commits.inject([]) do |commits, sha1|
22
+ format = "---%nmessage: >-%n %s%ntimestamp: %ci%n" +
23
+ "identifier: %H%nauthor: %n name: %an%n email: %ae%n"
24
+ commits << YAML.load(`git show -s --pretty=format:"#{format}" #{sha1}`)
25
+ end.reverse
26
+ end
27
+ end
28
+
29
+ def head
30
+ Dir.chdir(path) do
31
+ `git log --pretty=format:%H | head -1`.chomp
32
+ end
33
+ end
34
+
35
+ def short_head
36
+ head[0..6]
37
+ end
38
+
39
+ protected
40
+ def add(file)
41
+ run "git add #{file}"
42
+ end
43
+
44
+ def commit(message)
45
+ run %Q{git commit -m "#{message}"}
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,67 @@
1
+ require File.dirname(__FILE__) + "/abstract"
2
+ require "hpricot"
3
+
4
+ module Bob::Test
5
+ class SvnRepo < AbstractSCMRepo
6
+ def self.server_root
7
+ @root ||= Bob.directory.join("svn")
8
+ end
9
+
10
+ attr_reader :remote
11
+
12
+ def initialize(name, base_dir=Bob.directory)
13
+ super
14
+
15
+ @path = base_dir.join("svn-#{name}")
16
+ @remote = SvnRepo.server_root.join(name.to_s)
17
+ end
18
+
19
+ def create
20
+ create_remote
21
+
22
+ run "svn checkout file://#{remote} #{path}"
23
+
24
+ add_commit("First commit") do
25
+ run "echo 'just a test repo' >> README"
26
+ add "README"
27
+ end
28
+ end
29
+
30
+ def commits
31
+ Dir.chdir(path) do
32
+ doc = Hpricot::XML(`svn log --xml`)
33
+
34
+ (doc/:log/:logentry).inject([]) { |commits, commit|
35
+ commits << { "identifier" => commit["revision"],
36
+ "message" => commit.at("msg").inner_html,
37
+ "committed_at" => Time.parse(commit.at("date").inner_html) }
38
+ }.reverse
39
+ end
40
+ end
41
+
42
+ alias_method :short_head, :head
43
+
44
+ protected
45
+ def add(file)
46
+ run "svn add #{file}"
47
+ end
48
+
49
+ def commit(message)
50
+ run %Q{svn commit -m "#{message}"}
51
+ run "svn up"
52
+ end
53
+
54
+ private
55
+ def create_remote
56
+ SvnRepo.server_root.mkdir
57
+
58
+ run "svnadmin create #{remote}"
59
+
60
+ File.open(File.join(remote, "conf", "svnserve.conf"), "w") { |f|
61
+ f.puts "[general]"
62
+ f.puts "anon-access = write"
63
+ f.puts "auth-access = write"
64
+ }
65
+ end
66
+ end
67
+ end
data/test/bob_test.rb ADDED
@@ -0,0 +1,16 @@
1
+ require File.dirname(__FILE__) + "/helper"
2
+
3
+ class BobTest < Test::Unit::TestCase
4
+ test "directory" do
5
+ Bob.directory = "/foo/bar"
6
+ assert_equal "/foo/bar", Bob.directory.to_s
7
+ assert_instance_of Pathname, Bob.directory
8
+ end
9
+
10
+ test "logger" do
11
+ logger = Logger.new("/tmp/bob.log")
12
+ Bob.logger = logger
13
+
14
+ assert_same logger, Bob.logger
15
+ end
16
+ end
@@ -0,0 +1,52 @@
1
+ require File.dirname(__FILE__) + "/../helper"
2
+
3
+ class ThreadedBobTest < Test::Unit::TestCase
4
+ def setup
5
+ super
6
+
7
+ @repo = GitRepo.new(:test_repo)
8
+ @repo.create
9
+ end
10
+
11
+ test "with a successful threaded build" do
12
+ old_engine = Bob.engine
13
+
14
+ repo.add_successful_commit
15
+ commit_id = repo.commits.last["identifier"]
16
+ buildable = BuildableStub.for(@repo, commit_id)
17
+
18
+ begin
19
+ Thread.abort_on_exception = true
20
+ Bob.engine = Bob::Engine::Threaded.new(5)
21
+ buildable.build
22
+ Bob.engine.wait!
23
+
24
+ assert_equal :successful, buildable.status
25
+ assert_equal "Running tests...\n", buildable.output
26
+
27
+ commit = buildable.commit_info
28
+ assert_equal "This commit will work", commit["message"]
29
+ assert_equal Time.now.min, commit["committed_at"].min
30
+ ensure
31
+ Bob.engine = old_engine
32
+ end
33
+ end
34
+
35
+ class FakeLogger
36
+ attr_reader :msg
37
+
38
+ def error(msg)
39
+ @msg = msg
40
+ end
41
+ end
42
+
43
+ test "when something goes wrong" do
44
+ logger = FakeLogger.new
45
+
46
+ engine = Bob::Engine::Threaded.new(2, logger)
47
+ engine.call(proc { fail "foo" })
48
+ engine.wait!
49
+
50
+ assert_equal "Exception occured during build: foo", logger.msg
51
+ end
52
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,30 @@
1
+ require "test/unit"
2
+ require "contest"
3
+ require "hpricot"
4
+
5
+ begin
6
+ require "redgreen"
7
+ require "ruby-debug"
8
+ rescue LoadError
9
+ end
10
+
11
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"),
12
+ File.expand_path(File.dirname(__FILE__) + "/../test/helper"))
13
+
14
+ require "bob"
15
+ require "bob/test"
16
+
17
+ class Test::Unit::TestCase
18
+ include Bob
19
+ include Bob::Test
20
+
21
+ attr_reader :repo
22
+
23
+ def setup
24
+ Bob.logger = Logger.new("/dev/null")
25
+ Bob.directory = Pathname(__FILE__).dirname.join("..", "tmp").expand_path
26
+
27
+ Bob.directory.rmtree if Bob.directory.directory?
28
+ Bob.directory.mkdir
29
+ end
30
+ end
@@ -0,0 +1,66 @@
1
+ require File.dirname(__FILE__) + "/../helper"
2
+
3
+ class BobGitTest < Test::Unit::TestCase
4
+ def setup
5
+ super
6
+
7
+ @repo = GitRepo.new(:test_repo)
8
+ @repo.create
9
+ end
10
+
11
+ def path(uri, branch="master")
12
+ SCM::Git.new(uri, branch).__send__(:path)
13
+ end
14
+
15
+ test "converts repo uri into a path" do
16
+ assert_equal "git-github-com-integrity-bob-master",
17
+ path("git://github.com/integrity/bob")
18
+ assert_equal "git-example-org-foo-repo-master",
19
+ path("git@example.org:~foo/repo")
20
+ assert_equal "tmp-repo-git-master", path("/tmp/repo.git")
21
+ assert_equal "tmp-repo-git-foo", path("/tmp/repo.git", "foo")
22
+ end
23
+
24
+ test "with a successful build" do
25
+ repo.add_successful_commit
26
+
27
+ commit_id = repo.commits.last["identifier"]
28
+
29
+ buildable = BuildableStub.for(@repo, commit_id)
30
+ buildable.build
31
+
32
+ assert_equal :successful, buildable.status
33
+ assert_equal "Running tests...\n", buildable.output
34
+ assert_equal "This commit will work", buildable.commit_info["message"]
35
+ assert_equal commit_id, buildable.commit_info["identifier"]
36
+ assert buildable.commit_info["committed_at"].is_a?(Time)
37
+ end
38
+
39
+ test "with a failed build" do
40
+ repo.add_failing_commit
41
+
42
+ commit_id = repo.commits.last["identifier"]
43
+ buildable = BuildableStub.for(@repo, commit_id)
44
+
45
+ buildable.build
46
+
47
+ assert_equal :failed, buildable.status
48
+ assert_equal "Running tests...\n", buildable.output
49
+ assert_equal "This commit will fail", buildable.commit_info["message"]
50
+ assert_equal commit_id, buildable.commit_info["identifier"]
51
+ assert buildable.commit_info["committed_at"].is_a?(Time)
52
+ end
53
+
54
+ test "can build the head of a repository" do
55
+ repo.add_failing_commit
56
+ repo.add_successful_commit
57
+
58
+ buildable = BuildableStub.for(@repo, :head)
59
+
60
+ buildable.build
61
+
62
+ assert_equal :successful, buildable.status
63
+ assert_equal "Running tests...\n", buildable.output
64
+ assert_equal repo.head, buildable.commit_info["identifier"]
65
+ end
66
+ end
@@ -0,0 +1,66 @@
1
+ require File.dirname(__FILE__) + "/../helper"
2
+
3
+ class BobSvnTest < Test::Unit::TestCase
4
+ def setup
5
+ super
6
+
7
+ @repo = SvnRepo.new(:test_repo)
8
+ @repo.create
9
+ end
10
+
11
+ def path(uri)
12
+ SCM::Svn.new(uri, "").__send__(:path)
13
+ end
14
+
15
+ test "converts svn repo uri into a path" do
16
+ assert_equal "http-rubygems-rubyforge-org-svn",
17
+ path("http://rubygems.rubyforge.org/svn/")
18
+
19
+ assert_equal "svn-rubyforge-org-var-svn-rubygems",
20
+ path("svn://rubyforge.org/var/svn/rubygems")
21
+
22
+ assert_equal "svn-ssh-developername-rubyforge-org-var-svn-rubygems",
23
+ path("svn+ssh://developername@rubyforge.org/var/svn/rubygems")
24
+
25
+ assert_equal "home-user-code-repo",
26
+ path("/home/user/code/repo")
27
+ end
28
+
29
+ test "with a successful build" do
30
+ repo.add_successful_commit
31
+
32
+ buildable = BuildableStub.for(@repo, "2")
33
+ buildable.build
34
+
35
+ assert_equal :successful, buildable.status
36
+ assert_equal "Running tests...\n", buildable.output
37
+ assert_equal "This commit will work", buildable.commit_info["message"]
38
+ assert_equal "2", buildable.commit_info["identifier"]
39
+ assert buildable.commit_info["committed_at"].is_a?(Time)
40
+ end
41
+
42
+ test "with a failed build" do
43
+ repo.add_failing_commit
44
+
45
+ buildable = BuildableStub.for(@repo, "2")
46
+ buildable.build
47
+
48
+ assert_equal :failed, buildable.status
49
+ assert_equal "Running tests...\n", buildable.output
50
+ assert_equal "This commit will fail", buildable.commit_info["message"]
51
+ assert_equal "2", buildable.commit_info["identifier"]
52
+ assert buildable.commit_info["committed_at"].is_a?(Time)
53
+ end
54
+
55
+ test "can build the head of a repository" do
56
+ repo.add_failing_commit
57
+ repo.add_successful_commit
58
+
59
+ buildable = BuildableStub.for(@repo, :head)
60
+ buildable.build
61
+
62
+ assert_equal :successful, buildable.status
63
+ assert_equal "Running tests...\n", buildable.output
64
+ assert_equal repo.head, buildable.commit_info["identifier"]
65
+ end
66
+ end
data/test/test_test.rb ADDED
@@ -0,0 +1,62 @@
1
+ require File.dirname(__FILE__) + "/helper"
2
+
3
+ class BobTestTest < Test::Unit::TestCase
4
+ def assert_scm_repo(repo)
5
+ repo.create
6
+
7
+ assert_equal 1, repo.commits.size
8
+ assert_equal "First commit", repo.commits.first["message"]
9
+
10
+ repo.add_failing_commit
11
+ assert_equal 2, repo.commits.size
12
+ assert_equal "This commit will fail", repo.commits.last["message"]
13
+ assert_equal repo.commits.last["identifier"], repo.head
14
+ assert repo.short_head
15
+
16
+ repo.add_successful_commit
17
+ assert_equal 3, repo.commits.size
18
+ assert_equal "This commit will work", repo.commits.last["message"]
19
+ assert_equal repo.commits.last["identifier"], repo.head
20
+ end
21
+
22
+ def test_buildable_stub
23
+ b = BuildableStub.new(:git, "git://github.com/ry/node", "master", "HEAD", "make")
24
+
25
+ assert_equal "git", b.scm
26
+ assert_equal "git://github.com/ry/node", b.uri
27
+ assert_equal "master", b.branch
28
+ assert_equal "HEAD", b.commit
29
+ assert_equal "make", b.build_script
30
+ end
31
+
32
+ def test_scm_repo
33
+ assert_scm_repo(GitRepo.new(:my_test_project))
34
+ assert_scm_repo(SvnRepo.new(:my_test_project))
35
+ end
36
+
37
+ def test_buildable_git_repo
38
+ repo = GitRepo.new(:test_repo)
39
+ repo.create
40
+
41
+ b = BuildableStub.for(repo, repo.head)
42
+
43
+ assert_equal "git", b.scm
44
+ assert_equal "#{Bob.directory}/test_repo", b.uri
45
+ assert_equal "master", b.branch
46
+ assert_equal repo.head, b.commit
47
+ assert_equal "./test", b.build_script
48
+ end
49
+
50
+ def test_buildable_svn_repo
51
+ repo = SvnRepo.new(:test_repo)
52
+ repo.create
53
+
54
+ b = BuildableStub.for(repo, repo.head)
55
+
56
+ assert_equal "svn", b.scm
57
+ assert_equal "", b.branch
58
+ assert_equal repo.head, b.commit
59
+ assert_equal "./test", b.build_script
60
+ assert_equal "file://#{Bob.directory}/svn/test_repo", b.uri
61
+ end
62
+ end
metadata ADDED
@@ -0,0 +1,135 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jeroenvandijk-bob
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.1
5
+ platform: ruby
6
+ authors:
7
+ - "Nicol\xC3\xA1s Sanguinetti"
8
+ - Simon Rozet
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-09-16 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: addressable
18
+ type: :runtime
19
+ version_requirement:
20
+ version_requirements: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "0"
25
+ version:
26
+ - !ruby/object:Gem::Dependency
27
+ name: ninja
28
+ type: :runtime
29
+ version_requirement:
30
+ version_requirements: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: "0"
35
+ version:
36
+ - !ruby/object:Gem::Dependency
37
+ name: sr-mg
38
+ type: :development
39
+ version_requirement:
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: "0"
45
+ version:
46
+ - !ruby/object:Gem::Dependency
47
+ name: contest
48
+ type: :development
49
+ version_requirement:
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ - !ruby/object:Gem::Dependency
57
+ name: redgreen
58
+ type: :development
59
+ version_requirement:
60
+ version_requirements: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ - !ruby/object:Gem::Dependency
67
+ name: ruby-debug
68
+ type: :development
69
+ version_requirement:
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: "0"
75
+ version:
76
+ description: Bob the Builder will build your code. Simple.
77
+ email: info@integrityapp.com
78
+ executables: []
79
+
80
+ extensions: []
81
+
82
+ extra_rdoc_files:
83
+ - LICENSE
84
+ - README.rdoc
85
+ files:
86
+ - LICENSE
87
+ - README.rdoc
88
+ - Rakefile
89
+ - VERSION
90
+ - lib/bob.rb
91
+ - lib/bob/buildable.rb
92
+ - lib/bob/builder.rb
93
+ - lib/bob/scm.rb
94
+ - lib/bob/scm/abstract.rb
95
+ - lib/bob/scm/git.rb
96
+ - lib/bob/scm/svn.rb
97
+ - lib/bob/test.rb
98
+ - lib/bob/test/buildable_stub.rb
99
+ - lib/bob/test/scm/abstract.rb
100
+ - lib/bob/test/scm/git.rb
101
+ - lib/bob/test/scm/svn.rb
102
+ has_rdoc: false
103
+ homepage: http://integrityapp.com
104
+ licenses:
105
+ post_install_message:
106
+ rdoc_options:
107
+ - --charset=UTF-8
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: "0"
115
+ version:
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: "0"
121
+ version:
122
+ requirements: []
123
+
124
+ rubyforge_project: integrity
125
+ rubygems_version: 1.3.5
126
+ signing_key:
127
+ specification_version: 3
128
+ summary: Bob builds!
129
+ test_files:
130
+ - test/bob_test.rb
131
+ - test/engine/threaded_test.rb
132
+ - test/helper.rb
133
+ - test/scm/git_test.rb
134
+ - test/scm/svn_test.rb
135
+ - test/test_test.rb