marcinbunsch-bolt 0.1.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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Marcin Bunsch
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,24 @@
1
+ h1. Bolt - fast autotest alternative
2
+
3
+ Welcome to Bolt!
4
+
5
+ Bolt is a merge of several gems aimed at producing a fast, stable and easy to use version autotest.
6
+
7
+ What's the main difference? First of all, it does not use rakes for running test. Instead, it loads the environment and runs the tests within the main process instantly, reloading the changed files only.
8
+
9
+ When working with autotest, I was frustrated by the delay of the environment being loaded with every test run. And when trying to use mislav-rspactor it turned out it does not work with Mac OS X 10.4 (Tiger). So I decided to go ahead and make my own gem which would take the best elements of all these gems and work on my machine.
10
+
11
+ This is still in experimental phase, so all feedback is appreciated.
12
+
13
+ It takes solutions from several gems:
14
+
15
+ * *mislav-rspactor* - inspiration, structure and growl notifier
16
+ * *autotest* - obviously. Whole concept, parts of generic listener
17
+ * *Roman2K-rails_test_serving* - concept of running tests with preloaded environment, elements of test::unit runner
18
+
19
+ h1. Copyright
20
+
21
+ * *autotest* - ZenTest
22
+ * *rails-test-serving* - Roman2K
23
+ * *rspactor* - Mislav Marohnić, Andreas Wolff, Pelle Braendgaard
24
+ * Icons by DryIcons http://dryicons.com
data/Rakefile ADDED
@@ -0,0 +1,74 @@
1
+ # This Rakefile has been copied from mislav/rspactor
2
+ # Mislav, you rock, did I tell you this already? :)
3
+
4
+ task :default => :spec
5
+
6
+ desc "starts Bolt"
7
+ task :spec do
8
+ system "ruby -Ilib bin/bolt"
9
+ end
10
+
11
+ desc "generates .gemspec file"
12
+ task :gemspec => "version:read" do
13
+ spec = Gem::Specification.new do |gem|
14
+ gem.name = "bolt"
15
+ gem.summary = "Bolt is a merge of autotest and mislav/rspactor to produce a lightning fast, configurable and simple to set up autotest clone"
16
+ gem.email = "marcin@applicake.com"
17
+ gem.homepage = "http://github.com/marcinbunsch/bolt"
18
+ gem.authors = ["Marcin Bunsch", "Mislav Marohnić"]
19
+ gem.has_rdoc = false
20
+
21
+ gem.version = GEM_VERSION
22
+ gem.files = FileList['Rakefile', '{bin,lib,images,spec}/**/*', 'README*', 'LICENSE*']
23
+ gem.executables = Dir['bin/*'].map { |f| File.basename(f) }
24
+ end
25
+
26
+ spec_string = spec.to_ruby
27
+
28
+ begin
29
+ Thread.new { eval("$SAFE = 3\n#{spec_string}", binding) }.join
30
+ rescue
31
+ abort "unsafe gemspec: #{$!}"
32
+ else
33
+ File.open("#{spec.name}.gemspec", 'w') { |file| file.write spec_string }
34
+ end
35
+ end
36
+
37
+ desc "bump the version up"
38
+ task :bump => ["version:bump", :gemspec]
39
+
40
+ desc "reinstall the gem locally"
41
+ task :reinstall do
42
+ GEM_VERSION = File.read("VERSION")
43
+ system('sudo gem uninstall bolt')
44
+ system("gem build bolt.gemspec")
45
+ system("sudo gem install bolt-#{GEM_VERSION}.gem")
46
+ end
47
+
48
+ namespace :version do
49
+ task :read do
50
+ unless defined? GEM_VERSION
51
+ if File.exists?('VERSION')
52
+ GEM_VERSION = File.read("VERSION")
53
+ else
54
+ GEM_VERSION = '0.0.1'
55
+ end
56
+ end
57
+ end
58
+
59
+ desc "bump the version up"
60
+ task :bump => :read do
61
+ if ENV['VERSION']
62
+ GEM_VERSION.replace ENV['VERSION']
63
+ else
64
+ GEM_VERSION.sub!(/\d+$/) { |num| num.to_i + 1 }
65
+ end
66
+
67
+ File.open("VERSION", 'w') { |v| v.write GEM_VERSION }
68
+ end
69
+ end
70
+
71
+ task :release => :bump do
72
+ system %(git commit VERSION *.gemspec -m "release v#{GEM_VERSION}")
73
+ system %(git tag -am "release v#{GEM_VERSION}" v#{GEM_VERSION})
74
+ end
data/bin/bolt ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'bolt'
4
+
5
+ # Rails support
6
+ if File.exists?('test/test_helper.rb')
7
+ puts '** Rails found, loading environment'
8
+ ENV['RAILS_ENV'] = 'test'
9
+ require 'test/test_helper.rb'
10
+ end
11
+
12
+ # This is a hack for Rails Test::Unit to prevent raising errors when a test file is loaded again
13
+ module ActiveSupport
14
+ module Testing
15
+ module Declarative
16
+ # test "verify something" do
17
+ # ...
18
+ # end
19
+ def test(name, &block)
20
+ test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
21
+ defined = instance_method(test_name) rescue false
22
+ # raise "#{test_name} is already defined in #{self}" if defined # do not raise this error
23
+ if block_given?
24
+ define_method(test_name, &block)
25
+ else
26
+ define_method(test_name) do
27
+ flunk "No implementation provided for #{name}"
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ # start a listener
36
+ Bolt::Listener.new
data/images/LICENSE ADDED
@@ -0,0 +1,34 @@
1
+ DryIcons Free License Agreement
2
+
3
+ Read Full Legal Code
4
+
5
+ DryIcons is a service provided by our team of enthusiastic graphic and web designers and programmers. The purpose of this service is to provide only high-quality, free icons and free icon sets, as well as free vector graphics to the general public, with a specific target to designers, software and web developers.
6
+ All DryIcons' Works (meaning "icons, icon sets and graphics") are free of charge, but please read further under what Terms and Conditions.
7
+ All DryIcons Works are licensed under a DryIcons Free License. This means that you can use our icons, icon sets and graphics in any publicly accessible web site, web application or any form of presentation publicly accessible through the World Wide Web only according to the DryIcons Free License Terms and Conditions:
8
+
9
+ * You must put a back link with credits to http://dryicons.com on every page where DryIcons' Works are used (example: Icons by DryIcons);
10
+ * You must include the correct back link to DryIcons website, which is: http://dryicons.com;
11
+ * You must place the link on an easy-to-see, recognizable place, so there is no confusion about the Original Author of the Works (DryIcons);
12
+ * When copying, or paraphrasing description text (or title) on one of the Works, you must make sure there are no spelling mistakes;
13
+ * Do not try to take credit or imply in any way that you and not DryIcons is the Original Author of the Licensed Material (icons, icon sets and graphics).
14
+
15
+ What you CAN DO:
16
+
17
+ 1. All DryIcons' Works are being provided to You under the Terms of this agreement, which allows for use of our Works but does not transfer ownership. All DryIcons' Works remain property of DryIcons;
18
+ 2. You may use DryIcons' Works in any personal or commercial project unlimited number of times according to the DryIcons Free License Terms and Conditions;
19
+ 3. You may use DryIcons' Works in any Open Source project and application according to the DryIcons Free License Terms and Conditions;
20
+ 4. Your rights to DryIcons' Works are worldwide and for the duration of DryIcons' rights in the Works;
21
+ 5. Any uses other than the ones mentioned above must be approved by DryIcons in writing;
22
+ 6. Unauthorized use will result in immediate termination of this License, and with it, your rights to use DryIcons' Works.
23
+
24
+ What you CAN NOT DO:
25
+
26
+ 1. You may not alter, crop, modify, manipulate and create derivative works of DryIcons' Works. All Works must be used "AS IS";
27
+ 2. You may not redistribute, license, sell, lease, assign, convey or transfer DryIcons' Works, or offer free downloads in their present form or in a modified form to any third party;
28
+ 3. You may not distribute the DryIcons' Works (icons, icon sets and graphics) online in a downloadable format or enable them to be distributed via mobile devices. You may link to http://dryicons.com instead;
29
+ 4. You may not incorporate DryIcons' Works into a logo, trademark or service mark;
30
+ 5. You may not use DryIcons' Works directly from dryicons.com or any other location hosted on the dryicons.com domain or any other domain owned by DryIcons.
31
+
32
+ Copyright
33
+
34
+ 1. DryIcons.com reserves the copyrights and ownership rights of all DryIcons' Works downloaded from this website. We reserve the right to change parts of this License without notice and at our sole discretion.
data/images/failed.png ADDED
Binary file
Binary file
Binary file
@@ -0,0 +1,77 @@
1
+ require 'bolt/notifier'
2
+ require 'bolt/runner'
3
+ #
4
+ # Bolt::Listener
5
+ #
6
+ # The Listener waits for files to be saved and when one is found, it launches the finder to match the file with a test
7
+ #
8
+ module Bolt
9
+ class Listener
10
+ attr_accessor :selected, :notifier, :runner
11
+
12
+ # Constructor
13
+ def initialize
14
+ $stdout.puts "** Starting Bolt..."
15
+ # find appropriate listener
16
+ $stdout.puts "** Using #{listener.class}... "
17
+
18
+ # trap the INT signal
19
+ add_sigint_handler
20
+
21
+ # attach a notifier
22
+ self.notifier = Bolt::Notifier.new.selected
23
+
24
+ # attach a mapper
25
+ self.runner = Bolt::Runner.new.selected
26
+ self.runner.notifier = self.notifier
27
+
28
+ # attach the notifier to listener
29
+ listener.notifier = self.notifier
30
+
31
+ # attach runner mappings to listener to avoid searching in all files
32
+ listener.mappings = self.runner.class::MAPPINGS
33
+
34
+ # attach listener wrapper
35
+ listener.parent = self
36
+
37
+ # display info to user
38
+ notifier.info 'Bolt running', "Bolt is enabled and running in #{Dir.pwd}"
39
+
40
+ # if in Rails, start environment
41
+ listener.start
42
+
43
+ end
44
+
45
+ # handle updated files found by specific listener
46
+ def handle(updated_files)
47
+ # notifier.spotted(updated_files.first)
48
+ # send them to mapper
49
+
50
+ runner.handle(updated_files.first)
51
+ # run appropriate tests in runner
52
+ end
53
+
54
+ # Pick a listener to launch
55
+ def listener
56
+ return selected if selected
57
+ # TODO: os identification via RUBY_PLATFORM is flawed as it will return 'java' in jruby. Look for a different solution
58
+ os_string = RUBY_PLATFORM.downcase
59
+ self.selected= Bolt::Listeners::Generic.new
60
+ self.selected= Bolt::Listeners::Osx.start if os_string.include?("darwin")
61
+ # TODO:
62
+ # self.selected= Bolt::Listeners::Windows.new if os_string.include?("mswin")
63
+ # self.selected= Bolt::Listeners::Linux.new if os_string.include?("linux")
64
+ selected
65
+ end
66
+
67
+ # capture the INT signal
68
+ def add_sigint_handler
69
+ trap 'INT' do
70
+ $stdout.puts "\n** Exiting Bolt..."
71
+ notifier.info 'Bolt terminated', "Bolt has been terminated"
72
+ exit(0)
73
+ end
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,76 @@
1
+ require 'find'
2
+ #
3
+ # Bolt::Listeners::Generic
4
+ #
5
+ # The generic Listener, which polls the files after a specific interval
6
+ #
7
+ module Bolt
8
+ module Listeners
9
+ class Generic
10
+ attr_accessor :files, :interval, :busy, :notifier, :parent, :mappings
11
+
12
+ def initialize
13
+ self.interval = 2 # decrease the CPU load by increasing the interval
14
+ self.busy = false
15
+ end
16
+
17
+ def start
18
+ puts "** #{self.class} is scanning for files... "
19
+ # build a file collection
20
+ find_files
21
+ puts "** #{self.class} watching #{files.size} files... "
22
+ wait
23
+ end
24
+
25
+ # source: ZenTest/autotest.rb
26
+ def wait
27
+ Kernel.sleep self.interval until check_files
28
+ end
29
+
30
+ # check files to find these that have changed
31
+ def check_files
32
+ return if busy # if working on something already, skip the iteration
33
+ updated = []
34
+ files.each do |filename, mtime|
35
+ current_mtime = File.stat(filename).mtime
36
+ if current_mtime != mtime
37
+ updated << filename
38
+ # update the mtime in file registry so we it's only send once
39
+ files[filename] = current_mtime
40
+ $stdout.puts ">> Spotted change in #{filename}"
41
+ end
42
+ end
43
+ parent.handle(updated) if updated != []
44
+ false
45
+ end
46
+
47
+ ##
48
+ # Find the files to process, ignoring temporary files, source
49
+ # configuration management files, etc., and return a Hash mapping
50
+ # filename to modification time.
51
+ # source: ZenTest/autotest.rb
52
+ def find_files
53
+ result = {}
54
+ targets = ['.'] # start simple
55
+ targets.each do |target|
56
+ order = []
57
+ Find.find(target) do |f|
58
+
59
+ in_mappings = f =~ self.mappings
60
+ next if in_mappings.nil?
61
+ next if test ?d, f
62
+ next if f =~ /(swp|~|rej|orig)$/ # temporary/patch files
63
+ next if f =~ /\/\.?#/ # Emacs autosave/cvs merge files
64
+
65
+ filename = f.sub(/^\.\//, '')
66
+
67
+ result[filename] = File.stat(filename).mtime rescue next
68
+ end
69
+ end
70
+
71
+ self.files = result
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,114 @@
1
+ #
2
+ # Bolt::Listeners::OSX
3
+ #
4
+ # OSX-specific Listener, using solution taken from mislav/rspactor listener.rb
5
+ #
6
+ # TODO: I have Mac OS X 10.4 and this works only in OS X 10.5. Which means I haven't been able to test it and adapt it.
7
+ #
8
+ module Bolt
9
+ module Listeners
10
+ class Osx
11
+
12
+ attr_reader :last_check, :callback, :valid_extensions
13
+
14
+ def initialize(valid_extensions = nil)
15
+ @valid_extensions = %w(rb erb builder haml rhtml rxml yml conf opts)
16
+ timestamp_checked
17
+
18
+ @callback = lambda do |stream, ctx, num_events, paths, marks, event_ids|
19
+ changed_files = extract_changed_files_from_paths(split_paths(paths, num_events))
20
+ timestamp_checked
21
+ yield changed_files unless changed_files.empty?
22
+ end
23
+ run(Dir.pwd)
24
+ end
25
+
26
+ def self.start
27
+ begin
28
+ require 'osx/foundation'
29
+ rescue LoadError
30
+ puts "** Could not load osx/foundation. RubyCocoa not installed? Falling back to Bolt::Listeners::Generic"
31
+ return Bolt::Listeners::Generic.new
32
+ end
33
+
34
+ begin
35
+ OSX.require_framework '/System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework'
36
+ return self.new
37
+ rescue NameError
38
+ puts "** There was an error loading Bolt::Listeners::Osx. Falling back to Bolt::Listeners::Generic"
39
+ return Bolt::Listeners::Generic.new
40
+ end
41
+ end
42
+
43
+ def run(directories)
44
+ dirs = Array(directories)
45
+ stream = OSX::FSEventStreamCreate(OSX::KCFAllocatorDefault, callback, nil, dirs, OSX::KFSEventStreamEventIdSinceNow, 0.5, 0)
46
+ unless stream
47
+ $stderr.puts "Failed to create stream"
48
+ exit(1)
49
+ end
50
+
51
+ OSX::FSEventStreamScheduleWithRunLoop(stream, OSX::CFRunLoopGetCurrent(), OSX::KCFRunLoopDefaultMode)
52
+ unless OSX::FSEventStreamStart(stream)
53
+ $stderr.puts "Failed to start stream"
54
+ exit(1)
55
+ end
56
+
57
+ begin
58
+ OSX::CFRunLoopRun()
59
+ rescue Interrupt
60
+ OSX::FSEventStreamStop(stream)
61
+ OSX::FSEventStreamInvalidate(stream)
62
+ OSX::FSEventStreamRelease(stream)
63
+ end
64
+ end
65
+
66
+ def timestamp_checked
67
+ @last_check = Time.now
68
+ end
69
+
70
+ def split_paths(paths, num_events)
71
+ paths.regard_as('*')
72
+ rpaths = []
73
+ num_events.times { |i| rpaths << paths[i] }
74
+ rpaths
75
+ end
76
+
77
+ def extract_changed_files_from_paths(paths)
78
+ changed_files = []
79
+ paths.each do |path|
80
+ next if ignore_path?(path)
81
+ Dir.glob(path + "*").each do |file|
82
+ next if ignore_file?(file)
83
+ changed_files << file if file_changed?(file)
84
+ end
85
+ end
86
+ changed_files
87
+ end
88
+
89
+ def file_changed?(file)
90
+ File.stat(file).mtime > last_check
91
+ rescue Errno::ENOENT
92
+ false
93
+ end
94
+
95
+ def ignore_path?(path)
96
+ path =~ /(?:^|\/)\.(git|svn)/
97
+ end
98
+
99
+ def ignore_file?(file)
100
+ File.basename(file).index('.') == 0 or not valid_extension?(file)
101
+ end
102
+
103
+ def file_extension(file)
104
+ file =~ /\.(\w+)$/ and $1
105
+ end
106
+
107
+ def valid_extension?(file)
108
+ valid_extensions.nil? or valid_extensions.include?(file_extension(file))
109
+ end
110
+
111
+
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,37 @@
1
+ #
2
+ # Bolt::Notifier
3
+ #
4
+ # The Notifier sends notification of the test results to the user
5
+ #
6
+ module Bolt
7
+ class Notifier
8
+ attr_accessor :selected
9
+
10
+ # Constructor
11
+ def initialize
12
+ # find appropriate listener
13
+ $stdout.puts "** Using #{notifier.class}... \n"
14
+
15
+ # launch appropriate listener
16
+ # notifier.new
17
+
18
+ end
19
+
20
+ # Pick a listener to launch
21
+ def notifier
22
+ return selected if selected
23
+ self.selected= Bolt::Notifiers::Generic.new
24
+ # growl
25
+ output = %x[which growlnotify]
26
+ if output.to_s.include?('/growlnotify')
27
+ self.selected= Bolt::Notifiers::Growl.new
28
+ end
29
+ #self.selected= Bolt::Listeners::Generic
30
+ # self.selected= Bolt::Listeners::OSX if os_string.include?("darwin")
31
+ #self.selected= Bolt::Listeners::Windows if os_string.include?("mswin")
32
+ #self.selected= Bolt::Listeners::Linux if os_string.include?("linux")
33
+ selected
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,26 @@
1
+ #
2
+ # Bolt::Notifiers::Generic
3
+ #
4
+ # The Generic Notifier does not do anything, it's for stability
5
+ #
6
+ module Bolt
7
+ module Notifiers
8
+ class Generic
9
+
10
+ # info message
11
+ def info(name, description)
12
+ end
13
+
14
+ # message to be displayed when test file is missing
15
+ def test_file_missing(filename)
16
+ end
17
+
18
+ def result(filename, results)
19
+ end
20
+
21
+ def error(name, description)
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,55 @@
1
+ #
2
+ # Bolt::Notifiers::Generic
3
+ #
4
+ # The Generic Notifier does not do anything, it's for stability
5
+ # The Growl Notifer is copied from mislav/rspactor growl module
6
+ #
7
+ module Bolt
8
+ module Notifiers
9
+ class Growl
10
+
11
+ # generic notify method
12
+ def notify(title, msg, img, pri = 0)
13
+ system("growlnotify -w -n rspactor --image #{img} -p #{pri} -m #{msg.inspect} #{title} &")
14
+ end
15
+
16
+ # info message
17
+ def info(name, description)
18
+ image_path = File.dirname(__FILE__) + "/../../../images/pending.png"
19
+ notify name, description.to_s, image_path
20
+ end
21
+
22
+ # message to be displayed when test file is missing
23
+ def test_file_missing(filename)
24
+ image_path = File.dirname(__FILE__) + "/../../../images/failed.png"
25
+ message = "The following test file could not be found: #{filename}"
26
+ notify "Could not find test file", message, image_path
27
+ end
28
+
29
+ def result(filename, results)
30
+ message = results
31
+ if results.match('example') #rspec
32
+ if results.match('pending')
33
+ icon = 'pending'
34
+ elsif results.match('0 failures')
35
+ icon = 'success'
36
+ else
37
+ icon = 'failed'
38
+ end
39
+ elsif (results.match('0 failures, 0 errors')) # test::unit
40
+ icon = 'success'
41
+ else
42
+ icon = 'failed'
43
+ end
44
+ image_path = File.dirname(__FILE__) + "/../../../images/#{icon}.png"
45
+ notify "Test results for: #{filename}", message, image_path
46
+ end
47
+
48
+ def error(name, description)
49
+ image_path = File.dirname(__FILE__) + "/../../../images/failed.png"
50
+ notify name, description.to_s, image_path
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,27 @@
1
+ #
2
+ # Bolt::Runner
3
+ #
4
+ # The Runner maps the changed file to the appropriate test file and runs it
5
+ #
6
+ module Bolt
7
+ class Runner
8
+ attr_accessor :selected, :notifier
9
+
10
+ # Constructor
11
+ def initialize
12
+ # find appropriate listener
13
+ $stdout.puts "** Using #{runner.class}... \n"
14
+ end
15
+
16
+ # Pick a listener to launch
17
+ def runner
18
+ return selected if selected
19
+ # TODO: os identification via RUBY_PLATFORM is flawed as it will return 'java' in jruby. Look for a different solution
20
+
21
+ self.selected= Bolt::Runners::TestUnit.new
22
+ self.selected= Bolt::Runners::RSpec.new if File.directory?('spec')
23
+ selected
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,141 @@
1
+ require 'spec'
2
+ require 'stringio'
3
+ #
4
+ # Bolt::Runners::Rspec
5
+ #
6
+ # The Rspec Runner maps the filename to the appropriate spec
7
+ #
8
+ module Bolt
9
+ module Runners
10
+ class RSpec
11
+
12
+ MAPPINGS = /(\.\/app\/|\.\/lib\/|\.\/spec\/controllers|\.\/spec\/models|\.\/spec)/
13
+
14
+ attr_accessor :notifier, :test_io
15
+
16
+ def initialize
17
+ end
18
+
19
+ # handle specified file
20
+ def handle(filename)
21
+ puts '=> Rspec running test for ' + filename
22
+
23
+ # force reload of file
24
+ $".delete(filename)
25
+ $".delete(File.join(Dir.pwd, filename))
26
+
27
+ klassname = filename.sub('app/controllers', '').sub('app/models', '').sub('lib/', '')
28
+ test_class = klassname.sub('.rb', '').gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
29
+ root_klass = test_class.split('::').first
30
+
31
+ # remove the top constant/class from memory
32
+ # this is required to rebuild classes before test run
33
+ # one limitation - Spec/Test cannot be reloaded or it will crash
34
+ Object.send(:remove_const, root_klass) unless root_klass == 'Spec' or root_klass == 'Test'
35
+
36
+ if filename.include?('app/controllers') or filename.include?('app/models') or filename.include?('lib/')
37
+ begin
38
+ require File.join(Dir.pwd, filename)
39
+ rescue LoadError
40
+ notifier.error("Error in #{filename}", $!)
41
+ return false
42
+ rescue ArgumentError
43
+ notifier.error("Error in #{filename}", $!)
44
+ return false
45
+ end
46
+ end
47
+
48
+ test_files = translate(filename)
49
+
50
+ return if test_files == []
51
+
52
+ puts '==== Rspec running: ' + test_files.join(', ') + ' ===='
53
+
54
+ run(test_files)
55
+
56
+ puts '==== Rspec completed run ===='
57
+ end
58
+
59
+ # check whether file exists
60
+ def file_verified?(filename)
61
+ if !File.exists?(filename)
62
+ notifier.test_file_missing(filename)
63
+ puts "=> ERROR: could not find spec file: #{filename}"
64
+ return false
65
+ end
66
+ return true
67
+ end
68
+
69
+ # mapping is a copied and modified version of mislav/rspactor Inspector#translate
70
+ def translate(file)
71
+
72
+ basename = File.basename(file)
73
+ candidates = []
74
+ test_filename = nil
75
+ case file
76
+ when %r:^app/controllers/:
77
+ test_filename = file.sub('.rb', '_spec.rb').sub('app/controllers', 'spec/controllers')
78
+ when %r:^app/models/:
79
+ test_filename = "spec/models/#{basename.sub('.rb', '_spec.rb')}"
80
+ when %r:^app/views/:
81
+ file = file.sub('app/views/', '')
82
+ directory = file.split('/')[0..-2].compact.join('/')
83
+ test_filename = "spec/controllers/#{directory}_controller_spec.rb"
84
+ when %r:^spec/:
85
+ test_filename = file
86
+ when %r:^lib/:
87
+ # map libs to straight specs
88
+ test_filename = "spec/#{file.sub('lib/', '').sub('.rb', '_spec.rb')}"
89
+ when 'config/routes.rb'
90
+ test_filename = "spec/controllers/#{basename.sub('.rb', '_spec.rb')}"
91
+ when 'config/database.yml', 'db/schema.rb'
92
+ #candidates << 'models'
93
+ else
94
+ #
95
+ end
96
+ if test_filename and file_verified?(test_filename)
97
+ candidates << test_filename
98
+ end
99
+ if candidates == []
100
+ puts "=> NOTICE: could not find spec file for: #{file}"
101
+ end
102
+
103
+ candidates
104
+ end
105
+
106
+ def run(files)
107
+ file = files.first
108
+
109
+ require 'spec'
110
+
111
+ # redirect spec output to StringIO
112
+ io = StringIO.new
113
+ ::Spec::Runner.use(::Spec::Runner::OptionParser.new($stderr, io).options)
114
+
115
+ # refresh the loaded test file
116
+ $".delete(file)
117
+ require file
118
+
119
+ # run the tests in the Spec::Runner
120
+ ::Spec::Runner::CommandLine.run
121
+
122
+ # recreate the reporter to refresh the example count
123
+ ::Spec::Runner::Reporter.new(::Spec::Runner.options)
124
+
125
+ # remove all examples up to date
126
+ ::Spec::Runner.options.example_groups.each { |g| ::Spec::Runner.options.remove_example_group(g) }
127
+
128
+ # read the buffer
129
+ result = io.string.to_s.dup
130
+
131
+ # send buffer to stdout
132
+ puts result
133
+
134
+ # sent result to notifier
135
+ notifier.result(file, result.split("\n").compact.last)
136
+
137
+ end
138
+
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,166 @@
1
+ require 'test/unit'
2
+ require 'test/unit/ui/console/testrunner'
3
+ #
4
+ # Bolt::Runners::TestUnit
5
+ #
6
+ # The TestUnit Runners maps the filename to the appropriate test
7
+ #
8
+ module Bolt
9
+ module Runners
10
+ class TestUnit
11
+
12
+ MAPPINGS = /(\.\/app\/|\.\/lib\/|\.\/test\/functional|\.\/test\/unit)/
13
+
14
+ attr_accessor :notifier, :test_io
15
+
16
+ def initialize
17
+ fix_test_unit_io
18
+ end
19
+
20
+ def fix_test_unit_io
21
+ # Test::Unit stdio capture workaround, taken from Roman2K-rails-test-serving
22
+ self.test_io = StringIO.new
23
+ io = test_io
24
+ Test::Unit::UI::Console::TestRunner.class_eval do
25
+ alias_method :old_initialize, :initialize
26
+ def initialize(suite, output_level, io=Thread.current["test_runner_io"])
27
+ old_initialize(suite, output_level, io)
28
+ end
29
+ end
30
+ Thread.current["test_runner_io"] = io
31
+ end
32
+
33
+ # handle specified file
34
+ def handle(file)
35
+
36
+ # force reload of file
37
+ $".delete(file)
38
+ $".delete(File.join(Dir.pwd, file))
39
+
40
+ if file.include?('app/controllers') or file.include?('app/models') or file.include?('lib/')
41
+ begin
42
+ require File.join(Dir.pwd, file)
43
+ rescue LoadError
44
+ notifier.error("Error in #{file}", $!)
45
+ return []
46
+ rescue ArgumentError
47
+ notifier.error("Error in #{file}", $!)
48
+ return []
49
+ end
50
+ end
51
+
52
+ puts '=> Test::Unit running test for ' + file
53
+ test_files = translate(file)
54
+
55
+ puts '==== Test::Unit running: ' + test_files.join(', ') + ' ===='
56
+
57
+ run(test_files) if test_files != []
58
+
59
+ puts '==== Test::Unit completed run ===='
60
+
61
+ end
62
+
63
+ # check whether file exists
64
+ def file_verified?(filename)
65
+ if !File.exists?(filename)
66
+ notifier.test_file_missing(filename)
67
+ puts "=> ERROR: could not find test file: #{filename}"
68
+ return false
69
+ end
70
+ return true
71
+ end
72
+
73
+ # mapping is a copied and modified version of mislav/rspactor Inspector#translate
74
+ def translate(file)
75
+
76
+ basename = File.basename(file)
77
+ candidates = []
78
+ test_filename = nil
79
+ case file
80
+ when %r:^app/controllers/:
81
+ test_filename = file.sub('.rb', '_test.rb').sub('app/controllers', 'test/functional')
82
+ when %r:^app/models/:
83
+ test_filename = "test/unit/#{basename.sub('.rb', '_test.rb')}"
84
+ when %r:^app/views/:
85
+ file = file.sub('app/views/', '')
86
+ directory = file.split('/')[0..-2].compact.join('/')
87
+ test_filename = "test/functional/#{directory}_controller_test.rb"
88
+ when %r:^test/:
89
+ test_filename = file
90
+ when %r:^lib/:
91
+ # map libs to units
92
+ test_filename = "test/unit/#{file.sub('lib/', '').sub('.rb', '_test.rb')}"
93
+ when 'config/routes.rb'
94
+ test_filename = "test/functional/#{basename.sub('.rb', '_test.rb')}"
95
+ #candidates << 'controllers' << 'helpers' << 'views'
96
+ when 'config/database.yml', 'db/schema.rb'
97
+ #candidates << 'models'
98
+ else
99
+ #
100
+ end
101
+ if test_filename and file_verified?(test_filename)
102
+ candidates << test_filename
103
+ end
104
+ if candidates == []
105
+ puts "=> NOTICE: could not find test file for: #{file}"
106
+ end
107
+ # puts candidates.inspect
108
+ candidates
109
+ end
110
+
111
+ def run(files)
112
+ file = files.first
113
+ puts "** Running #{file}"
114
+ # make sure that you reload the test file
115
+ #load file
116
+ #contents = File.open(file).read
117
+ # puts contents
118
+ #eval contents
119
+
120
+ # This is Rails' String#camelcase
121
+ klassname = file.sub('test/functional/', '').sub('test/unit/', '')
122
+ test_class = klassname.sub('.rb', '').gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
123
+
124
+ # create dummy wrapper modules if test is in subfolder
125
+ test_class.split('::').each do |part|
126
+ eval "module ::#{part}; end" if !part.match('Test')
127
+ end
128
+
129
+ $".delete(file)
130
+
131
+ #(defined?(ActiveRecord::Base) ? ActiveRecord::Base.instance_eval { subclasses }.each { |c| c.reset_column_information } : nil)
132
+ #(defined?(ActiveSupport::Dependencies) ? ActiveSupport::Dependencies.clear : nil)
133
+
134
+ begin
135
+ require file
136
+ rescue LoadError
137
+ notifier.error("Error in #{file}", $!)
138
+ return
139
+ rescue ArgumentError
140
+ notifier.error("Error in #{file}", $!)
141
+ return
142
+ end
143
+
144
+ # TODO: change that to run multiple suites
145
+ #klass = Kernel.const_get(test_class) - this threw errors
146
+ klass = eval(test_class)
147
+
148
+ Test::Unit::UI::Console::TestRunner.run(klass)
149
+
150
+ # Invoke method to test that writes to stdout.
151
+ result = test_io.string.to_s.dup
152
+
153
+ # clear the buffer
154
+ test_io.truncate(0)
155
+
156
+ # sent result to notifier
157
+ notifier.result(file, result.split("\n").compact.last)
158
+
159
+ # sent result to stdio
160
+ puts result
161
+
162
+ end
163
+
164
+ end
165
+ end
166
+ end
data/lib/bolt.rb ADDED
@@ -0,0 +1,38 @@
1
+ # Why Bolt? Cause it's a cool name, that's why :)
2
+ module Bolt
3
+ autoload :Mapper, 'bolt/mapper'
4
+ autoload :Runner, 'bolt/runner'
5
+ autoload :Notifier, 'bolt/notifier'
6
+ autoload :Listener, 'bolt/listener'
7
+
8
+ #
9
+ # Bolt::Listeners
10
+ #
11
+ # Wrapper for specific listeners
12
+ #
13
+ module Listeners
14
+ autoload :Generic, 'bolt/listeners/generic'
15
+ autoload :Kqueue, 'bolt/listeners/kqueue'
16
+ autoload :Osx, 'bolt/listeners/osx'
17
+ end
18
+
19
+ #
20
+ # Bolt::Runners
21
+ #
22
+ # Wrapper for specific runners
23
+ #
24
+ module Runners
25
+ autoload :TestUnit, 'bolt/runners/test_unit'
26
+ autoload :RSpec, 'bolt/runners/rspec'
27
+ end
28
+
29
+ #
30
+ # Bolt::Notifiers
31
+ #
32
+ # Wrapper for specific notifier
33
+ #
34
+ module Notifiers
35
+ autoload :Generic, 'bolt/notifiers/generic'
36
+ autoload :Growl, 'bolt/notifiers/growl'
37
+ end
38
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec'
2
+ require 'bolt/runners/rspec'
3
+
4
+ describe Bolt::Runners::RSpec do
5
+
6
+ before(:all) do
7
+ @runner = described_class.new
8
+ end
9
+
10
+ it 'should translate controllers' do
11
+ @runner.stub('file_verified?').and_return(true)
12
+ @runner.translate('app/controllers/test_controller.rb').should == ['spec/controllers/test_controller_spec.rb']
13
+ end
14
+
15
+ it 'should translate models' do
16
+ @runner.stub('file_verified?').and_return(true)
17
+ @runner.translate('app/models/test.rb').should == ['spec/models/test_spec.rb']
18
+ end
19
+
20
+ it 'should translate views' do
21
+ @runner.stub('file_verified?').and_return(true)
22
+ @runner.translate('app/views/test/test.html.erb').should == ['spec/controllers/test_controller_spec.rb']
23
+ end
24
+
25
+ it 'should translate lib' do
26
+ @runner.stub('file_verified?').and_return(true)
27
+ @runner.translate('lib/test.rb').should == ['spec/test_spec.rb']
28
+ end
29
+
30
+ it 'should translate lib with subfolders' do
31
+ @runner.stub('file_verified?').and_return(true)
32
+ @runner.translate('lib/testing/test.rb').should == ['spec/testing/test_spec.rb']
33
+ end
34
+
35
+ it 'should translate specs to themselves' do
36
+ @runner.stub('file_verified?').and_return(true)
37
+ @runner.translate('spec/controllers/test_controller_spec.rb').should == ['spec/controllers/test_controller_spec.rb']
38
+ @runner.translate('spec/test_spec.rb').should == ['spec/test_spec.rb']
39
+ @runner.translate('spec/testing/test_spec.rb').should == ['spec/testing/test_spec.rb']
40
+ end
41
+
42
+ it 'should return no results if file is not present' do
43
+ @runner.stub('file_verified?').and_return(false)
44
+ @runner.translate('lib/testing/test.rb').should == []
45
+ end
46
+
47
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec'
2
+ require 'bolt/runners/test_unit'
3
+
4
+ describe Bolt::Runners::TestUnit do
5
+
6
+ before(:all) do
7
+ @runner = described_class.new
8
+ end
9
+
10
+ it 'should translate controllers' do
11
+ @runner.stub('file_verified?').and_return(true)
12
+ @runner.translate('app/controllers/test_controller.rb').should == ['test/functional/test_controller_test.rb']
13
+ end
14
+
15
+ it 'should translate models' do
16
+ @runner.stub('file_verified?').and_return(true)
17
+ @runner.translate('app/models/test.rb').should == ['test/unit/test_test.rb']
18
+ end
19
+
20
+ it 'should translate views' do
21
+ @runner.stub('file_verified?').and_return(true)
22
+ @runner.translate('app/views/test/test.html.erb').should == ['test/functional/test_controller_test.rb']
23
+ end
24
+
25
+ it 'should translate lib' do
26
+ @runner.stub('file_verified?').and_return(true)
27
+ @runner.translate('lib/test.rb').should == ['test/unit/test_test.rb']
28
+ end
29
+
30
+ it 'should translate lib with subfolders' do
31
+ @runner.stub('file_verified?').and_return(true)
32
+ @runner.translate('lib/testing/test.rb').should == ['test/unit/testing/test_test.rb']
33
+ end
34
+
35
+ it 'should translate tests to itself' do
36
+ @runner.stub('file_verified?').and_return(true)
37
+ @runner.translate('test/functional/test_controller_test.rb').should == ['test/functional/test_controller_test.rb']
38
+ @runner.translate('test/unit/test_test.rb').should == ['test/unit/test_test.rb']
39
+ @runner.translate('test/unit/testing/test_test.rb').should == ['test/unit/testing/test_test.rb']
40
+ end
41
+
42
+ it 'should return no results if file is not present' do
43
+ @runner.stub('file_verified?').and_return(false)
44
+ @runner.translate('lib/testing/test.rb').should == []
45
+ end
46
+
47
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: marcinbunsch-bolt
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Marcin Bunsch
8
+ - "Mislav Marohni\xC4\x87"
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-06-15 00:00:00 -07:00
14
+ default_executable: bolt
15
+ dependencies: []
16
+
17
+ description:
18
+ email: marcin@applicake.com
19
+ executables:
20
+ - bolt
21
+ extensions: []
22
+
23
+ extra_rdoc_files: []
24
+
25
+ files:
26
+ - Rakefile
27
+ - bin/bolt
28
+ - lib/bolt
29
+ - lib/bolt/listener.rb
30
+ - lib/bolt/listeners
31
+ - lib/bolt/listeners/generic.rb
32
+ - lib/bolt/listeners/osx.rb
33
+ - lib/bolt/notifier.rb
34
+ - lib/bolt/notifiers
35
+ - lib/bolt/notifiers/generic.rb
36
+ - lib/bolt/notifiers/growl.rb
37
+ - lib/bolt/runner.rb
38
+ - lib/bolt/runners
39
+ - lib/bolt/runners/rspec.rb
40
+ - lib/bolt/runners/test_unit.rb
41
+ - lib/bolt.rb
42
+ - images/failed.png
43
+ - images/LICENSE
44
+ - images/pending.png
45
+ - images/success.png
46
+ - spec/bolt
47
+ - spec/bolt/runners
48
+ - spec/bolt/runners/rspec_spec.rb
49
+ - spec/bolt/runners/test_unit_spec.rb
50
+ - README.textile
51
+ - LICENSE
52
+ has_rdoc: false
53
+ homepage: http://github.com/marcinbunsch/bolt
54
+ post_install_message:
55
+ rdoc_options: []
56
+
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: "0"
70
+ version:
71
+ requirements: []
72
+
73
+ rubyforge_project:
74
+ rubygems_version: 1.2.0
75
+ signing_key:
76
+ specification_version: 3
77
+ summary: Bolt is a merge of autotest and mislav/rspactor to produce a lightning fast, configurable and simple to set up autotest clone
78
+ test_files: []
79
+