1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/test/error_highlight/test_error_highlight.rb
eileencodes 350d0aa023 [ruby/error_highlight] Support nodes in spot
Fixes a bug where `spot` was using the wrong local variable.

We want to use error highlight with code that has been eval'd,
specifically ERB templates. We can recover the compiled source code of
the ERB template but we need an API to pass the node into error
highlight's `spot`.

Required Ruby PR: https://github.com/ruby/ruby/pull/6593

https://github.com/ruby/error_highlight/commit/0b1b650a59

Co-authored-by: Aaron Patterson <tenderlove@ruby-lang.org>
2022-10-31 04:58:38 +00:00

1292 lines
22 KiB
Ruby

require "test/unit"
require "error_highlight"
require "tempfile"
class ErrorHighlightTest < Test::Unit::TestCase
class DummyFormatter
def self.message_for(corrections)
""
end
end
def setup
if defined?(DidYouMean)
@did_you_mean_old_formatter = DidYouMean.formatter
DidYouMean.formatter = DummyFormatter
end
end
def teardown
if defined?(DidYouMean)
DidYouMean.formatter = @did_you_mean_old_formatter
end
end
if Exception.method_defined?(:detailed_message)
def assert_error_message(klass, expected_msg, &blk)
err = assert_raise(klass, &blk)
assert_equal(expected_msg.chomp, err.detailed_message(highlight: false).sub(/ \((?:NoMethod|Name)Error\)/, ""))
end
else
def assert_error_message(klass, expected_msg, &blk)
err = assert_raise(klass, &blk)
assert_equal(expected_msg.chomp, err.message)
end
end
def test_CALL_noarg_1
assert_error_message(NoMethodError, <<~END) do
undefined method `foo' for nil:NilClass
nil.foo + 1
^^^^
END
nil.foo + 1
end
end
def test_CALL_noarg_2
assert_error_message(NoMethodError, <<~END) do
undefined method `foo' for nil:NilClass
.foo + 1
^^^^
END
nil
.foo + 1
end
end
def test_CALL_noarg_3
assert_error_message(NoMethodError, <<~END) do
undefined method `foo' for nil:NilClass
foo + 1
^^^
END
nil.
foo + 1
end
end
def test_CALL_noarg_4
assert_error_message(NoMethodError, <<~END) do
undefined method `foo' for nil:NilClass
(nil).foo + 1
^^^^
END
(nil).foo + 1
end
end
def test_CALL_arg_1
assert_error_message(NoMethodError, <<~END) do
undefined method `foo' for nil:NilClass
nil.foo (42)
^^^^
END
nil.foo (42)
end
end
def test_CALL_arg_2
assert_error_message(NoMethodError, <<~END) do
undefined method `foo' for nil:NilClass
.foo (
^^^^
END
nil
.foo (
42
)
end
end
def test_CALL_arg_3
assert_error_message(NoMethodError, <<~END) do
undefined method `foo' for nil:NilClass
foo (
^^^
END
nil.
foo (
42
)
end
end
def test_CALL_arg_4
assert_error_message(NoMethodError, <<~END) do
undefined method `foo' for nil:NilClass
nil.foo(42)
^^^^
END
nil.foo(42)
end
end
def test_CALL_arg_5
assert_error_message(NoMethodError, <<~END) do
undefined method `foo' for nil:NilClass
.foo(
^^^^
END
nil
.foo(
42
)
end
end
def test_CALL_arg_6
assert_error_message(NoMethodError, <<~END) do
undefined method `foo' for nil:NilClass
foo(
^^^
END
nil.
foo(
42
)
end
end
def test_QCALL_1
assert_error_message(NoMethodError, <<~END) do
undefined method `foo' for 1:Integer
1&.foo
^^^^^
END
1&.foo
end
end
def test_QCALL_2
assert_error_message(NoMethodError, <<~END) do
undefined method `foo' for 1:Integer
1&.foo(42)
^^^^^
END
1&.foo(42)
end
end
def test_CALL_aref_1
assert_error_message(NoMethodError, <<~END) do
undefined method `[]' for nil:NilClass
nil [ ]
^^^
END
nil [ ]
end
end
def test_CALL_aref_2
assert_error_message(NoMethodError, <<~END) do
undefined method `[]' for nil:NilClass
nil [0]
^^^
END
nil [0]
end
end
def test_CALL_aref_3
assert_error_message(NoMethodError, <<~END) do
undefined method `[]' for nil:NilClass
END
nil [
0
]
end
end
def test_CALL_aref_4
v = Object.new
assert_error_message(NoMethodError, <<~END) do
undefined method `[]' for #{ v.inspect }
v &.[](0)
^^^^
END
v &.[](0)
end
end
def test_CALL_aref_5
assert_error_message(NoMethodError, <<~END) do
undefined method `[]' for nil:NilClass
(nil)[ ]
^^^
END
(nil)[ ]
end
end
def test_CALL_aset
assert_error_message(NoMethodError, <<~END) do
undefined method `[]=' for nil:NilClass
nil.[]=
^^^^
END
nil.[]=
end
end
def test_CALL_op_asgn
v = nil
assert_error_message(NoMethodError, <<~END) do
undefined method `+' for nil:NilClass
v += 42
^
END
v += 42
end
end
def test_CALL_special_call_1
assert_error_message(NoMethodError, <<~END) do
undefined method `call' for nil:NilClass
END
nil.()
end
end
def test_CALL_special_call_2
assert_error_message(NoMethodError, <<~END) do
undefined method `call' for nil:NilClass
END
nil.(42)
end
end
def test_CALL_send
assert_error_message(NoMethodError, <<~END) do
undefined method `foo' for nil:NilClass
nil.send(:foo, 42)
^^^^^
END
nil.send(:foo, 42)
end
end
def test_ATTRASGN_1
assert_error_message(NoMethodError, <<~END) do
undefined method `[]=' for nil:NilClass
nil [ ] = 42
^^^^^
END
nil [ ] = 42
end
end
def test_ATTRASGN_2
assert_error_message(NoMethodError, <<~END) do
undefined method `[]=' for nil:NilClass
nil [0] = 42
^^^^^
END
nil [0] = 42
end
end
def test_ATTRASGN_3
assert_error_message(NoMethodError, <<~END) do
undefined method `foo=' for nil:NilClass
nil.foo = 42
^^^^^^
END
nil.foo = 42
end
end
def test_ATTRASGN_4
assert_error_message(NoMethodError, <<~END) do
undefined method `[]=' for nil:NilClass
(nil)[0] = 42
^^^^^
END
(nil)[0] = 42
end
end
def test_ATTRASGN_5
assert_error_message(NoMethodError, <<~END) do
undefined method `foo=' for nil:NilClass
(nil).foo = 42
^^^^^^
END
(nil).foo = 42
end
end
def test_OPCALL_binary_1
assert_error_message(NoMethodError, <<~END) do
undefined method `+' for nil:NilClass
nil + 42
^
END
nil + 42
end
end
def test_OPCALL_binary_2
assert_error_message(NoMethodError, <<~END) do
undefined method `+' for nil:NilClass
nil + # comment
^
END
nil + # comment
42
end
end
def test_OPCALL_binary_3
assert_error_message(NoMethodError, <<~END) do
undefined method `+' for nil:NilClass
(nil) + 42
^
END
(nil) + 42
end
end
def test_OPCALL_unary_1
assert_error_message(NoMethodError, <<~END) do
undefined method `+@' for nil:NilClass
+ nil
^
END
+ nil
end
end
def test_OPCALL_unary_2
assert_error_message(NoMethodError, <<~END) do
undefined method `+@' for nil:NilClass
+(nil)
^
END
+(nil)
end
end
def test_FCALL_1
assert_error_message(NoMethodError, <<~END) do
undefined method `foo' for nil:NilClass
nil.instance_eval { foo() }
^^^
END
nil.instance_eval { foo() }
end
end
def test_FCALL_2
assert_error_message(NoMethodError, <<~END) do
undefined method `foo' for nil:NilClass
nil.instance_eval { foo(42) }
^^^
END
nil.instance_eval { foo(42) }
end
end
def test_VCALL_2
assert_error_message(NameError, <<~END) do
undefined local variable or method `foo' for nil:NilClass
nil.instance_eval { foo }
^^^
END
nil.instance_eval { foo }
end
end
def test_OP_ASGN1_aref_1
v = nil
assert_error_message(NoMethodError, <<~END) do
undefined method `[]' for nil:NilClass
v [0] += 42
^^^
END
v [0] += 42
end
end
def test_OP_ASGN1_aref_2
v = nil
assert_error_message(NoMethodError, <<~END) do
undefined method `[]' for nil:NilClass
v [0] += # comment
^^^
END
v [0] += # comment
42
end
end
def test_OP_ASGN1_aref_3
v = nil
assert_error_message(NoMethodError, <<~END) do
undefined method `[]' for nil:NilClass
END
v [
0
] += # comment
42
end
end
def test_OP_ASGN1_aref_4
v = nil
assert_error_message(NoMethodError, <<~END) do
undefined method `[]' for nil:NilClass
(v)[0] += 42
^^^
END
(v)[0] += 42
end
end
def test_OP_ASGN1_op_1
v = Object.new
def v.[](x); nil; end
assert_error_message(NoMethodError, <<~END) do
undefined method `+' for nil:NilClass
v [0] += 42
^
END
v [0] += 42
end
end
def test_OP_ASGN1_op_2
v = Object.new
def v.[](x); nil; end
assert_error_message(NoMethodError, <<~END) do
undefined method `+' for nil:NilClass
v [0 ] += # comment
^
END
v [0 ] += # comment
42
end
end
def test_OP_ASGN1_op_3
v = Object.new
def v.[](x); nil; end
assert_error_message(NoMethodError, <<~END) do
undefined method `+' for nil:NilClass
END
v [
0
] +=
42
end
end
def test_OP_ASGN1_op_4
v = Object.new
def v.[](x); nil; end
assert_error_message(NoMethodError, <<~END) do
undefined method `+' for nil:NilClass
(v)[0] += 42
^
END
(v)[0] += 42
end
end
def test_OP_ASGN1_aset_1
v = Object.new
def v.[](x); 1; end
assert_error_message(NoMethodError, <<~END) do
undefined method `[]=' for #{ v.inspect }
v [0] += 42
^^^^^^
END
v [0] += 42
end
end
def test_OP_ASGN1_aset_2
v = Object.new
def v.[](x); 1; end
assert_error_message(NoMethodError, <<~END) do
undefined method `[]=' for #{ v.inspect }
v [0] += # comment
^^^^^^
END
v [0] += # comment
42
end
end
def test_OP_ASGN1_aset_3
v = Object.new
def v.[](x); 1; end
assert_error_message(NoMethodError, <<~END) do
undefined method `[]=' for #{ v.inspect }
END
v [
0
] +=
42
end
end
def test_OP_ASGN1_aset_4
v = Object.new
def v.[](x); 1; end
assert_error_message(NoMethodError, <<~END) do
undefined method `[]=' for #{ v.inspect }
(v)[0] += 42
^^^^^^
END
(v)[0] += 42
end
end
def test_OP_ASGN2_read_1
v = nil
assert_error_message(NoMethodError, <<~END) do
undefined method `foo' for nil:NilClass
v.foo += 42
^^^^
END
v.foo += 42
end
end
def test_OP_ASGN2_read_2
v = nil
assert_error_message(NoMethodError, <<~END) do
undefined method `foo' for nil:NilClass
v.foo += # comment
^^^^
END
v.foo += # comment
42
end
end
def test_OP_ASGN2_read_3
v = nil
assert_error_message(NoMethodError, <<~END) do
undefined method `foo' for nil:NilClass
(v).foo += 42
^^^^
END
(v).foo += 42
end
end
def test_OP_ASGN2_op_1
v = Object.new
def v.foo; nil; end
assert_error_message(NoMethodError, <<~END) do
undefined method `+' for nil:NilClass
v.foo += 42
^
END
v.foo += 42
end
end
def test_OP_ASGN2_op_2
v = Object.new
def v.foo; nil; end
assert_error_message(NoMethodError, <<~END) do
undefined method `+' for nil:NilClass
v.foo += # comment
^
END
v.foo += # comment
42
end
end
def test_OP_ASGN2_op_3
v = Object.new
def v.foo; nil; end
assert_error_message(NoMethodError, <<~END) do
undefined method `+' for nil:NilClass
(v).foo += 42
^
END
(v).foo += 42
end
end
def test_OP_ASGN2_write_1
v = Object.new
def v.foo; 1; end
assert_error_message(NoMethodError, <<~END) do
undefined method `foo=' for #{ v.inspect }
v.foo += 42
^^^^^^^
END
v.foo += 42
end
end
def test_OP_ASGN2_write_2
v = Object.new
def v.foo; 1; end
assert_error_message(NoMethodError, <<~END) do
undefined method `foo=' for #{ v.inspect }
v.foo += # comment
^^^^^^^
END
v.foo += # comment
42
end
end
def test_OP_ASGN2_write_3
v = Object.new
def v.foo; 1; end
assert_error_message(NoMethodError, <<~END) do
undefined method `foo=' for #{ v.inspect }
(v).foo += 42
^^^^^^^
END
(v).foo += 42
end
end
def test_CONST
assert_error_message(NameError, <<~END) do
uninitialized constant ErrorHighlightTest::NotDefined
1 + NotDefined + 1
^^^^^^^^^^
END
1 + NotDefined + 1
end
end
def test_COLON2_1
assert_error_message(NameError, <<~END) do
uninitialized constant ErrorHighlightTest::NotDefined
ErrorHighlightTest::NotDefined
^^^^^^^^^^^^
END
ErrorHighlightTest::NotDefined
end
end
def test_COLON2_2
assert_error_message(NameError, <<~END) do
uninitialized constant ErrorHighlightTest::NotDefined
NotDefined
^^^^^^^^^^
END
ErrorHighlightTest::
NotDefined
end
end
def test_COLON3
assert_error_message(NameError, <<~END) do
uninitialized constant NotDefined
::NotDefined
^^^^^^^^^^^^
END
::NotDefined
end
end
module OP_CDECL_TEST
Nil = nil
end
def test_OP_CDECL_read_1
assert_error_message(NameError, <<~END) do
uninitialized constant ErrorHighlightTest::OP_CDECL_TEST::NotDefined
OP_CDECL_TEST::NotDefined += 1
^^^^^^^^^^^^
END
OP_CDECL_TEST::NotDefined += 1
end
end
def test_OP_CDECL_read_2
assert_error_message(NameError, <<~END) do
uninitialized constant ErrorHighlightTest::OP_CDECL_TEST::NotDefined
OP_CDECL_TEST::NotDefined += # comment
^^^^^^^^^^^^
END
OP_CDECL_TEST::NotDefined += # comment
1
end
end
def test_OP_CDECL_read_3
assert_error_message(NameError, <<~END) do
uninitialized constant ErrorHighlightTest::OP_CDECL_TEST::NotDefined
END
OP_CDECL_TEST::
NotDefined += 1
end
end
def test_OP_CDECL_op_1
assert_error_message(NoMethodError, <<~END) do
undefined method `+' for nil:NilClass
OP_CDECL_TEST::Nil += 1
^
END
OP_CDECL_TEST::Nil += 1
end
end
def test_OP_CDECL_op_2
assert_error_message(NoMethodError, <<~END) do
undefined method `+' for nil:NilClass
OP_CDECL_TEST::Nil += # comment
^
END
OP_CDECL_TEST::Nil += # comment
1
end
end
def test_OP_CDECL_op_3
assert_error_message(NoMethodError, <<~END) do
undefined method `+' for nil:NilClass
Nil += 1
^
END
OP_CDECL_TEST::
Nil += 1
end
end
def test_OP_CDECL_toplevel_1
assert_error_message(NameError, <<~END) do
uninitialized constant NotDefined
::NotDefined += 1
^^^^^^^^^^^^
END
::NotDefined += 1
end
end
def test_OP_CDECL_toplevel_2
assert_error_message(NoMethodError, <<~END) do
undefined method `+' for ErrorHighlightTest:Class
::ErrorHighlightTest += 1
^
END
::ErrorHighlightTest += 1
end
end
def test_explicit_raise_name_error
assert_error_message(NameError, <<~END) do
NameError
raise NameError
^^^^^
END
raise NameError
end
end
def test_explicit_raise_no_method_error
assert_error_message(NoMethodError, <<~END) do
NoMethodError
raise NoMethodError
^^^^^
END
raise NoMethodError
end
end
def test_const_get
assert_error_message(NameError, <<~END) do
uninitialized constant ErrorHighlightTest::NotDefined
ErrorHighlightTest.const_get(:NotDefined)
^^^^^^^^^^
END
ErrorHighlightTest.const_get(:NotDefined)
end
end
def test_local_variable_get
b = binding
assert_error_message(NameError, <<~END) do
local variable `foo' is not defined for #{ b.inspect }
b.local_variable_get(:foo)
^^^^^^^^^^^^^^^^^^^
END
b.local_variable_get(:foo)
end
end
def test_multibyte
assert_error_message(NoMethodError, <<~END) do
undefined method `' for nil:NilClass
END
nil.あいうえお
end
end
if false
def test_args_CALL_1
assert_error_message(TypeError, <<~END) do
nil can't be coerced into Integer
1.+(nil)
^^^
END
1.+(nil)
end
end
def test_args_CALL_2
v = []
assert_error_message(TypeError, <<~END) do
no implicit conversion from nil to integer
v[nil]
^^^
END
v[nil]
end
end
def test_args_ATTRASGN_1
v = []
assert_error_message(ArgumentError, <<~END) do
wrong number of arguments (given 1, expected 2..3)
v [ ] = 1
^^^^^^
END
v [ ] = 1
end
end
def test_args_ATTRASGN_2
v = []
assert_error_message(TypeError, <<~END) do
no implicit conversion from nil to integer
v [nil] = 1
^^^^^^^^
END
v [nil] = 1
end
end
def test_args_ATTRASGN_3
assert_error_message(TypeError, <<~END) do
no implicit conversion of String into Integer
$stdin.lineno = "str"
^^^^^
END
$stdin.lineno = "str"
end
end
def test_args_OPCALL
assert_error_message(TypeError, <<~END) do
nil can't be coerced into Integer
1 + nil
^^^
END
1 + nil
end
end
def test_args_FCALL_1
assert_error_message(TypeError, <<~END) do
no implicit conversion of Symbol into String
"str".instance_eval { gsub("foo", :sym) }
^^^^^^^^^^^
END
"str".instance_eval { gsub("foo", :sym) }
end
end
def test_args_FCALL_2
assert_error_message(TypeError, <<~END) do
no implicit conversion of Symbol into String
"str".instance_eval { gsub "foo", :sym }
^^^^^^^^^^^
END
"str".instance_eval { gsub "foo", :sym }
end
end
def test_args_OP_ASGN1_aref_1
v = []
assert_error_message(TypeError, <<~END) do
no implicit conversion from nil to integer
v [nil] += 42
^^^^^^^^^^
END
v [nil] += 42
end
end
def test_args_OP_ASGN1_aref_2
v = []
assert_error_message(ArgumentError, <<~END) do
wrong number of arguments (given 0, expected 1..2)
v [ ] += 42
^^^^^^^^
END
v [ ] += 42
end
end
def test_args_OP_ASGN1_op
v = [1]
assert_error_message(TypeError, <<~END) do
nil can't be coerced into Integer
v [0] += nil
^^^^^^^^^
END
v [0] += nil
end
end
def test_args_OP_ASGN2
v = Object.new
def v.foo; 1; end
assert_error_message(TypeError, <<~END) do
nil can't be coerced into Integer
v.foo += nil
^^^
END
v.foo += nil
end
end
end
def test_custom_formatter
custom_formatter = Object.new
def custom_formatter.message_for(spot)
"\n\n" + spot.except(:script_lines).inspect
end
original_formatter, ErrorHighlight.formatter = ErrorHighlight.formatter, custom_formatter
assert_error_message(NoMethodError, <<~END) do
undefined method `time' for 1:Integer
{:first_lineno=>#{ __LINE__ + 3 }, :first_column=>7, :last_lineno=>#{ __LINE__ + 3 }, :last_column=>12, :snippet=>" 1.time {}\\n"}
END
1.time {}
end
ensure
ErrorHighlight.formatter = original_formatter
end
def test_hard_tabs
Tempfile.create(["error_highlight_test", ".rb"], binmode: true) do |tmp|
tmp << "\t \t1.time {}\n"
tmp.close
assert_error_message(NoMethodError, <<~END.gsub("_", "\t")) do
undefined method `time' for 1:Integer
_ _1.time {}
_ _ ^^^^^
END
load tmp.path
end
end
end
def test_no_final_newline
Tempfile.create(["error_highlight_test", ".rb"], binmode: true) do |tmp|
tmp << "1.time {}"
tmp.close
assert_error_message(NoMethodError, <<~END) do
undefined method `time' for 1:Integer
1.time {}
^^^^^
END
load tmp.path
end
end
end
def test_simulate_funcallv_from_embedded_ruby
assert_error_message(NoMethodError, <<~END) do
undefined method `foo' for nil:NilClass
END
nil.foo + 1
rescue NoMethodError => exc
def exc.backtrace_locations = []
raise
end
end
def test_spoofed_filename
Tempfile.create(["error_highlight_test", ".rb"], binmode: true) do |tmp|
tmp << "module Dummy\nend\n"
tmp.close
assert_error_message(NameError, <<~END) do
undefined local variable or method `foo' for "dummy":String
END
"dummy".instance_eval do
eval <<-END, nil, tmp.path
foo
END
end
end
end
end
def raise_name_error
1.time
end
def test_spot_with_backtrace_location
lineno = __LINE__
begin
raise_name_error
rescue NameError => exc
end
spot = ErrorHighlight.spot(exc).except(:script_lines)
assert_equal(lineno - 4, spot[:first_lineno])
assert_equal(lineno - 4, spot[:last_lineno])
assert_equal(5, spot[:first_column])
assert_equal(10, spot[:last_column])
assert_equal(" 1.time\n", spot[:snippet])
spot = ErrorHighlight.spot(exc, backtrace_location: exc.backtrace_locations[1]).except(:script_lines)
assert_equal(lineno + 2, spot[:first_lineno])
assert_equal(lineno + 2, spot[:last_lineno])
assert_equal(6, spot[:first_column])
assert_equal(22, spot[:last_column])
assert_equal(" raise_name_error\n", spot[:snippet])
end
def test_spot_with_node
omit unless RubyVM::AbstractSyntaxTree.respond_to?(:node_id_for_backtrace_location)
begin
raise_name_error
rescue NameError => exc
end
bl = exc.backtrace_locations.first
expected_spot = ErrorHighlight.spot(exc, backtrace_location: bl)
ast = RubyVM::AbstractSyntaxTree.parse_file(__FILE__, keep_script_lines: true)
node_id = RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location(bl)
node = find_node_by_id(ast, node_id)
actual_spot = ErrorHighlight.spot(node)
assert_equal expected_spot, actual_spot
end
private
def find_node_by_id(node, node_id)
return node if node.node_id == node_id
node.children.each do |child|
next unless child.is_a?(RubyVM::AbstractSyntaxTree::Node)
found = find_node_by_id(child, node_id)
return found if found
end
return false
end
end