kicker 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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