integrity-bob 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ dist/*
3
+ tmp/*
4
+ test/tmp/*
5
+ doc/*
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,44 @@
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 "metric_fu" if RUBY_VERSION < "1.9"
12
+ rescue LoadError
13
+ end
14
+
15
+ begin
16
+ require "mg"
17
+ MG.new("bob.gemspec")
18
+ rescue LoadError
19
+ end
20
+
21
+ desc "Default: run all tests"
22
+ task :default => :test
23
+
24
+ SCMs = %w[git svn]
25
+
26
+ desc "Run unit tests"
27
+ task :test => SCMs.map { |scm| "test:#{scm}" } do
28
+ ruby "test/bob_test.rb"
29
+ ruby "test/engine/threaded_test.rb"
30
+ end
31
+
32
+ SCMs.each { |scm|
33
+ desc "Run unit tests with #{scm}"
34
+ task "test:#{scm}" do
35
+ ruby "test/scm/#{scm}_test.rb"
36
+ end
37
+ }
38
+
39
+ (defined?(RDoc::Task) ? RDoc::Task : Rake::RDocTask).new do |rd|
40
+ rd.main = "README.rdoc"
41
+ rd.title = "Documentation for Bob the Builder"
42
+ rd.rdoc_files.include("README.rdoc", "LICENSE", "lib/**/*.rb")
43
+ rd.rdoc_dir = "doc"
44
+ end
data/bob.gemspec ADDED
@@ -0,0 +1,51 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "bob"
3
+ s.version = "0.2.0"
4
+ s.date = "2009-07-02"
5
+
6
+ s.description = "Bob the Builder will build your code. Simple."
7
+ s.summary = "Bob builds!"
8
+ s.homepage = "http://integrityapp.com"
9
+
10
+ s.authors = ["Nicolás Sanguinetti", "Simon Rozet"]
11
+ s.email = "info@integrityapp.com"
12
+
13
+ s.require_paths = ["lib"]
14
+ s.rubyforge_project = "integrity"
15
+ s.has_rdoc = true
16
+ s.rubygems_version = "1.3.1"
17
+
18
+ s.add_dependency "addressable"
19
+
20
+ if s.respond_to?(:add_development_dependency)
21
+ s.add_development_dependency "sr-mg"
22
+ s.add_development_dependency "sr-bob-test"
23
+ s.add_development_dependency "contest"
24
+ s.add_development_dependency "redgreen"
25
+ s.add_development_dependency "ruby-debug"
26
+ end
27
+
28
+ s.files = %w[
29
+ .gitignore
30
+ LICENSE
31
+ README.rdoc
32
+ Rakefile
33
+ bob.gemspec
34
+ lib/bob.rb
35
+ lib/bob/buildable.rb
36
+ lib/bob/builder.rb
37
+ lib/bob/engine.rb
38
+ lib/bob/engine/foreground.rb
39
+ lib/bob/engine/threaded.rb
40
+ lib/bob/scm.rb
41
+ lib/bob/scm/abstract.rb
42
+ lib/bob/scm/git.rb
43
+ lib/bob/scm/svn.rb
44
+ lib/core_ext/object.rb
45
+ test/bob_test.rb
46
+ test/engine/threaded_test.rb
47
+ test/helper.rb
48
+ test/scm/git_test.rb
49
+ test/scm/svn_test.rb
50
+ ]
51
+ end
@@ -0,0 +1,70 @@
1
+ module Bob
2
+ # Mixin to add to your classes.
3
+ module Buildable
4
+ # Builds the list of commits you pass. You must provide an
5
+ # enumerable with commit identifiers appropriate to the
6
+ # repository the code is in (SHAs if git, rev numbers if svn,
7
+ # etc), or a single string with a commit id.
8
+ def build(commits)
9
+ Bob.build(self, commits)
10
+ end
11
+
12
+ # What kind of repository this buildable represents. Must
13
+ # return a Symbol (:git, :svn, etc.)
14
+ #
15
+ # <b>You must implement this in the classes where you mixin this module</b>
16
+ def scm
17
+ raise NotImplementedError
18
+ end
19
+
20
+ # Full URI to the repository to clone/checkout.
21
+ #
22
+ # <b>You must implement this in the classes where you mixin this module</b>
23
+ def uri
24
+ raise NotImplementedError
25
+ end
26
+
27
+ # Branch of the code you want to watch in order to build.
28
+ #
29
+ # <b>You must implement this in the classes where you mixin this module</b>
30
+ def branch
31
+ raise NotImplementedError
32
+ end
33
+
34
+ # Script that will be run in the buildable's checked out code,
35
+ # if it returns a status code of 0 it will be considered a
36
+ # successfull build. Else it will be considered a failed build.
37
+ #
38
+ # <b>You must implement this in the classes where you mixin this module</b>
39
+ def build_script
40
+ raise NotImplementedError
41
+ end
42
+
43
+ # Callback sent when a build starts. The first argument is a
44
+ # string with whatever identifier is appropriate for a repository
45
+ # of this kind. The second is a hash with information about the
46
+ # commit.
47
+ #
48
+ # <tt>:author</tt>:: A string with the name/email of the committer
49
+ # <tt>:message</tt>:: The commit message
50
+ # <tt>:committed_at</tt>:: A Time object with the timestamp of the
51
+ # commit
52
+ #
53
+ # <b>You must implement this in the classes where you mixin this module</b>
54
+ def start_building(commit_id, commit_info)
55
+ raise NotImplementedError
56
+ end
57
+
58
+ # Callback sent after a build finishes. The first argument is a
59
+ # string with whatever identifier is appropriate for a respository
60
+ # of this kind. The second is a boolean which is true if the build
61
+ # was successful or false if it failed. And the last one is a string
62
+ # with the full output returned by the build process (STDOUT and
63
+ # STDERR interleaved)
64
+ #
65
+ # <b>You must implement this in the classes where you mixin this module</b>
66
+ def finish_building(commit_id, build_status, build_output)
67
+ raise NotImplementedError
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,64 @@
1
+ module Bob
2
+ # A Builder will take care of building a buildable (wow, you didn't see that coming,
3
+ # right?).
4
+ class Builder
5
+ attr_reader :buildable
6
+
7
+ # Instantiate the Builder, passing an object that understands the <tt>Buildable</tt>
8
+ # interface, and a <tt>commit_id</tt>.
9
+ #
10
+ # You can pass <tt>:head</tt> as the commit id, in which case it will resolve to the
11
+ # head commit of the current branch (for example, "HEAD" under git, or the latest
12
+ # revision under svn)
13
+ def initialize(buildable, commit_id)
14
+ @buildable = buildable
15
+ @commit_id = commit_id
16
+ end
17
+
18
+ # This is where the magic happens:
19
+ #
20
+ # 1. Check out the repo to the appropriate commit.
21
+ # 2. Notify the buildable that the build is starting.
22
+ # 3. Run the build script on it in the background.
23
+ # 4. Reports the build back to the buildable.
24
+ def build
25
+ Bob.logger.info "Building #{commit_id} of the #{buildable.scm} repo at #{buildable.uri}"
26
+
27
+ in_background do
28
+ scm.with_commit(commit_id) {
29
+ buildable.start_building(commit_id, scm.info(commit_id))
30
+ build_status, build_output = run_build_script
31
+ buildable.finish_building(commit_id, build_status, build_output)
32
+ }
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def commit_id
39
+ @commit_id == :head ? scm.head : @commit_id
40
+ end
41
+
42
+ def run_build_script
43
+ build_output = nil
44
+
45
+ Bob.logger.debug("Running the build script for #{buildable.uri}")
46
+ IO.popen(build_script, "r") { |output| build_output = output.read }
47
+ Bob.logger.debug("Ran build script `#{build_script}` and got:\n#{build_output}")
48
+
49
+ [$?.success?, build_output]
50
+ end
51
+
52
+ def build_script
53
+ "(cd #{scm.working_dir} && #{buildable.build_script} 2>&1)"
54
+ end
55
+
56
+ def scm
57
+ @scm ||= SCM.new(buildable.scm, buildable.uri, buildable.branch)
58
+ end
59
+
60
+ def in_background(&block)
61
+ Bob.engine.call(block)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,6 @@
1
+ module Bob
2
+ module Engine
3
+ # Dummy engine that just runs in the foreground (useful for tests).
4
+ Foreground = lambda {|b| b.call }
5
+ end
6
+ end
@@ -0,0 +1,138 @@
1
+ require "thread"
2
+
3
+ module Bob
4
+ module Engine
5
+ # A thread pool based build engine. This engine simply adds jobs to an
6
+ # in-memory queue, and processes them as soon as possible.
7
+ class Threaded
8
+ # The optional pool size controls how many threads will be created.
9
+ def initialize(pool_size = 2)
10
+ @pool = ThreadPool.new(pool_size)
11
+ end
12
+
13
+ # Adds a job to the queue.
14
+ def call(job)
15
+ @pool << job
16
+ end
17
+
18
+ # The number of jobs currently in the queue.
19
+ def njobs
20
+ @pool.njobs
21
+ end
22
+
23
+ # This method will not return until #njobs returns 0.
24
+ def wait!
25
+ Thread.pass until @pool.njobs == 0
26
+ end
27
+
28
+ # Manage a pool of threads, allowing for spin up / spin down of the
29
+ # contained threads.
30
+ # Simply processes work added to it's queue via #push.
31
+ # The default size for the pool is 2 threads.
32
+ class ThreadPool
33
+ # A thread safe single value for use as a counter.
34
+ class Incrementor
35
+ # The value passed in will be the initial value.
36
+ def initialize(v = 0)
37
+ @m = Mutex.new
38
+ @v = v
39
+ end
40
+
41
+ # Add the given value to self, default 1.
42
+ def inc(v = 1)
43
+ sync { @v += v }
44
+ end
45
+
46
+ # Subtract the given value to self, default 1.
47
+ def dec(v = 1)
48
+ sync { @v -= v }
49
+ end
50
+
51
+ # Simply shows the value inspect for convenience.
52
+ def inspect
53
+ @v.inspect
54
+ end
55
+
56
+ # Extract the value.
57
+ def to_i
58
+ @v
59
+ end
60
+
61
+ private
62
+
63
+ # Wrap the given block in a mutex.
64
+ def sync(&b)
65
+ @m.synchronize &b
66
+ end
67
+ end
68
+
69
+ # The number of threads in the pool.
70
+ attr_reader :size
71
+
72
+ # The job queue.
73
+ attr_reader :jobs
74
+
75
+ # Set the size of the thread pool. Asynchronously run down threads
76
+ # that are no longer required, and synchronously spawn new required
77
+ # threads.
78
+ def size=(other)
79
+ @size = other
80
+
81
+ if @workers.size > @size
82
+ (@workers.size - @size).times do
83
+ @workers.shift[:run] = false
84
+ end
85
+ else
86
+ (@size - @workers.size).times do
87
+ @workers << spawn
88
+ end
89
+ end
90
+ end
91
+
92
+ # Default pool size is 2 threads.
93
+ def initialize(size = nil)
94
+ size ||= 2
95
+ @jobs = Queue.new
96
+ @njobs = Incrementor.new
97
+ @workers = Array.new(size) { spawn }
98
+ end
99
+
100
+ # Adds a job to the queue, the job can be any number of objects
101
+ # responding to call, and/or a block.
102
+ def add(*jobs, &blk)
103
+ jobs = jobs + Array(blk)
104
+
105
+ jobs.each do |job|
106
+ @jobs << job
107
+ @njobs.inc
108
+ end
109
+ end
110
+
111
+ alias_method :push, :add
112
+ alias_method :<<, :add
113
+
114
+ # A peak at the number of jobs in the queue. N.B. May differ, but
115
+ # should be more accurate than +jobs.size+.
116
+ def njobs
117
+ @njobs.to_i
118
+ end
119
+
120
+ private
121
+
122
+ # Create a new thread and return it. The thread will run until the
123
+ # thread-local value +:run+ is changed to false or nil.
124
+ def spawn
125
+ Thread.new do
126
+ c = Thread.current
127
+ c[:run] = true
128
+
129
+ while c[:run]
130
+ @jobs.pop.call
131
+ @njobs.dec
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
data/lib/bob/engine.rb ADDED
@@ -0,0 +1,9 @@
1
+ module Bob
2
+ # Different engines to run code in background. An engine is any object
3
+ # that responds to #call and takes a Proc object, which should be executed
4
+ # "in the background". The different engines are:
5
+ module Engine
6
+ autoload :Foreground, "bob/engine/foreground"
7
+ autoload :Threaded, "bob/engine/threaded"
8
+ end
9
+ end
@@ -0,0 +1,59 @@
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 into <tt>working_dir</tt> at the specified revision
12
+ # and call the passed block
13
+ def with_commit(commit_id)
14
+ update_code
15
+ checkout(commit_id)
16
+ yield
17
+ end
18
+
19
+ # Directory where the code will be checked out. Make sure the user
20
+ # running Bob is allowed to write to this directory (or you'll get a
21
+ # <tt>Errno::EACCESS</tt>)
22
+ def working_dir
23
+ @working_dir ||= "#{Bob.directory}/#{path}".tap { |dir|
24
+ FileUtils.mkdir_p(dir)
25
+ }
26
+ end
27
+
28
+ # Get some information about the specified commit. Returns a hash with:
29
+ #
30
+ # [<tt>:author</tt>] Commit author's name and email
31
+ # [<tt>:message</tt>] Commit message
32
+ # [<tt>:committed_at</tt>] Commit date (as a <tt>Time</tt> object)
33
+ def info(commit_id)
34
+ raise NotImplementedError
35
+ end
36
+
37
+ # Return the identifier for the last commit in this branch of the
38
+ # repository.
39
+ def head
40
+ raise NotImplementedError
41
+ end
42
+
43
+ protected
44
+
45
+ def run(command, cd=true)
46
+ command = "(#{cd ? "cd #{working_dir} && " : ""}#{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
+ end
58
+ end
59
+ end
@@ -0,0 +1,45 @@
1
+ module Bob
2
+ module SCM
3
+ class Git < Abstract
4
+ def info(commit_id)
5
+ format = %Q(---%n:author: %an <%ae>%n:message: >-%n %s%n:committed_at: %ci%n)
6
+ YAML.load(`cd #{working_dir} && git show -s --pretty=format:"#{format}" #{commit_id}`).tap { |info|
7
+ info[:committed_at] = Time.parse(info[:committed_at])
8
+ }
9
+ end
10
+
11
+ def head
12
+ `git ls-remote --heads #{uri} #{branch} | cut -f1`.chomp
13
+ end
14
+
15
+ private
16
+
17
+ def update_code
18
+ cloned? ? fetch : clone
19
+ end
20
+
21
+ def cloned?
22
+ File.directory?("#{working_dir}/.git")
23
+ end
24
+
25
+ def clone
26
+ run "git clone #{uri} #{working_dir}", false
27
+ end
28
+
29
+ def fetch
30
+ git "fetch origin"
31
+ end
32
+
33
+ def checkout(commit_id)
34
+ # First checkout the branch just in case the commit_id
35
+ # turns out to be HEAD or other non-sha identifier
36
+ git "checkout origin/#{branch}"
37
+ git "reset --hard #{commit_id}"
38
+ end
39
+
40
+ def git(command)
41
+ run "git #{command}"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,45 @@
1
+ require "bob/scm/abstract"
2
+
3
+ module Bob
4
+ module SCM
5
+ class Svn < Abstract
6
+ def info(revision)
7
+ dump = `svn log --non-interactive --revision #{revision} #{uri}`.split("\n")
8
+ meta = dump[1].split(" | ")
9
+
10
+ { :message => dump[3],
11
+ :author => meta[1],
12
+ :committed_at => Time.parse(meta[2]) }
13
+ end
14
+
15
+ def head
16
+ `svn info #{uri}`.split("\n").detect { |l| l =~ /^Revision: (\d+)/ }
17
+ $1.to_s
18
+ end
19
+
20
+ def with_commit(commit_id)
21
+ update_code
22
+ checkout(commit_id)
23
+ yield
24
+ end
25
+
26
+ private
27
+
28
+ def update_code
29
+ initial_checkout unless checked_out?
30
+ end
31
+
32
+ def checkout(revision)
33
+ run("svn up -q -r#{revision}")
34
+ end
35
+
36
+ def initial_checkout(revision=nil)
37
+ run("svn co -q #{uri} #{working_dir}")
38
+ end
39
+
40
+ def checked_out?
41
+ File.directory?(working_dir + "/.svn")
42
+ end
43
+ end
44
+ end
45
+ 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
data/lib/bob.rb ADDED
@@ -0,0 +1,57 @@
1
+ require "fileutils"
2
+ require "yaml"
3
+ require "logger"
4
+ require "time"
5
+ require "addressable/uri"
6
+
7
+ require "bob/buildable"
8
+ require "bob/builder"
9
+ require "bob/scm"
10
+ require "bob/engine"
11
+ require "core_ext/object"
12
+
13
+ module Bob
14
+ # Builds the specified <tt>buildable</tt>. This object must understand
15
+ # the API described in the README.
16
+ #
17
+ # The second argument will take an array of commit_ids, which should be
18
+ # strings with the relevant identifier (a SHA1 hash for git repositories,
19
+ # a numerical revision for svn repositories, etc).
20
+ #
21
+ # You can pass :head as a commit identifier to build the latest commit
22
+ # in the repo. Examples:
23
+ #
24
+ # Bob.build(buildable, :head) # just build the head
25
+ # Bob.build(buildable, ["4", "3", "2"]) # build revision 4, 3, and 2
26
+ # # (in that order)
27
+ # Bob.build(buildable, [:head, "a30fb12"]) # build the HEAD and a30fb12
28
+ # # commits in this repo.
29
+ def self.build(buildable, commit_ids)
30
+ Array(commit_ids).each do |commit_id|
31
+ Builder.new(buildable, commit_id).build
32
+ end
33
+ end
34
+
35
+ # Directory where the code for the different buildables will be checked out.
36
+ # Make sure the user running Bob is allowed to write to this directory.
37
+ def self.directory
38
+ @directory || "/tmp"
39
+ end
40
+
41
+ # What will you use to build in background. Must respond to <tt>call</tt> and
42
+ # take a block which will be run "in background". The default is to run in
43
+ # foreground.
44
+ def self.engine
45
+ @engine || Engine::Foreground
46
+ end
47
+
48
+ # What to log with (must implement ruby's Logger interface). Logs to STDOUT
49
+ # by default.
50
+ def self.logger
51
+ @logger || Logger.new(STDOUT)
52
+ end
53
+
54
+ class << self
55
+ attr_writer :directory, :engine, :logger
56
+ end
57
+ end
@@ -0,0 +1,7 @@
1
+ class Object #:nodoc:
2
+ # Add Object#tap for backwards compatibility
3
+ def tap
4
+ yield self
5
+ self
6
+ end unless method_defined?(:tap)
7
+ end
data/test/bob_test.rb ADDED
@@ -0,0 +1,22 @@
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
7
+ end
8
+
9
+ test "logger" do
10
+ logger = Logger.new("/tmp/bob.log")
11
+ Bob.logger = logger
12
+
13
+ assert_same logger, Bob.logger
14
+ end
15
+
16
+ test "engine" do
17
+ engine = Object.new
18
+ Bob.engine = engine
19
+
20
+ assert_same engine, Bob.engine
21
+ end
22
+ end
@@ -0,0 +1,36 @@
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
+
10
+ @buildable = BuildableStub.call(@repo)
11
+ end
12
+
13
+ test "with a successful threaded build" do
14
+ old_engine = Bob.engine
15
+
16
+ repo.add_successful_commit
17
+ commit_id = repo.commits.last[:identifier]
18
+
19
+ begin
20
+ Thread.abort_on_exception = true
21
+ Bob.engine = Bob::Engine::Threaded.new(5)
22
+ Bob.build(buildable, commit_id)
23
+ Bob.engine.wait!
24
+
25
+ status, output = buildable.builds[commit_id]
26
+ assert_equal :successful, status
27
+ assert_equal "Running tests...\n", output
28
+
29
+ commit = buildable.metadata[commit_id]
30
+ assert_equal "This commit will work", commit[:message]
31
+ assert_equal Time.now.min, commit[:committed_at].min
32
+ ensure
33
+ Bob.engine = old_engine
34
+ end
35
+ end
36
+ 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, :buildable
22
+
23
+ def setup
24
+ Bob.logger = Logger.new("/dev/null")
25
+ Bob.engine = Bob::Engine::Foreground
26
+ Bob.directory = File.expand_path(File.dirname(__FILE__) + "/../tmp")
27
+
28
+ FileUtils.rm_rf(Bob.directory) if File.directory?(Bob.directory)
29
+ end
30
+ end
@@ -0,0 +1,85 @@
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
+
10
+ @buildable = BuildableStub.call(@repo)
11
+ end
12
+
13
+ def path(uri, branch="master")
14
+ SCM::Git.new(uri, branch).__send__(:path)
15
+ end
16
+
17
+ test "converts repo uri into a path" do
18
+ assert_equal "git-github-com-integrity-bob-master",
19
+ path("git://github.com/integrity/bob")
20
+ assert_equal "git-example-org-foo-repo-master",
21
+ path("git@example.org:~foo/repo")
22
+ assert_equal "tmp-repo-git-master", path("/tmp/repo.git")
23
+ assert_equal "tmp-repo-git-foo", path("/tmp/repo.git", "foo")
24
+ end
25
+
26
+ test "with a successful build" do
27
+ repo.add_successful_commit
28
+
29
+ commit_id = repo.commits.last[:identifier]
30
+
31
+ buildable.build(commit_id)
32
+
33
+ status, output = buildable.builds[commit_id]
34
+ assert_equal :successful, status
35
+ assert_equal "Running tests...\n", output
36
+
37
+ assert_equal 1, buildable.metadata.length
38
+
39
+ commit = buildable.metadata[commit_id]
40
+ assert_equal "This commit will work", commit[:message]
41
+ assert commit[:committed_at].is_a?(Time)
42
+ end
43
+
44
+ test "with a failed build" do
45
+ repo.add_failing_commit
46
+
47
+ commit_id = repo.commits.last[:identifier]
48
+
49
+ buildable.build(commit_id)
50
+
51
+ status, output = buildable.builds[commit_id]
52
+ assert_equal :failed, status
53
+ assert_equal "Running tests...\n", output
54
+
55
+ assert_equal 1, buildable.metadata.length
56
+
57
+ commit = buildable.metadata[commit_id]
58
+ assert_equal "This commit will fail", commit[:message]
59
+ assert commit[:committed_at].is_a?(Time)
60
+ end
61
+
62
+ test "with multiple commits" do
63
+ 2.times { repo.add_failing_commit }
64
+ commits = repo.commits.collect { |c| c[:identifier] }
65
+
66
+ buildable.build(commits)
67
+
68
+ assert_equal 2, commits.length
69
+ assert_equal 2, buildable.metadata.length
70
+ assert_equal 2, buildable.builds.length
71
+ end
72
+
73
+ test "can build the head of a repository" do
74
+ repo.add_failing_commit
75
+ repo.add_successful_commit
76
+
77
+ buildable.build(:head)
78
+
79
+ assert_equal 1, buildable.builds.length
80
+
81
+ status, output = buildable.builds[repo.head]
82
+ assert_equal :successful, status
83
+ assert_equal "Running tests...\n", output
84
+ end
85
+ end
@@ -0,0 +1,87 @@
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
+
10
+ @buildable = BuildableStub.call(@repo)
11
+ end
12
+
13
+ def path(uri)
14
+ SCM::Svn.new(uri, "").__send__(:path)
15
+ end
16
+
17
+ test "converts svn repo uri into a path" do
18
+ assert_equal "http-rubygems-rubyforge-org-svn",
19
+ path("http://rubygems.rubyforge.org/svn/")
20
+
21
+ assert_equal "svn-rubyforge-org-var-svn-rubygems",
22
+ path("svn://rubyforge.org/var/svn/rubygems")
23
+
24
+ assert_equal "svn-ssh-developername-rubyforge-org-var-svn-rubygems",
25
+ path("svn+ssh://developername@rubyforge.org/var/svn/rubygems")
26
+
27
+ assert_equal "home-user-code-repo",
28
+ path("/home/user/code/repo")
29
+ end
30
+
31
+ test "with a successful build" do
32
+ repo.add_successful_commit
33
+
34
+ buildable.build("2")
35
+
36
+ assert_equal 1, buildable.metadata.length
37
+
38
+ status, output = buildable.builds["2"]
39
+ assert_equal :successful, status
40
+ assert_equal "Running tests...\n", output
41
+
42
+ assert_equal 1, buildable.metadata.length
43
+
44
+ commit = buildable.metadata["2"]
45
+ assert commit[:committed_at].is_a?(Time)
46
+ assert_equal "This commit will work", commit[:message]
47
+ end
48
+
49
+ test "with a failed build" do
50
+ repo.add_failing_commit
51
+
52
+ buildable.build("2")
53
+
54
+ status, output = buildable.builds["2"]
55
+ assert_equal :failed, status
56
+ assert_equal "Running tests...\n", output
57
+
58
+ assert_equal 1, buildable.metadata.length
59
+
60
+ commit = buildable.metadata["2"]
61
+ assert commit[:committed_at].is_a?(Time)
62
+ assert_equal "This commit will fail", commit[:message]
63
+ end
64
+
65
+ test "with multiple commits" do
66
+ repo.add_successful_commit
67
+ 2.times { repo.add_failing_commit }
68
+
69
+ buildable.build(repo.commits.collect { |c| c[:identifier] })
70
+
71
+ assert_equal 3, buildable.metadata.length
72
+ assert_equal 3, buildable.builds.length
73
+ end
74
+
75
+ test "can build the head of a repository" do
76
+ repo.add_failing_commit
77
+ repo.add_successful_commit
78
+
79
+ buildable.build(:head)
80
+
81
+ assert_equal 1, buildable.builds.length
82
+
83
+ status, output = buildable.builds["3"]
84
+ assert_equal :successful, status
85
+ assert_equal "Running tests...\n", output
86
+ end
87
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: integrity-bob
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
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-07-02 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: sr-mg
28
+ type: :development
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-bob-test
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
+
84
+ files:
85
+ - .gitignore
86
+ - LICENSE
87
+ - README.rdoc
88
+ - Rakefile
89
+ - bob.gemspec
90
+ - lib/bob.rb
91
+ - lib/bob/buildable.rb
92
+ - lib/bob/builder.rb
93
+ - lib/bob/engine.rb
94
+ - lib/bob/engine/foreground.rb
95
+ - lib/bob/engine/threaded.rb
96
+ - lib/bob/scm.rb
97
+ - lib/bob/scm/abstract.rb
98
+ - lib/bob/scm/git.rb
99
+ - lib/bob/scm/svn.rb
100
+ - lib/core_ext/object.rb
101
+ - test/bob_test.rb
102
+ - test/engine/threaded_test.rb
103
+ - test/helper.rb
104
+ - test/scm/git_test.rb
105
+ - test/scm/svn_test.rb
106
+ has_rdoc: true
107
+ homepage: http://integrityapp.com
108
+ post_install_message:
109
+ rdoc_options: []
110
+
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: "0"
118
+ version:
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: "0"
124
+ version:
125
+ requirements: []
126
+
127
+ rubyforge_project: integrity
128
+ rubygems_version: 1.2.0
129
+ signing_key:
130
+ specification_version: 2
131
+ summary: Bob builds!
132
+ test_files: []
133
+