Skip to content

Commit f86ba5e

Browse files
Deprecator improvements
In preparation for removing deprecations for certain generators, this commit documents the Deprecator and how to test deprecated generators. It also adds a unit test using newly added matchers based on Rails deprecation matchers. With this change, we can remove generators in a follow up PR while having the Deprecator covered and documented.
1 parent 130a504 commit f86ba5e

File tree

5 files changed

+163
-26
lines changed

5 files changed

+163
-26
lines changed

lib/helpers/deprecator.rb

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,70 @@
11
# frozen_string_literal: true
22

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

66
# rubocop:disable Style/ClassVars
77
module Faker
8+
# Provides a way to rename generators, including their namespaces, with a deprecation cycle in which
9+
# both the old and new names work, but using the old one prints a deprecation message.
10+
#
11+
# Deprecator provides a deprecate_generator method to be used when
12+
# renaming a generator. For example, let's say we want to change the following Generator's
13+
# name to <tt>Faker::NewGenerator</tt>:
14+
#
15+
# module Faker
16+
# class Generator
17+
# def self.generate
18+
# "be kind"
19+
# end
20+
# end
21+
# end
22+
#
23+
# To rename it, you need to do the update the name and declare the deprecation by
24+
# including the <tt>Deprecator</tt> module and using the deprecate_generator method:
25+
#
26+
# module Faker
27+
# class NewGenerator
28+
# def self.generate
29+
# "be kind"
30+
# end
31+
# end
32+
#
33+
# include Deprecator
34+
# deprecate_generator('DeprecatedGenerator', NewGenerator)
35+
# end
36+
#
37+
# The first argument is a constant name (no colons) as a string. It is the name of
38+
# the constant you want to deprecate.
39+
#
40+
# The second argument is the constant path of the replacement (no colons) as a constant.
41+
#
42+
# For this to work, a +const_missing+ hook is installed. When users
43+
# reference the deprecated constant, the callback prints the
44+
# message and constantizes the replacement.
45+
#
46+
# With that in place, references to <tt>Faker::Deprecator</tt> still work, they
47+
# evaluate to <tt>Faker::NewGenerator</tt> now, and trigger a deprecation warning:
48+
#
49+
# Faker::Generator.generate
50+
# # DEPRECATION WARNING: Faker::Generator is deprecated. Use Faker::NewGenerator instead
51+
# # "be kind"
52+
#
53+
# For testing the deprecations, we provide <tt>assert_deprecated</tt>
54+
# and <tt>assert_not_deprecated</tt> matchers.
55+
#
56+
# There's also a <tt>Faker::Deprecator.skip_warning</tt> helper to silence
57+
# the deprecation messages in the *test* output. Use it for generators that have lots of tests
58+
# to avoid too many noise when running the tests.
859
module Deprecator
960
def self.included(base)
1061
extension = Module.new do
1162
def const_missing(missing_const_name)
1263
if class_variable_defined?(:@@_deprecated_constants) && (replacement = class_variable_get(:@@_deprecated_constants)[missing_const_name.to_s])
1364
unless Faker::Deprecator.skip_warning?
14-
$stdout.puts("DEPRECATION WARNING: #{name}::#{replacement[:old_generator]} is deprecated. Use #{replacement[:new_constant]} instead.")
65+
deprecated_message = "#{name}::#{replacement[:old_generator]} is deprecated."
66+
replacement_message = "Use #{replacement[:new_constant]} instead."
67+
$stdout.puts("DEPRECATION WARNING: #{deprecated_message} #{replacement_message}")
1568
end
1669

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

2376
def deprecate_generator(old_generator_name, new_generator_constant)
2477
class_variable_set(:@@_deprecated_constants, {}) unless class_variable_defined?(:@@_deprecated_constants)
25-
class_variable_get(:@@_deprecated_constants)[old_generator_name] = { new_constant: new_generator_constant, old_generator: old_generator_name }
78+
class_variable_get(:@@_deprecated_constants)[old_generator_name] = {
79+
new_constant: new_generator_constant,
80+
old_generator: old_generator_name
81+
}
2682
end
2783
end
2884

2985
base.singleton_class.prepend extension
3086
end
3187

88+
# Silence deprecation warnings within the block.
89+
#
90+
# Faker::Generator.generate
91+
# # => DEPRECATION WARNING: Faker::Generator is deprecated. Use Faker::NewGenerator instead.
92+
#
93+
# Faker::Deprecator.skip_warning do
94+
# Faker::Generator.generate
95+
# end
96+
# # => nil
3297
def self.skip_warning
3398
original = Faker::Deprecator.skip
3499
Faker::Deprecator.skip = true
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# frozen_string_literal: true
2+
3+
require_relative '../test_helper'
4+
5+
class TestFakerDeprecation < Test::Unit::TestCase
6+
def test_using_a_deprecated_generator_returns_a_warning_message
7+
assert_deprecated do
8+
Faker::Dogs.say
9+
end
10+
11+
assert_equal 'meow', Faker::Dogs.say
12+
end
13+
14+
def test_using_a_non_deprecated_generator_does_not_return_a_warning_message
15+
assert_not_deprecated do
16+
Faker::Cats.say
17+
end
18+
assert_equal 'meow', Faker::Cats.say
19+
end
20+
21+
def test_testing_a_deprecated_generator_with_skip_warning_does_not_return_a_warning_message
22+
actual_stdout, actual_stderr = capture_output do
23+
Faker::Deprecator.skip_warning do
24+
Faker::Dogs.say
25+
end
26+
end
27+
28+
assert_empty(actual_stdout)
29+
assert_empty(actual_stderr)
30+
assert_equal 'meow', Faker::Dogs.say
31+
end
32+
33+
def test_deprecated_with_skip_warning_does_not_generate_message
34+
Faker::Deprecator.skip_warning do
35+
assert_not_deprecated do
36+
Faker::Dogs.say
37+
end
38+
end
39+
40+
assert_equal 'meow', Faker::Dogs.say
41+
end
42+
end
43+
44+
module Faker
45+
class Cats < Base
46+
def self.say
47+
'meow'
48+
end
49+
end
50+
51+
include Faker::Deprecator
52+
deprecate_generator('Dogs', Cats)
53+
end

test/support/deprecation.rb

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# frozen_string_literal: true
2+
3+
# Based on Rails Testing Deprecator
4+
# https://github.com/rails/rails/blob/main/activesupport/lib/active_support/testing/deprecation.rb
5+
6+
# Asserts that a matching deprecation warning was emitted during the execution of the yielded block.
7+
#
8+
# assert_deprecated do
9+
# DeprecatedGenerator.generate
10+
# end
11+
#
12+
def assert_deprecated(&block)
13+
warning = with_captured_stdout(&block)
14+
result = yield block
15+
16+
refute_predicate warning, :empty?, 'Expected a deprecation warning within the block but received none'
17+
18+
result
19+
end
20+
21+
# Asserts that no deprecation warnings are emitted during the execution of the yielded block.
22+
#
23+
# assert_not_deprecated do
24+
# Faker::Internet.email
25+
# end
26+
def assert_not_deprecated(&block)
27+
warning = with_captured_stdout(&block)
28+
result = yield block
29+
30+
assert_predicate warning, :empty?, "Expected no deprecation warning within the block but received a deprecation: #{warning}"
31+
result
32+
end
33+
34+
def with_captured_stdout(&block)
35+
original_stdout = $stdout
36+
$stdout = StringIO.new
37+
yield block
38+
$stdout.string
39+
ensure
40+
$stdout = original_stdout
41+
end

test/test_faker_deprecator.rb

Lines changed: 0 additions & 23 deletions
This file was deleted.

test/test_helper.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
require_relative 'support/assert_not_english'
1313
require_relative 'support/assert_email_regex'
14+
require_relative 'support/deprecation'
1415
require 'minitest/autorun'
1516
require 'test/unit'
1617
require 'rubygems'

0 commit comments

Comments
 (0)