zeus-justinf 0.13.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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