kicker 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +10 -0
- data/.kick +20 -0
- data/LICENSE +54 -0
- data/README.rdoc +141 -0
- data/Rakefile +37 -0
- data/TODO.rdoc +1 -0
- data/VERSION.yml +4 -0
- data/bin/kicker +5 -0
- data/html/images/kikker.jpg +0 -0
- data/kicker.gemspec +95 -0
- data/lib/kicker.rb +135 -0
- data/lib/kicker/callback_chain.rb +77 -0
- data/lib/kicker/core_ext.rb +30 -0
- data/lib/kicker/growl.rb +24 -0
- data/lib/kicker/options.rb +49 -0
- data/lib/kicker/recipes/could_not_handle_file.rb +5 -0
- data/lib/kicker/recipes/dot_kick.rb +35 -0
- data/lib/kicker/recipes/execute_cli_command.rb +6 -0
- data/lib/kicker/recipes/ignore.rb +39 -0
- data/lib/kicker/recipes/jstest.rb +8 -0
- data/lib/kicker/recipes/rails.rb +54 -0
- data/lib/kicker/utils.rb +71 -0
- data/lib/kicker/validate.rb +24 -0
- data/test/callback_chain_test.rb +150 -0
- data/test/core_ext_test.rb +28 -0
- data/test/filesystem_change_test.rb +100 -0
- data/test/fixtures/a_file_thats_reloaded.rb +2 -0
- data/test/initialization_test.rb +165 -0
- data/test/options_test.rb +30 -0
- data/test/recipes/could_not_handle_file_test.rb +11 -0
- data/test/recipes/dot_kick_test.rb +26 -0
- data/test/recipes/execute_cli_command_test.rb +32 -0
- data/test/recipes/ignore_test.rb +29 -0
- data/test/recipes/jstest_test.rb +31 -0
- data/test/recipes/rails_test.rb +73 -0
- data/test/test_helper.rb +6 -0
- data/test/utils_test.rb +123 -0
- data/vendor/growlnotifier/growl.rb +170 -0
- data/vendor/growlnotifier/growl_helpers.rb +25 -0
- data/vendor/rucola/fsevents.rb +136 -0
- metadata +110 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "Array#take_and_map" do
|
4
|
+
before do
|
5
|
+
@array = %w{ foo bar baz }
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should remove elements from the array for which the block evaluates to true" do
|
9
|
+
@array.take_and_map { |x| x =~ /^ba/ }
|
10
|
+
@array.should == %w{ foo }
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should return a new array of the return values of each block call that evaluates to true" do
|
14
|
+
@array.take_and_map { |x| $1 if x =~ /^ba(\w)/ }.should == %w{ r z }
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should flatten and compact the result array" do
|
18
|
+
@array.take_and_map do |x|
|
19
|
+
x =~ /^ba/ ? %w{ f o o } : [nil]
|
20
|
+
end.should == %w{ f o o f o o }
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should not flatten and compact the result array if specified" do
|
24
|
+
@array.take_and_map(false) do |x|
|
25
|
+
x =~ /^ba/ ? %w{ f o o } : [nil]
|
26
|
+
end.should == [[nil], %w{ f o o }, %w{ f o o }]
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "Kicker, when a change occurs" do
|
4
|
+
before do
|
5
|
+
remove_tmp_files!
|
6
|
+
|
7
|
+
Kicker.any_instance.stubs(:last_command_succeeded?).returns(true)
|
8
|
+
Kicker.any_instance.stubs(:log)
|
9
|
+
@kicker = Kicker.new({})
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should store the current time as when the last change occurred" do
|
13
|
+
now = Time.now
|
14
|
+
Time.stubs(:now).returns(now)
|
15
|
+
|
16
|
+
@kicker.send(:finished_processing!)
|
17
|
+
@kicker.last_event_processed_at.should.be now
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should return an array of files that have changed since the last event" do
|
21
|
+
file1 = touch('1')
|
22
|
+
file2 = touch('2')
|
23
|
+
file3 = touch('3')
|
24
|
+
file4 = touch('4')
|
25
|
+
@kicker.send(:finished_processing!)
|
26
|
+
|
27
|
+
events = [event(file1, file2), event(file3, file4)]
|
28
|
+
|
29
|
+
@kicker.send(:changed_files, events).should == []
|
30
|
+
@kicker.send(:finished_processing!)
|
31
|
+
|
32
|
+
sleep(1)
|
33
|
+
touch('2')
|
34
|
+
|
35
|
+
@kicker.send(:changed_files, events).should == [file2]
|
36
|
+
@kicker.send(:finished_processing!)
|
37
|
+
|
38
|
+
sleep(1)
|
39
|
+
touch('1')
|
40
|
+
touch('3')
|
41
|
+
|
42
|
+
@kicker.send(:changed_files, events).should == [file1, file3]
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should not break when determining changed files from events with missing files" do
|
46
|
+
file1 = touch('1')
|
47
|
+
file2 = touch('2')
|
48
|
+
@kicker.send(:finished_processing!)
|
49
|
+
sleep(1)
|
50
|
+
touch('2')
|
51
|
+
|
52
|
+
events = [event(file1, file2), event('/does/not/exist')]
|
53
|
+
@kicker.send(:changed_files, events).should == [file2]
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should return relative file paths if the path is relative to the current work dir" do
|
57
|
+
sleep(1)
|
58
|
+
file = touch('1')
|
59
|
+
|
60
|
+
Dir.stubs(:pwd).returns('/tmp')
|
61
|
+
@kicker.send(:changed_files, [event(file)]).should == [File.basename(file)]
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should call the full_chain with all changed files" do
|
65
|
+
files = %w{ /file/1 /file/2 }
|
66
|
+
events = [event('/file/1'), event('/file/2')]
|
67
|
+
|
68
|
+
@kicker.expects(:changed_files).with(events).returns(files)
|
69
|
+
@kicker.full_chain.expects(:call).with(files)
|
70
|
+
@kicker.expects(:finished_processing!)
|
71
|
+
|
72
|
+
@kicker.send(:process, events)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should not call the full_chain if there were no changed files" do
|
76
|
+
@kicker.stubs(:changed_files).returns([])
|
77
|
+
@kicker.full_chain.expects(:call).never
|
78
|
+
@kicker.expects(:finished_processing!).never
|
79
|
+
|
80
|
+
@kicker.send(:process, [event()])
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def touch(file)
|
86
|
+
file = "/tmp/kicker_test_tmp_#{file}"
|
87
|
+
`touch #{file}`
|
88
|
+
file
|
89
|
+
end
|
90
|
+
|
91
|
+
def event(*files)
|
92
|
+
event = stub('FSEvent')
|
93
|
+
event.stubs(:path).returns('/tmp')
|
94
|
+
event
|
95
|
+
end
|
96
|
+
|
97
|
+
def remove_tmp_files!
|
98
|
+
Dir.glob("/tmp/kicker_test_tmp_*").each { |f| File.delete(f) }
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
module ReloadDotKick; end
|
4
|
+
|
5
|
+
describe "Kicker" do
|
6
|
+
before do
|
7
|
+
Kicker.any_instance.stubs(:start)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should add kicker/recipes to the load path" do
|
11
|
+
$:.should.include File.expand_path('../../lib/kicker/recipes', __FILE__)
|
12
|
+
end
|
13
|
+
|
14
|
+
if File.exist?(File.expand_path('~/.kick'))
|
15
|
+
it "should add ~/.kick to the load path" do
|
16
|
+
$:.should.include File.expand_path('~/.kick')
|
17
|
+
end
|
18
|
+
else
|
19
|
+
puts "[!] ~/.kick does not exist, skipping an example."
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should return the default paths to watch" do
|
23
|
+
Kicker.paths.should == %w{ . }
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should check if a .kick file exists and if so load it and add the ReloadDotKick handler" do
|
27
|
+
File.expects(:exist?).with('.kick').returns(true)
|
28
|
+
Kicker.expects(:require).with('dot_kick')
|
29
|
+
ReloadDotKick.expects(:save_state)
|
30
|
+
Kicker.expects(:load).with('.kick')
|
31
|
+
Kicker.run
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should check if a recipe exists and load it" do
|
35
|
+
Kicker.stubs(:load_dot_kick)
|
36
|
+
|
37
|
+
Kicker.expects(:require).with('rails')
|
38
|
+
Kicker.expects(:require).with('ignore')
|
39
|
+
Kicker.run(%w{ -r rails -r ignore })
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should raise if a recipe does not exist" do
|
43
|
+
Kicker.expects(:require).never
|
44
|
+
lambda { Kicker.run(%w{ -r foobar -r rails }) }.should.raise
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "Kicker, when initializing" do
|
49
|
+
before do
|
50
|
+
@now = Time.now
|
51
|
+
Time.stubs(:now).returns(@now)
|
52
|
+
|
53
|
+
@kicker = Kicker.new(:paths => %w{ /some/dir a/relative/path })
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should return the extended paths to watch" do
|
57
|
+
@kicker.paths.should == ['/some/dir', File.expand_path('a/relative/path')]
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should have assigned the current time to last_event_processed_at" do
|
61
|
+
@kicker.last_event_processed_at.should == @now
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should use the default paths if no paths were given" do
|
65
|
+
Kicker.new({}).paths.should == [File.expand_path('.')]
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should use the default FSEvents latency if none was given" do
|
69
|
+
@kicker.latency.should == 1
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should use the given FSEvents latency if one was given" do
|
73
|
+
Kicker.new(:latency => 3.5).latency.should == 3.5
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "Kicker, when starting" do
|
78
|
+
before do
|
79
|
+
@kicker = Kicker.new(:paths => %w{ /some/file.rb })
|
80
|
+
@kicker.stubs(:log)
|
81
|
+
Rucola::FSEvents.stubs(:start_watching)
|
82
|
+
OSX.stubs(:CFRunLoopRun)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should show the usage banner and exit when there are no callbacks defined at all" do
|
86
|
+
@kicker.stubs(:validate_paths_exist!)
|
87
|
+
Kicker.stubs(:process_chain).returns([])
|
88
|
+
Kicker.stubs(:pre_process_chain).returns([])
|
89
|
+
|
90
|
+
Kicker::OPTION_PARSER_CALLBACK.stubs(:call).returns(mock('OptionParser', :help => 'help'))
|
91
|
+
@kicker.expects(:puts).with("help")
|
92
|
+
@kicker.expects(:exit)
|
93
|
+
|
94
|
+
@kicker.start
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should warn the user and exit if any of the given paths doesn't exist" do
|
98
|
+
@kicker.stubs(:validate_paths_and_command!)
|
99
|
+
|
100
|
+
@kicker.expects(:puts).with("The given path `/some/file.rb' does not exist")
|
101
|
+
@kicker.expects(:exit).with(1)
|
102
|
+
|
103
|
+
@kicker.start
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should start a FSEvents stream with the assigned latency" do
|
107
|
+
@kicker.stubs(:validate_options!)
|
108
|
+
|
109
|
+
Rucola::FSEvents.expects(:start_watching).with(['/some'], :latency => @kicker.latency)
|
110
|
+
@kicker.start
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should start a FSEvents stream which watches all paths, but the dirnames of paths if they're files" do
|
114
|
+
@kicker.stubs(:validate_options!)
|
115
|
+
File.stubs(:directory?).with('/some/file.rb').returns(false)
|
116
|
+
|
117
|
+
Rucola::FSEvents.expects(:start_watching).with(['/some'], :latency => @kicker.latency)
|
118
|
+
@kicker.start
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should start a FSEvents stream with a block which calls #process with any generated events" do
|
122
|
+
@kicker.stubs(:validate_options!)
|
123
|
+
|
124
|
+
Rucola::FSEvents.expects(:start_watching).yields(['event'])
|
125
|
+
@kicker.expects(:process).with(['event'])
|
126
|
+
|
127
|
+
@kicker.start
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should setup a signal handler for `INT' which stops the FSEvents stream and exits" do
|
131
|
+
@kicker.stubs(:validate_options!)
|
132
|
+
|
133
|
+
watch_dog = stub('Rucola::FSEvents')
|
134
|
+
Rucola::FSEvents.stubs(:start_watching).returns(watch_dog)
|
135
|
+
|
136
|
+
@kicker.expects(:trap).with('INT').yields
|
137
|
+
watch_dog.expects(:stop)
|
138
|
+
@kicker.expects(:exit)
|
139
|
+
|
140
|
+
@kicker.start
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should start a CFRunLoop" do
|
144
|
+
@kicker.stubs(:validate_options!)
|
145
|
+
|
146
|
+
OSX.expects(:CFRunLoopRun)
|
147
|
+
@kicker.start
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should register with growl if growl should be used" do
|
151
|
+
@kicker.stubs(:validate_options!)
|
152
|
+
Kicker.use_growl = true
|
153
|
+
|
154
|
+
Growl::Notifier.sharedInstance.expects(:register).with('Kicker', Kicker::GROWL_NOTIFICATIONS.values)
|
155
|
+
@kicker.start
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should _not_ register with growl if growl should not be used" do
|
159
|
+
@kicker.stubs(:validate_options!)
|
160
|
+
Kicker.use_growl = false
|
161
|
+
|
162
|
+
Growl::Notifier.sharedInstance.expects(:register).never
|
163
|
+
@kicker.start
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "Kicker.parse_options" do
|
4
|
+
it "should parse the paths" do
|
5
|
+
Kicker.parse_options([])[:paths].should.be nil
|
6
|
+
|
7
|
+
Kicker.parse_options(%w{ /some/file.rb })[:paths].should == %w{ /some/file.rb }
|
8
|
+
Kicker.parse_options(%w{ /some/file.rb /a/dir /and/some/other/file.rb })[:paths].should ==
|
9
|
+
%w{ /some/file.rb /a/dir /and/some/other/file.rb }
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should parse if growl shouldn't be used" do
|
13
|
+
Kicker.parse_options([])[:growl].should == true
|
14
|
+
Kicker.parse_options(%w{ --no-growl })[:growl].should == false
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should parse the Growl command to use when the user clicks the Growl succeeded message" do
|
18
|
+
Kicker.parse_options(%w{ --growl-command ls })[:growl_command].should == 'ls'
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should parse the latency to pass to FSEvents" do
|
22
|
+
Kicker.parse_options(%w{ -l 2.5 })[:latency].should == 2.5
|
23
|
+
Kicker.parse_options(%w{ --latency 3.5 })[:latency].should == 3.5
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should parse recipe requires" do
|
27
|
+
Kicker.parse_options(%w{ -r rails -r jstest })[:recipes].should == %w{ rails jstest }
|
28
|
+
Kicker.parse_options(%w{ --recipe rails --recipe jstest })[:recipes].should == %w{ rails jstest }
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "Kicker, concerning the default `could not handle file' callback" do
|
4
|
+
it "should log that it could not handle the given files" do
|
5
|
+
Kicker::Utils.expects(:log).with('')
|
6
|
+
Kicker::Utils.expects(:log).with("Could not handle: /file/1, /file/2")
|
7
|
+
Kicker::Utils.expects(:log).with('')
|
8
|
+
|
9
|
+
Kicker.post_process_chain.last.call(%w{ /file/1 /file/2 })
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
before = Kicker.process_chain.dup
|
4
|
+
require 'kicker/recipes/dot_kick'
|
5
|
+
DOT_KICK = (Kicker.process_chain - before).first
|
6
|
+
|
7
|
+
describe "The .kick handler" do
|
8
|
+
it "should reset $LOADED_FEATURES and callback chains to state before loading .kick and reload .kick" do
|
9
|
+
ReloadDotKick.save_state
|
10
|
+
|
11
|
+
features_before_dot_kick = $LOADED_FEATURES.dup
|
12
|
+
chains_before_dot_kick = Kicker.full_chain.map { |c| c.dup }
|
13
|
+
|
14
|
+
ReloadDotKick.expects(:load).with('.kick').twice
|
15
|
+
|
16
|
+
2.times do
|
17
|
+
require File.expand_path('../../fixtures/a_file_thats_reloaded', __FILE__)
|
18
|
+
process {}
|
19
|
+
DOT_KICK.call(%w{ .kick })
|
20
|
+
end
|
21
|
+
|
22
|
+
$FROM_RELOADED_FILE.should == 2
|
23
|
+
$LOADED_FEATURES.should == features_before_dot_kick
|
24
|
+
Kicker.full_chain.should == chains_before_dot_kick
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "Kicker, concerning the `execute a command-line' callback" do
|
4
|
+
it "should parse the command and add the callback" do
|
5
|
+
before = Kicker.pre_process_chain.length
|
6
|
+
|
7
|
+
Kicker.parse_options(%w{ -e ls })
|
8
|
+
Kicker.pre_process_chain.length.should == before + 1
|
9
|
+
|
10
|
+
Kicker.parse_options(%w{ --execute ls })
|
11
|
+
Kicker.pre_process_chain.length.should == before + 2
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should call execute with the given command" do
|
15
|
+
Kicker.parse_options(%w{ -e ls })
|
16
|
+
|
17
|
+
callback = Kicker.pre_process_chain.last
|
18
|
+
callback.should.be.instance_of Proc
|
19
|
+
|
20
|
+
Kicker::Utils.expects(:execute).with('sh -c "ls"')
|
21
|
+
|
22
|
+
callback.call(%w{ /file/1 /file/2 }).should.not.be.instance_of Array
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should clear the files array to halt the chain" do
|
26
|
+
Kicker::Utils.stubs(:execute)
|
27
|
+
|
28
|
+
files = %w{ /file/1 /file/2 }
|
29
|
+
Kicker.pre_process_chain.last.call(files)
|
30
|
+
files.should.be.empty
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
before = Kicker.pre_process_chain.dup
|
4
|
+
require 'kicker/recipes/ignore'
|
5
|
+
IGNORE = (Kicker.pre_process_chain - before).first
|
6
|
+
|
7
|
+
describe "The Ignore handler" do
|
8
|
+
it "should remove files that match the given regexp" do
|
9
|
+
ignore(/^fo{2}bar/)
|
10
|
+
|
11
|
+
files = %w{ Rakefile foobar foobarbaz }
|
12
|
+
IGNORE.call(files)
|
13
|
+
files.should == %w{ Rakefile }
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should remove files that match the given string" do
|
17
|
+
ignore('bazbla')
|
18
|
+
|
19
|
+
files = %w{ Rakefile bazbla bazblabla }
|
20
|
+
IGNORE.call(files)
|
21
|
+
files.should == %w{ Rakefile bazblabla }
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should ignore a few file types by default" do
|
25
|
+
files = %w{ Rakefile foo/bar/dev.log .svn/foo svn-commit.tmp .git/foo tmp }
|
26
|
+
IGNORE.call(files)
|
27
|
+
files.should == %w{ Rakefile }
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
before = Kicker.process_chain.dup
|
4
|
+
require 'kicker/recipes/jstest'
|
5
|
+
JSTEST = (Kicker.process_chain - before).first
|
6
|
+
|
7
|
+
describe "The HeadlessSquirrel handler" do
|
8
|
+
before do
|
9
|
+
@files = %w{ Rakefile }
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should match any test case files" do
|
13
|
+
@files += %w{ test/javascripts/ui_test.html test/javascripts/admin_test.js }
|
14
|
+
|
15
|
+
Kicker::Utils.expects(:execute).
|
16
|
+
with("jstest test/javascripts/ui_test.html test/javascripts/admin_test.html")
|
17
|
+
|
18
|
+
JSTEST.call(@files)
|
19
|
+
@files.should == %w{ Rakefile }
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should map public/javascripts libs to test/javascripts" do
|
23
|
+
@files += %w{ public/javascripts/ui.js public/javascripts/admin.js }
|
24
|
+
|
25
|
+
Kicker::Utils.expects(:execute).
|
26
|
+
with("jstest test/javascripts/ui_test.html test/javascripts/admin_test.html")
|
27
|
+
|
28
|
+
JSTEST.call(@files)
|
29
|
+
@files.should == %w{ Rakefile }
|
30
|
+
end
|
31
|
+
end
|