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.
@@ -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