jeroenvandijk-bob 0.3.1
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/LICENSE +22 -0
- data/README.rdoc +30 -0
- data/Rakefile +70 -0
- data/VERSION +1 -0
- data/lib/bob.rb +35 -0
- data/lib/bob/buildable.rb +74 -0
- data/lib/bob/builder.rb +53 -0
- data/lib/bob/scm.rb +25 -0
- data/lib/bob/scm/abstract.rb +65 -0
- data/lib/bob/scm/git.rb +34 -0
- data/lib/bob/scm/svn.rb +37 -0
- data/lib/bob/test.rb +4 -0
- data/lib/bob/test/buildable_stub.rb +43 -0
- data/lib/bob/test/scm/abstract.rb +62 -0
- data/lib/bob/test/scm/git.rb +48 -0
- data/lib/bob/test/scm/svn.rb +67 -0
- data/test/bob_test.rb +16 -0
- data/test/engine/threaded_test.rb +52 -0
- data/test/helper.rb +30 -0
- data/test/scm/git_test.rb +66 -0
- data/test/scm/svn_test.rb +66 -0
- data/test/test_test.rb +62 -0
- metadata +135 -0
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
|
data/lib/bob/builder.rb
ADDED
@@ -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
|
data/lib/bob/scm/git.rb
ADDED
@@ -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
|
data/lib/bob/scm/svn.rb
ADDED
@@ -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,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
|