From 64b610a7e095805706e9dd43bd49ef69d982b1a3 Mon Sep 17 00:00:00 2001 From: Nick Quaranto Date: Wed, 11 Jan 2012 16:59:44 -0500 Subject: [PATCH] Rocco it up --- README | 18 ++++++++++ README.md | 74 ---------------------------------------- lib/m.rb | 14 +++++--- lib/m/runner.rb | 27 ++++++++++++++- lib/m/test_collection.rb | 16 +++++++++ lib/m/test_method.rb | 20 +++++++++++ lib/m/version.rb | 2 -- 7 files changed, 90 insertions(+), 81 deletions(-) create mode 100644 README delete mode 100644 README.md delete mode 100644 lib/m/version.rb diff --git a/README b/README new file mode 100644 index 0000000..5b4f05e --- /dev/null +++ b/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: + + diff --git a/README.md b/README.md deleted file mode 100644 index 737c275..0000000 --- a/README.md +++ /dev/null @@ -1,74 +0,0 @@ -# m [![m ci](https://secure.travis-ci.org/qrush/m.png)](http://travis-ci.org/qrush/m) - -`m` stands for :metal: (metal), which is a better test/unit test runner. @sferik took `t` so this was the next best option. - -![Rush is a heavy metal band. Look it up on Wikipedia.](https://raw.github.com/qrush/m/master/rush.jpg) - -[Rush at the Bristol Colston Hall May 1979](http://www.flickr.com/photos/8507625@N02/3468299995/) - -## Install - -Install via: - - 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! - -## 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. - -Given this file: - - $ cat -n test/example_test.rb - 1 require 'test/unit' - 2 - 3 class ExampleTest < Test::Unit::TestCase - 4 def test_apple - 5 assert_equal 1, 1 - 6 end - 7 - 8 def test_banana - 9 assert_equal 1, 1 - 10 end - 11 end - -You can run a test by line number, using format `m TEST_FILE:LINE_NUMBER_OF_TEST`: - - $ m test/example_test.rb:4 - Run options: -n /test_apple/ - - # Running tests: - - . - - Finished tests in 0.000525s, 1904.7619 tests/s, 1904.7619 assertions/s. - - 1 tests, 1 assertions, 0 failures, 0 errors, 0 skips - -Hit the wrong line number? No problem, `m` helps you out: - - $ m test/example_test.rb:2 - No tests found on line 2. Valid tests to run: - - test_apple: m test/examples/test_unit_example_test.rb:4 - test_banana: m test/examples/test_unit_example_test.rb:8 - -Want to run the whole test? Just leave off the line number. - - $ m test/example_test.rb - Run options: - - # Running tests: - - .. - - Finished tests in 0.001293s, 1546.7904 tests/s, 3093.5808 assertions/s. - - 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. - -## License - -This gem is MIT licensed, please see `LICENSE` for more information. diff --git a/lib/m.rb b/lib/m.rb index b21e43f..a3e7c80 100644 --- a/lib/m.rb +++ b/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) @@ -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: # @@ -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" diff --git a/lib/m/runner.rb b/lib/m/runner.rb index 1154de7..e6a1364 100644 --- a/lib/m/runner.rb +++ b/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 diff --git a/lib/m/test_collection.rb b/lib/m/test_collection.rb index fdae340..42861b2 100644 --- a/lib/m/test_collection.rb +++ b/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 diff --git a/lib/m/test_method.rb b/lib/m/test_method.rb index 03bc1dd..824a6a0 100644 --- a/lib/m/test_method.rb +++ b/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 diff --git a/lib/m/version.rb b/lib/m/version.rb deleted file mode 100644 index c1cd072..0000000 --- a/lib/m/version.rb +++ /dev/null @@ -1,2 +0,0 @@ -module M -end