2018-03-04 10:09:32 -05:00
|
|
|
require_relative '../../spec_helper'
|
|
|
|
require_relative 'fixtures/classes'
|
2017-05-07 08:04:49 -04:00
|
|
|
|
|
|
|
describe "Kernel#warn" do
|
|
|
|
before :each do
|
|
|
|
@before_verbose = $VERBOSE
|
|
|
|
@before_separator = $/
|
|
|
|
end
|
|
|
|
|
|
|
|
after :each do
|
2020-01-20 18:37:44 -05:00
|
|
|
$VERBOSE = nil
|
2017-05-07 08:04:49 -04:00
|
|
|
$/ = @before_separator
|
2020-01-20 18:37:44 -05:00
|
|
|
$VERBOSE = @before_verbose
|
2017-05-07 08:04:49 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it "is a private method" do
|
|
|
|
Kernel.should have_private_instance_method(:warn)
|
|
|
|
end
|
|
|
|
|
Add rb_category_warn{,ing} for warning messages with categories
This adds the following C-API functions that can be used to emit
warnings with categories included:
```c
void rb_category_warn(const char *, const char*, ...)
void rb_category_warning(const char*, const char*, ...)
```
Internally in error.c, there is an rb_warn_category function
that will call Warning.warn with the string and the category
keyword if it doesn't have an arity of 1, and will call
Warning.warn with just the string if it has an arity of 1.
This refactors the rb_warn_deprecated{,_to_remove} functions
to use rb_warn_category.
This makes Kernel#warn accept a category keyword and pass it
to Warning.warn, so that Ruby methods can more easily emit
warnings with categories. rb_warn_category makes sure that
the passed category is a already defined category symbol
before calling Warning.warn.
The only currently defined warning category is :deprecated,
since that is what is already used. More categories can be
added in later commits.
2020-09-03 11:00:10 -04:00
|
|
|
it "accepts multiple arguments" do
|
2017-05-07 08:04:49 -04:00
|
|
|
Kernel.method(:warn).arity.should < 0
|
|
|
|
end
|
|
|
|
|
|
|
|
it "does not append line-end if last character is line-end" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> {
|
2017-05-07 08:04:49 -04:00
|
|
|
$VERBOSE = true
|
|
|
|
warn("this is some simple text with line-end\n")
|
|
|
|
}.should output(nil, "this is some simple text with line-end\n")
|
|
|
|
end
|
|
|
|
|
|
|
|
it "calls #write on $stderr if $VERBOSE is true" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> {
|
2017-05-07 08:04:49 -04:00
|
|
|
$VERBOSE = true
|
|
|
|
warn("this is some simple text")
|
|
|
|
}.should output(nil, "this is some simple text\n")
|
|
|
|
end
|
|
|
|
|
|
|
|
it "calls #write on $stderr if $VERBOSE is false" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> {
|
2017-05-07 08:04:49 -04:00
|
|
|
$VERBOSE = false
|
|
|
|
warn("this is some simple text")
|
|
|
|
}.should output(nil, "this is some simple text\n")
|
|
|
|
end
|
|
|
|
|
|
|
|
it "does not call #write on $stderr if $VERBOSE is nil" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> {
|
2017-05-07 08:04:49 -04:00
|
|
|
$VERBOSE = nil
|
|
|
|
warn("this is some simple text")
|
|
|
|
}.should output(nil, "")
|
|
|
|
end
|
|
|
|
|
|
|
|
it "writes each argument on a line when passed multiple arguments" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> {
|
2017-05-07 08:04:49 -04:00
|
|
|
$VERBOSE = true
|
|
|
|
warn("line 1", "line 2")
|
|
|
|
}.should output(nil, "line 1\nline 2\n")
|
|
|
|
end
|
|
|
|
|
|
|
|
it "writes each array element on a line when passes an array" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> {
|
2017-05-07 08:04:49 -04:00
|
|
|
$VERBOSE = true
|
|
|
|
warn(["line 1", "line 2"])
|
|
|
|
}.should output(nil, "line 1\nline 2\n")
|
|
|
|
end
|
|
|
|
|
|
|
|
it "does not write strings when passed no arguments" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> {
|
2017-05-07 08:04:49 -04:00
|
|
|
$VERBOSE = true
|
|
|
|
warn
|
|
|
|
}.should output("", "")
|
|
|
|
end
|
|
|
|
|
|
|
|
it "writes the default record separator and NOT $/ to $stderr after the warning message" do
|
2019-07-27 06:40:09 -04:00
|
|
|
-> {
|
2017-05-07 08:04:49 -04:00
|
|
|
$VERBOSE = true
|
|
|
|
$/ = 'rs'
|
|
|
|
warn("")
|
|
|
|
}.should output(nil, /\n/)
|
|
|
|
end
|
2018-06-13 17:41:45 -04:00
|
|
|
|
2019-02-21 10:38:59 -05:00
|
|
|
it "writes to_s representation if passed a non-string" do
|
|
|
|
obj = mock("obj")
|
|
|
|
obj.should_receive(:to_s).and_return("to_s called")
|
2019-07-27 06:40:09 -04:00
|
|
|
-> {
|
2019-02-21 10:38:59 -05:00
|
|
|
$VERBOSE = true
|
|
|
|
warn(obj)
|
|
|
|
}.should output(nil, "to_s called\n")
|
|
|
|
end
|
|
|
|
|
2020-02-08 05:43:27 -05:00
|
|
|
describe ":uplevel keyword argument" do
|
|
|
|
before :each do
|
|
|
|
$VERBOSE = true
|
|
|
|
end
|
|
|
|
|
|
|
|
it "prepends a message with specified line from the backtrace" do
|
|
|
|
w = KernelSpecs::WarnInNestedCall.new
|
|
|
|
|
|
|
|
-> { w.f4("foo", 0) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.warn_call_lineno}: warning: foo|)
|
|
|
|
-> { w.f4("foo", 1) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.f1_call_lineno}: warning: foo|)
|
|
|
|
-> { w.f4("foo", 2) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.f2_call_lineno}: warning: foo|)
|
|
|
|
-> { w.f4("foo", 3) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.f3_call_lineno}: warning: foo|)
|
2018-06-13 17:41:45 -04:00
|
|
|
end
|
2019-09-29 12:01:32 -04:00
|
|
|
|
2020-07-27 15:41:08 -04:00
|
|
|
# Test both explicitly without and with RubyGems as RubyGems overrides Kernel#warn
|
|
|
|
it "shows the caller of #require and not #require itself without RubyGems" do
|
|
|
|
file = fixture(__FILE__ , "warn_require_caller.rb")
|
|
|
|
ruby_exe(file, options: "--disable-gems", args: "2>&1").should == "#{file}:2: warning: warn-require-warning\n"
|
|
|
|
end
|
|
|
|
|
2021-07-29 16:11:21 -04:00
|
|
|
it "shows the caller of #require and not #require itself with RubyGems loaded" do
|
|
|
|
file = fixture(__FILE__ , "warn_require_caller.rb")
|
|
|
|
ruby_exe(file, options: "-rrubygems", args: "2>&1").should == "#{file}:2: warning: warn-require-warning\n"
|
2020-07-27 15:41:08 -04:00
|
|
|
end
|
|
|
|
|
2020-10-11 07:36:25 -04:00
|
|
|
guard -> { Kernel.instance_method(:tap).source_location } do
|
|
|
|
it "skips <internal: core library methods defined in Ruby" do
|
|
|
|
file, line = Kernel.instance_method(:tap).source_location
|
|
|
|
file.should.start_with?('<internal:')
|
|
|
|
|
|
|
|
file = fixture(__FILE__ , "warn_core_method.rb")
|
|
|
|
n = 9
|
|
|
|
ruby_exe(file, options: "--disable-gems", args: "2>&1").lines.should == [
|
|
|
|
"#{file}:#{n+0}: warning: use X instead\n",
|
|
|
|
"#{file}:#{n+1}: warning: use X instead\n",
|
|
|
|
"#{file}:#{n+2}: warning: use X instead\n",
|
|
|
|
"#{file}:#{n+4}: warning: use X instead\n",
|
|
|
|
]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
Add rb_category_warn{,ing} for warning messages with categories
This adds the following C-API functions that can be used to emit
warnings with categories included:
```c
void rb_category_warn(const char *, const char*, ...)
void rb_category_warning(const char*, const char*, ...)
```
Internally in error.c, there is an rb_warn_category function
that will call Warning.warn with the string and the category
keyword if it doesn't have an arity of 1, and will call
Warning.warn with just the string if it has an arity of 1.
This refactors the rb_warn_deprecated{,_to_remove} functions
to use rb_warn_category.
This makes Kernel#warn accept a category keyword and pass it
to Warning.warn, so that Ruby methods can more easily emit
warnings with categories. rb_warn_category makes sure that
the passed category is a already defined category symbol
before calling Warning.warn.
The only currently defined warning category is :deprecated,
since that is what is already used. More categories can be
added in later commits.
2020-09-03 11:00:10 -04:00
|
|
|
ruby_version_is "3.0" do
|
|
|
|
it "accepts :category keyword with a symbol" do
|
|
|
|
-> {
|
|
|
|
$VERBOSE = true
|
|
|
|
warn("message", category: :deprecated)
|
|
|
|
}.should output(nil, "message\n")
|
|
|
|
end
|
|
|
|
|
|
|
|
it "accepts :category keyword with nil" do
|
|
|
|
-> {
|
|
|
|
$VERBOSE = true
|
|
|
|
warn("message", category: nil)
|
|
|
|
}.should output(nil, "message\n")
|
|
|
|
end
|
|
|
|
|
|
|
|
it "accepts :category keyword with object convertible to symbol" do
|
|
|
|
o = Object.new
|
|
|
|
def o.to_sym; :deprecated; end
|
|
|
|
-> {
|
|
|
|
$VERBOSE = true
|
|
|
|
warn("message", category: o)
|
|
|
|
}.should output(nil, "message\n")
|
|
|
|
end
|
|
|
|
|
|
|
|
it "raises if :category keyword is not nil and not convertible to symbol" do
|
|
|
|
-> {
|
|
|
|
$VERBOSE = true
|
|
|
|
warn("message", category: Object.new)
|
|
|
|
}.should raise_error(TypeError)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-02-08 05:43:27 -05:00
|
|
|
it "converts first arg using to_s" do
|
|
|
|
w = KernelSpecs::WarnInNestedCall.new
|
|
|
|
|
|
|
|
-> { w.f4(false, 0) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.warn_call_lineno}: warning: false|)
|
|
|
|
-> { w.f4(nil, 1) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.f1_call_lineno}: warning: |)
|
|
|
|
obj = mock("obj")
|
|
|
|
obj.should_receive(:to_s).and_return("to_s called")
|
|
|
|
-> { w.f4(obj, 2) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.f2_call_lineno}: warning: to_s called|)
|
2019-09-29 12:01:32 -04:00
|
|
|
end
|
2020-02-08 05:43:27 -05:00
|
|
|
|
|
|
|
it "does not prepend caller information if the uplevel argument is too large" do
|
|
|
|
w = KernelSpecs::WarnInNestedCall.new
|
|
|
|
-> { w.f4("foo", 100) }.should output(nil, "warning: foo\n")
|
|
|
|
end
|
|
|
|
|
|
|
|
it "prepends even if a message is empty or nil" do
|
|
|
|
w = KernelSpecs::WarnInNestedCall.new
|
|
|
|
|
|
|
|
-> { w.f4("", 0) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.warn_call_lineno}: warning: \n$|)
|
|
|
|
-> { w.f4(nil, 0) }.should output(nil, %r|core/kernel/fixtures/classes.rb:#{w.warn_call_lineno}: warning: \n$|)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "converts value to Integer" do
|
|
|
|
w = KernelSpecs::WarnInNestedCall.new
|
|
|
|
|
|
|
|
-> { w.f4(0.1) }.should output(nil, %r|classes.rb:#{w.warn_call_lineno}:|)
|
|
|
|
-> { w.f4(Rational(1, 2)) }.should output(nil, %r|classes.rb:#{w.warn_call_lineno}:|)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "raises ArgumentError if passed negative value" do
|
|
|
|
-> { warn "", uplevel: -2 }.should raise_error(ArgumentError)
|
|
|
|
-> { warn "", uplevel: -100 }.should raise_error(ArgumentError)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "raises ArgumentError if passed -1" do
|
|
|
|
-> { warn "", uplevel: -1 }.should raise_error(ArgumentError)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "raises TypeError if passed not Integer" do
|
|
|
|
-> { warn "", uplevel: "" }.should raise_error(TypeError)
|
|
|
|
-> { warn "", uplevel: [] }.should raise_error(TypeError)
|
|
|
|
-> { warn "", uplevel: {} }.should raise_error(TypeError)
|
|
|
|
-> { warn "", uplevel: Object.new }.should raise_error(TypeError)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
it "treats empty hash as no keyword argument" do
|
|
|
|
h = {}
|
|
|
|
-> { warn(**h) }.should_not complain(verbose: true)
|
|
|
|
-> { warn('foo', **h) }.should complain("foo\n")
|
2018-06-13 17:41:45 -04:00
|
|
|
end
|
2020-10-24 09:52:37 -04:00
|
|
|
|
|
|
|
it "does not call Warning.warn if self is the Warning module" do
|
|
|
|
# RubyGems redefines Kernel#warn so we need to use a subprocess and disable RubyGems here
|
|
|
|
code = <<-RUBY
|
|
|
|
def Warning.warn(*args, **kwargs)
|
|
|
|
raise 'should not be called'
|
|
|
|
end
|
|
|
|
Kernel.instance_method(:warn).bind(Warning).call('Kernel#warn spec edge case')
|
|
|
|
RUBY
|
|
|
|
out = ruby_exe(code, args: "2>&1", options: "--disable-gems")
|
|
|
|
out.should == "Kernel#warn spec edge case\n"
|
|
|
|
$?.should.success?
|
|
|
|
end
|
|
|
|
|
|
|
|
it "avoids recursion if Warning#warn is redefined and calls super" do
|
|
|
|
# This works because of the spec above, which is the workaround for it.
|
|
|
|
# Note that redefining Warning#warn is a mistake which would naturally end in infinite recursion,
|
|
|
|
# Warning.extend Module.new { def warn } should be used instead.
|
|
|
|
# RubyGems redefines Kernel#warn so we need to use a subprocess and disable RubyGems here
|
|
|
|
code = <<-RUBY
|
|
|
|
module Warning
|
|
|
|
def warn(*args, **kwargs)
|
|
|
|
super
|
|
|
|
end
|
|
|
|
end
|
|
|
|
warn "avoid infinite recursion"
|
|
|
|
RUBY
|
|
|
|
out = ruby_exe(code, args: "2>&1", options: "--disable-gems")
|
|
|
|
out.should == "avoid infinite recursion\n"
|
|
|
|
$?.should.success?
|
|
|
|
end
|
2017-05-07 08:04:49 -04:00
|
|
|
end
|