Skip to content

Commit

Permalink
Merge pull request #2997 from faker-ruby/sb-deprecator-improvements
Browse files Browse the repository at this point in the history
Deprecator improvements
  • Loading branch information
stefannibrasil authored Sep 12, 2024
2 parents caa5507 + d8b2327 commit b09aa0d
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 29 deletions.
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ source 'https://rubygems.org'
gemspec

gem 'benchmark'
gem 'minitest', '5.25.0'
gem 'minitest', '5.25.1'
gem 'pry', '0.14.2'
gem 'rake', '13.2.1'
gem 'rubocop', '1.65.1'
Expand Down
4 changes: 2 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ GEM
json (2.7.2)
language_server-protocol (3.17.0.3)
method_source (1.0.0)
minitest (5.25.0)
minitest (5.25.1)
parallel (1.25.1)
parser (3.3.4.0)
ast (~> 2.4.1)
Expand Down Expand Up @@ -71,7 +71,7 @@ PLATFORMS
DEPENDENCIES
benchmark
faker!
minitest (= 5.25.0)
minitest (= 5.25.1)
pry (= 0.14.2)
rake (= 13.2.1)
rubocop (= 1.65.1)
Expand Down
71 changes: 68 additions & 3 deletions lib/helpers/deprecator.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,70 @@
# frozen_string_literal: true

# Based on Rails ActiveSupport Deprecator
# https://github.com/rails/rails/blob/6f0d1ad14b92b9f5906e44740fce8b4f1c7075dc/activesupport/lib/active_support/deprecation/constant_accessor.rb
# https://github.com/rails/rails/blob/main/activesupport/lib/active_support/deprecation/constant_accessor.rb

# rubocop:disable Style/ClassVars
module Faker
# Provides a way to rename generators, including their namespaces, with a deprecation cycle in which
# both the old and new names work, but using the old one prints a deprecation message.
#
# Deprecator provides a deprecate_generator method to be used when
# renaming a generator. For example, let's say we want to change the following Generator's
# name to <tt>Faker::NewGenerator</tt>:
#
# module Faker
# class Generator
# def self.generate
# "be kind"
# end
# end
# end
#
# To rename it, you need to do the update the name and declare the deprecation by
# including the <tt>Deprecator</tt> module and using the deprecate_generator method:
#
# module Faker
# class NewGenerator
# def self.generate
# "be kind"
# end
# end
#
# include Deprecator
# deprecate_generator('DeprecatedGenerator', NewGenerator)
# end
#
# The first argument is a constant name (no colons) as a string. It is the name of
# the constant you want to deprecate.
#
# The second argument is the constant path of the replacement (no colons) as a constant.
#
# For this to work, a +const_missing+ hook is installed. When users
# reference the deprecated constant, the callback prints the
# message and constantizes the replacement.
#
# With that in place, references to <tt>Faker::Deprecator</tt> still work, they
# evaluate to <tt>Faker::NewGenerator</tt> now, and trigger a deprecation warning:
#
# Faker::Generator.generate
# # DEPRECATION WARNING: Faker::Generator is deprecated. Use Faker::NewGenerator instead
# # "be kind"
#
# For testing the deprecations, we provide <tt>assert_deprecated</tt>
# and <tt>assert_not_deprecated</tt> matchers.
#
# There's also a <tt>Faker::Deprecator.skip_warning</tt> helper to silence
# the deprecation messages in the *test* output. Use it for generators that have lots of tests
# to avoid too many noise when running the tests.
module Deprecator
def self.included(base)
extension = Module.new do
def const_missing(missing_const_name)
if class_variable_defined?(:@@_deprecated_constants) && (replacement = class_variable_get(:@@_deprecated_constants)[missing_const_name.to_s])
unless Faker::Deprecator.skip_warning?
$stdout.puts("DEPRECATION WARNING: #{name}::#{replacement[:old_generator]} is deprecated. Use #{replacement[:new_constant]} instead.")
deprecated_message = "#{name}::#{replacement[:old_generator]} is deprecated."
replacement_message = "Use #{replacement[:new_constant]} instead."
$stdout.puts("DEPRECATION WARNING: #{deprecated_message} #{replacement_message}")
end

return replacement[:new_constant]
Expand All @@ -22,13 +75,25 @@ def const_missing(missing_const_name)

def deprecate_generator(old_generator_name, new_generator_constant)
class_variable_set(:@@_deprecated_constants, {}) unless class_variable_defined?(:@@_deprecated_constants)
class_variable_get(:@@_deprecated_constants)[old_generator_name] = { new_constant: new_generator_constant, old_generator: old_generator_name }
class_variable_get(:@@_deprecated_constants)[old_generator_name] = {
new_constant: new_generator_constant,
old_generator: old_generator_name
}
end
end

base.singleton_class.prepend extension
end

# Silence deprecation warnings within the block.
#
# Faker::Generator.generate
# # => DEPRECATION WARNING: Faker::Generator is deprecated. Use Faker::NewGenerator instead.
#
# Faker::Deprecator.skip_warning do
# Faker::Generator.generate
# end
# # => nil
def self.skip_warning
original = Faker::Deprecator.skip
Faker::Deprecator.skip = true
Expand Down
53 changes: 53 additions & 0 deletions test/helpers/test_faker_deprecator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# frozen_string_literal: true

require_relative '../test_helper'

class TestFakerDeprecation < Test::Unit::TestCase
def test_using_a_deprecated_generator_returns_a_warning_message
assert_deprecated do
Faker::Dogs.say
end

assert_equal 'meow', Faker::Dogs.say
end

def test_using_a_non_deprecated_generator_does_not_return_a_warning_message
assert_not_deprecated do
Faker::Cats.say
end
assert_equal 'meow', Faker::Cats.say
end

def test_testing_a_deprecated_generator_with_skip_warning_does_not_return_a_warning_message
actual_stdout, actual_stderr = capture_output do
Faker::Deprecator.skip_warning do
Faker::Dogs.say
end
end

assert_empty(actual_stdout)
assert_empty(actual_stderr)
assert_equal 'meow', Faker::Dogs.say
end

def test_deprecated_with_skip_warning_does_not_generate_message
Faker::Deprecator.skip_warning do
assert_not_deprecated do
Faker::Dogs.say
end
end

assert_equal 'meow', Faker::Dogs.say
end
end

module Faker
class Cats < Base
def self.say
'meow'
end
end

include Faker::Deprecator
deprecate_generator('Dogs', Cats)
end
41 changes: 41 additions & 0 deletions test/support/deprecation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

# Based on Rails Testing Deprecator
# https://github.com/rails/rails/blob/main/activesupport/lib/active_support/testing/deprecation.rb

# Asserts that a matching deprecation warning was emitted during the execution of the yielded block.
#
# assert_deprecated do
# DeprecatedGenerator.generate
# end
#
def assert_deprecated(&block)
warning = with_captured_stdout(&block)
result = yield block

refute_predicate warning, :empty?, 'Expected a deprecation warning within the block but received none'

result
end

# Asserts that no deprecation warnings are emitted during the execution of the yielded block.
#
# assert_not_deprecated do
# Faker::Internet.email
# end
def assert_not_deprecated(&block)
warning = with_captured_stdout(&block)
result = yield block

assert_predicate warning, :empty?, "Expected no deprecation warning within the block but received a deprecation: #{warning}"
result
end

def with_captured_stdout(&block)
original_stdout = $stdout
$stdout = StringIO.new
yield block
$stdout.string
ensure
$stdout = original_stdout
end
23 changes: 0 additions & 23 deletions test/test_faker_deprecator.rb

This file was deleted.

1 change: 1 addition & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

require_relative 'support/assert_not_english'
require_relative 'support/assert_email_regex'
require_relative 'support/deprecation'
require 'minitest/autorun'
require 'test/unit'
require 'rubygems'
Expand Down

0 comments on commit b09aa0d

Please sign in to comment.