kicker 2.1.0 → 2.2.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.
Files changed (43) hide show
  1. data/.gitignore +10 -0
  2. data/.kick +37 -0
  3. data/README.rdoc +24 -21
  4. data/Rakefile +0 -1
  5. data/TODO.rdoc +4 -34
  6. data/VERSION +1 -0
  7. data/html/images/kikker.jpg +0 -0
  8. data/kicker.gemspec +106 -0
  9. data/lib/kicker.rb +46 -60
  10. data/lib/kicker/callback_chain.rb +4 -4
  11. data/lib/kicker/core_ext.rb +9 -1
  12. data/lib/kicker/growl.rb +54 -19
  13. data/lib/kicker/log_status_helper.rb +38 -0
  14. data/lib/kicker/options.rb +59 -34
  15. data/lib/kicker/recipes.rb +58 -0
  16. data/lib/kicker/recipes/could_not_handle_file.rb +5 -3
  17. data/lib/kicker/recipes/dot_kick.rb +17 -5
  18. data/lib/kicker/recipes/execute_cli_command.rb +1 -1
  19. data/lib/kicker/recipes/ignore.rb +8 -6
  20. data/lib/kicker/recipes/jstest.rb +7 -5
  21. data/lib/kicker/recipes/rails.rb +108 -43
  22. data/lib/kicker/recipes/ruby.rb +155 -0
  23. data/lib/kicker/utils.rb +36 -32
  24. data/test/callback_chain_test.rb +1 -1
  25. data/test/core_ext_test.rb +15 -5
  26. data/test/filesystem_change_test.rb +1 -1
  27. data/test/growl_test.rb +85 -0
  28. data/test/initialization_test.rb +25 -56
  29. data/test/log_status_helper_test.rb +56 -0
  30. data/test/options_test.rb +50 -12
  31. data/test/recipes/could_not_handle_file_test.rb +10 -0
  32. data/test/recipes/dot_kick_test.rb +1 -5
  33. data/test/recipes/execute_cli_command_test.rb +3 -3
  34. data/test/recipes/ignore_test.rb +1 -1
  35. data/test/recipes/jstest_test.rb +1 -1
  36. data/test/recipes/rails_test.rb +118 -18
  37. data/test/recipes/ruby_test.rb +154 -0
  38. data/test/recipes_test.rb +39 -0
  39. data/test/test_helper.rb +1 -1
  40. data/test/utils_test.rb +103 -48
  41. metadata +19 -6
  42. data/VERSION.yml +0 -4
  43. data/lib/kicker/validate.rb +0 -24
@@ -0,0 +1,155 @@
1
+ class Ruby
2
+ class << self
3
+ # Assigns the type of tests to run. Eg: `test' or `spec'.
4
+ attr_writer :test_type
5
+
6
+ # Returns the type of tests to run. Eg: `test' or `spec'.
7
+ #
8
+ # Defaults to `test' if no `spec' directory exists.
9
+ def test_type
10
+ @test_type ||= File.exist?('spec') ? 'spec' : 'test'
11
+ end
12
+
13
+ # Assigns the ruby command to run the tests with. Eg: `ruby19' or `specrb'.
14
+ #
15
+ # This can be set from the command line with the `-b' or `--ruby' options.
16
+ attr_writer :runner_bin
17
+
18
+ # Returns the ruby command to run the tests with. Eg: `ruby' or `spec'.
19
+ #
20
+ # Defaults to `ruby' if test_type is `test' and `spec' if test_type is
21
+ # `spec'.
22
+ def runner_bin
23
+ @runner_bin ||= test_type == 'test' ? 'ruby' : 'spec'
24
+ end
25
+
26
+ # Assigns the root directory of where test cases will be looked up.
27
+ attr_writer :test_cases_root
28
+
29
+ # Returns the root directory of where test cases will be looked up.
30
+ #
31
+ # Defaults to the value of test_type. Eg: `test' or `spec'.
32
+ def test_cases_root
33
+ @test_cases_root ||= test_type
34
+ end
35
+
36
+ attr_writer :test_options #:nodoc:
37
+
38
+ # Assigns extra options that are to be passed on to the runner_bin.
39
+ #
40
+ # Ruby.test_options << '-I ./lib/foo'
41
+ def test_options
42
+ @test_options ||= []
43
+ end
44
+
45
+ # Runs the given tests, if there are any, with the method defined by
46
+ # test_type. If test_type is `test' the run_with_test_runner method is
47
+ # used. The same applies when test_type is `spec'.
48
+ def run_tests(tests)
49
+ send("run_with_#{test_type}_runner", tests) unless tests.empty?
50
+ end
51
+
52
+ def test_runner_command(tests)
53
+ "#{runner_bin} #{test_options.join(' ')} -r #{tests.join(' -r ')} -e ''"
54
+ end
55
+
56
+ # Runs the given tests with `ruby' as unit-test tests.
57
+ #
58
+ # If you want to adjust the logging, stdout and growl, override this, call
59
+ # test_runner_command with the tests to get the command and call execute
60
+ # with the custom logging block.
61
+ def run_with_test_runner(tests)
62
+ execute(test_runner_command(tests)) do |status|
63
+ if status.after? && status.growl?
64
+ status.output.split("\n").last
65
+ end
66
+ end
67
+ end
68
+
69
+ def spec_runner_command(tests)
70
+ "#{runner_bin} #{test_options.join(' ')} #{tests.join(' ')}"
71
+ end
72
+
73
+ # Runs the given tests with `spec' as RSpec tests.
74
+ #
75
+ # If you want to adjust the logging, stdout and growl, override this, call
76
+ # spec_runner_command with the tests to get the command and call execute
77
+ # with the custom logging block.
78
+ def run_with_spec_runner(tests)
79
+ execute(spec_runner_command(tests)) do |status|
80
+ if status.after? && status.growl?
81
+ status.output.split("\n").last
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ def self.call(files) #:nodoc:
88
+ handler = new(files)
89
+ handler.handle!
90
+ run_tests(handler.tests)
91
+ end
92
+
93
+ # The list of collected tests.
94
+ attr_reader :tests
95
+
96
+ def initialize(files) #:nodoc:
97
+ @files = files
98
+ @tests = []
99
+ end
100
+
101
+ # A shortcut to Ruby.test_type.
102
+ def test_type
103
+ self.class.test_type
104
+ end
105
+
106
+ # A shortcut to Ruby.runner_bin.
107
+ def runner_bin
108
+ self.class.runner_bin
109
+ end
110
+
111
+ # A shortcut to Ruby.test_cases_root.
112
+ def test_cases_root
113
+ self.class.test_cases_root
114
+ end
115
+
116
+ # Returns the file for +name+ if it exists.
117
+ #
118
+ # test_file('foo') # => "test/foo_test.rb"
119
+ # test_file('foo/bar') # => "test/foo/bar_test.rb"
120
+ # test_file('does/not/exist') # => nil
121
+ def test_file(name)
122
+ file = File.join(test_cases_root, "#{name}_#{test_type}.rb")
123
+ file if File.exist?(file)
124
+ end
125
+
126
+ # This method is called to collect tests. Override this if you're subclassing
127
+ # and make sure to call +super+.
128
+ def handle!
129
+ @tests.concat(@files.take_and_map do |file|
130
+ case file
131
+ # Match any ruby test file
132
+ when /^#{test_cases_root}\/.+_#{test_type}\.rb$/
133
+ file
134
+
135
+ # A file such as ./lib/namespace/foo.rb is mapped to:
136
+ # * ./test/namespace/foo_test.rb
137
+ # * ./test/foo_test.rb
138
+ when /^lib\/(.+)\.rb$/
139
+ if namespaced = test_file($1)
140
+ namespaced
141
+ elsif in_test_root = test_file(File.basename(file, '.rb'))
142
+ in_test_root
143
+ end
144
+ end
145
+ end)
146
+ end
147
+ end
148
+
149
+ options.on('-b', '--ruby [PATH]', "Use an alternate Ruby binary for spawned test runners. (Default is `ruby')") do |command|
150
+ Ruby.runner_bin = command
151
+ end
152
+
153
+ recipe :ruby do
154
+ process Ruby
155
+ end
@@ -2,25 +2,13 @@ class Kicker
2
2
  module Utils #:nodoc:
3
3
  extend self
4
4
 
5
- def execute(command)
5
+ def execute(command, &block)
6
6
  @last_command = command
7
+ status = LogStatusHelper.new(block, command)
7
8
 
8
- log "Change occured, executing command: #{command}"
9
- Kicker.growl(GROWL_NOTIFICATIONS[:change], 'Kicker: Change occured, executing command:', command) if Kicker.use_growl
10
-
11
- output = `#{command}`
12
- output.strip.split("\n").each { |line| log " #{line}" }
13
-
14
- log "Command #{last_command_succeeded? ? 'succeeded' : "failed (#{last_command_status})"}"
15
-
16
- if Kicker.use_growl
17
- if last_command_succeeded?
18
- callback = Kicker.growl_command.nil? ? GROWL_DEFAULT_CALLBACK : lambda { system(Kicker.growl_command) }
19
- Kicker.growl(GROWL_NOTIFICATIONS[:succeeded], "Kicker: Command succeeded", output, &callback)
20
- else
21
- Kicker.growl(GROWL_NOTIFICATIONS[:failed], "Kicker: Command failed (#{last_command_status})", output, &GROWL_DEFAULT_CALLBACK)
22
- end
23
- end
9
+ will_execute_command(status)
10
+ status.result(`#{command}`, last_command_succeeded?, last_command_status)
11
+ did_execute_command(status)
24
12
  end
25
13
 
26
14
  def last_command
@@ -28,15 +16,14 @@ class Kicker
28
16
  end
29
17
 
30
18
  def log(message)
31
- puts "[#{Time.now}] #{message}"
32
- end
33
-
34
- def run_ruby_tests(tests)
35
- execute "ruby -r #{tests.join(' -r ')} -e ''" unless tests.empty?
19
+ if Kicker.quiet
20
+ puts message
21
+ else
22
+ now = Time.now
23
+ puts "#{now.strftime('%H:%M:%S')}.#{now.usec.to_s[0,2]} | #{message}"
24
+ end
36
25
  end
37
26
 
38
- private
39
-
40
27
  def last_command_succeeded?
41
28
  $?.success?
42
29
  end
@@ -44,6 +31,29 @@ class Kicker
44
31
  def last_command_status
45
32
  $?.to_i
46
33
  end
34
+
35
+ private
36
+
37
+ def will_execute_command(status)
38
+ message = status.call(:stdout) || "Executing: #{status.command}"
39
+ log(message) unless message.empty?
40
+ Kicker::Growl.change_occured(status) if Kicker::Growl.use? && !Kicker.silent?
41
+ end
42
+
43
+ def did_execute_command(status)
44
+ if message = status.call(:stdout)
45
+ log(message) unless message.empty?
46
+ else
47
+ if status.success? && Kicker.silent?
48
+ log 'Success'
49
+ else
50
+ puts("\n#{status.output.strip}\n\n")
51
+ log(status.success? ? "Success" : "Failed (#{status.exit_code})")
52
+ end
53
+ end
54
+
55
+ Kicker::Growl.result(status) if Kicker::Growl.use?
56
+ end
47
57
  end
48
58
  end
49
59
 
@@ -54,18 +64,12 @@ module Kernel
54
64
  end
55
65
 
56
66
  # Executes the +command+, logs the output, and optionally growls.
57
- def execute(command)
58
- Kicker::Utils.execute(command)
67
+ def execute(command, &block)
68
+ Kicker::Utils.execute(command, &block)
59
69
  end
60
70
 
61
71
  # Returns the last executed command.
62
72
  def last_command
63
73
  Kicker::Utils.last_command
64
74
  end
65
-
66
- # A convenience method that takes an array of Ruby test files and runs them
67
- # collectively.
68
- def run_ruby_tests(tests)
69
- Kicker::Utils.run_ruby_tests(tests)
70
- end
71
75
  end
@@ -12,7 +12,7 @@ describe "Kicker, concerning its callback chains" do
12
12
  end
13
13
 
14
14
  it "should be accessible by an instance" do
15
- kicker = Kicker.new({})
15
+ kicker = Kicker.new
16
16
 
17
17
  @chains.each do |chain|
18
18
  kicker.send(chain).should == Kicker.send(chain)
@@ -2,12 +2,12 @@ require File.expand_path('../test_helper', __FILE__)
2
2
 
3
3
  describe "Array#take_and_map" do
4
4
  before do
5
- @array = %w{ foo bar baz }
5
+ @array = %w{ foo bar baz foo/bar.baz foo/bar/baz }
6
6
  end
7
7
 
8
8
  it "should remove elements from the array for which the block evaluates to true" do
9
9
  @array.take_and_map { |x| x =~ /^ba/ }
10
- @array.should == %w{ foo }
10
+ @array.should == %w{ foo foo/bar.baz foo/bar/baz }
11
11
  end
12
12
 
13
13
  it "should return a new array of the return values of each block call that evaluates to true" do
@@ -21,8 +21,18 @@ describe "Array#take_and_map" do
21
21
  end
22
22
 
23
23
  it "should not flatten and compact the result array if specified" do
24
- @array.take_and_map(false) do |x|
24
+ @array.take_and_map(nil, false) do |x|
25
25
  x =~ /^ba/ ? %w{ f o o } : [nil]
26
- end.should == [[nil], %w{ f o o }, %w{ f o o }]
26
+ end.should == [[nil], %w{ f o o }, %w{ f o o }, [nil], [nil]]
27
27
  end
28
- end
28
+
29
+ it "should take only files matching the pattern" do
30
+ @array.take_and_map('**/*') { |x| x.reverse }.should ==
31
+ %w{ foo/bar.baz foo/bar/baz }.map { |s| s.reverse }
32
+ end
33
+
34
+ it "should not remove files not matching the pattern" do
35
+ @array.take_and_map('**/*') { |x| x }
36
+ @array.should == %w{ foo bar baz }
37
+ end
38
+ end
@@ -6,7 +6,7 @@ describe "Kicker, when a change occurs" do
6
6
 
7
7
  Kicker.any_instance.stubs(:last_command_succeeded?).returns(true)
8
8
  Kicker.any_instance.stubs(:log)
9
- @kicker = Kicker.new({})
9
+ @kicker = Kicker.new
10
10
  end
11
11
 
12
12
  it "should store the current time as when the last change occurred" do
@@ -0,0 +1,85 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ describe "Kicker::Growl" do
4
+ before do
5
+ @growler = Kicker::Growl
6
+ end
7
+
8
+ after do
9
+ Kicker.silent = false
10
+ end
11
+
12
+ it "should growl that an event occurred" do
13
+ status = Kicker::LogStatusHelper.new(nil, 'ls -l')
14
+ @growler.expects(:growl).with(@growler.notifications[:change], 'Kicker: Executing', 'ls -l')
15
+ @growler.change_occured(status)
16
+ end
17
+
18
+ it "should growl that an event occurred with the status callback" do
19
+ status = Kicker::LogStatusHelper.new(proc { |s| 'foo' if s.growl? }, 'ls -l')
20
+ @growler.expects(:growl).with(@growler.notifications[:change], 'Kicker: Executing', 'foo')
21
+ @growler.change_occured(status)
22
+ end
23
+
24
+ it "should use the default click callback if a command succeeded and no user callback is defined" do
25
+ status = Kicker::LogStatusHelper.new(nil, 'ls -l')
26
+ status.result("line 1\nline 2", true, 0)
27
+
28
+ OSX::NSWorkspace.sharedWorkspace.expects(:launchApplication).with('Terminal')
29
+ @growler.expects(:growl).with(
30
+ @growler.notifications[:succeeded],
31
+ 'Kicker: Success',
32
+ "line 1\nline 2"
33
+ ).yields
34
+
35
+ @growler.result(status)
36
+ end
37
+
38
+ it "should use the default click callback if a command failed and no user callback is defined" do
39
+ status = Kicker::LogStatusHelper.new(nil, 'ls -l')
40
+ status.result("line 1\nline 2", false, 123)
41
+
42
+ OSX::NSWorkspace.sharedWorkspace.expects(:launchApplication).with('Terminal')
43
+ @growler.expects(:growl).with(
44
+ @growler.notifications[:failed],
45
+ 'Kicker: Failed (123)',
46
+ "line 1\nline 2"
47
+ ).yields
48
+
49
+ @growler.failed(status)
50
+ end
51
+
52
+ it "should only growl that the command succeeded in silent mode" do
53
+ Kicker.silent = true
54
+ status = Kicker::LogStatusHelper.new(nil, 'ls -l')
55
+ status.result("line 1\nline 2", true, 0)
56
+
57
+ @growler.expects(:growl).with(@growler.notifications[:succeeded], 'Kicker: Success', '')
58
+ @growler.result(status)
59
+ end
60
+
61
+ it "should only growl that the command failed in silent mode" do
62
+ Kicker.silent = true
63
+ status = Kicker::LogStatusHelper.new(nil, 'ls -l')
64
+ status.result("line 1\nline 2", false, 123)
65
+
66
+ @growler.expects(:growl).with(@growler.notifications[:failed], 'Kicker: Failed (123)', '')
67
+ @growler.failed(status)
68
+ end
69
+
70
+ it "should growl that the command succeeded with the status callback" do
71
+ status = Kicker::LogStatusHelper.new(proc { |s| 'foo' if s.growl? }, 'ls -l')
72
+ status.result("line 1\nline 2", true, 0)
73
+
74
+ @growler.expects(:growl).with(@growler.notifications[:succeeded], 'Kicker: Success', 'foo')
75
+ @growler.succeeded(status)
76
+ end
77
+
78
+ it "should growl that the command failed with the status callback" do
79
+ status = Kicker::LogStatusHelper.new(proc { |s| 'foo' if s.growl? }, 'ls -l')
80
+ status.result("line 1\nline 2", false, 123)
81
+
82
+ @growler.expects(:growl).with(@growler.notifications[:failed], 'Kicker: Failed (123)', 'foo')
83
+ @growler.failed(status)
84
+ end
85
+ end
@@ -7,87 +7,55 @@ describe "Kicker" do
7
7
  Kicker.any_instance.stubs(:start)
8
8
  end
9
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
10
  it "should return the default paths to watch" do
23
11
  Kicker.paths.should == %w{ . }
24
12
  end
25
13
 
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
14
+ it "should default the FSEvents latency to 1" do
15
+ Kicker.latency.should == 1
45
16
  end
46
17
  end
47
18
 
48
19
  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 })
20
+ after do
21
+ Kicker.paths = %w{ . }
54
22
  end
55
23
 
56
24
  it "should return the extended paths to watch" do
57
- @kicker.paths.should == ['/some/dir', File.expand_path('a/relative/path')]
25
+ Kicker.paths = %w{ /some/dir a/relative/path }
26
+ Kicker.new.paths.should == ['/some/dir', File.expand_path('a/relative/path')]
58
27
  end
59
28
 
60
29
  it "should have assigned the current time to last_event_processed_at" do
61
- @kicker.last_event_processed_at.should == @now
30
+ now = Time.now; Time.stubs(:now).returns(now)
31
+ Kicker.new.last_event_processed_at.should == now
62
32
  end
63
33
 
64
34
  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
35
+ Kicker.new.paths.should == [File.expand_path('.')]
74
36
  end
75
37
  end
76
38
 
77
39
  describe "Kicker, when starting" do
78
40
  before do
79
- @kicker = Kicker.new(:paths => %w{ /some/file.rb })
41
+ Kicker.paths = %w{ /some/file.rb }
42
+ @kicker = Kicker.new
80
43
  @kicker.stubs(:log)
44
+ @kicker.startup_chain.stubs(:call)
81
45
  Rucola::FSEvents.stubs(:start_watching)
82
46
  OSX.stubs(:CFRunLoopRun)
83
47
  end
84
48
 
49
+ after do
50
+ Kicker.latency = 1
51
+ Kicker.paths = %w{ . }
52
+ end
53
+
85
54
  it "should show the usage banner and exit when there are no callbacks defined at all" do
86
55
  @kicker.stubs(:validate_paths_exist!)
87
- Kicker.stubs(:process_chain).returns([])
88
- Kicker.stubs(:pre_process_chain).returns([])
56
+ Kicker.stubs(:startup_chain).returns(Kicker::CallbackChain.new)
89
57
 
90
- Kicker::OPTION_PARSER_CALLBACK.stubs(:call).returns(mock('OptionParser', :help => 'help'))
58
+ Kicker::Options.stubs(:parser).returns(mock('OptionParser', :help => 'help'))
91
59
  @kicker.expects(:puts).with("help")
92
60
  @kicker.expects(:exit)
93
61
 
@@ -106,7 +74,8 @@ describe "Kicker, when starting" do
106
74
  it "should start a FSEvents stream with the assigned latency" do
107
75
  @kicker.stubs(:validate_options!)
108
76
 
109
- Rucola::FSEvents.expects(:start_watching).with(['/some'], :latency => @kicker.latency)
77
+ Kicker.latency = 2.34
78
+ Rucola::FSEvents.expects(:start_watching).with(['/some'], :latency => 2.34)
110
79
  @kicker.start
111
80
  end
112
81
 
@@ -114,7 +83,7 @@ describe "Kicker, when starting" do
114
83
  @kicker.stubs(:validate_options!)
115
84
  File.stubs(:directory?).with('/some/file.rb').returns(false)
116
85
 
117
- Rucola::FSEvents.expects(:start_watching).with(['/some'], :latency => @kicker.latency)
86
+ Rucola::FSEvents.expects(:start_watching).with(['/some'], :latency => Kicker.latency)
118
87
  @kicker.start
119
88
  end
120
89
 
@@ -142,15 +111,15 @@ describe "Kicker, when starting" do
142
111
 
143
112
  it "should register with growl if growl should be used" do
144
113
  @kicker.stubs(:validate_options!)
145
- Kicker.use_growl = true
114
+ Kicker::Growl.use = true
146
115
 
147
- Growl::Notifier.sharedInstance.expects(:register).with('Kicker', Kicker::GROWL_NOTIFICATIONS.values)
116
+ Growl::Notifier.sharedInstance.expects(:register).with('Kicker', Kicker::Growl::NOTIFICATIONS.values)
148
117
  @kicker.start
149
118
  end
150
119
 
151
120
  it "should _not_ register with growl if growl should not be used" do
152
121
  @kicker.stubs(:validate_options!)
153
- Kicker.use_growl = false
122
+ Kicker::Growl.use = false
154
123
 
155
124
  Growl::Notifier.sharedInstance.expects(:register).never
156
125
  @kicker.start