xcb 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +0 -0
- data/bin/xcb +39 -0
- data/lib/directory_monitor.rb +30 -0
- data/lib/extreme_continuous_builder.rb +108 -0
- data/lib/notifiers.rb +111 -0
- data/lib/stacking_config.rb +40 -0
- data/lib/xcb_command.rb +109 -0
- data/test/builder_test.rb +61 -0
- data/test/directory_monitor_test.rb +90 -0
- data/test/exec_test.rb +9 -0
- data/test/growl_notifier_test.rb +48 -0
- data/test/notifiers_test.rb +10 -0
- data/test/stacking_config_test.rb +47 -0
- data/test/text_notifier_test.rb +44 -0
- metadata +73 -0
data/README
ADDED
File without changes
|
data/bin/xcb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
gem 'xcb'
|
4
|
+
|
5
|
+
|
6
|
+
require 'xcb_command'
|
7
|
+
|
8
|
+
config = StackingConfig.load(".xcb.yaml") <<
|
9
|
+
StackingConfig.load("config/xcb.yaml") <<
|
10
|
+
StackingConfig.load(ENV["HOME"] + "/.xcb.conf") <<
|
11
|
+
StackingConfig.load("/etc/xcb.conf")
|
12
|
+
|
13
|
+
xcb = XCB.new(config)
|
14
|
+
|
15
|
+
case ARGV.shift
|
16
|
+
when nil, "", "build"
|
17
|
+
status = xcb.build
|
18
|
+
puts status.inspect
|
19
|
+
exit (status == :succesful)
|
20
|
+
when "clean"
|
21
|
+
xcb.clean
|
22
|
+
when "start"
|
23
|
+
xcb.start_building
|
24
|
+
when "stop"
|
25
|
+
xcb.stop_building
|
26
|
+
when "growltest"
|
27
|
+
ExtremeContinuousBuilder::GrowlNotifier.setup config
|
28
|
+
ExtremeContinuousBuilder::GrowlNotifier.instance.notify :succesful, "Test OK"
|
29
|
+
puts "You should see a notification"
|
30
|
+
|
31
|
+
else
|
32
|
+
puts "Commands:"
|
33
|
+
puts "\tbuild:\tPerforms one build"
|
34
|
+
puts "\tstart:\tStarts building the the background"
|
35
|
+
puts "\tstop:\tStops building in the background"
|
36
|
+
puts "\tclean:\tClears out PIDs and logs"
|
37
|
+
puts
|
38
|
+
exit 1
|
39
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class DirectoryMonitor
|
2
|
+
def initialize(dir, ops = {})
|
3
|
+
@base_dir = dir
|
4
|
+
@mtimes = nil
|
5
|
+
@ignore = [ops[:ignore]].flatten.compact
|
6
|
+
update! if ops[:update]
|
7
|
+
end
|
8
|
+
|
9
|
+
def has_changes?
|
10
|
+
old_mtimes = @mtimes
|
11
|
+
@last_result = old_mtimes != update!
|
12
|
+
had_changes?
|
13
|
+
end
|
14
|
+
|
15
|
+
def had_changes?
|
16
|
+
@last_result
|
17
|
+
end
|
18
|
+
|
19
|
+
def update!
|
20
|
+
ignore_list = @ignore.map{|ig| Dir.glob(ig)}.flatten.map{|f|File.expand_path f}
|
21
|
+
files = Dir.glob(File.join(@base_dir, "**/*")).map{|f|File.expand_path f}
|
22
|
+
@mtimes = files.inject({}) do |result, file|
|
23
|
+
if ignore_list.include? file
|
24
|
+
result
|
25
|
+
else
|
26
|
+
result.merge( { file => File.mtime(file) } )
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/notifiers'
|
2
|
+
require "timeout"
|
3
|
+
|
4
|
+
module ExtremeContinuousBuilder
|
5
|
+
class Build
|
6
|
+
attr_reader :output, :success, :status
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
@options = options
|
10
|
+
@log_file_path = options[:log_file_path]
|
11
|
+
@lock_file_path = options[:lock_file_path]
|
12
|
+
@status = Status.new(log_file_path)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Runs the tests, returns one of the fiollowing:
|
16
|
+
# ::failed: The test started failing
|
17
|
+
# ::broken: The test is still failing
|
18
|
+
# ::succesful: The test is still working
|
19
|
+
# ::revived: The test started working, you fixed it
|
20
|
+
def run
|
21
|
+
under_lock do
|
22
|
+
previous_status = @status.recall
|
23
|
+
|
24
|
+
if status = make
|
25
|
+
@status.keep(:succesful)
|
26
|
+
(previous_status == :succesful or previous_status == false) ? :succesful : :revived
|
27
|
+
else
|
28
|
+
@status.keep(:failed)
|
29
|
+
previous_status == :failed ? :broken : :failed
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def clean
|
35
|
+
File.delete log_file_path if File.exist? log_file_path
|
36
|
+
File.delete lock_file_path if File.exist? lock_file_path
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def log_file_path
|
41
|
+
@log_file_path ||= File.join(@options[:application_root], ".last_build.log")
|
42
|
+
end
|
43
|
+
|
44
|
+
def lock_file_path
|
45
|
+
@lock_file_path ||= File.join(@options[:application_root], '.continuous_builder.pid')
|
46
|
+
end
|
47
|
+
|
48
|
+
def make
|
49
|
+
@output = `cd #{@options[:application_root]} && #{@options[:bin_path]}rake #{@options[:task_name]} 2>&1`
|
50
|
+
make_successful?
|
51
|
+
end
|
52
|
+
|
53
|
+
def make_successful?
|
54
|
+
$?.exitstatus == 0
|
55
|
+
end
|
56
|
+
|
57
|
+
def under_lock
|
58
|
+
wait_for_lock
|
59
|
+
|
60
|
+
begin
|
61
|
+
grab_lock
|
62
|
+
yield
|
63
|
+
ensure
|
64
|
+
release_lock
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def wait_for_lock
|
69
|
+
begin
|
70
|
+
Timeout::timeout(600) do # timeout after 10 minutes
|
71
|
+
while File.exists?(lock_file_path)
|
72
|
+
sleep(2) # check back after 10 seconds
|
73
|
+
end
|
74
|
+
end
|
75
|
+
rescue Timeout::Error
|
76
|
+
exit(1) # abort build
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def grab_lock
|
81
|
+
File.open(lock_file_path, 'w') { |f| f << $$ }
|
82
|
+
end
|
83
|
+
|
84
|
+
def release_lock
|
85
|
+
File.delete(lock_file_path)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class Status
|
90
|
+
def initialize(path)
|
91
|
+
@path = path
|
92
|
+
end
|
93
|
+
|
94
|
+
def keep(status)
|
95
|
+
File.open(@path, "w+", 0777) { |file| file.write(status.to_s) }
|
96
|
+
end
|
97
|
+
|
98
|
+
def recall
|
99
|
+
if File.exists?(@path)
|
100
|
+
value = File.read(@path)
|
101
|
+
value.empty? ? false : value.to_sym
|
102
|
+
else
|
103
|
+
false
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
data/lib/notifiers.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'ruby-growl'
|
3
|
+
require 'ruby-growl'
|
4
|
+
require 'singleton'
|
5
|
+
|
6
|
+
module ExtremeContinuousBuilder
|
7
|
+
def self.notifiers
|
8
|
+
constants.select{|c|c =~ /.+Notifier$/}.map{|name| const_get(name).instance}
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
class Notifier
|
13
|
+
include Singleton
|
14
|
+
def setup(settings)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.setup(settings)
|
18
|
+
instance.setup(settings) if instance.respond_to? :setup
|
19
|
+
end
|
20
|
+
|
21
|
+
BUILD_RESULT_TYPES = [:failed, :broken, :succesful, :revived]
|
22
|
+
|
23
|
+
#declare noop methods
|
24
|
+
BUILD_RESULT_TYPES.each do |name|
|
25
|
+
code = lambda{|build|}
|
26
|
+
define_method name, code
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class GrowlNotifier < Notifier
|
31
|
+
def setup(settings)
|
32
|
+
@application_name = settings[:application_name]
|
33
|
+
growl_host = settings[:growl_host] || "localhost"
|
34
|
+
growl_password = settings[:growl_password]
|
35
|
+
@has_been_setup = true
|
36
|
+
|
37
|
+
@growl_notifier = Growl.new(growl_host, "XCB",
|
38
|
+
BUILD_RESULT_TYPES.map{|sym| sym.to_s}, nil, growl_password)
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
BUILD_RESULT_TYPES.each do |name|
|
43
|
+
code = lambda do |build|
|
44
|
+
notify name, build.output
|
45
|
+
end
|
46
|
+
define_method name, code
|
47
|
+
end
|
48
|
+
|
49
|
+
def notify(type, text)
|
50
|
+
notifier.notify type.to_s, format_title(type), build_summary(text)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
LINE_TO_LOOK_FOR = /[0-9]+ tests, [0-9]+ assertions, [0-9]+ failures, [0-9]+ errors/
|
55
|
+
def build_summary(text)
|
56
|
+
text.split("\n").select{|l| l =~ LINE_TO_LOOK_FOR }.join("\n")
|
57
|
+
end
|
58
|
+
|
59
|
+
def format_title(type)
|
60
|
+
"[#{@application_name}] Build #{type.to_s}"
|
61
|
+
end
|
62
|
+
|
63
|
+
def notifier=(n)
|
64
|
+
@growl_notifier = n
|
65
|
+
end
|
66
|
+
|
67
|
+
def notifier
|
68
|
+
@growl_notifier
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
class TextFileNotifier < Notifier
|
74
|
+
def setup(options)
|
75
|
+
@application_name = options[:application_name]
|
76
|
+
@text_file_path = options[:text_file_path]
|
77
|
+
@has_been_setup = true
|
78
|
+
end
|
79
|
+
|
80
|
+
def failed(build)
|
81
|
+
update_file "[#{@application_name}] Build failed", build.output
|
82
|
+
end
|
83
|
+
|
84
|
+
def broken(build)
|
85
|
+
update_file "[#{@application_name}] Build still broken", build.output
|
86
|
+
end
|
87
|
+
|
88
|
+
def revived(build)
|
89
|
+
clear_file
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
def file_name
|
94
|
+
@text_file_path
|
95
|
+
end
|
96
|
+
|
97
|
+
def clear_file
|
98
|
+
File.delete(file_name) if File.exists? file_name
|
99
|
+
end
|
100
|
+
|
101
|
+
def update_file(status, text)
|
102
|
+
File.open(file_name, "w", 0777) do |file|
|
103
|
+
file.write(status.to_s)
|
104
|
+
file.write("\n")
|
105
|
+
file.write("="*80)
|
106
|
+
file.write("\n")
|
107
|
+
file.write(text)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
class StackingConfig
|
4
|
+
def initialize(ops = {})
|
5
|
+
@parents = []
|
6
|
+
@data = {}
|
7
|
+
@location = ops.delete(:location)
|
8
|
+
end
|
9
|
+
|
10
|
+
def << (other)
|
11
|
+
@parents = [other] + @parents
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](key)
|
16
|
+
raise ArgumentError, "Expected key to be a symbol, or string" unless key.respond_to? :to_sym
|
17
|
+
@data[key.to_sym] || get_first_parent_result(key.to_sym)
|
18
|
+
end
|
19
|
+
|
20
|
+
def []=(key, value)
|
21
|
+
raise ArgumentError, "Expected key to be a symbol, or string" unless key.respond_to? :to_sym
|
22
|
+
@data[key.to_sym] = value
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.load(file)
|
26
|
+
config = new(:location => file)
|
27
|
+
if File.exist? file
|
28
|
+
data = YAML.load_file(file)
|
29
|
+
data.each {|k,v| config[k] = v }
|
30
|
+
end
|
31
|
+
config
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_first_parent_result(k)
|
35
|
+
@parents.each do |p|
|
36
|
+
return p[k] if p[k]
|
37
|
+
end
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
end
|
data/lib/xcb_command.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/extreme_continuous_builder'
|
2
|
+
require File.dirname(__FILE__) + '/stacking_config'
|
3
|
+
require File.dirname(__FILE__) + '/directory_monitor'
|
4
|
+
|
5
|
+
class XCB
|
6
|
+
def initialize(config)
|
7
|
+
@app_root = config[:app_root] || "."
|
8
|
+
@poller_pid_file = config[:task] || ".xcb_poller.pid"
|
9
|
+
|
10
|
+
@build = ExtremeContinuousBuilder::Build.new(
|
11
|
+
:task_name => config[:task] || 'test',
|
12
|
+
:application_root => @app_root
|
13
|
+
)
|
14
|
+
|
15
|
+
@project_name = config[:app_name] ||
|
16
|
+
if File.basename(Dir.pwd) == "trunk"
|
17
|
+
File.basename(File.dirname(Dir.pwd))
|
18
|
+
else
|
19
|
+
File.basename(Dir.pwd)
|
20
|
+
end
|
21
|
+
|
22
|
+
@text_output_path = config[:text_file_path] || "build_result.txt"
|
23
|
+
|
24
|
+
notifier_options = {
|
25
|
+
:application_name => @project_name,
|
26
|
+
:growl_host => config[:growl_host] || "localhost",
|
27
|
+
:growl_password => config[:growl_password],
|
28
|
+
:text_file_path => @text_output_path,
|
29
|
+
}
|
30
|
+
|
31
|
+
ExtremeContinuousBuilder.notifiers.each do |notifier|
|
32
|
+
notifier.setup(notifier_options)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def start_building
|
37
|
+
@monitor = DirectoryMonitor.new(@app_root, :ignore => @text_output_path)
|
38
|
+
|
39
|
+
daemonize!
|
40
|
+
|
41
|
+
File.open(@poller_pid_file, "w") {|f| f.write Process.pid }
|
42
|
+
|
43
|
+
while pid_file_is_me?
|
44
|
+
if @monitor.has_changes?
|
45
|
+
build
|
46
|
+
sleep 10
|
47
|
+
else
|
48
|
+
sleep 1
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def stop_building
|
54
|
+
if read_pid_file.nil?
|
55
|
+
:not_running
|
56
|
+
else
|
57
|
+
pid = read_pid_file
|
58
|
+
Process.kill("TERM", pid) if pid
|
59
|
+
delete_pid_file
|
60
|
+
:stopped
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def build
|
65
|
+
status = @build.run
|
66
|
+
|
67
|
+
if [:failed, :revived, :broken, :succesful].include? status
|
68
|
+
ExtremeContinuousBuilder.notifiers.each do |notifier|
|
69
|
+
notifier.send(status, @build) if notifier.respond_to? status
|
70
|
+
end
|
71
|
+
status
|
72
|
+
else
|
73
|
+
raise "Unexpected build result: #{status.inspect}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def clean
|
78
|
+
@build.clean
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
protected
|
83
|
+
# Checks that the PID file that eas created still represents this proccess
|
84
|
+
def pid_file_is_me?
|
85
|
+
read_pid_file == Process.pid
|
86
|
+
end
|
87
|
+
|
88
|
+
def write_pid_file!
|
89
|
+
File.open(@poller_pid_file, "w") {|f| f.write Process.pid }
|
90
|
+
end
|
91
|
+
|
92
|
+
def read_pid_file
|
93
|
+
File.read(@poller_pid_file).to_i if File.exist? @poller_pid_file
|
94
|
+
end
|
95
|
+
|
96
|
+
def delete_pid_file
|
97
|
+
File.delete @poller_pid_file if File.exist? @poller_pid_file
|
98
|
+
end
|
99
|
+
|
100
|
+
def daemonize!
|
101
|
+
exit!(0) if fork
|
102
|
+
Process::setsid
|
103
|
+
exit!(0) if fork
|
104
|
+
STDIN.reopen("/dev/null")
|
105
|
+
STDERR.reopen("/dev/null", "w")
|
106
|
+
STDOUT.reopen("/dev/null", "w")
|
107
|
+
yield if block_given?
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
|
4
|
+
class BuilderTest < Test::Unit::TestCase
|
5
|
+
def test_ok_with_clean
|
6
|
+
clean_logs
|
7
|
+
builder = ExtremeContinuousBuilder::Build.new(XCB_OPS.merge({:task_name => "test PASS_COUNT=6"}))
|
8
|
+
assert_equal(:succesful, builder.run)
|
9
|
+
assert_equal(:succesful, status.recall)
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_ok_with_succesful
|
13
|
+
clean_logs
|
14
|
+
set_last_result(:succesful)
|
15
|
+
builder = ExtremeContinuousBuilder::Build.new(XCB_OPS.merge({:task_name => "test PASS_COUNT=6"}))
|
16
|
+
assert_equal(:succesful, builder.run)
|
17
|
+
assert_equal(:succesful, status.recall)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_ok_with_failed
|
21
|
+
clean_logs
|
22
|
+
set_last_result(:failed)
|
23
|
+
builder = ExtremeContinuousBuilder::Build.new(XCB_OPS.merge({:task_name => "test PASS_COUNT=6"}))
|
24
|
+
assert_equal(:revived, builder.run)
|
25
|
+
assert_equal(:succesful, status.recall)
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def test_locking
|
30
|
+
build = ExtremeContinuousBuilder::Build.new XCB_OPS
|
31
|
+
build.send :grab_lock
|
32
|
+
Thread.new(build) {|b| sleep 1; b.send :release_lock}
|
33
|
+
started_time = Time.now
|
34
|
+
build.send :wait_for_lock
|
35
|
+
assert((Time.now - started_time).to_i > 1, "It all happened too fast")
|
36
|
+
assert((Time.now - started_time).to_i < 3, "It all happened too slow")
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_bad_with_clean
|
40
|
+
clean_logs
|
41
|
+
builder = ExtremeContinuousBuilder::Build.new(XCB_OPS.merge({:task_name => "test PASS_COUNT=3"}))
|
42
|
+
assert_equal(:failed, builder.run)
|
43
|
+
assert_equal(:failed, status.recall)
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_bad_with_succesful
|
47
|
+
clean_logs
|
48
|
+
set_last_result(:succesful)
|
49
|
+
builder = ExtremeContinuousBuilder::Build.new(XCB_OPS.merge({:task_name => "test PASS_COUNT=3"}))
|
50
|
+
assert_equal(:failed, builder.run)
|
51
|
+
assert_equal(:failed, status.recall)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_bad_with_failed
|
55
|
+
clean_logs
|
56
|
+
set_last_result(:failed)
|
57
|
+
builder = ExtremeContinuousBuilder::Build.new(XCB_OPS.merge({:task_name => "test PASS_COUNT=3"}))
|
58
|
+
assert_equal(:broken, builder.run)
|
59
|
+
assert_equal(:failed, status.recall)
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
class DirectoryMonitorTest < Test::Unit::TestCase
|
3
|
+
BASE_TEST_DIR = "/tmp/DirectoryMonitorTest"
|
4
|
+
TEST_DIRS = ["#{BASE_TEST_DIR}/dir1", "#{BASE_TEST_DIR}/dir2", "#{BASE_TEST_DIR}/dir1/dir3", "#{BASE_TEST_DIR}/dir1/dir4"]
|
5
|
+
DIRS_TO_KILL = TEST_DIRS.reverse + [BASE_TEST_DIR]
|
6
|
+
|
7
|
+
def setup
|
8
|
+
teardown if File.exist? BASE_TEST_DIR
|
9
|
+
Dir.mkdir BASE_TEST_DIR
|
10
|
+
TEST_DIRS.each do |d|
|
11
|
+
Dir.mkdir d
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def teardown
|
16
|
+
DIRS_TO_KILL.each do |d|
|
17
|
+
Dir.glob(d + "/*.tmp", File::FNM_DOTMATCH).each do |file|
|
18
|
+
File.delete file if File.file? file
|
19
|
+
end
|
20
|
+
Dir.delete d if File.exist? d
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_initial_usage
|
25
|
+
monitor = DirectoryMonitor.new(BASE_TEST_DIR)
|
26
|
+
assert_equal(nil, monitor.had_changes?)
|
27
|
+
assert_equal(true, monitor.has_changes?)
|
28
|
+
assert_equal(true, monitor.had_changes?)
|
29
|
+
assert_equal(false, monitor.has_changes?)
|
30
|
+
assert_equal(false, monitor.had_changes?)
|
31
|
+
assert_equal(false, monitor.has_changes?)
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_initial_usage_with_update
|
35
|
+
monitor = DirectoryMonitor.new(BASE_TEST_DIR, :update => true)
|
36
|
+
assert_equal(nil, monitor.had_changes?)
|
37
|
+
assert_equal(false, monitor.has_changes?)
|
38
|
+
assert_equal(false, monitor.had_changes?)
|
39
|
+
|
40
|
+
monitor = DirectoryMonitor.new(BASE_TEST_DIR, :update => true)
|
41
|
+
assert_equal(nil, monitor.had_changes?)
|
42
|
+
assert_equal(false, monitor.has_changes?)
|
43
|
+
assert_equal(false, monitor.had_changes?)
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_simple_change
|
47
|
+
monitor = DirectoryMonitor.new(BASE_TEST_DIR, :update => true)
|
48
|
+
assert_equal(false, monitor.has_changes?)
|
49
|
+
assert_equal(false, monitor.has_changes?)
|
50
|
+
|
51
|
+
touch BASE_TEST_DIR, "test_file_one.tmp"
|
52
|
+
assert_equal(true, monitor.has_changes?)
|
53
|
+
assert_equal(false, monitor.has_changes?)
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_ignore_dotfiles
|
57
|
+
monitor = DirectoryMonitor.new(BASE_TEST_DIR, :update => true)
|
58
|
+
|
59
|
+
touch BASE_TEST_DIR, ".test_file_one.tmp"
|
60
|
+
|
61
|
+
assert_equal(false, monitor.has_changes?)
|
62
|
+
assert_equal(false, monitor.has_changes?)
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def test_ignore_ignore_files
|
67
|
+
monitor = DirectoryMonitor.new(BASE_TEST_DIR, :update => true,
|
68
|
+
:ignore => File.join(BASE_TEST_DIR, "test_file_ignore.tmp"))
|
69
|
+
|
70
|
+
touch BASE_TEST_DIR, "test_file_ignore.tmp"
|
71
|
+
assert_equal(false, monitor.has_changes?)
|
72
|
+
touch BASE_TEST_DIR, "test_file_one.tmp"
|
73
|
+
assert_equal(true, monitor.has_changes?)
|
74
|
+
|
75
|
+
sleep 1 # Must sleep between changes, otherwise the files mtimes will be
|
76
|
+
# the same despite being touched again.
|
77
|
+
monitor = DirectoryMonitor.new(BASE_TEST_DIR, :update => true,
|
78
|
+
:ignore => [File.join(BASE_TEST_DIR, "test_file_ignore.tmp")])
|
79
|
+
|
80
|
+
touch BASE_TEST_DIR, "test_file_ignore.tmp"
|
81
|
+
assert_equal(false, monitor.has_changes?)
|
82
|
+
touch BASE_TEST_DIR, "test_file_one.tmp"
|
83
|
+
assert_equal(true, monitor.has_changes?)
|
84
|
+
end
|
85
|
+
|
86
|
+
def touch(*file_name_parts)
|
87
|
+
file_name = File.join(file_name_parts)
|
88
|
+
File.open(file_name, "w+") {|f| f.write ""}
|
89
|
+
end
|
90
|
+
end
|
data/test/exec_test.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
|
4
|
+
class GrowlNotifierTest < Test::Unit::TestCase
|
5
|
+
include ExtremeContinuousBuilder
|
6
|
+
def test_growl_notifier_revived
|
7
|
+
clean_logs
|
8
|
+
builder = Build.new(XCB_OPS.merge({:task_name => "test PASS_COUNT=6"}))
|
9
|
+
assert_equal(:succesful, builder.run)
|
10
|
+
|
11
|
+
GrowlNotifier.instance.send :notifier=, nil
|
12
|
+
GrowlNotifier.setup(XCB_OPS)
|
13
|
+
|
14
|
+
GrowlNotifier.instance.revived builder
|
15
|
+
|
16
|
+
my_growler = GrowlNotifier.instance.send(:notifier)
|
17
|
+
|
18
|
+
assert_equal(1, my_growler.notifys.size)
|
19
|
+
assert_equal({
|
20
|
+
:type => "revived",
|
21
|
+
:title => "[fake_project] Build revived",
|
22
|
+
:text => "6 tests, 6 assertions, 0 failures, 0 errors",
|
23
|
+
}, my_growler.notifys[0])
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def test_growl_notifier_broken
|
28
|
+
clean_logs
|
29
|
+
builder = Build.new(XCB_OPS.merge({:task_name => "test PASS_COUNT=3"}))
|
30
|
+
assert_equal(:failed, builder.run)
|
31
|
+
|
32
|
+
GrowlNotifier.instance.send :notifier=, nil
|
33
|
+
GrowlNotifier.setup(XCB_OPS)
|
34
|
+
GrowlNotifier.instance.failed builder
|
35
|
+
my_growler = GrowlNotifier.instance.send(:notifier)
|
36
|
+
|
37
|
+
assert_equal(1, my_growler.notifys.size)
|
38
|
+
assert_equal({
|
39
|
+
:type => "failed",
|
40
|
+
:title => "[fake_project] Build failed",
|
41
|
+
:text => "6 tests, 6 assertions, 3 failures, 0 errors",
|
42
|
+
}, my_growler.notifys[0])
|
43
|
+
end
|
44
|
+
|
45
|
+
# def test_flunk
|
46
|
+
# flunk
|
47
|
+
# end
|
48
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
|
4
|
+
class NotifiersTest < Test::Unit::TestCase
|
5
|
+
include ExtremeContinuousBuilder
|
6
|
+
NOTIFIERS = [GrowlNotifier.instance, TextFileNotifier.instance]
|
7
|
+
def test_notifiers_list
|
8
|
+
assert_equal(NOTIFIERS, ExtremeContinuousBuilder.notifiers.sort_by{|m| m.to_s})
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class StackingConfigTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@one = StackingConfig.load(File.dirname(__FILE__) + "/fixtures/stacking_config/one.yaml")
|
6
|
+
@two = StackingConfig.load(File.dirname(__FILE__) + "/fixtures/stacking_config/two.yaml")
|
7
|
+
@three = StackingConfig.load(File.dirname(__FILE__) + "/fixtures/stacking_config/three.yaml")
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
def test_basic_loding
|
12
|
+
assert_equal(1, @one[:one_only])
|
13
|
+
assert_equal(1, @two[:two_only])
|
14
|
+
|
15
|
+
assert_nil(@two[:one_only])
|
16
|
+
assert_nil(@one[:two_only])
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_simple_stack
|
20
|
+
stack = StackingConfig.new() << @one << @two
|
21
|
+
assert_equal(1, stack[:one_only])
|
22
|
+
assert_equal(1, stack[:two_only])
|
23
|
+
assert_equal(3, stack[:two_overides_to_3])
|
24
|
+
|
25
|
+
assert_equal(1, @one[:one_only])
|
26
|
+
assert_equal(nil, @one[:two_only])
|
27
|
+
assert_equal(2, @one[:two_overides_to_3])
|
28
|
+
|
29
|
+
assert_equal(nil, @two[:one_only])
|
30
|
+
assert_equal(1, @two[:two_only])
|
31
|
+
assert_equal(3, @two[:two_overides_to_3])
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_larger_stack
|
35
|
+
stack = StackingConfig.new() << @one << @two << @three
|
36
|
+
assert_equal(1, stack[:one_only])
|
37
|
+
assert_equal(1, stack[:two_only])
|
38
|
+
assert_equal(1, stack[:three_only])
|
39
|
+
assert_equal(3, stack[:three_overides_to_3])
|
40
|
+
|
41
|
+
assert_equal(1, @one[:one_only])
|
42
|
+
assert_equal(nil, @one[:two_only])
|
43
|
+
|
44
|
+
assert_equal(nil, @two[:one_only])
|
45
|
+
assert_equal(1, @two[:two_only])
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
|
4
|
+
class TestNotifierTest < Test::Unit::TestCase
|
5
|
+
include ExtremeContinuousBuilder
|
6
|
+
def test_text_notifier_failed
|
7
|
+
clean_logs
|
8
|
+
builder = ExtremeContinuousBuilder::Build.new(XCB_OPS.merge({:task_name => "test PASS_COUNT=3"}))
|
9
|
+
assert_equal(:failed, builder.run)
|
10
|
+
path = XCB_OPS[:text_file_path]
|
11
|
+
|
12
|
+
TextFileNotifier.setup XCB_OPS
|
13
|
+
TextFileNotifier.instance.failed builder
|
14
|
+
|
15
|
+
assert(File.exist?(path), "File should exist.")
|
16
|
+
assert_equal("[fake_project] Build failed\n" + ("="*80) + "\n" + builder.output, File.read(path))
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_text_notifier_broken
|
20
|
+
clean_logs
|
21
|
+
builder = Build.new(XCB_OPS.merge({:task_name => "test PASS_COUNT=3"}))
|
22
|
+
assert_equal(:failed, builder.run)
|
23
|
+
path = XCB_OPS[:text_file_path]
|
24
|
+
|
25
|
+
TextFileNotifier.setup XCB_OPS
|
26
|
+
TextFileNotifier.instance.broken builder
|
27
|
+
|
28
|
+
assert(File.exist?(path), "File should exist.")
|
29
|
+
assert_equal("[fake_project] Build still broken\n" + ("="*80) + "\n" + builder.output, File.read(path))
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_text_notifier_revived
|
33
|
+
clean_logs
|
34
|
+
builder = Build.new(XCB_OPS.merge({:task_name => "test PASS_COUNT=6"}))
|
35
|
+
assert_equal(:succesful, builder.run)
|
36
|
+
path = XCB_OPS[:text_file_path]
|
37
|
+
|
38
|
+
File.open(path, "w") {|f| f.write "Hello World"}
|
39
|
+
TextFileNotifier.setup XCB_OPS
|
40
|
+
TextFileNotifier.instance.revived builder
|
41
|
+
assert(!File.exist?(path), "File should exist.")
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: xcb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tom Lea
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-07-01 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: ruby-growl
|
17
|
+
version_requirement:
|
18
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: "0"
|
23
|
+
version:
|
24
|
+
description: XCB provides the xcb command to continuously build a project in the background.
|
25
|
+
email: xcb@clockworkninja.co.uk
|
26
|
+
executables:
|
27
|
+
- xcb
|
28
|
+
extensions: []
|
29
|
+
|
30
|
+
extra_rdoc_files: []
|
31
|
+
|
32
|
+
files:
|
33
|
+
- README
|
34
|
+
- bin/xcb
|
35
|
+
- lib/directory_monitor.rb
|
36
|
+
- lib/extreme_continuous_builder.rb
|
37
|
+
- lib/notifiers.rb
|
38
|
+
- lib/stacking_config.rb
|
39
|
+
- lib/xcb_command.rb
|
40
|
+
has_rdoc: true
|
41
|
+
homepage: http://clockworkninja.co.uk/
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
44
|
+
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: "0"
|
52
|
+
version:
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
version:
|
59
|
+
requirements:
|
60
|
+
- Growl with network access enabled
|
61
|
+
rubyforge_project: xconbuild
|
62
|
+
rubygems_version: 1.0.1
|
63
|
+
signing_key:
|
64
|
+
specification_version: 2
|
65
|
+
summary: XCB provides the xcb command to continuously build a project in the background.
|
66
|
+
test_files:
|
67
|
+
- test/builder_test.rb
|
68
|
+
- test/directory_monitor_test.rb
|
69
|
+
- test/exec_test.rb
|
70
|
+
- test/growl_notifier_test.rb
|
71
|
+
- test/notifiers_test.rb
|
72
|
+
- test/stacking_config_test.rb
|
73
|
+
- test/text_notifier_test.rb
|