zeus 0.10.2 → 0.11.0

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