From f86ba5e35f190909b99918876b8404e0b1869873 Mon Sep 17 00:00:00 2001 From: Stefanni Brasil Date: Fri, 30 Aug 2024 09:00:09 -0600 Subject: [PATCH 1/2] 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. --- lib/helpers/deprecator.rb | 71 +++++++++++++++++++++++++-- test/helpers/test_faker_deprecator.rb | 53 ++++++++++++++++++++ test/support/deprecation.rb | 41 ++++++++++++++++ test/test_faker_deprecator.rb | 23 --------- test/test_helper.rb | 1 + 5 files changed, 163 insertions(+), 26 deletions(-) create mode 100644 test/helpers/test_faker_deprecator.rb create mode 100644 test/support/deprecation.rb delete mode 100644 test/test_faker_deprecator.rb diff --git a/lib/helpers/deprecator.rb b/lib/helpers/deprecator.rb index 8cf7532ba8..fd46ac5849 100644 --- a/lib/helpers/deprecator.rb +++ b/lib/helpers/deprecator.rb @@ -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 Faker::NewGenerator: + # + # 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 Deprecator 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 Faker::Deprecator still work, they + # evaluate to Faker::NewGenerator 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 assert_deprecated + # and assert_not_deprecated matchers. + # + # There's also a Faker::Deprecator.skip_warning 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] @@ -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 diff --git a/test/helpers/test_faker_deprecator.rb b/test/helpers/test_faker_deprecator.rb new file mode 100644 index 0000000000..931bf96e87 --- /dev/null +++ b/test/helpers/test_faker_deprecator.rb @@ -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 diff --git a/test/support/deprecation.rb b/test/support/deprecation.rb new file mode 100644 index 0000000000..0f98e5a2fe --- /dev/null +++ b/test/support/deprecation.rb @@ -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 diff --git a/test/test_faker_deprecator.rb b/test/test_faker_deprecator.rb deleted file mode 100644 index 0083a2203c..0000000000 --- a/test/test_faker_deprecator.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -require_relative 'test_helper' - -class TestFakerDeprecation < Test::Unit::TestCase - def test_using_a_deprecated_generator_returns_a_warning_message - actual_stdout, actual_stderr = capture_output do - Faker::IDNumber.valid - end - - assert_includes(actual_stdout, 'DEPRECATION WARNING: Faker::IDNumber is deprecated. Use Faker::IdNumber instead') - assert_empty(actual_stderr) - end - - def test_using_a_non_deprecated_generator_does_not_return_a_warning_message - actual_stdout, actual_stderr = capture_output do - Faker::IdNumber.valid - end - - assert_empty(actual_stdout) - assert_empty(actual_stderr) - end -end diff --git a/test/test_helper.rb b/test/test_helper.rb index 5d615e92e6..53588e9296 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -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' From d8b23276ea25729f6e66f83193ecedeec651e82b Mon Sep 17 00:00:00 2001 From: Stefanni Brasil Date: Fri, 30 Aug 2024 12:18:09 -0600 Subject: [PATCH 2/2] Bump minitest version for ruby head compatibility --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 2a2cff4bb4..1627e22992 100644 --- a/Gemfile +++ b/Gemfile @@ -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' diff --git a/Gemfile.lock b/Gemfile.lock index 2f33a90102..4465b72b62 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) @@ -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)