Narnach-minitest 0.2

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Wes Oldenbeuving
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.rdoc ADDED
@@ -0,0 +1,35 @@
1
+ == Minitest
2
+ Minitest is a simple autotester intended to be used with rSpec and rCov.
3
+ It can be used with 'plain' ruby projects and Ruby on Rails.
4
+
5
+ == Installation
6
+ === From gem
7
+ Use gem to install minitest. The gem is located on github.
8
+ sudo gem install narnach-minitest -s http://gems.github.com
9
+ === From git
10
+ From the project root, use rake to install:
11
+ git clone git://github.com/Narnach/minitest.git
12
+ cd minitest
13
+ rake install
14
+ This will build the gem and install it for you.
15
+
16
+ == Usage
17
+ Minitest has the following command line options:
18
+ recent:: Only specs for files modified within the last hour are executed on the first run. All files are still monitored for changes, though.
19
+ profile:: Force rspec output format to 'profile', combined with coloured output and unified diffs.
20
+
21
+ == Examples
22
+ When you start working on a codebase, it makes sense to run all specs:
23
+ minitest
24
+ When you resume work on a codebase you recently worked on, it might be useful skip running the specs for unchanged files on the first run:
25
+ minitest recent
26
+ When you want to see which specs are slow and could use optimizing:
27
+ minitest profile
28
+ You can also combine options:
29
+ minitest recent profile
30
+
31
+ == About
32
+ Author:: Wes 'Narnach' Oldenbeuving (narnach@gmail.com)
33
+ Website:: http://www.github.com/Narnach/minitest
34
+ Copyright:: Copyright (c) 2008 Wes Oldenbeuving
35
+ License:: MIT license. See MIT-LICENSE (in the gem directory) for license details.
data/Rakefile ADDED
@@ -0,0 +1,44 @@
1
+ require "rake"
2
+ require "rake/clean"
3
+ require "rake/gempackagetask"
4
+ require 'rubygems'
5
+ require 'lib/minitest'
6
+
7
+ ################################################################################
8
+ ### Gem
9
+ ################################################################################
10
+
11
+ begin
12
+ # Parse gemspec using the github safety level.
13
+ data = File.read('minitest.gemspec')
14
+ spec = nil
15
+ Thread.new { spec = eval("$SAFE = 3\n%s" % data)}.join
16
+
17
+ # Create the gem tasks
18
+ Rake::GemPackageTask.new(spec) do |package|
19
+ package.gem_spec = spec
20
+ end
21
+ rescue Exception => e
22
+ printf "WARNING: Error caught (%s): %s\n", e.class.name, e.message
23
+ end
24
+
25
+ desc 'Package and install the gem for the current version'
26
+ task :install => :gem do
27
+ system "sudo gem install -l pkg/minitest-%s.gem" % spec.version
28
+ end
29
+
30
+ namespace :gem do
31
+ desc 'Re-generate the gemspec'
32
+ task :gemspec do
33
+ files = Dir["*.rdoc"] + %w( Rakefile MIT-LICENSE ) + Dir["{spec,lib,bin}/**/*"]
34
+ tests = Dir["spec/**/*"]
35
+ data = File.read('minitest.gemspec.template')
36
+ files_string = "[%s]" % files.sort.uniq.map{|f| "'%s'" % f}.join(", ")
37
+ tests_string = "[%s]" % tests.sort.uniq.map{|f| "'%s'" % f}.join(", ")
38
+ data.gsub!(':FILES:', files_string)
39
+ data.gsub!(':TEST_FILES:', tests_string)
40
+ File.open('minitest.gemspec','wb') { |f| f.puts(data) }
41
+ puts data
42
+ end
43
+ end
44
+
data/bin/minitest ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__),'..','lib'))
3
+ require 'minitest'
4
+
5
+ minitest = Minitest.new
6
+ minitest.recent = (ARGV.include? "recent")
7
+ minitest.spec_opts = "--format profile --colour --diff unified" if ARGV.include? "profile"
8
+ minitest.start
@@ -0,0 +1,76 @@
1
+ # Dirmonitor's purpose is to serve as a file monitoring helper for Minitest.
2
+ # Its intended functionality is:
3
+ # - Keep track of new files in monitored directories.
4
+ # - Keep track of changed files in monitored directories.
5
+ # - Link these files to their specs, so Minitest can run the specs.
6
+ class DirMonitor
7
+ attr_reader :known_files, :dirs
8
+
9
+ # Setup a new DirMonitor.
10
+ # Directories can be provided in a number of ways:
11
+ # DirMonitor.new 'lib', 'app'
12
+ # DirMonitor.new :lib, :app
13
+ # DirMonitor.new %w[lib app]
14
+ # Each of these examples will result in the same list of directories being stored for use.
15
+ def initialize(*dirs)
16
+ @dirs = dirs.flatten.map{|dir| dir.to_s}
17
+ @known_files = []
18
+ end
19
+
20
+ # Scan for all files in the directories and their sub-directories.
21
+ # The results are stored as a single array in @known_files.
22
+ def scan
23
+ results = []
24
+ dirs.each do |dir|
25
+ files_in_dir = Dir.glob(File.join(dir,'**','*'))
26
+ results.concat(files_in_dir)
27
+ end
28
+ @known_files = results
29
+ end
30
+
31
+ # Scan for new files.
32
+ # All new file names are yielded.
33
+ def scan_new(&block) # :yields: file
34
+ old_known_files = @known_files
35
+ scan
36
+ new_files = known_files - old_known_files
37
+ new_files.each do |new_file|
38
+ block.call(new_file)
39
+ end
40
+ end
41
+
42
+ # Scans for new files, like scan_new does, but yields the name of both the file and spec.
43
+ # spec_for is used to determine what the name of the file's spec _should_ be.
44
+ # Does not yield a file/spec name when the spec does not exist.
45
+ def scan_new_with_spec(&block) # :yields: file, spec
46
+ scan_new do |file|
47
+ spec = spec_for(file)
48
+ block.call(file, spec) if File.exists?(spec)
49
+ end
50
+ end
51
+
52
+ # Find the (theoretical) spec file name for a given file.
53
+ # The assumptions are:
54
+ # - All specs reside in the 'spec' directory.
55
+ # - All specs file names have the suffix '_spec.rb', instead of only the '.rb' extension.
56
+ # - The file name for a non-ruby file spec simply has '_spec.rb' suffixed to the entire file name.
57
+ # The returned file name does not necessarily have to exist.
58
+ def spec_for(file)
59
+ base = File.basename(file)
60
+ extension = File.extname(base)
61
+ dir = File.dirname(file)
62
+ dir_array = dir.split('/')
63
+ if extension == '.rb' and dir_array.first=='spec'
64
+ return file
65
+ end
66
+ if extension == '.rb'
67
+ base_without_extension = base[0, base.size - extension.size]
68
+ spec_file = base_without_extension + '_spec' + extension
69
+ else
70
+ spec_file = base + '_spec.rb'
71
+ end
72
+ dir_array[0]='spec'
73
+ spec_dir = dir_array.join('/')
74
+ return File.join(spec_dir, spec_file)
75
+ end
76
+ end
data/lib/minitest.rb ADDED
@@ -0,0 +1,204 @@
1
+ # = Minitest
2
+ # The default usage of Minitest is this:
3
+ # minitest = Minitest.new
4
+ # minitest.start
5
+ # This will do the following:
6
+ # - Lookup all spec files in the spec/ directory.
7
+ # - Lookup all possible associated files in the lib/ and app/ directories.
8
+ # - Remember the mtimes (last modification times) of all relevant files.
9
+ # - Every second, check all known relevant files: if their mtime changes, run rspec on their spec file.
10
+ # - Run rcov (code coverage tester) on all specs when exiting (Press ctrl-C on send SIGINT to the process)
11
+ class Minitest
12
+ attr_reader :file_mtime
13
+ attr_reader :file_spec
14
+ attr_accessor :rcov_ignores
15
+ attr_accessor :recent
16
+ attr_accessor :recent_time
17
+ attr_accessor :source_dirs
18
+ attr_accessor :source_extensions
19
+ attr_accessor :spec_cmd
20
+ attr_accessor :spec_opts
21
+
22
+ DEFAULT_RCOV_IGNORES = %w[spec/ db/ plugins/ vendor/ config/]
23
+ DEFAULT_RECENT_TIME = 3600
24
+ DEFAULT_SOURCE_DIRS = %w[lib app]
25
+ DEFAULT_SOURCE_EXTENSIONS = %w[rb haml rhtml erb]
26
+
27
+ def initialize
28
+ @file_spec = {} # Map files to their specs
29
+ @file_mtime = {} # Map files to their mtimes
30
+ @need_testing = [] # Specs that need testing
31
+ @first_run = true
32
+ @active = false
33
+ end
34
+
35
+ def active?
36
+ @active == true
37
+ end
38
+
39
+ def first_run?
40
+ @first_run == true
41
+ end
42
+
43
+ # Partial filepaths to exclude from rcov output
44
+ def rcov_ignores
45
+ ignores = @rcov_ignores || DEFAULT_RCOV_IGNORES
46
+ ignores << spec_cmd
47
+ ignores.join(",")
48
+ end
49
+
50
+ # Command line string to run rcov for all monitored specs.
51
+ def rcov
52
+ "#{rcov_cmd} -T --exclude \"#{rcov_ignores}\" -Ilib #{spec_cmd} -- " + self.unique_specs.join(" ")
53
+ end
54
+
55
+ def recent?
56
+ @recent == true
57
+ end
58
+
59
+ # Maximum amount of seconds since a file has been changed for it to count as
60
+ # recently changed.
61
+ def recent_time
62
+ @recent_time || DEFAULT_RECENT_TIME
63
+ end
64
+
65
+ # Command line string to run rspec for an array of specs. Defaults to all specs.
66
+ def rspec(specs=self.unique_specs)
67
+ "#{spec_cmd} #{specs.join(" ")} #{spec_opts}"
68
+ end
69
+
70
+ def source_dirs
71
+ @source_dirs || DEFAULT_SOURCE_DIRS
72
+ end
73
+
74
+ def source_extensions
75
+ @source_extensions || DEFAULT_SOURCE_EXTENSIONS
76
+ end
77
+
78
+ def rcov_cmd
79
+ @rcov_cmd ||= find_rcov_cmd
80
+ end
81
+
82
+ # The command to use to run specs.
83
+ def spec_cmd
84
+ @spec_cmd ||= ( File.exist?('script/spec') ? 'script/spec' : find_spec_cmd )
85
+ end
86
+
87
+ def spec_opts
88
+ @spec_opts ||= ( File.exist?('spec/spec.opts') ? '-O spec/spec.opts' : '' )
89
+ end
90
+
91
+ def start
92
+ @active = true
93
+ find_specs
94
+
95
+ if self.file_spec.values.uniq.size == 0
96
+ puts "There are no specs to run."
97
+ return
98
+ end
99
+
100
+ need_testing = find_first_run_specs
101
+
102
+ trap_int_for_rcov
103
+ while active? do
104
+ if need_testing.size > 0
105
+ print "\nTesting files: #{need_testing.join(" ")}\n"
106
+ system rspec(need_testing)
107
+ end
108
+ sleep 1
109
+ need_testing = find_specs_to_check
110
+ end
111
+ end
112
+
113
+ def trap_int_for_rcov
114
+ Signal.trap("INT") do
115
+ print "\nNow we run rcov and we're done.\n\n"
116
+ puts rcov
117
+ system rcov
118
+ @active = false
119
+ end
120
+ end
121
+
122
+ def unique_specs
123
+ self.file_spec.values.uniq.sort
124
+ end
125
+
126
+ private
127
+
128
+ def find_first_run_specs
129
+ need_checking = self.file_mtime.keys.dup
130
+ specs_to_check = []
131
+ if first_run? and recent?
132
+ need_checking.reject! { |file| @file_mtime[file] < ( Time.now - recent_time ) }
133
+ if need_checking.size > 0
134
+ puts "This first run will only test files changed in the last hour. All other files are still monitored."
135
+ else
136
+ puts "No files were changed in the last hour, so no files are tested for the first run."
137
+ end
138
+ end
139
+ specs_to_check = need_checking.map {|f| @file_spec[f]}
140
+ specs_to_check.uniq!
141
+ specs_to_check.sort!
142
+ @first_run = false
143
+ return specs_to_check
144
+ end
145
+
146
+ def find_sources_for_spec(spec)
147
+ found_files = []
148
+ for dir in self.source_dirs
149
+ next unless spec[0,4]=='spec'
150
+
151
+ file = spec.dup
152
+ file[0,4]=dir
153
+ file.gsub!("_spec.",".")
154
+ candidates = self.source_extensions.map { |ext| file.gsub(/[^.]+\Z/, ext)}
155
+ candidates = candidates.select { |f| File.exist?(f) }
156
+ found_files += candidates
157
+ end
158
+ found_files.uniq!
159
+ return found_files
160
+ end
161
+
162
+ def find_rcov_cmd
163
+ `which rcov`.strip
164
+ end
165
+
166
+ def find_spec_cmd
167
+ `which spec`.strip
168
+ end
169
+
170
+ def find_specs
171
+ Dir.glob("spec/**/*_spec.rb").each do |spec|
172
+ # If a spec changes, run it again.
173
+ map_file_to_spec(spec,spec)
174
+ for file in find_sources_for_spec(spec)
175
+ map_file_to_spec(file,spec)
176
+ end
177
+ end
178
+ end
179
+
180
+ def find_specs_to_check
181
+ specs = []
182
+ @file_mtime.each do |file, old_mtime|
183
+ current_mtime = File.mtime(file)
184
+ if current_mtime != old_mtime
185
+ specs << @file_spec[file]
186
+ store_mtime file
187
+ end
188
+ end
189
+ specs.uniq!
190
+ specs.sort!
191
+ return specs
192
+ end
193
+
194
+ def map_file_to_spec(file,spec)
195
+ return if @file_mtime.has_key? file
196
+ @file_spec[file.dup]=spec.dup
197
+ store_mtime file
198
+ store_mtime spec
199
+ end
200
+
201
+ def store_mtime(file)
202
+ @file_mtime[file.dup] = File.mtime(file)
203
+ end
204
+ end
@@ -0,0 +1,107 @@
1
+ $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__),'..','lib'))
2
+ require 'dir_monitor'
3
+
4
+ describe DirMonitor, ".new" do
5
+ it "should accept multiple directories" do
6
+ dm = DirMonitor.new('lib','app')
7
+ dm.dirs.should == ['lib','app']
8
+ end
9
+
10
+ it "should accept an array with multiple directories" do
11
+ dm = DirMonitor.new(%w[lib app])
12
+ dm.dirs.should == ['lib','app']
13
+ end
14
+
15
+ it "should have no known files" do
16
+ dm = DirMonitor.new('lib')
17
+ dm.known_files.should == []
18
+ end
19
+ end
20
+
21
+ describe DirMonitor, "#scan" do
22
+ it "should find all files in the directories" do
23
+ known_files1 = %w[lib/minitest.rb lib/dir_monitor.rb]
24
+ known_files2 = %w[app/another.rb app/files.rb app/more/things.txt]
25
+ Dir.should_receive(:glob).with('lib/**/*').and_return(known_files1)
26
+ Dir.should_receive(:glob).with('app/**/*').and_return(known_files2)
27
+ dm = DirMonitor.new('lib', 'app')
28
+ dm.scan
29
+ dm.known_files.should == known_files1 + known_files2
30
+ end
31
+ end
32
+
33
+ describe DirMonitor, "#scan_new" do
34
+ it "should yield the names of all new files" do
35
+ known_files = %w[lib/minitest.rb lib/dir_monitor.rb]
36
+ Dir.should_receive(:glob).with('lib/**/*').and_return(known_files)
37
+ dm = DirMonitor.new('lib')
38
+ yield_results = []
39
+ dm.scan_new do |file|
40
+ yield_results << file
41
+ end
42
+ yield_results.should == known_files
43
+ end
44
+
45
+ it "should not yield known file names" do
46
+ known_files = %w[lib/minitest.rb lib/dir_monitor.rb]
47
+ known_files2 = %w[lib/minitest.rb lib/dir_monitor2.rb]
48
+ Dir.should_receive(:glob).with('lib/**/*').and_return(known_files, known_files2)
49
+ dm = DirMonitor.new('lib')
50
+ dm.scan
51
+ yield_results = []
52
+ dm.scan_new do |file|
53
+ yield_results << file
54
+ end
55
+ yield_results.should == known_files2 - known_files
56
+ end
57
+ end
58
+
59
+ describe DirMonitor, "#spec_for" do
60
+ it "should find the spec for a given file" do
61
+ file = 'lib/dir_monitor.rb'
62
+ spec = 'spec/dir_monitor_spec.rb'
63
+ dm = DirMonitor.new
64
+ dm.spec_for(file).should == spec
65
+ end
66
+
67
+ it "should find the spec for non-ruby files" do
68
+ file = 'app/views/posts/post.html.haml'
69
+ spec = 'spec/views/posts/post.html.haml_spec.rb'
70
+ dm = DirMonitor.new
71
+ dm.spec_for(file).should == spec
72
+ end
73
+
74
+ it "should map specs to themselves" do
75
+ spec = 'spec/dir_monitor_spec.rb'
76
+ dm = DirMonitor.new
77
+ dm.spec_for(spec).should == spec
78
+ end
79
+ end
80
+
81
+ describe DirMonitor, "#scan_new_with_spec" do
82
+ it "should yield new files and their specs" do
83
+ file = 'lib/dir_monitor.rb'
84
+ spec = 'spec/dir_monitor_spec.rb'
85
+ Dir.should_receive(:glob).with('lib/**/*').and_return([file])
86
+ File.should_receive(:exists?).with(spec).and_return(true)
87
+ dm = DirMonitor.new 'lib'
88
+ results = []
89
+ dm.scan_new_with_spec do |new_file, new_spec|
90
+ results << {new_file => new_spec}
91
+ end
92
+ results.should == [{file=>spec}]
93
+ end
94
+
95
+ it "should not yield files with non-existent specs" do
96
+ file = 'lib/dir_monitor.rb'
97
+ spec = 'spec/dir_monitor_spec.rb'
98
+ Dir.should_receive(:glob).with('lib/**/*').and_return([file])
99
+ File.should_receive(:exists?).with(spec).and_return(false)
100
+ dm = DirMonitor.new 'lib'
101
+ results = []
102
+ dm.scan_new_with_spec do |new_file, new_spec|
103
+ results << {new_file => new_spec}
104
+ end
105
+ results.should == []
106
+ end
107
+ end
@@ -0,0 +1,5 @@
1
+ $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__),'..','lib'))
2
+ require 'minitest'
3
+
4
+ describe Minitest do
5
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,3 @@
1
+ --diff unified
2
+ --colour
3
+ --format specdoc
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: Narnach-minitest
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.2"
5
+ platform: ruby
6
+ authors:
7
+ - Wes Oldenbeuving
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-06-21 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">"
21
+ - !ruby/object:Gem::Version
22
+ version: 0.0.0
23
+ version:
24
+ - !ruby/object:Gem::Dependency
25
+ name: rcov
26
+ version_requirement:
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ">"
30
+ - !ruby/object:Gem::Version
31
+ version: 0.0.0
32
+ version:
33
+ description: Minitest is a simple autotester tool, which uses rSpec and rCov to test ruby and rails projects.
34
+ email: narnach@gmail.com
35
+ executables:
36
+ - minitest
37
+ extensions: []
38
+
39
+ extra_rdoc_files:
40
+ - README.rdoc
41
+ - MIT-LICENSE
42
+ files:
43
+ - MIT-LICENSE
44
+ - README.rdoc
45
+ - Rakefile
46
+ - bin/minitest
47
+ - lib/dir_monitor.rb
48
+ - lib/minitest.rb
49
+ - spec/dir_monitor_spec.rb
50
+ - spec/minitest_spec.rb
51
+ - spec/spec.opts
52
+ has_rdoc: true
53
+ homepage: http://www.github.com/Narnach/minitest
54
+ post_install_message:
55
+ rdoc_options:
56
+ - --inline-source
57
+ - --line-numbers
58
+ - --main
59
+ - README.rdoc
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 1.8.0
67
+ version:
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: "0"
73
+ version:
74
+ requirements: []
75
+
76
+ rubyforge_project:
77
+ rubygems_version: 1.0.1
78
+ signing_key:
79
+ specification_version: 2
80
+ summary: Minitest is a simple autotester tool.
81
+ test_files:
82
+ - spec/dir_monitor_spec.rb
83
+ - spec/minitest_spec.rb
84
+ - spec/spec.opts