xcb 0.0.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/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
|