zeus-justinf 0.13.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +36 -0
- data/MIT-LICENSE +22 -0
- data/Rakefile +22 -0
- data/bin/zeus +17 -0
- data/build/zeus-darwin-amd64 +0 -0
- data/build/zeus-linux-386 +0 -0
- data/build/zeus-linux-amd64 +0 -0
- data/ext/inotify-wrapper/extconf.rb +24 -0
- data/ext/inotify-wrapper/inotify-wrapper.cpp +116 -0
- data/lib/zeus.rb +226 -0
- data/lib/zeus/load_tracking.rb +64 -0
- data/lib/zeus/m.rb +354 -0
- data/lib/zeus/m/test_collection.rb +52 -0
- data/lib/zeus/m/test_method.rb +39 -0
- data/lib/zeus/plan.rb +6 -0
- data/lib/zeus/rails.rb +256 -0
- data/lib/zeus/version.rb +3 -0
- data/spec/fake_mini_test.rb +42 -0
- data/spec/m_spec.rb +110 -0
- data/spec/rails_spec.rb +62 -0
- data/spec/spec_helper.rb +38 -0
- data/zeus.gemspec +35 -0
- metadata +126 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
module Zeus
|
2
|
+
class LoadTracking
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def features_loaded_by(&block)
|
6
|
+
old_features = all_features()
|
7
|
+
yield
|
8
|
+
new_features = all_features() - old_features
|
9
|
+
return new_features
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_feature(file)
|
13
|
+
path = if File.exist?(File.expand_path(file))
|
14
|
+
File.expand_path(file)
|
15
|
+
else
|
16
|
+
find_in_load_path(file)
|
17
|
+
end
|
18
|
+
add_extra_feature(path) if path
|
19
|
+
end
|
20
|
+
|
21
|
+
def all_features
|
22
|
+
untracked = defined?($untracked_features) ? $untracked_features : []
|
23
|
+
$LOADED_FEATURES + untracked
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def add_extra_feature(path)
|
29
|
+
$untracked_features ||= []
|
30
|
+
$untracked_features << path
|
31
|
+
end
|
32
|
+
|
33
|
+
def find_in_load_path(file)
|
34
|
+
$LOAD_PATH.map { |path| "#{path}/#{file}" }.detect{ |file| File.exist? file }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module Kernel
|
41
|
+
|
42
|
+
def load(file, *a)
|
43
|
+
Kernel.load(file, *a)
|
44
|
+
end
|
45
|
+
|
46
|
+
class << self
|
47
|
+
alias_method :__load_without_zeus, :load
|
48
|
+
def load(file, *a)
|
49
|
+
Zeus::LoadTracking.add_feature(file)
|
50
|
+
__load_without_zeus(file, *a)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
require 'yaml'
|
56
|
+
module YAML
|
57
|
+
class << self
|
58
|
+
alias_method :__load_file_without_zeus, :load_file
|
59
|
+
def load_file(file, *a)
|
60
|
+
Zeus::LoadTracking.add_feature(file)
|
61
|
+
__load_file_without_zeus(file, *a)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/zeus/m.rb
ADDED
@@ -0,0 +1,354 @@
|
|
1
|
+
# This is very largely based on @qrush's M, but there are many modifications.
|
2
|
+
|
3
|
+
# we need to load all dependencies up front, because bundler will
|
4
|
+
# remove us from the load path soon.
|
5
|
+
require "rubygems"
|
6
|
+
require "zeus/m/test_collection"
|
7
|
+
require "zeus/m/test_method"
|
8
|
+
|
9
|
+
# the Gemfile may specify a version of method_source, but we also want to require it here.
|
10
|
+
# To avoid possible "you've activated X; gemfile specifies Y" errors, we actually scan
|
11
|
+
# Gemfile.lock for a specific version, and require exactly that version if present.
|
12
|
+
gemfile_lock = ROOT_PATH + "/Gemfile.lock"
|
13
|
+
if File.exists?(gemfile_lock)
|
14
|
+
version = File.read(ROOT_PATH + "/Gemfile.lock").
|
15
|
+
scan(/\bmethod_source\s*\(([\d\.]+)\)/).flatten[0]
|
16
|
+
|
17
|
+
gem "method_source", version if version
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'method_source'
|
21
|
+
|
22
|
+
module Zeus
|
23
|
+
#`m` stands for metal, which is a better test/unit test runner that can run
|
24
|
+
#tests by line number.
|
25
|
+
#
|
26
|
+
#[![m ci](https://secure.travis-ci.org/qrush/m.png)](http://travis-ci.org/qrush/m)
|
27
|
+
#
|
28
|
+
#![Rush is a heavy metal band. Look it up on Wikipedia.](https://raw.github.com/qrush/m/master/rush.jpg)
|
29
|
+
#
|
30
|
+
#<sub>[Rush at the Bristol Colston Hall May 1979](http://www.flickr.com/photos/8507625@N02/3468299995/)</sub>
|
31
|
+
### Install
|
32
|
+
#
|
33
|
+
### Usage
|
34
|
+
#
|
35
|
+
#Basically, I was sick of using the `-n` flag to grab one test to run. Instead, I
|
36
|
+
#prefer how RSpec's test runner allows tests to be run by line number.
|
37
|
+
#
|
38
|
+
#Given this file:
|
39
|
+
#
|
40
|
+
# $ cat -n test/example_test.rb
|
41
|
+
# 1 require 'test/unit'
|
42
|
+
# 2
|
43
|
+
# 3 class ExampleTest < Test::Unit::TestCase
|
44
|
+
# 4 def test_apple
|
45
|
+
# 5 assert_equal 1, 1
|
46
|
+
# 6 end
|
47
|
+
# 7
|
48
|
+
# 8 def test_banana
|
49
|
+
# 9 assert_equal 1, 1
|
50
|
+
# 10 end
|
51
|
+
# 11 end
|
52
|
+
#
|
53
|
+
#You can run a test by line number, using format `m TEST_FILE:LINE_NUMBER_OF_TEST`:
|
54
|
+
#
|
55
|
+
# $ m test/example_test.rb:4
|
56
|
+
# Run options: -n /test_apple/
|
57
|
+
#
|
58
|
+
# # Running tests:
|
59
|
+
#
|
60
|
+
# .
|
61
|
+
#
|
62
|
+
# Finished tests in 0.000525s, 1904.7619 tests/s, 1904.7619 assertions/s.
|
63
|
+
#
|
64
|
+
# 1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
|
65
|
+
#
|
66
|
+
#Hit the wrong line number? No problem, `m` helps you out:
|
67
|
+
#
|
68
|
+
# $ m test/example_test.rb:2
|
69
|
+
# No tests found on line 2. Valid tests to run:
|
70
|
+
#
|
71
|
+
# test_apple: m test/examples/test_unit_example_test.rb:4
|
72
|
+
# test_banana: m test/examples/test_unit_example_test.rb:8
|
73
|
+
#
|
74
|
+
#Want to run the whole test? Just leave off the line number.
|
75
|
+
#
|
76
|
+
# $ m test/example_test.rb
|
77
|
+
# Run options:
|
78
|
+
#
|
79
|
+
# # Running tests:
|
80
|
+
#
|
81
|
+
# ..
|
82
|
+
#
|
83
|
+
# Finished tests in 0.001293s, 1546.7904 tests/s, 3093.5808 assertions/s.
|
84
|
+
#
|
85
|
+
# 1 tests, 2 assertions, 0 failures, 0 errors, 0 skips
|
86
|
+
#
|
87
|
+
#### Supports
|
88
|
+
#
|
89
|
+
#`m` works with a few Ruby test frameworks:
|
90
|
+
#
|
91
|
+
#* `Test::Unit`
|
92
|
+
#* `ActiveSupport::TestCase`
|
93
|
+
#* `MiniTest::Unit::TestCase`
|
94
|
+
#
|
95
|
+
### License
|
96
|
+
#
|
97
|
+
#This gem is MIT licensed, please see `LICENSE` for more information.
|
98
|
+
|
99
|
+
### M, your metal test runner
|
100
|
+
# Maybe this gem should have a longer name? Metal?
|
101
|
+
module M
|
102
|
+
M::VERSION = "1.2.1" unless defined?(M::VERSION)
|
103
|
+
|
104
|
+
# Accept arguments coming from bin/m and run tests.
|
105
|
+
def self.run(argv)
|
106
|
+
Runner.new(argv).run
|
107
|
+
end
|
108
|
+
|
109
|
+
### Runner is in charge of running your tests.
|
110
|
+
# Instead of slamming all of this junk in an `M` class, it's here instead.
|
111
|
+
class Runner
|
112
|
+
def initialize(argv)
|
113
|
+
@argv = argv
|
114
|
+
end
|
115
|
+
|
116
|
+
# There's two steps to running our tests:
|
117
|
+
# 1. Parsing the given input for the tests we need to find (or groups of tests)
|
118
|
+
# 2. Run those tests we found that match what you wanted
|
119
|
+
def run
|
120
|
+
parse
|
121
|
+
execute
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def parse
|
127
|
+
# With no arguments,
|
128
|
+
if @argv.empty?
|
129
|
+
# Just shell out to `rake test`.
|
130
|
+
require 'rake'
|
131
|
+
Rake::Task['test'].invoke
|
132
|
+
exit
|
133
|
+
else
|
134
|
+
parse_options! @argv
|
135
|
+
|
136
|
+
# Parse out ARGV, it should be coming in in a format like `test/test_file.rb:9`
|
137
|
+
_, line = @argv.first.split(':')
|
138
|
+
@line ||= line.nil? ? nil : line.to_i
|
139
|
+
|
140
|
+
@files = []
|
141
|
+
@argv.each do |arg|
|
142
|
+
add_file(arg)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def add_file(arg)
|
148
|
+
file = arg.split(':').first
|
149
|
+
if Dir.exist?(file)
|
150
|
+
files = Dir.glob("#{file}/**/*test*.rb")
|
151
|
+
@files.concat(files)
|
152
|
+
else
|
153
|
+
files = Dir.glob(file)
|
154
|
+
files == [] and abort "Couldn't find test file '#{file}'!"
|
155
|
+
@files.concat(files)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def parse_options!(argv)
|
160
|
+
require 'optparse'
|
161
|
+
|
162
|
+
OptionParser.new do |opts|
|
163
|
+
opts.banner = 'Options:'
|
164
|
+
opts.version = M::VERSION
|
165
|
+
|
166
|
+
opts.on '-h', '--help', 'Display this help.' do
|
167
|
+
puts "Usage: m [OPTIONS] [FILES]\n\n", opts
|
168
|
+
exit
|
169
|
+
end
|
170
|
+
|
171
|
+
opts.on '--version', 'Display the version.' do
|
172
|
+
puts "m #{M::VERSION}"
|
173
|
+
exit
|
174
|
+
end
|
175
|
+
|
176
|
+
opts.on '-l', '--line LINE', Integer, 'Line number for file.' do |line|
|
177
|
+
@line = line
|
178
|
+
end
|
179
|
+
|
180
|
+
opts.on '-n', '--name NAME', String, 'Name or pattern for test methods to run.' do |name|
|
181
|
+
if name[0] == "/" && name[-1] == "/"
|
182
|
+
@test_name = Regexp.new(name[1..-2])
|
183
|
+
else
|
184
|
+
@test_name = name
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
opts.parse! argv
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def execute
|
193
|
+
generate_tests_to_run
|
194
|
+
|
195
|
+
test_arguments = build_test_arguments
|
196
|
+
|
197
|
+
# directly run the tests from here and exit with the status of the tests passing or failing
|
198
|
+
case framework
|
199
|
+
when :minitest
|
200
|
+
nerf_test_unit_autorunner
|
201
|
+
exit(MiniTest::Unit.runner.run(test_arguments).to_i)
|
202
|
+
when :testunit1, :testunit2
|
203
|
+
exit Test::Unit::AutoRunner.run(false, nil, test_arguments)
|
204
|
+
else
|
205
|
+
not_supported
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def generate_tests_to_run
|
210
|
+
# Locate tests to run that may be inside of this line. There could be more than one!
|
211
|
+
all_tests = tests
|
212
|
+
if @line
|
213
|
+
@tests_to_run = all_tests.within(@line)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def build_test_arguments
|
218
|
+
if @line
|
219
|
+
abort_with_no_test_found_by_line_number if @tests_to_run.empty?
|
220
|
+
|
221
|
+
# assemble the regexp to run these tests,
|
222
|
+
test_names = @tests_to_run.map(&:escaped_name).join('|')
|
223
|
+
|
224
|
+
# set up the args needed for the runner
|
225
|
+
["-n", "/(#{test_names})/"]
|
226
|
+
elsif user_specified_name?
|
227
|
+
abort_with_no_test_found_by_name unless tests.contains?(@test_name)
|
228
|
+
|
229
|
+
test_names = test_name_to_s
|
230
|
+
["-n", test_names]
|
231
|
+
else
|
232
|
+
[]
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def abort_with_no_test_found_by_line_number
|
237
|
+
abort_with_valid_tests_msg "No tests found on line #{@line}. "
|
238
|
+
end
|
239
|
+
|
240
|
+
def abort_with_no_test_found_by_name
|
241
|
+
abort_with_valid_tests_msg "No test name matches '#{test_name_to_s}'. "
|
242
|
+
end
|
243
|
+
|
244
|
+
def abort_with_valid_tests_msg message=""
|
245
|
+
message << "Valid tests to run:\n\n"
|
246
|
+
# For every test ordered by line number,
|
247
|
+
# spit out the test name and line number where it starts,
|
248
|
+
tests.by_line_number do |test|
|
249
|
+
message << "#{sprintf("%0#{tests.column_size}s", test.escaped_name)}: zeus test #{@files[0]}:#{test.start_line}\n"
|
250
|
+
end
|
251
|
+
|
252
|
+
# fail like a good unix process should.
|
253
|
+
abort message
|
254
|
+
end
|
255
|
+
|
256
|
+
def test_name_to_s
|
257
|
+
@test_name.is_a?(Regexp)? "/#{Regexp.escape(@test_name.source)}/" : Regexp.escape(@test_name)
|
258
|
+
end
|
259
|
+
|
260
|
+
def user_specified_name?
|
261
|
+
!@test_name.nil?
|
262
|
+
end
|
263
|
+
|
264
|
+
def framework
|
265
|
+
@framework ||= begin
|
266
|
+
if defined?(MiniTest)
|
267
|
+
:minitest
|
268
|
+
elsif defined?(Test)
|
269
|
+
if Test::Unit::TestCase.respond_to?(:test_suites)
|
270
|
+
:testunit2
|
271
|
+
else
|
272
|
+
:testunit1
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# Finds all test suites in this test file, with test methods included.
|
279
|
+
def suites
|
280
|
+
# Since we're not using `ruby -Itest -Ilib` to run the tests, we need to add this directory to the `LOAD_PATH`
|
281
|
+
$:.unshift "./test", "./lib"
|
282
|
+
|
283
|
+
if framework == :testunit1
|
284
|
+
Test::Unit::TestCase.class_eval {
|
285
|
+
@@test_suites = {}
|
286
|
+
def self.inherited(klass)
|
287
|
+
@@test_suites[klass] = true
|
288
|
+
end
|
289
|
+
def self.test_suites
|
290
|
+
@@test_suites.keys
|
291
|
+
end
|
292
|
+
def self.test_methods
|
293
|
+
public_instance_methods(true).grep(/^test/).map(&:to_s)
|
294
|
+
end
|
295
|
+
}
|
296
|
+
end
|
297
|
+
|
298
|
+
begin
|
299
|
+
# Fire up the Ruby files. Let's hope they actually have tests.
|
300
|
+
@files.each { |f| load f }
|
301
|
+
rescue LoadError => e
|
302
|
+
# Fail with a happier error message instead of spitting out a backtrace from this gem
|
303
|
+
abort "Failed loading test file:\n#{e.message}"
|
304
|
+
end
|
305
|
+
|
306
|
+
# Figure out what test framework we're using
|
307
|
+
case framework
|
308
|
+
when :minitest
|
309
|
+
suites = MiniTest::Unit::TestCase.test_suites
|
310
|
+
when :testunit1, :testunit2
|
311
|
+
suites = Test::Unit::TestCase.test_suites
|
312
|
+
else
|
313
|
+
not_supported
|
314
|
+
end
|
315
|
+
|
316
|
+
# Use some janky internal APIs to group test methods by test suite.
|
317
|
+
suites.inject({}) do |suites, suite_class|
|
318
|
+
# End up with a hash of suite class name to an array of test methods, so we can later find them and ignore empty test suites
|
319
|
+
suites[suite_class] = suite_class.test_methods if suite_class.test_methods.size > 0
|
320
|
+
suites
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
# Shoves tests together in our custom container and collection classes.
|
325
|
+
# Memoize it since it's unnecessary to do this more than one for a given file.
|
326
|
+
def tests
|
327
|
+
@tests ||= begin
|
328
|
+
# With each suite and array of tests,
|
329
|
+
# and with each test method present in this test file,
|
330
|
+
# shove a new test method into this collection.
|
331
|
+
suites.inject(TestCollection.new) do |collection, (suite_class, test_methods)|
|
332
|
+
test_methods.each do |test_method|
|
333
|
+
find_locations = (@files.size == 1 && @line)
|
334
|
+
collection << TestMethod.create(suite_class, test_method, find_locations)
|
335
|
+
end
|
336
|
+
collection
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def nerf_test_unit_autorunner
|
342
|
+
return unless defined?(Test::Unit::Runner)
|
343
|
+
if Test::Unit::Runner.class_variable_get("@@installed_at_exit")
|
344
|
+
Test::Unit::Runner.class_variable_set("@@stop_auto_run", true)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
# Fail loudly if this isn't supported
|
349
|
+
def not_supported
|
350
|
+
abort "This test framework is not supported! Please open up an issue at https://github.com/qrush/m !"
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Zeus
|
4
|
+
module M
|
5
|
+
### Custom wrapper around an array of test methods
|
6
|
+
# In charge of some smart querying, filtering, sorting, etc on the the
|
7
|
+
# test methods
|
8
|
+
class TestCollection
|
9
|
+
include Enumerable
|
10
|
+
extend Forwardable
|
11
|
+
# This should act like an array, so forward some common methods over to the
|
12
|
+
# internal collection
|
13
|
+
def_delegators :@collection, :size, :<<, :each, :empty?
|
14
|
+
|
15
|
+
def initialize(collection = nil)
|
16
|
+
@collection = collection || []
|
17
|
+
end
|
18
|
+
|
19
|
+
# Slice out tests that may be within the given line.
|
20
|
+
# Returns a new TestCollection with the results.
|
21
|
+
def within(line)
|
22
|
+
# Into a new collection, filter only the tests that...
|
23
|
+
self.class.new(select do |test|
|
24
|
+
# are within the given boundary for this method
|
25
|
+
# or include everything if the line given is nil (no line)
|
26
|
+
line.nil? || (test.start_line..test.end_line).include?(line)
|
27
|
+
end)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Used to line up method names in `#sprintf` when `m` aborts
|
31
|
+
def column_size
|
32
|
+
# Boil down the collection of test methods to the name of the method's
|
33
|
+
# size, then find the largest one
|
34
|
+
@column_size ||= map { |test| test.name.to_s.size }.max
|
35
|
+
end
|
36
|
+
|
37
|
+
# Be considerate when printing out tests and pre-sort them by line number
|
38
|
+
def by_line_number(&block)
|
39
|
+
# On each member of the collection, sort by line number and yield
|
40
|
+
# the block into the sorted collection
|
41
|
+
sort_by(&:start_line).each(&block)
|
42
|
+
end
|
43
|
+
|
44
|
+
def contains? test_name
|
45
|
+
@collection.each do |test|
|
46
|
+
return true if test_name.match(test.name)
|
47
|
+
end
|
48
|
+
false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|