zeus 0.10.2 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Binary file
Binary file
Binary file
@@ -12,9 +12,8 @@
12
12
  "generate": ["g"]
13
13
  },
14
14
  "test_environment": {
15
- "testtask": [],
16
- "test_helper": {"testrb": []},
17
- "spec_helper": {"rspec": []}
15
+ "cucumber_environment": {"cucumber": []},
16
+ "test_helper": {"test": ["rspec", "testrb"]}
18
17
  }
19
18
  }
20
19
  }
@@ -76,9 +76,10 @@ module Zeus
76
76
  }
77
77
 
78
78
  Process.wait(pid)
79
- code = $?.exitstatus
79
+ code = $?.exitstatus || 0
80
80
 
81
81
  local.write "#{code}\0"
82
+
82
83
  local.close
83
84
  end
84
85
 
@@ -0,0 +1,310 @@
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
+ VERSION = "1.2.1" unless defined?(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.parse! argv
181
+ end
182
+ end
183
+
184
+ def execute
185
+ # Locate tests to run that may be inside of this line. There could be more than one!
186
+ all_tests = tests
187
+ if @line
188
+ tests_to_run = all_tests.within(@line)
189
+ end
190
+
191
+ # If we didn't find any tests,
192
+ if tests_to_run == []
193
+ # Otherwise we found no tests on this line, so you need to pick one.
194
+ message = "No tests found on line #{@line}. Valid tests to run:\n\n"
195
+
196
+ # For every test ordered by line number,
197
+ # spit out the test name and line number where it starts,
198
+ tests.by_line_number do |test|
199
+ message << "#{sprintf("%0#{tests.column_size}s", test.name)}: zeus test #{@files[0]}:#{test.start_line}\n"
200
+ end
201
+
202
+ # fail like a good unix process should.
203
+ abort message
204
+ end
205
+
206
+ if @line
207
+ # assemble the regexp to run these tests,
208
+ test_names = tests_to_run.map(&:name).join('|')
209
+
210
+ # set up the args needed for the runner
211
+ test_arguments = ["-n", "/(#{test_names})/"]
212
+ else
213
+ test_arguments = []
214
+ end
215
+
216
+ # directly run the tests from here and exit with the status of the tests passing or failing
217
+ case framework
218
+ when :minitest
219
+ exit MiniTest::Unit.runner.run test_arguments
220
+ when :testunit1, :testunit2
221
+ exit Test::Unit::AutoRunner.run(false, nil, test_arguments)
222
+ else
223
+ not_supported
224
+ end
225
+ end
226
+
227
+ def framework
228
+ @framework ||= begin
229
+ if defined?(MiniTest)
230
+ :minitest
231
+ elsif defined?(Test)
232
+ if Test::Unit::TestCase.respond_to?(:test_suites)
233
+ :testunit2
234
+ else
235
+ :testunit1
236
+ end
237
+ end
238
+ end
239
+ end
240
+
241
+ # Finds all test suites in this test file, with test methods included.
242
+ def suites
243
+ # Since we're not using `ruby -Itest -Ilib` to run the tests, we need to add this directory to the `LOAD_PATH`
244
+ $:.unshift "./test", "./lib"
245
+
246
+ if framework == :testunit1
247
+ Test::Unit::TestCase.class_eval {
248
+ @@test_suites = {}
249
+ def self.inherited(klass)
250
+ @@test_suites[klass] = true
251
+ end
252
+ def self.test_suites
253
+ @@test_suites.keys
254
+ end
255
+ def self.test_methods
256
+ public_instance_methods(true).grep(/^test/).map(&:to_s)
257
+ end
258
+ }
259
+ end
260
+
261
+ begin
262
+ # Fire up the Ruby files. Let's hope they actually have tests.
263
+ @files.each { |f| load f }
264
+ rescue LoadError => e
265
+ # Fail with a happier error message instead of spitting out a backtrace from this gem
266
+ abort "Failed loading test file:\n#{e.message}"
267
+ end
268
+
269
+ # Figure out what test framework we're using
270
+ case framework
271
+ when :minitest
272
+ suites = MiniTest::Unit::TestCase.test_suites
273
+ when :testunit1, :testunit2
274
+ suites = Test::Unit::TestCase.test_suites
275
+ else
276
+ not_supported
277
+ end
278
+
279
+ # Use some janky internal APIs to group test methods by test suite.
280
+ suites.inject({}) do |suites, suite_class|
281
+ # 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
282
+ suites[suite_class] = suite_class.test_methods if suite_class.test_methods.size > 0
283
+ suites
284
+ end
285
+ end
286
+
287
+ # Shoves tests together in our custom container and collection classes.
288
+ # Memoize it since it's unnecessary to do this more than one for a given file.
289
+ def tests
290
+ @tests ||= begin
291
+ # With each suite and array of tests,
292
+ # and with each test method present in this test file,
293
+ # shove a new test method into this collection.
294
+ suites.inject(TestCollection.new) do |collection, (suite_class, test_methods)|
295
+ test_methods.each do |test_method|
296
+ find_locations = (@files.size == 1 && @line)
297
+ collection << TestMethod.create(suite_class, test_method, find_locations)
298
+ end
299
+ collection
300
+ end
301
+ end
302
+ end
303
+
304
+ # Fail loudly if this isn't supported
305
+ def not_supported
306
+ abort "This test framework is not supported! Please open up an issue at https://github.com/qrush/m !"
307
+ end
308
+ end
309
+ end
310
+ end
@@ -0,0 +1,45 @@
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
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
+ end
44
+ end
45
+ end
@@ -0,0 +1,35 @@
1
+ module Zeus
2
+ module M
3
+ ### Simple data structure for what a test method contains.
4
+ #
5
+ # Too lazy to make a class for this when it's really just a bag of data
6
+ # without any behavior.
7
+ #
8
+ # Includes the name of this method, what line on the file it begins on,
9
+ # and where it ends.
10
+ class TestMethod < Struct.new(:name, :start_line, :end_line)
11
+ # Set up a new test method for this test suite class
12
+ def self.create(suite_class, test_method, find_locations = true)
13
+ # Hopefully it's been defined as an instance method, so we'll need to
14
+ # look up the ruby Method instance for it
15
+ method = suite_class.instance_method(test_method)
16
+
17
+ if find_locations
18
+ # Ruby can find the starting line for us, so pull that out of the array
19
+ start_line = method.source_location.last
20
+
21
+ # Ruby can't find the end line however, and I'm too lazy to write
22
+ # a parser. Instead, `method_source` adds `Method#source` so we can
23
+ # deduce this ourselves.
24
+ #
25
+ # The end line should be the number of line breaks in the method source,
26
+ # added to the starting line and subtracted by one.
27
+ end_line = method.source.split("\n").size + start_line - 1
28
+ end
29
+
30
+ # Shove the given attributes into a new databag
31
+ new(test_method, start_line, end_line)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -4,17 +4,80 @@ BOOT_PATH = File.expand_path('config/boot', ROOT_PATH)
4
4
  APP_PATH = File.expand_path('config/application', ROOT_PATH)
5
5
 
6
6
  require 'zeus'
7
+ require 'zeus/m'
7
8
 
8
9
  module Zeus
9
10
  class Rails < Plan
11
+ def deprecated
12
+ puts "Zeus 0.11.0 changed zeus.json. You'll have to rm zeus.json && zeus init."
13
+ end
14
+ alias_method :spec_helper, :deprecated
15
+ alias_method :testrb, :deprecated
16
+ alias_method :rspec, :deprecated
17
+
18
+
10
19
  def after_fork
11
20
  reconnect_activerecord
12
21
  restart_girl_friday
22
+ reconnect_redis
23
+ end
24
+
25
+ def _monkeypatch_rake
26
+ require 'rake/testtask'
27
+ Rake::TestTask.class_eval {
28
+
29
+ # Create the tasks defined by this task lib.
30
+ def define
31
+ desc "Run tests" + (@name==:test ? "" : " for #{@name}")
32
+ task @name do
33
+ # ruby "#{ruby_opts_string} #{run_code} #{file_list_string} #{option_list}"
34
+ rails_env = ENV['RAILS_ENV']
35
+ rubyopt = ENV['RUBYOPT']
36
+ ENV['RAILS_ENV'] = nil
37
+ ENV['RUBYOPT'] = nil # bundler sets this to require bundler :|
38
+ puts "zeus test #{file_list_string}"
39
+ system "zeus test #{file_list_string}"
40
+ ENV['RAILS_ENV'] = rails_env
41
+ ENV['RUBYOPT'] = rubyopt
42
+ end
43
+ self
44
+ end
45
+
46
+ alias_method :_original_define, :define
47
+
48
+ def self.inherited(klass)
49
+ return unless klass.name == "TestTaskWithoutDescription"
50
+ klass.class_eval {
51
+ def self.method_added(sym)
52
+ class_eval do
53
+ if !@rails_hack_reversed
54
+ @rails_hack_reversed = true
55
+ alias_method :define, :_original_define
56
+ def desc(*)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ }
62
+ end
63
+ }
13
64
  end
14
65
 
15
66
  def boot
67
+ _monkeypatch_rake
68
+
16
69
  require BOOT_PATH
17
- require 'rails/all'
70
+ # config/application.rb normally requires 'rails/all'.
71
+ # Some 'alternative' ORMs such as Mongoid give instructions to switch this require
72
+ # out for a list of railties, not including ActiveRecord.
73
+ # We grep config/application.rb for all requires of rails/all or railties, and require them.
74
+ rails_components = File.read(APP_PATH + ".rb").
75
+ scan(/^\s*require\s*['"](.*railtie.*|rails\/all)['"]/).flatten
76
+
77
+ rails_components == ["rails/all"] if rails_components == []
78
+ rails_components.each do |component|
79
+ require component
80
+ end
18
81
  end
19
82
 
20
83
  def default_bundle
@@ -30,7 +93,6 @@ module Zeus
30
93
 
31
94
  def prerake
32
95
  require 'rake'
33
- load 'Rakefile'
34
96
  end
35
97
 
36
98
  def rake
@@ -71,50 +133,39 @@ module Zeus
71
133
  $rails_rake_task = 'yup' # lie to skip eager loading
72
134
  ::Rails.application.require_environment!
73
135
  $rails_rake_task = nil
74
- $LOAD_PATH.unshift(ROOT_PATH) unless $LOAD_PATH.include?(ROOT_PATH)
75
136
 
76
- if Dir.exist?(ROOT_PATH + "/test")
77
- test = File.join(ROOT_PATH, 'test')
78
- $LOAD_PATH.unshift(test) unless $LOAD_PATH.include?(test)
79
- end
80
-
81
- if Dir.exist?(ROOT_PATH + "/spec")
82
- spec = File.join(ROOT_PATH, 'spec')
83
- $LOAD_PATH.unshift(spec) unless $LOAD_PATH.include?(spec)
84
- end
137
+ $LOAD_PATH.unshift ".", "./lib", "./test", "./spec"
85
138
  end
86
139
 
87
140
  def test_helper
88
- require 'test_helper'
89
- end
90
-
91
- def testrb
92
- argv = ARGV
93
-
94
- # try to find pattern by line using testrbl
95
- if defined?(Testrbl) && argv.size == 1 and argv.first =~ /^\S+:\d+$/
96
- file, line = argv.first.split(':')
97
- argv = [file, '-n', "/#{Testrbl.send(:pattern_from_file, File.readlines(file), line)}/"]
98
- puts "using -n '#{argv[2]}'" # let users copy/paste or adjust the pattern
141
+ if File.exists?(ROOT_PATH + "/spec/spec_helper.rb")
142
+ require 'spec_helper'
143
+ elsif File.exist?(ROOT_PATH + "/test/minitest_helper.rb")
144
+ require 'minitest_helper'
145
+ else
146
+ require 'test_helper'
99
147
  end
148
+ end
100
149
 
101
- runner = Test::Unit::AutoRunner.new(true)
102
- if runner.process_args(argv)
103
- exit runner.run
150
+ def test
151
+ if defined?(RSpec)
152
+ exit RSpec::Core::Runner.run(ARGV)
104
153
  else
105
- abort runner.options.banner + " tests..."
154
+ Zeus::M.run(ARGV)
106
155
  end
107
156
  end
108
157
 
109
- def spec_helper
110
- require 'spec_helper'
158
+ def cucumber_environment
159
+ require 'cucumber/rspec/disable_option_parser'
160
+ require 'cucumber/cli/main'
161
+ cucumber_runtime = Cucumber::Runtime.new
111
162
  end
112
163
 
113
- def rspec
114
- exit RSpec::Core::Runner.run(ARGV)
164
+ def cucumber
165
+ cucumber_main = Cucumber::Cli::Main.new(ARGV.dup)
166
+ exit cucumber_main.execute!(cucumber_runtime)
115
167
  end
116
168
 
117
-
118
169
  private
119
170
 
120
171
  def restart_girl_friday
@@ -131,6 +182,13 @@ module Zeus
131
182
  ActiveRecord::Base.establish_connection rescue nil
132
183
  end
133
184
 
185
+ def reconnect_redis
186
+ return unless defined?(Redis::Client)
187
+ ObjectSpace.each_object(Redis::Client) do |client|
188
+ client.connect
189
+ end
190
+ end
191
+
134
192
  end
135
193
  end
136
194
 
@@ -26,4 +26,6 @@ Gem::Specification.new do |gem|
26
26
  gem.require_paths = ["lib"]
27
27
  gem.version = version
28
28
  gem.license = "MIT"
29
+
30
+ gem.add_dependency "method_source", ">= 0.6.7"
29
31
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zeus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.2
4
+ version: 0.11.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-13 00:00:00.000000000 Z
13
- dependencies: []
12
+ date: 2012-09-16 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: method_source
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.6.7
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 0.6.7
14
30
  description: Boot any rails app in under a second
15
31
  email:
16
32
  - burke@libbey.me
@@ -31,6 +47,9 @@ files:
31
47
  - ext/inotify-wrapper/inotify-wrapper.cpp
32
48
  - Gemfile
33
49
  - lib/zeus/load_tracking.rb
50
+ - lib/zeus/m/test_collection.rb
51
+ - lib/zeus/m/test_method.rb
52
+ - lib/zeus/m.rb
34
53
  - lib/zeus/plan.rb
35
54
  - lib/zeus/rails.rb
36
55
  - lib/zeus.rb