zeus-justinf 0.13.5
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.
- 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
|
+
#[](http://travis-ci.org/qrush/m)
|
27
|
+
#
|
28
|
+
#
|
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
|