Skip to content

Commit

Permalink
Rocco it up
Browse files Browse the repository at this point in the history
  • Loading branch information
qrush committed Jan 12, 2012
1 parent 88f9abc commit 64b610a
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 81 deletions.
18 changes: 18 additions & 0 deletions README
@@ -0,0 +1,18 @@
________________________________________________________________________________
________________________/\\\\____________/\\\\__________________________________
________________________\/\\\\\\________/\\\\\\_________________________________
_________________________\/\\\//\\\____/\\\//\\\________________________________
__________________________\/\\\\///\\\/\\\/_\/\\\_______________________________
___________________________\/\\\__\///\\\/___\/\\\______________________________
____________________________\/\\\____\///_____\/\\\_____________________________
_____________________________\/\\\_____________\/\\\____________________________
______________________________\/\\\_____________\/\\\___________________________
_______________________________\///______________\///___________________________
________________________________________________________________________________


m is a better test/unit test runner for Ruby that can run tests by
line number (and more!). Please see the Rocco generated docs for
more information:

<http://quaran.to/rocco/>
74 changes: 0 additions & 74 deletions README.md

This file was deleted.

14 changes: 10 additions & 4 deletions lib/m.rb
@@ -1,4 +1,5 @@
#`m` stands for :metal: (metal), which is a better test/unit test runner. @sferik took `t` so this was the next best option.
#`m` stands for metal, which is a better test/unit test runner that can run
#tests by line number.
#[![m ci](https://secure.travis-ci.org/qrush/m.png)](http://travis-ci.org/qrush/m)
#
#![Rush is a heavy metal band. Look it up on Wikipedia.](https://raw.github.com/qrush/m/master/rush.jpg)
Expand All @@ -10,11 +11,14 @@
#
# gem install m
#
#`m` is Ruby 1.9+ only. Sorry, but `method_source`, `sourcify`, and `ruby_parser` all have trouble with 1.8 so I'm giving up and only supporting 1.9 for now. Patches are welcome!
#`m` is Ruby 1.9+ only. Sorry, but `method_source`, `sourcify`, and `ruby_parser`
#all have trouble with 1.8 so I'm giving up and only supporting 1.9 for now.
#Patches are welcome!
#
### Usage
#
#Basically, I was sick of using the `-n` flag to grab one test to run. Instead, I prefer how RSpec's test runner allows tests to be run by line number.
#Basically, I was sick of using the `-n` flag to grab one test to run. Instead, I
#prefer how RSpec's test runner allows tests to be run by line number.
#
#Given this file:
#
Expand Down Expand Up @@ -65,12 +69,14 @@
#
# 1 tests, 2 assertions, 0 failures, 0 errors, 0 skips
#
#`m` also works with `ActiveSupport::TestCase` as well, so it will work great with your Rails test suites.
#`m` also works with `ActiveSupport::TestCase` as well, so it will work great with
#your Rails test suites.
#
### License
#
#This gem is MIT licensed, please see `LICENSE` for more information.

### M, setup and other top level fun
#### Stdlib requires
# Using delegators and open structs since I'm too lazy to make objects
require "forwardable"
Expand Down
27 changes: 26 additions & 1 deletion lib/m/runner.rb
@@ -1,41 +1,66 @@
module M
### Runner is in charge of running your tests.
# Instead of slamming all of this junk in an `M` class, it's here instead.
class Runner
def initialize(argv)
# Parse out ARGV, it should be coming in in a format like `test/test_file.rb:9`
@file, line = argv.first.split(':')
@line = line.to_i
end

def run
# Locate tests to run that may be inside of this line. There could be more than one!
tests_to_run = tests.within(@line)

# If we found any tests,
if tests_to_run.size > 0
# assemble the regexp to run these tests,
test_names = tests_to_run.map(&:name).join('|')
exit Test::Unit::AutoRunner.run(false, nil, ["-n", "/(#{test_names})/"])

# directly run the tests from here and exit with the status of the tests passing or failing
exit Test::Unit::AutoRunner.run(false, nil, ["-n", "/(#{test_names}/"])
else
# Otherwise we found no tests on this line, so you need to pick one.
message = "No tests found on line #{@line}. Valid tests to run:\n\n"

# For every test ordered by line number,
tests.by_line_number do |test|
# spit out the test name and line number where it starts,
message << "#{sprintf("%0#{tests.column_size}s", test.name)}: m #{@file}:#{test.start_line}\n"
end
# fail like a good unix process should.
abort message
end
end

private

# Finds all test suites in this test file, with test methods included.
def suites
# Since we're not using `ruby -Itest` to run the tests, we need to add this directory to the `LOAD_PATH`
$:.unshift "./test"

# Fire up this Ruby file. Let's hope it actually has tests.
load @file

# Use some janky internal test/unit API to group test methods by test suite.
Test::Unit::TestCase.test_suites.inject({}) do |suites, suite_class|
# 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
suites[suite_class] = suite_class.test_methods unless suite_class.test_methods.empty?
suites
end
end

# Shoves tests together in our custom container and collection classes
def tests
# Memoize it since it's unnecessary to do this more than one for a given file
@tests ||= begin
collection = TestCollection.new
# With each suite and array of tests,
suites.each do |suite_class, test_methods|
# And with each test method present in this test file,
test_methods.each do |test_method|
# Shove a new test method into this collection
collection << TestMethod.create(suite_class, test_method)
end
end
Expand Down
16 changes: 16 additions & 0 deletions lib/m/test_collection.rb
@@ -1,24 +1,40 @@
module M
### Custom wrapper around an array of test methods
# In charge of some smart querying, filtering, sorting, etc on the the
# test methods
class TestCollection
include Enumerable
extend Forwardable
# This should act like an array, so forward some common methods over to the
# internal collection
def_delegators :@collection, :size, :<<, :each

def initialize(collection = nil)
@collection = collection || []
end

# Slice out tests that may be within the given line.
# Returns a new TestCollection with the results.
def within(line)
# Into a new collection, filter only the tests that...
self.class.new(select do |test|
# are within the given boundary for this method
# or include everything if the line given is zero (no line)
line.zero? || (test.start_line..test.end_line).include?(line)
end)
end

# Used to line up method names in `#sprintf` when `m` aborts
def column_size
# Boil down the collection of test methods to the name of the method's
# size, then find the largest one
@column_size ||= map { |test| test.name.to_s.size }.max
end

# Be considerate when printing out tests and pre-sort them by line number
def by_line_number(&block)
# On each member of the collection, sort by line number and yield
# the block into the sorted collection
sort_by(&:start_line).each(&block)
end
end
Expand Down
20 changes: 20 additions & 0 deletions lib/m/test_method.rb
@@ -1,10 +1,30 @@
module M
### Simple data structure for what a test method contains.
#
# Too lazy to make a class for this when it's really just a bag of data
# without any behavior.
#
# Includes the name of this method, what line on the file it begins on,
# and where it ends.
class TestMethod < Struct.new(:name, :start_line, :end_line)
# Set up a new test method for this test suite class
def self.create(suite_class, test_method)
# Hopefully it's been defined as an instance method, so we'll need to
# look up the ruby Method instance for it
method = suite_class.instance_method(test_method)

# Ruby can find the starting line for us, so pull that out of the array
start_line = method.source_location.last

# Ruby can't find the end line however, and I'm too lazy to write
# a parser. Instead, `method_source` adds `Method#source` so we can
# deduce this ourselves.
#
# The end line should be the number of line breaks in the method source,
# added to the starting line and subtracted by one.
end_line = method.source.split("\n").size + start_line - 1

# Shove the given attributes into a new databag
new(test_method, start_line, end_line)
end
end
Expand Down
2 changes: 0 additions & 2 deletions lib/m/version.rb

This file was deleted.

0 comments on commit 64b610a

Please sign in to comment.