cerberus 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +7 -0
- data/LICENSE +21 -0
- data/README +57 -0
- data/Rakefile +129 -0
- data/bin/cerberus +3 -0
- data/doc/FAQ +0 -0
- data/lib/cerberus/cli.rb +68 -0
- data/lib/cerberus/config.rb +30 -0
- data/lib/cerberus/constants.rb +4 -0
- data/lib/cerberus/latch.rb +22 -0
- data/lib/cerberus/manager.rb +246 -0
- data/lib/cerberus/utils.rb +30 -0
- data/lib/cerberus/version.rb +9 -0
- data/test/config_test.rb +25 -0
- data/test/data/application.dump +80 -0
- data/test/functional_test.rb +95 -0
- data/test/integration_test.rb +51 -0
- data/test/test_helper.rb +56 -0
- metadata +83 -0
data/CHANGES
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2006 Anatol Pomozov
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
data/README
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
Cerberus developed by Anatol Pomozov (anatol.pomozov@gmail.com) and shared under MIT license.
|
2
|
+
|
3
|
+
Cerberus is continuous integration (CI) software. Cerberus could be periodically run from scheduler
|
4
|
+
and check if application tests are broken. If it happend then Cerberus sends notification to developers.
|
5
|
+
|
6
|
+
For more CI theory read this document from Martin Fowler
|
7
|
+
http://www.martinfowler.com/articles/continuousIntegration.html.
|
8
|
+
|
9
|
+
Requirements:
|
10
|
+
ruby - 1.8.2 or higher
|
11
|
+
rake - 0.7 or higher
|
12
|
+
svn client - 1.2 or higher
|
13
|
+
|
14
|
+
|
15
|
+
What 'Cerberus' name means?
|
16
|
+
Quote from Wikipedia (http://en.wikipedia.org/wiki/Cerberus)
|
17
|
+
|
18
|
+
Cerberus or Kerberos (Kerberos, demon of the pit), was the hound of Hades-a monstrous three-headed dog (sometimes said to have 50 or 100 heads) with a snake for a tail and innumerable snake heads on his back.
|
19
|
+
He guarded the gate to Hades (the Greek underworld) and ensured that the dead could not leave and the living could not enter. His brother was Orthrus. He is the offspring of Echidna and Typhon.
|
20
|
+
|
21
|
+
|
22
|
+
So Cerberus will guard your tests and not allow your project to go to the world of dead.
|
23
|
+
|
24
|
+
|
25
|
+
To use Cerberus it is very easy. First install it. Easiest way to do it through RubyGems package manager.
|
26
|
+
|
27
|
+
'gem install cerberus'
|
28
|
+
|
29
|
+
then you need to add project that will be watched by Cerberus. Do it by
|
30
|
+
|
31
|
+
cerberus add (DIR|SVN_URL)? #Adds project to list of watched applications
|
32
|
+
|
33
|
+
as second parameted you could pass URL to subversion repository or directory with working SVN folder.
|
34
|
+
If this parameted absent then would be added project from current directory
|
35
|
+
|
36
|
+
Go to ~./cerberus and edit config.yml file (only once after installing Cerberus). Enter your configuration options here
|
37
|
+
like email server, password, user_name and other options. See ActiveMailer description - Cerberus uses it as notification
|
38
|
+
layer. Mine config file looks like this
|
39
|
+
mail:
|
40
|
+
address: mail.somesever.com
|
41
|
+
user_name: anatol
|
42
|
+
password: anatol
|
43
|
+
domain: somesever.com
|
44
|
+
authentication: login
|
45
|
+
|
46
|
+
Also go to ~/.cerberus/config/<PROJECT_NAME>.yml and add comma-separated list of recipients for this project
|
47
|
+
You could also change <PROJECT_NAME> to any other name. This name what Cerberus will use.
|
48
|
+
|
49
|
+
And then run Cerberus
|
50
|
+
|
51
|
+
cerberus build PROJECT_NAME #Run project
|
52
|
+
|
53
|
+
It will check out latest sources and run tests for your app. If tests are broken - recipients will recieve notifications.
|
54
|
+
|
55
|
+
But of course better run Cerberus automatically from Cron. Run it each 10 minutes for project will be ok.
|
56
|
+
|
57
|
+
Well, thats all. If you have any questions, proposals - just let me know.
|
data/Rakefile
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/packagetask'
|
5
|
+
require 'rake/gempackagetask'
|
6
|
+
require 'rake/contrib/rubyforgepublisher'
|
7
|
+
|
8
|
+
require "./lib/cerberus/version"
|
9
|
+
|
10
|
+
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
11
|
+
PKG_NAME = 'cerberus'
|
12
|
+
PKG_VERSION = Cerberus::VERSION::STRING + PKG_BUILD
|
13
|
+
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
14
|
+
|
15
|
+
RELEASE_NAME = "REL #{PKG_VERSION}"
|
16
|
+
|
17
|
+
RUBY_FORGE_PROJECT = "cerberus"
|
18
|
+
RUBY_FORGE_USER = "anatol"
|
19
|
+
|
20
|
+
task :default => [:test, :clean]
|
21
|
+
|
22
|
+
desc "Run the unit tests in test/unit"
|
23
|
+
Rake::TestTask.new(:test) do |t|
|
24
|
+
t.libs << "test"
|
25
|
+
t.pattern = 'test/*_test.rb'
|
26
|
+
t.verbose = true
|
27
|
+
end
|
28
|
+
|
29
|
+
desc "Clean all generated files"
|
30
|
+
task :clean => :clobber_package do
|
31
|
+
rm_rf './test/__workdir'
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
GEM_SPEC = Gem::Specification.new do |s|
|
36
|
+
s.name = PKG_NAME
|
37
|
+
s.version = PKG_VERSION
|
38
|
+
s.platform = Gem::Platform::RUBY
|
39
|
+
s.required_ruby_version = ">=1.8.2"
|
40
|
+
|
41
|
+
s.summary = "Cerberus is a Continuous Integration tool that could be easily run from Cron."
|
42
|
+
s.description = <<-DESC.strip.gsub(/\n\s+/, " ")
|
43
|
+
Cerberus is a Continuous Integration software for Ruby projects. CI helps you keep your project
|
44
|
+
in a good shape.
|
45
|
+
|
46
|
+
For now Cerberus only work with projects that use Subversion but in the future it would be provided
|
47
|
+
support for other VCS.
|
48
|
+
|
49
|
+
Cerberus could be easily invoked from Cron (for Unix) or nnCron (for Windows) utilities.
|
50
|
+
DESC
|
51
|
+
|
52
|
+
s.add_dependency 'actionmailer', '>= 1.2.1'
|
53
|
+
s.add_dependency 'rake', '>= 0.7.1'
|
54
|
+
|
55
|
+
s.files = Dir.glob("{bin,doc,lib,test}/**/*").delete_if { |item| item.include?('__workdir') }
|
56
|
+
s.files += %w(LICENSE README CHANGES Rakefile)
|
57
|
+
|
58
|
+
s.bindir = "bin"
|
59
|
+
s.executables = ["cerberus"]
|
60
|
+
s.default_executable = "cerberus"
|
61
|
+
|
62
|
+
s.require_path = 'lib'
|
63
|
+
|
64
|
+
s.has_rdoc = true
|
65
|
+
s.extra_rdoc_files = [ "README" ]
|
66
|
+
s.rdoc_options = [ "--main", "README" ]
|
67
|
+
|
68
|
+
s.test_suite_file = "test/integration_test.rb"
|
69
|
+
|
70
|
+
s.author = "Anatol Pomozov"
|
71
|
+
s.email = "anatol.pomozov@gmail.com"
|
72
|
+
s.homepage = "http://rubyforge.org/projects/cerberus"
|
73
|
+
s.rubyforge_project = "cerberus"
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
Rake::GemPackageTask.new(GEM_SPEC) do |p|
|
78
|
+
p.gem_spec = GEM_SPEC
|
79
|
+
p.need_tar = true
|
80
|
+
p.need_zip = true
|
81
|
+
end
|
82
|
+
|
83
|
+
task :install => [:clean, :test, :package] do
|
84
|
+
system "gem install pkg/#{PKG_NAME}-#{PKG_VERSION}.gem"
|
85
|
+
end
|
86
|
+
|
87
|
+
task :uninstall => [:clean] do
|
88
|
+
system "gem uninstall #{PKG_NAME}"
|
89
|
+
end
|
90
|
+
|
91
|
+
desc "Look for TODO and FIXME tags in the code"
|
92
|
+
task :todo do
|
93
|
+
Pathname.new(File.dirname(__FILE__)).egrep(/#.*(FIXME|TODO|TBD|DEPRECATED)/) do |match|
|
94
|
+
puts match
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
task :reinstall => [:uninstall, :install] do
|
99
|
+
end
|
100
|
+
|
101
|
+
task :release_files => [:clean, :package] do
|
102
|
+
require 'meta_project'
|
103
|
+
project = MetaProject::Project::XForge::RubyForge.new(RUBY_FORGE_PROJECT)
|
104
|
+
|
105
|
+
release_files = FileList[
|
106
|
+
"pkg/#{PKG_FILE_NAME}.gem",
|
107
|
+
"pkg/#{PKG_FILE_NAME}.zip",
|
108
|
+
"pkg/#{PKG_FILE_NAME}.tgz"
|
109
|
+
]
|
110
|
+
|
111
|
+
Rake::XForge::Release.new(project) do |release|
|
112
|
+
release.user_name = RUBY_FORGE_USER
|
113
|
+
release.password = ENV['RUBYFORGE_PASSWORD']
|
114
|
+
release.files = release_files.to_a
|
115
|
+
release.package_name = PKG_NAME
|
116
|
+
release.release_name = "Cerberus #{PKG_VERSION}"
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
task :publish_news do
|
122
|
+
require 'meta_project'
|
123
|
+
Rake::XForge::NewsPublisher.new(project) do |publisher|
|
124
|
+
publisher.user_name = RUBY_FORGE_USER
|
125
|
+
publisher.password = ENV['RUBYFORGE_PASSWORD']
|
126
|
+
publisher.subject = "Cerberus #{PKG_VERSION} Released"
|
127
|
+
publisher.details = "I am glad to announce first public release of Cerberus tool. Version #{PKG_VERSION} is out. Cerberus is a simple command-line Continuous integration tool for Ruby project. Install Cerberus with 'gem install cerberus' then add project 'cerberus add SVN_URL' and then build it 'cerberus build YOUR_PROJECT_NAME'"
|
128
|
+
end
|
129
|
+
end
|
data/bin/cerberus
ADDED
data/doc/FAQ
ADDED
File without changes
|
data/lib/cerberus/cli.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'cerberus/manager'
|
2
|
+
require 'cerberus/utils'
|
3
|
+
require 'cerberus/version'
|
4
|
+
require 'cerberus/constants'
|
5
|
+
|
6
|
+
module Cerberus
|
7
|
+
class CLI
|
8
|
+
include Cerberus::Utils
|
9
|
+
|
10
|
+
def initialize(*args)
|
11
|
+
say HELP if args.empty?
|
12
|
+
|
13
|
+
command = args.shift
|
14
|
+
say HELP unless %w(add build).include?(command)
|
15
|
+
|
16
|
+
cli_options = extract_options(args)
|
17
|
+
|
18
|
+
case command
|
19
|
+
when 'add'
|
20
|
+
path = args.shift || Dir.pwd
|
21
|
+
|
22
|
+
command = Cerberus::Add.new(path, cli_options)
|
23
|
+
command.run
|
24
|
+
when 'build'
|
25
|
+
say HELP if args.empty?
|
26
|
+
|
27
|
+
application_name = args.shift
|
28
|
+
|
29
|
+
command = Cerberus::Build.new(application_name, cli_options)
|
30
|
+
begin
|
31
|
+
command.run
|
32
|
+
rescue Exception => e
|
33
|
+
File.open("#{HOME}/work/#{application_name}/error.log", File::WRONLY|File::APPEND|File::CREAT) do |f|
|
34
|
+
f.puts Time.now.strftime("%a, %d %b %Y %H:%M:%S %z")
|
35
|
+
f.puts e.message
|
36
|
+
f.puts e.backtrace.collect{|line| ' '*5 + line}
|
37
|
+
f.puts "\n"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def extract_options(args)
|
45
|
+
result = {}
|
46
|
+
args_copy = args.dup
|
47
|
+
args_copy.each do |arg|
|
48
|
+
if arg =~ /^(\w+)=(.*)$/
|
49
|
+
result[$1.downcase.to_sym] = $2
|
50
|
+
args.delete(arg)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
result
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
HELP = %{
|
59
|
+
Cerberus is a Continuous Integration tool that could be run from commad line.
|
60
|
+
|
61
|
+
Usage:
|
62
|
+
cerberus add <URL> --- add project from svn repository to list watched of applications
|
63
|
+
cerberus add <PATH> --- add project from local path to list of watched applications
|
64
|
+
cerberus build <APPLICATION_NAME> --- build watched application
|
65
|
+
|
66
|
+
Version #{Cerberus::VERSION::STRING}
|
67
|
+
}.gsub("\n ","\n")
|
68
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_support'
|
3
|
+
|
4
|
+
require 'cerberus/constants'
|
5
|
+
|
6
|
+
module Cerberus
|
7
|
+
class Config
|
8
|
+
def initialize(app_name, cli_options = {})
|
9
|
+
@config = HashWithIndifferentAccess.new
|
10
|
+
if app_name
|
11
|
+
@config.merge!(YAML.load(IO.read(CONFIG_FILE))) if test(?f, CONFIG_FILE)
|
12
|
+
@config.merge!(YAML.load(IO.read(HOME + "/config/#{app_name}.yml")))
|
13
|
+
end
|
14
|
+
@config.merge!(cli_options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def [](name)
|
18
|
+
@config[name]
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def symbolize_hash(hash)
|
23
|
+
hash.each_pair{|k,v|
|
24
|
+
if v === Hash
|
25
|
+
hash[k] = HashWithIndifferentAccess.new(symbolize_hash(v))
|
26
|
+
end
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Cerberus
|
2
|
+
module FSLatch
|
3
|
+
#Emulate File.flock
|
4
|
+
def self.lock(lock_file, wait_for_unlock=true)
|
5
|
+
counter = 0
|
6
|
+
while File.exists?(lock_file)
|
7
|
+
return unless wait_for_unlock #if file exists then return
|
8
|
+
|
9
|
+
sleep(10) #sleep for 10 seconds
|
10
|
+
counter += 1
|
11
|
+
raise "Could not wait anymore file unlocking '#{lock_file}'" if counter > 20 #if we are waiting more than 200 secs then raise exception
|
12
|
+
end
|
13
|
+
|
14
|
+
begin
|
15
|
+
File.new(lock_file, 'w')
|
16
|
+
yield
|
17
|
+
ensure
|
18
|
+
File.rm(lockfile)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,246 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'action_mailer'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
require 'cerberus/utils'
|
6
|
+
require 'cerberus/constants'
|
7
|
+
require 'cerberus/config'
|
8
|
+
|
9
|
+
module Cerberus
|
10
|
+
class Add
|
11
|
+
include Cerberus::Utils
|
12
|
+
|
13
|
+
def initialize(path, cli_options = {})
|
14
|
+
@path, @config = path, Config.new(nil, cli_options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
checkout = Checkout.new(@path, @config)
|
19
|
+
say "Can't find any svn application under #{@path}" unless checkout.url
|
20
|
+
|
21
|
+
application_name = @config[:application_name] || File.basename(File.expand_path(@path)).strip
|
22
|
+
|
23
|
+
create_default_config
|
24
|
+
|
25
|
+
config_name = "#{HOME}/config/#{application_name}.yml"
|
26
|
+
say "Application #{application_name} already present in Cerberus" if File.exists?(config_name)
|
27
|
+
|
28
|
+
app_config = {
|
29
|
+
'url' => checkout.url,
|
30
|
+
'recipients' => @config[:recipients]
|
31
|
+
}
|
32
|
+
dump_yml(config_name, app_config)
|
33
|
+
puts "Application '#{application_name}' was successfully added to Cerberus" unless @config[:quiet]
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def create_default_config
|
38
|
+
default_mail_config =
|
39
|
+
{'mail'=>
|
40
|
+
{ 'delivery_method'=>'smtp',
|
41
|
+
'address'=>'somserver.com',
|
42
|
+
'port' => 25,
|
43
|
+
'domain'=>'somserver.com',
|
44
|
+
'user_name'=>'secret_user',
|
45
|
+
'password'=>'secret_password',
|
46
|
+
'authentication' => 'plain'
|
47
|
+
},
|
48
|
+
'sender' => "'Cerberus' <cerberus@example.com>"}
|
49
|
+
dump_yml(CONFIG_FILE, default_mail_config, false)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Build
|
54
|
+
include Cerberus::Utils
|
55
|
+
attr_reader :output, :success, :checkout, :status
|
56
|
+
|
57
|
+
def initialize(application_name, cli_options = {})
|
58
|
+
unless File.exists?("#{HOME}/config/#{application_name}.yml")
|
59
|
+
say "Project #{application_name} does not present in Cerberus"
|
60
|
+
end
|
61
|
+
|
62
|
+
app_root = "#{HOME}/work/#{application_name}"
|
63
|
+
|
64
|
+
def_options = {:application_root => app_root + '/sources', :application_name => application_name} #pseudo options that stored in config. Could not be set in any config file not through CLI
|
65
|
+
@config = Config.new(application_name, cli_options.merge(def_options))
|
66
|
+
|
67
|
+
@status = Status.new("#{app_root}/status.log")
|
68
|
+
|
69
|
+
@checkout = Checkout.new(@config[:application_root], @config)
|
70
|
+
end
|
71
|
+
|
72
|
+
def run
|
73
|
+
@checkout.update!
|
74
|
+
|
75
|
+
previous_status = @status.recall
|
76
|
+
|
77
|
+
state =
|
78
|
+
if @checkout.has_changes? or not previous_status
|
79
|
+
if status = make
|
80
|
+
@status.keep(:succesful)
|
81
|
+
previous_status == :failed ? :revived : :succesful
|
82
|
+
else
|
83
|
+
@status.keep(:failed)
|
84
|
+
previous_status == :failed ? :broken : :failed
|
85
|
+
end
|
86
|
+
else
|
87
|
+
:unchanged
|
88
|
+
end
|
89
|
+
|
90
|
+
case state
|
91
|
+
when :failed
|
92
|
+
Notifier.deliver_failure(self, @config)
|
93
|
+
when :revived
|
94
|
+
Notifier.deliver_revival(self, @config)
|
95
|
+
when :broken
|
96
|
+
Notifier.deliver_broken(self, @config)
|
97
|
+
when :unchanged, :succesful
|
98
|
+
unless previous_status #If it first time we build application then let everyone to know that we have Cerberus now
|
99
|
+
Notifier.deliver_setup(self, @config)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Smile, be happy, it's all good
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
def make
|
108
|
+
Dir.chdir @config[:application_root]
|
109
|
+
|
110
|
+
@output = `#{@config[:bin_path]}#{choose_rake_exec()} #{@config[:rake_task]} 2>&1`
|
111
|
+
|
112
|
+
make_successful?
|
113
|
+
end
|
114
|
+
|
115
|
+
def make_successful?
|
116
|
+
$?.exitstatus == 0 and not @output.include?('rake aborted!')
|
117
|
+
end
|
118
|
+
|
119
|
+
def choose_rake_exec
|
120
|
+
ext = ['']
|
121
|
+
|
122
|
+
if os() == :windows
|
123
|
+
ext << '.bat' << '.cmd'
|
124
|
+
end
|
125
|
+
|
126
|
+
ext.each{|e|
|
127
|
+
begin
|
128
|
+
out = `rake#{e} --version`
|
129
|
+
return "rake#{e}" if out =~ /rake/
|
130
|
+
rescue
|
131
|
+
end
|
132
|
+
}
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
class Checkout
|
137
|
+
def initialize(path, options = {})
|
138
|
+
raise "Path can't be nil" unless path
|
139
|
+
|
140
|
+
@path, @options = path.strip, options
|
141
|
+
@encoded_path = (@path.include?(' ') ? "\"#{@path}\"" : @path)
|
142
|
+
end
|
143
|
+
|
144
|
+
def update!
|
145
|
+
if test(?d, @path + '/.svn')
|
146
|
+
@status = execute("svn update")
|
147
|
+
else
|
148
|
+
FileUtils.mkpath(@path) unless test(?d,@path)
|
149
|
+
@status = execute("svn checkout", nil, @options[:url])
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def has_changes?
|
154
|
+
@status =~ /[A-Z]\s+[\w\/]+/
|
155
|
+
end
|
156
|
+
|
157
|
+
def current_revision
|
158
|
+
info['Revision'].to_i
|
159
|
+
end
|
160
|
+
|
161
|
+
def url
|
162
|
+
info['URL']
|
163
|
+
end
|
164
|
+
|
165
|
+
def last_commit_message
|
166
|
+
message = execute("svn log", "--limit 1 -v")
|
167
|
+
#strip first line that contains command line itself (svn log --limit ...)
|
168
|
+
if ((idx = message.index('-'*72)) != 0 )
|
169
|
+
message[idx..-1]
|
170
|
+
else
|
171
|
+
message
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def last_author
|
176
|
+
info['Last Changed Author']
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
def info
|
181
|
+
@info ||= YAML.load(execute("svn info"))
|
182
|
+
end
|
183
|
+
|
184
|
+
def execute(command, parameters = nil, pre_parameters = nil)
|
185
|
+
`#{@options[:bin_path]}#{command} #{pre_parameters} #{@encoded_path} #{parameters}`
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
class Status
|
190
|
+
def initialize(path)
|
191
|
+
@path = path
|
192
|
+
end
|
193
|
+
|
194
|
+
def keep(status)
|
195
|
+
File.open(@path, "w+", 0777) { |file| file.write(status.to_s) }
|
196
|
+
end
|
197
|
+
|
198
|
+
def recall
|
199
|
+
value = File.exists?(@path) ? File.read(@path) : false
|
200
|
+
value.blank? ? false : value.to_sym
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
class Notifier < ActionMailer::Base
|
205
|
+
include Cerberus::Utils
|
206
|
+
|
207
|
+
def failure(build, options)
|
208
|
+
@subject = "Build broken by #{build.checkout.last_author} (##{build.checkout.current_revision})"
|
209
|
+
send_message(build, options)
|
210
|
+
end
|
211
|
+
|
212
|
+
def broken(build, options)
|
213
|
+
@subject = "Build still broken (##{build.checkout.current_revision})"
|
214
|
+
send_message(build, options)
|
215
|
+
end
|
216
|
+
|
217
|
+
def revival(build, options)
|
218
|
+
@subject = "Build fixed by #{build.checkout.last_author} (##{build.checkout.current_revision})"
|
219
|
+
send_message(build, options)
|
220
|
+
end
|
221
|
+
|
222
|
+
def setup(build, options)
|
223
|
+
@subject = "Cerberus set up for project (##{build.checkout.current_revision})"
|
224
|
+
send_message(build, options)
|
225
|
+
end
|
226
|
+
|
227
|
+
private
|
228
|
+
def send_message(build, options)
|
229
|
+
mail_config = options[:mail] || {}
|
230
|
+
[:authentication, :delivery_method].each do |k|
|
231
|
+
mail_config[k] = mail_config[k].to_sym if mail_config[k]
|
232
|
+
end
|
233
|
+
|
234
|
+
ActionMailer::Base.delivery_method = mail_config[:delivery_method] if mail_config[:delivery_method]
|
235
|
+
ActionMailer::Base.server_settings = mail_config
|
236
|
+
|
237
|
+
@subject = "[#{options[:application_name]}] " + @subject
|
238
|
+
@body = [ build.checkout.last_commit_message, build.output ].join("\n\n")
|
239
|
+
|
240
|
+
@recipients, @sent_on = options[:recipients], Time.now
|
241
|
+
|
242
|
+
@from = options[:sender] || "'Cerberus' <cerberus@example.com>"
|
243
|
+
raise "Please specify recipient addresses for application '#{options[:application_name]}'" unless options[:recipients]
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Cerberus
|
4
|
+
module Utils
|
5
|
+
def say(info)
|
6
|
+
puts info
|
7
|
+
exit 1
|
8
|
+
end
|
9
|
+
|
10
|
+
def os
|
11
|
+
case RUBY_PLATFORM
|
12
|
+
when /mswin/
|
13
|
+
:windows
|
14
|
+
else
|
15
|
+
:unix
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def dump_yml(file, what, overwrite = true)
|
20
|
+
if overwrite or not File.exists?(file)
|
21
|
+
FileUtils.mkpath(File.dirname(file))
|
22
|
+
File.open(file, 'w') {|f| YAML::dump(what, f) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def load_yml(file_name, default = {})
|
27
|
+
File.exists?(file_name) ? YAML::load(IO.read(file_name)) : default
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/test/config_test.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'cerberus/config'
|
3
|
+
|
4
|
+
class ConfigTest < Test::Unit::TestCase
|
5
|
+
def test_config
|
6
|
+
dump_yml(HOME + "/config.yml", {'a'=>'conf', 'd'=>'conf', 'm' => 'conf'})
|
7
|
+
dump_yml(HOME + "/config/abra.yml", {'a'=>'app', 'd'=>'app', 'g' => 'app'})
|
8
|
+
cfg = Cerberus::Config.new('abra', :a => 'cli', :b=>'cli', :e=>'cli')
|
9
|
+
|
10
|
+
assert_equal nil, cfg[:mamba]
|
11
|
+
assert_equal 'cli', cfg[:a]
|
12
|
+
assert_equal 'cli', cfg[:b]
|
13
|
+
assert_equal 'app', cfg[:d]
|
14
|
+
assert_equal 'app', cfg[:g]
|
15
|
+
assert_equal 'conf', cfg[:m]
|
16
|
+
|
17
|
+
|
18
|
+
assert_equal nil, cfg['mamba']
|
19
|
+
assert_equal 'cli', cfg['a']
|
20
|
+
assert_equal 'cli', cfg['b']
|
21
|
+
assert_equal 'app', cfg['d']
|
22
|
+
assert_equal 'app', cfg['g']
|
23
|
+
assert_equal 'conf', cfg['m']
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
SVN-fs-dump-format-version: 2
|
2
|
+
|
3
|
+
UUID: 3ede5f56-4031-f743-bcde-a87e4ff3590e
|
4
|
+
|
5
|
+
Revision-number: 0
|
6
|
+
Prop-content-length: 56
|
7
|
+
Content-length: 56
|
8
|
+
|
9
|
+
K 8
|
10
|
+
svn:date
|
11
|
+
V 27
|
12
|
+
2006-06-29T09:59:06.497680Z
|
13
|
+
PROPS-END
|
14
|
+
|
15
|
+
Revision-number: 1
|
16
|
+
Prop-content-length: 113
|
17
|
+
Content-length: 113
|
18
|
+
|
19
|
+
K 7
|
20
|
+
svn:log
|
21
|
+
V 10
|
22
|
+
Added test
|
23
|
+
K 10
|
24
|
+
svn:author
|
25
|
+
V 8
|
26
|
+
Apomozov
|
27
|
+
K 8
|
28
|
+
svn:date
|
29
|
+
V 27
|
30
|
+
2006-06-29T10:48:02.581619Z
|
31
|
+
PROPS-END
|
32
|
+
|
33
|
+
Node-path: Rakefile
|
34
|
+
Node-kind: file
|
35
|
+
Node-action: add
|
36
|
+
Prop-content-length: 10
|
37
|
+
Text-content-length: 220
|
38
|
+
Text-content-md5: b3f8886d55aca8f73578c7a896d1779a
|
39
|
+
Content-length: 230
|
40
|
+
|
41
|
+
PROPS-END
|
42
|
+
require 'rake'
|
43
|
+
require 'rake/testtask'
|
44
|
+
|
45
|
+
task :default => :test
|
46
|
+
|
47
|
+
desc "Run the unit tests in test/unit"
|
48
|
+
Rake::TestTask.new(:test) do |t|
|
49
|
+
t.libs << "test"
|
50
|
+
t.pattern = 'test/*_test.rb'
|
51
|
+
t.verbose = true
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
Node-path: test
|
56
|
+
Node-kind: dir
|
57
|
+
Node-action: add
|
58
|
+
Prop-content-length: 10
|
59
|
+
Content-length: 10
|
60
|
+
|
61
|
+
PROPS-END
|
62
|
+
|
63
|
+
|
64
|
+
Node-path: test/a_test.rb
|
65
|
+
Node-kind: file
|
66
|
+
Node-action: add
|
67
|
+
Prop-content-length: 10
|
68
|
+
Text-content-length: 101
|
69
|
+
Text-content-md5: 566f0fba7037bdfe73a4dc77368dabdc
|
70
|
+
Content-length: 111
|
71
|
+
|
72
|
+
PROPS-END
|
73
|
+
require 'test/unit'
|
74
|
+
|
75
|
+
class ATest < Test::Unit::TestCase
|
76
|
+
def test_ok
|
77
|
+
assert true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'cerberus/cli'
|
4
|
+
|
5
|
+
#require 'mocks/notifier'
|
6
|
+
|
7
|
+
class FunctionalTest < Test::Unit::TestCase
|
8
|
+
def setup
|
9
|
+
FileUtils.rm_rf HOME
|
10
|
+
end
|
11
|
+
|
12
|
+
def teardown
|
13
|
+
FileUtils.rm_rf HOME
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_add_by_url
|
17
|
+
assert !File.exists?(HOME + '/config/svn_repo.yml')
|
18
|
+
|
19
|
+
command = Cerberus::Add.new(" #{SVN_URL} ", :quiet => true)
|
20
|
+
command.run
|
21
|
+
|
22
|
+
assert File.exists?(HOME + '/config/svn_repo.yml')
|
23
|
+
assert_equal SVN_URL, load_yml(HOME + '/config/svn_repo.yml')['url']
|
24
|
+
|
25
|
+
assert File.exists?(HOME + '/config.yml')
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_add_by_dir
|
29
|
+
sources_dir = File.dirname(__FILE__) + '/..'
|
30
|
+
|
31
|
+
command = Cerberus::Add.new(sources_dir, :quiet => true)
|
32
|
+
command.run
|
33
|
+
|
34
|
+
project_config = HOME + "/config/#{File.basename(File.expand_path(sources_dir))}.yml" #name of added application should be calculated from File System path
|
35
|
+
|
36
|
+
assert File.exists?(project_config)
|
37
|
+
assert_match %r{svn(\+ssh)?://(\w+@)?rubyforge.org/var/svn/cerberus}, load_yml(project_config)['url']
|
38
|
+
|
39
|
+
assert File.exists?(HOME + '/config.yml')
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_build
|
43
|
+
add_application('myapp', SVN_URL)
|
44
|
+
|
45
|
+
build = Cerberus::Build.new('myapp')
|
46
|
+
build.run
|
47
|
+
assert_equal 1, ActionMailer::Base.deliveries.size #first email that project was setup
|
48
|
+
|
49
|
+
status_file = HOME + '/work/myapp/status.log'
|
50
|
+
assert File.exists?(status_file)
|
51
|
+
assert_equal 'succesful', IO.read(status_file)
|
52
|
+
|
53
|
+
FileUtils.rm status_file
|
54
|
+
build = Cerberus::Build.new('myapp')
|
55
|
+
build.run
|
56
|
+
assert File.exists?(status_file)
|
57
|
+
|
58
|
+
assert_equal 2, ActionMailer::Base.deliveries.size #first email that project was setup
|
59
|
+
|
60
|
+
build = Cerberus::Build.new('myapp')
|
61
|
+
build.run
|
62
|
+
assert_equal 2, ActionMailer::Base.deliveries.size #Number of mails not changed
|
63
|
+
|
64
|
+
|
65
|
+
#remove status file to run project again
|
66
|
+
FileUtils.rm status_file
|
67
|
+
add_test_case_to_project('myapp', 'assert false') { #if assertion failed
|
68
|
+
build = Cerberus::Build.new('myapp')
|
69
|
+
build.run
|
70
|
+
|
71
|
+
assert_equal 'failed', IO.read(status_file)
|
72
|
+
}
|
73
|
+
assert_equal 3, ActionMailer::Base.deliveries.size #We should receive mail if project fails
|
74
|
+
|
75
|
+
|
76
|
+
#remove status file to run project again
|
77
|
+
FileUtils.rm status_file
|
78
|
+
add_test_case_to_project('myapp', 'raise "Some exception here"') { #if we have exception
|
79
|
+
build = Cerberus::Build.new('myapp')
|
80
|
+
build.run
|
81
|
+
|
82
|
+
assert_equal 'failed', IO.read(status_file)
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_have_no_awkward_header
|
87
|
+
add_application('myapp', SVN_URL)
|
88
|
+
|
89
|
+
build = Cerberus::Build.new('myapp')
|
90
|
+
build.run
|
91
|
+
|
92
|
+
assert build.checkout.last_commit_message !~ /-rHEAD -v/
|
93
|
+
assert_equal 0, build.checkout.last_commit_message.index('-' * 72)
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
class IntegrationTest < Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
FileUtils.rm_rf HOME
|
8
|
+
end
|
9
|
+
|
10
|
+
def teardown
|
11
|
+
FileUtils.rm_rf HOME
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_add_project_as_url
|
15
|
+
output = run_cerb(" add #{SVN_URL} ")
|
16
|
+
assert_match /was successfully added/, output
|
17
|
+
assert File.exists?(HOME + '/config/svn_repo.yml')
|
18
|
+
assert_equal SVN_URL, load_yml(HOME + '/config/svn_repo.yml')['url']
|
19
|
+
|
20
|
+
#try to add second time
|
21
|
+
output = run_cerb("add #{SVN_URL}")
|
22
|
+
assert_match /already present/, output
|
23
|
+
assert File.exists?(HOME + '/config/svn_repo.yml')
|
24
|
+
assert_equal SVN_URL, load_yml(HOME + '/config/svn_repo.yml')['url']
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_add_project_with_parameters
|
28
|
+
output = run_cerb(" add #{SVN_URL} APPLICATION_NAME=hello_world RECIPIENTS=aa@gmail.com")
|
29
|
+
assert_match /was successfully added/, output
|
30
|
+
|
31
|
+
assert File.exists?(HOME + '/config/hello_world.yml')
|
32
|
+
cfg = load_yml(HOME + '/config/hello_world.yml')
|
33
|
+
|
34
|
+
assert_equal SVN_URL, cfg['url']
|
35
|
+
assert_equal 'aa@gmail.com', cfg['recipients']
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_run_project
|
39
|
+
add_application('svn_repo', SVN_URL, 'quiet' => true)
|
40
|
+
|
41
|
+
run_cerb("build svn_repo")
|
42
|
+
assert File.exists?(HOME + '/work/svn_repo/status.log')
|
43
|
+
assert_equal 'succesful', IO.read(HOME + '/work/svn_repo/status.log')
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_run_unexist_project
|
47
|
+
output = run_cerb("build some_project")
|
48
|
+
assert_equal 'Project some_project does not present in Cerberus', output.strip
|
49
|
+
assert !test(?d, HOME + '/work/some_project')
|
50
|
+
end
|
51
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__) + '/../lib'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
require 'cerberus/utils'
|
6
|
+
|
7
|
+
class Test::Unit::TestCase
|
8
|
+
include Cerberus::Utils
|
9
|
+
|
10
|
+
TEMP_DIR = File.expand_path(File.dirname(__FILE__)) + '/__workdir'
|
11
|
+
|
12
|
+
SVN_REPO = TEMP_DIR + '/svn_repo'
|
13
|
+
SVN_URL = 'file:///' + SVN_REPO.gsub(/\\/,'/').gsub(/^\//,'').gsub(' ', '%20')
|
14
|
+
|
15
|
+
HOME = TEMP_DIR + '/home'
|
16
|
+
ENV['CERBERUS_HOME'] = HOME
|
17
|
+
|
18
|
+
def self.refresh_subversion
|
19
|
+
FileUtils.rm_rf TEMP_DIR
|
20
|
+
FileUtils.mkpath SVN_REPO
|
21
|
+
`svnadmin create "#{SVN_REPO}"`
|
22
|
+
`svnadmin load "#{SVN_REPO}" < "#{File.dirname(__FILE__)}/data/application.dump"`
|
23
|
+
end
|
24
|
+
|
25
|
+
refresh_subversion
|
26
|
+
|
27
|
+
CERBERUS_PATH = File.expand_path(File.dirname(__FILE__) + '/../')
|
28
|
+
def run_cerb(args)
|
29
|
+
`ruby -I"#{CERBERUS_PATH}/lib" "#{CERBERUS_PATH}/bin/cerberus" #{args}`
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_test_case_to_project(project_name, content)
|
33
|
+
test_case_name = "#{HOME}/work/#{project_name}/sources/test/#{rand(10000)}_test.rb"
|
34
|
+
File.open(test_case_name, 'w') { |f|
|
35
|
+
f << "require 'test/unit'
|
36
|
+
|
37
|
+
class A#{rand(10000)}Test < Test::Unit::TestCase
|
38
|
+
def test_ok
|
39
|
+
#{content}
|
40
|
+
end
|
41
|
+
end"
|
42
|
+
}
|
43
|
+
|
44
|
+
yield
|
45
|
+
|
46
|
+
FileUtils.rm test_case_name
|
47
|
+
end
|
48
|
+
|
49
|
+
def add_application(app_name, url, options = {})
|
50
|
+
opt = options.dup
|
51
|
+
opt['url'] = url
|
52
|
+
opt['recipients'] = 'somebody@com.com'
|
53
|
+
opt['mail'] = {'delivery_method' => 'test'}
|
54
|
+
dump_yml(HOME + "/config/#{app_name}.yml", opt)
|
55
|
+
end
|
56
|
+
end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.0
|
3
|
+
specification_version: 1
|
4
|
+
name: cerberus
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.1.0
|
7
|
+
date: 2006-07-16 00:00:00 +04:00
|
8
|
+
summary: Cerberus is a Continuous Integration tool that could be easily run from Cron.
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: anatol.pomozov@gmail.com
|
12
|
+
homepage: http://rubyforge.org/projects/cerberus
|
13
|
+
rubyforge_project: cerberus
|
14
|
+
description: Cerberus is a Continuous Integration software for Ruby projects. CI helps you keep your project in a good shape. For now Cerberus only work with projects that use Subversion but in the future it would be provided support for other VCS. Cerberus could be easily invoked from Cron (for Unix) or nnCron (for Windows) utilities.
|
15
|
+
autorequire:
|
16
|
+
default_executable: cerberus
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.8.2
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Anatol Pomozov
|
31
|
+
files:
|
32
|
+
- bin/cerberus
|
33
|
+
- doc/FAQ
|
34
|
+
- lib/cerberus
|
35
|
+
- lib/cerberus/cli.rb
|
36
|
+
- lib/cerberus/config.rb
|
37
|
+
- lib/cerberus/constants.rb
|
38
|
+
- lib/cerberus/latch.rb
|
39
|
+
- lib/cerberus/manager.rb
|
40
|
+
- lib/cerberus/utils.rb
|
41
|
+
- lib/cerberus/version.rb
|
42
|
+
- test/config_test.rb
|
43
|
+
- test/data
|
44
|
+
- test/functional_test.rb
|
45
|
+
- test/integration_test.rb
|
46
|
+
- test/test_helper.rb
|
47
|
+
- test/data/application.dump
|
48
|
+
- LICENSE
|
49
|
+
- README
|
50
|
+
- CHANGES
|
51
|
+
- Rakefile
|
52
|
+
test_files:
|
53
|
+
- test/integration_test.rb
|
54
|
+
rdoc_options:
|
55
|
+
- --main
|
56
|
+
- README
|
57
|
+
extra_rdoc_files:
|
58
|
+
- README
|
59
|
+
executables:
|
60
|
+
- cerberus
|
61
|
+
extensions: []
|
62
|
+
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
dependencies:
|
66
|
+
- !ruby/object:Gem::Dependency
|
67
|
+
name: actionmailer
|
68
|
+
version_requirement:
|
69
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: 1.2.1
|
74
|
+
version:
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rake
|
77
|
+
version_requirement:
|
78
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.7.1
|
83
|
+
version:
|