Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

making warnings less scary #59

Closed
wants to merge 10 commits into from
Closed
1 change: 1 addition & 0 deletions Manifest.txt
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ test/rubygems/rubygems_plugin.rb
test/rubygems/sff/discover.rb test/rubygems/sff/discover.rb
test/rubygems/simple_gem.rb test/rubygems/simple_gem.rb
test/rubygems/test_config.rb test/rubygems/test_config.rb
test/rubygems/test_deprecate.rb
test/rubygems/test_gem.rb test/rubygems/test_gem.rb
test/rubygems/test_gem_builder.rb test/rubygems/test_gem_builder.rb
test/rubygems/test_gem_command.rb test/rubygems/test_gem_command.rb
Expand Down
1 change: 1 addition & 0 deletions lib/rubygems.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ class << Gem
# * Daniel Berger -- djberg96(at)gmail.com # * Daniel Berger -- djberg96(at)gmail.com
# * Phil Hagelberg -- technomancy(at)gmail.com # * Phil Hagelberg -- technomancy(at)gmail.com
# * Ryan Davis -- ryand-ruby(at)zenspider.com # * Ryan Davis -- ryand-ruby(at)zenspider.com
# * Alex Chaffee -- alex(at)stinky.com
# #
# (If your name is missing, PLEASE let us know!) # (If your name is missing, PLEASE let us know!)
# #
Expand Down
114 changes: 103 additions & 11 deletions lib/rubygems/deprecate.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -21,25 +21,114 @@
# end # end


module Deprecate module Deprecate

@skip = nil


def self.skip # :nodoc: def self.skip # :nodoc:
@skip ||= false @skip.nil? ? false : @skip
end end


def self.skip= v # :nodoc: def self.skip= v # :nodoc:
@skip = v @skip = v
end end


def self.saved_warnings # :nodoc:
@saved_warnings ||= []
end

def self.add_warning w # :nodoc:
warn "Warning: #{w.message} (Further warnings suppressed until exit.)\n#{w.loc}" if saved_warnings.empty?
unless saved_warnings.include? w
@saved_warnings << w
end
end

at_exit do
unless Deprecate.saved_warnings.size == 0
warn Deprecate.report
end
end

def self.report
out = "\n"
out << "Some of your installed gems called deprecated methods. See http://blog.zenspider.com/2011/05/rubygems-18-is-coming.html for background. Use 'gem pristine --all' to fix or 'rubygems update --system 1.7.2' to downgrade.\n"
last_warning = nil
warnings = @saved_warnings.sort.each do |w|
if last_warning and last_warning.target == w.target and last_warning.method_name == w.method_name
if last_warning.file == w.file
out << ",#{w.line}"
else
out << "\n#{w.loc}"
end
else
out << "\n#{w.message}\n#{w.loc}"
end
last_warning = w
end
out << "\n"
out
end

## ##
# Temporarily turn off warnings. Intended for tests only. # Temporarily turn off warnings. Intended for tests only.


def skip_during def skip_during(will_skip = true)
Deprecate.skip, original = true, Deprecate.skip Deprecate.skip, original = will_skip, Deprecate.skip
yield yield
ensure ensure
Deprecate.skip = original Deprecate.skip = original
end end


class Warning
attr_accessor :target, :method_name, :replacement, :year, :month, :location

def initialize options
@target, @method_name, @replacement, @year, @month, @location =
options[:target], options[:method_name], options[:replacement], options[:year], options[:month], options[:location]
end

def ==(other)
target == other.target and
method_name == other.method_name and
location == other.location
end

def <=>(other)
self.compare_string <=> other.compare_string
end

def compare_string
[target, method_name, file, line].join('|')
end

def to_s
[target, method_name, replacement, year, month, location].map(&:inspect).join('|')
end

def message
[ "#{target}#{method_name} is deprecated",
replacement == :none ? " with no replacement" : "; use #{replacement} instead",
". It will be removed on or after %4d-%02d-01." % [year, month]
].join
end

def loc
" called from #{location.join(":")}"
end

def full_name
"#{target}#{method_name}"
end

def line
location.last
end

def file
location.first
end
end

## ##
# Simple deprecation method that deprecates +name+ by wrapping it up # Simple deprecation method that deprecates +name+ by wrapping it up
# in a dummy method. It warns on each call to the dummy method # in a dummy method. It warns on each call to the dummy method
Expand All @@ -51,14 +140,17 @@ def deprecate name, repl, year, month
old = "_deprecated_#{name}" old = "_deprecated_#{name}"
alias_method old, name alias_method old, name
define_method name do |*args, &block| # TODO: really works on 1.8.7? define_method name do |*args, &block| # TODO: really works on 1.8.7?
klass = self.kind_of? Module unless Deprecate.skip
target = klass ? "#{self}." : "#{self.class}#" warning = Warning.new({
msg = [ "NOTE: #{target}#{name} is deprecated", :target => (self.kind_of? Module) ? "#{self}." : "#{self.class}#",
repl == :none ? " with no replacement" : ", use #{repl}", :method_name => name,
". It will be removed on or after %4d-%02d-01." % [year, month], :location => Gem.location_of_caller,
"\n#{target}#{name} called from #{Gem.location_of_caller.join(":")}", :replacement => repl,
] :year => year,
warn "#{msg.join}." unless Deprecate.skip :month => month
})
Deprecate.add_warning warning
end
send old, *args, &block send old, *args, &block
end end
} }
Expand Down
5 changes: 5 additions & 0 deletions lib/rubygems/install_update_options.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ def add_install_update_options
"meeting version requirement") do |value, options| "meeting version requirement") do |value, options|
options[:conservative] = true options[:conservative] = true
end end

add_option(:"Install/Update", "--[no-]warnings",
"Display warnings about deprecated methods") do |value, options|
Deprecate.skip = !value
end
end end


## ##
Expand Down
202 changes: 202 additions & 0 deletions test/rubygems/test_deprecate.rb
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,202 @@
require 'rubygems/test_case'
require 'rubygems/builder'
require 'rubygems/package'

require 'rubygems/deprecate'

class TestDeprecate < Gem::TestCase

def setup
Deprecate.saved_warnings.clear
@original_skip = Deprecate.skip
Deprecate.skip = false
end

def teardown
Deprecate.saved_warnings.clear
Deprecate.skip = @original_skip
end

def test_defaults
assert_equal false, @original_skip
end

def test_assignment
Deprecate.skip = false
assert_equal false, Deprecate.skip

Deprecate.skip = true
assert_equal true, Deprecate.skip

Deprecate.skip = nil
assert([true,false].include? Deprecate.skip)
end

def test_skip
Deprecate.skip_during do
assert_equal true, Deprecate.skip
end

Deprecate.skip_during(false) do
assert_equal false, Deprecate.skip
end

Deprecate.skip_during(nil) do
assert_equal false, Deprecate.skip
end

Deprecate.skip = nil
end

### stolen from Wrong::Helpers

# Usage:
# capturing { puts "hi" } => "hi\n"
# capturing(:stderr) { $stderr.puts "hi" } => "hi\n"
# out, err = capturing(:stdout, :stderr) { ... }
#
# see http://www.justskins.com/forums/closing-stderr-105096.html for more explanation
def capturing(*streams)
streams = [:stdout] if streams.empty?
original = {}
captured = {}

# reassign the $ variable (which is used by well-behaved code e.g. puts)
streams.each do |stream|
original[stream] = (stream == :stdout ? $stdout : $stderr)
captured[stream] = StringIO.new
reassign_stream(stream, captured)
end

yield

# return either one string, or an array of two strings
if streams.size == 1
captured[streams.first].string
else
[captured[streams[0]].string.to_s, captured[streams[1]].string.to_s]
end

ensure

streams.each do |stream|
# bail if stream was reassigned inside the block
if (stream == :stdout ? $stdout : $stderr) != captured[stream]
raise "#{stream} was reassigned while being captured"
end
# support nested calls to capturing
original[stream] << captured[stream].string if original[stream].is_a? StringIO
reassign_stream(stream, original)
end
end

private
def reassign_stream(which, streams)
case which
when :stdout
$stdout = streams[which]
when :stderr
$stderr = streams[which]
end
end
### end of Wrong::Helpers code

public
def test_has_a_place_to_save_warnings
assert_empty Deprecate.saved_warnings
end

class Thing
extend Deprecate
attr_accessor :message
def foo
@message = "foo"
end
def bar
@message = "bar"
end
def goo
@message = "goo"
end
deprecate :foo, :bar, 2099, 3
deprecate :goo, :bar, 2099, 4
end

def test_deprecated_method_calls_the_old_method
capturing :stderr do
thing = Thing.new
thing.foo
assert_equal "foo", thing.message
end
end

def test_deprecated_method_outputs_a_warning
out, err = capturing(:stdout, :stderr) do
thing = Thing.new
thing.foo
end
assert_equal "", out
assert err =~ /Thing#foo is deprecated; use bar instead/, err
assert err =~ /on or after 2099-03-01/, err
end

def test_saves_warnings
capturing :stderr do
thing = Thing.new
thing.foo
thing.goo
assert_equal 2, Deprecate.saved_warnings.size
end
end

def test_suppresses_duplicate_warnings
capturing :stderr do
line1 = line2 = nil
thing = Thing.new
3.times do
thing.foo; line1 = __LINE__
end
thing.foo; line2 = __LINE__
assert_equal 2, Deprecate.saved_warnings.size
assert_equal line1, Deprecate.saved_warnings[0].location.last
assert_equal line2, Deprecate.saved_warnings[1].location.last
end
end

def test_suppresses_further_warnings_until_exit
Deprecate.saved_warnings.clear
end

def test_report
err = capturing :stderr do
thing = Thing.new
line = nil
thing.foo; line = __LINE__
thing.foo
thing.goo
thing.foo
# also throw one in from a different file
Deprecate.add_warning Deprecate::Warning.new({
:target => "TestDeprecate::Thing#",
:method_name => :foo,
:location => ["/some/other/file.rb", 99],
:replacement => :bar,
:year => 2099,
:month => 1
})
s = Deprecate.report
f = __FILE__
assert_equal s, <<-REPORT

Some of your installed gems called deprecated methods. See http://blog.zenspider.com/2011/05/rubygems-18-is-coming.html for background. Use 'gem pristine --all' to fix or 'rubygems update --system 1.7.2' to downgrade.

TestDeprecate::Thing#foo is deprecated; use bar instead. It will be removed on or after 2099-03-01.
called from #{f}:#{line},#{line+1},#{line+3}
called from /some/other/file.rb:99
TestDeprecate::Thing#goo is deprecated; use bar instead. It will be removed on or after 2099-04-01.
called from #{f}:#{line+2}
REPORT

end
end
end