Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 3 additions & 19 deletions lib/m/test_method.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require "method_source"
require "m/test_parser"

module M
### Simple data structure for what a test method contains.
Expand All @@ -9,26 +9,10 @@ module M
# Includes the name of this method, what line on the file it begins on,
# and where it ends.
TestMethod = Struct.new :name, :start_line, :end_line do
# 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
_file, line_range = TestParser.definition_for method
new test_method, line_range.begin, line_range.end
end
end
end
86 changes: 86 additions & 0 deletions lib/m/test_parser.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
require "ripper"

module M
# Parse a test file to extract the line ranges of all tests in both
# method-style (def test_foo) and declarative-style (test "foo" do)
class TestParser < Ripper
# Brazenly stolen from Rails::TestUnit::TestParser by Carl Brasic @brasic

# Helper to translate a method object into the path and line range where
# the method was defined.
def self.definition_for(method_obj)
path, begin_line = method_obj.source_location
begins_to_ends = new(File.read(path), path).parse
return unless (end_line = begins_to_ends[begin_line])
[path, (begin_line..end_line)]
end

def initialize(*)
# A hash mapping the 1-indexed line numbers that tests start on to where they end.
@begins_to_ends = {}
super
end

def parse
super
@begins_to_ends
end

# method test e.g. `def test_some_description`
# This event's first argument gets the `ident` node containing the method
# name, which we have overridden to return the line number of the ident
# instead.
def on_def(begin_line, *)
@begins_to_ends[begin_line] = lineno
end

# Everything past this point is to support declarative tests, which
# require more work to get right because of the many different ways
# methods can be invoked in ruby, all of which are parsed differently.
#
# The approach is just to store the current line number when the
# "test" method is called and pass it up the tree so it's available at
# the point when we also know the line where the associated block ends.

def on_method_add_block(begin_line, end_line)
if begin_line && end_line
@begins_to_ends[begin_line] = end_line
end
end

def on_command_call(*, begin_lineno, _args)
begin_lineno
end

def first_arg(arg, *)
arg
end

def just_lineno(*)
lineno
end

alias_method :on_method_add_arg, :first_arg
alias_method :on_command, :first_arg
alias_method :on_stmts_add, :first_arg
alias_method :on_arg_paren, :first_arg
alias_method :on_bodystmt, :first_arg

alias_method :on_ident, :just_lineno
alias_method :on_do_block, :just_lineno
alias_method :on_stmts_new, :just_lineno
alias_method :on_brace_block, :just_lineno

def on_args_new
[]
end

def on_args_add(parts, part)
parts << part
end

def on_args_add_block(args, *rest)
args.first
end
end
end
1 change: 0 additions & 1 deletion m.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ Gem::Specification.new do |gem|
gem.require_paths = ["lib"]
gem.version = M::VERSION

gem.add_runtime_dependency "method_source", ">= 0.6.7"
gem.add_runtime_dependency "rake", ">= 0.9.2.2"

gem.add_development_dependency "activesupport"
Expand Down