kicker 2.0.0
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/.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
|