Shopify Deprecation Toolkit

repository·main·Indexed 19 days ago

https://github.com/shopify/deprecation_toolkit

A Ruby gem designed to help developers manage and eliminate code deprecations using a 'shitlist' approach. It records existing deprecations into YAML files and can be configured to raise errors (DeprecationIntroduced, DeprecationRemoved, or DeprecationMismatch) when new or unexpected deprecations are encountered during tests. It supports Minitest and RSpec, integrates with ActiveSupport::Deprecation, and can treat Kernel#warn messages as deprecations.

Tokens
4.4K
Snippets
24
Records
26
Agent score
67%

What's inside deprecation_toolkit

  1. How Deprecation Toolkit works

    main

    Deprecation Toolkit uses a "shitlist approach" to manage code deprecations. It works in two phases:

    1. Recording: The gem records all existing deprecations into .yml files.
    2. Enforcement: When running tests, if a deprecation is encountered that was not in the recorded files, the gem triggers a configured behavior (by default, it raises an error).

    This allows teams to progressively resolve existing deprecations while ensuring no new ones are introduced to the codebase.

  2. Use Deprecation Toolkit without Rails

    main

    When using the toolkit in a non-Rails environment, you must ensure your ActiveSupport::Deprecation instances are configured to use the :notify behavior and that the attach_to value matches the underscored version of the deprecator's gem_name.

    Example setup:

    # Define your deprecator
    MyGem.deprecator = ActiveSupport::Deprecation.new("2.0", "MyGem::Something")
    
    # In your test helper
    MyGem.deprecator.behavior = :notify
    
    DeprecationToolkit::Configuration.configure do |config|
      # attach_to must be the underscored gem name
      config.attach_to = "my_gem_something"
      config.behavior = :notify
    end
    # defined in the gem:
    MyGem.deprecator = ActiveSupport::Deprecation.new("2.0", "MyGem::Something")
    
    # in the test helper:
    MyGem.deprecator.behavior = :notify
    
    DeprecationToolkit::Configuration.configure do |config|
      config.attach_to = MyGem.deprecator.gem_name.underscore.tr("/", "_")
      # or more simply
      # config.attach_to = "my_gem_something"
      config.behavior = :notify
    end
  3. Setup Deprecation Toolkit with RSpec

    main

    To use the toolkit with RSpec instead of the default Minitest, follow these steps:

    1. Set the test runner in your configuration:
      DeprecationToolkit::Configuration.test_runner = :rspec
    2. Require the RSpec hooks in your spec_helper.rb or rails_helper.rb:
      require "deprecation_toolkit/rspec"
    3. To record deprecations during an RSpec run, set the DEPRECATION_BEHAVIOR environment variable to record, r, or record-deprecations.
    DEPRECATION_BEHAVIOR="record" bundle exec rspec path/to/file_spec.rb
    # In your configuration
    DeprecationToolkit::Configuration.test_runner = :rspec
    
    # In spec_helper.rb or rails_helper.rb
    require "deprecation_toolkit/rspec"
  4. Record deprecations using the CLI or Configuration

    main

    To record existing deprecations into .yml files, you can use either the CLI flag during test execution or set the behavior in your configuration.

    Using the CLI flag: Run your tests with the --record-deprecations flag or the -r shortcut.

    rails test <path_to_my_test.rb> -r

    Using Configuration: Set the DeprecationToolkit::Configuration.behavior to DeprecationToolkit::Behaviors::Record.

  5. Integrate Deprecation Toolkit with RSpec

    main

    To use the Deprecation Toolkit within an RSpec test suite, include the DeprecationToolkit::RSpecPlugin module. The plugin automatically configures an RSpec before(:suite) hook to initialize the toolkit's behaviors, attach subscribers, and set up notification behaviors.

    If you want the toolkit to record deprecations during your test run, set the DEPRECATION_BEHAVIOR environment variable to r, record, or record-deprecations.

    # Ensure the plugin is loaded in your spec_helper.rb or rails_helper.rb
    require 'deprecation_toolkit/rspec_plugin'
    
    # To enable recording mode via the environment:
    # DEPRECATION_BEHAVIOR=record bundle exec rspec
  6. Integrate Deprecation Toolkit with Minitest

    main
    The toolkit automatically registers itself as a Minitest extension if Minitest.load(:deprecation_toolkit) is available. Once loaded, the toolkit hooks into the Minitest lifecycle by adding trigger_deprecation_toolkit_behavior to the TEARDOWN_METHODS. This ensures that deprecation tracking and reporting occur automatically after each test execution.
  7. Configure deprecation behaviors

    main

    Behaviors define the action taken when a non-recorded deprecation is encountered.

    Built-in Behaviors:

    • DeprecationToolkit::Behaviors::Raise (Default): Raises DeprecationToolkit::Behaviors::DeprecationIntroduced if a new deprecation is found, or DeprecationToolkit::Behaviors::DeprecationRemoved if a recorded deprecation is no longer present.
    • DeprecationToolkit::Behaviors::Record: Records the deprecation into a .yml file.
    • DeprecationToolkit::Behaviors::CIRecordHelper: Designed for CI environments. It outputs a JSON representation of deprecations. You can then download this log and run the deprecation_toolkit:record_from_ci_output rake task locally to convert the JSON back into .yml files.
    • DeprecationToolkit::Behaviors::Disabled: Does nothing; useful for temporarily disabling the toolkit without removing the gem.

    You can also implement a custom behavior by creating an object that responds to trigger(test, deprecations, recorded_deprecations).

    # Using a built-in behavior
    DeprecationToolkit::Configuration.behavior = DeprecationToolkit::Behaviors::Record
    
    # Using a custom behavior
    class StatsdBehavior
      def trigger(test, deprecations, recorded_deprecations)
         # Custom logic, e.g., sending a statsd event
      end
    end
    
    DeprecationToolkit::Configuration.behavior = StatsdBehavior.new
  8. Configure the deprecation storage path

    main

    You can control where recorded deprecations are saved and read from using deprecation_path. By default, they are stored in test/deprecations.

    deprecation_path accepts:

    • A String: A static path.
    • A Proc: A proc that receives the path of the running test file as an argument, allowing for dynamic pathing (e.g., separating admin and storefront deprecations).
    DeprecationToolkit::Configuration.deprecation_path = 'test/deprecations'
    
    DeprecationToolkit::Configuration.deprecation_path = -> (test_location) do
      if test_location == 'admin_test.rb'
        'test/deprecations/admin'
      else
        'test/deprecations/storefront'
      end
    end
  9. Treat warnings as deprecations

    main

    If your dependencies use Kernel#warn instead of ActiveSupport::Deprecation, you can treat those warnings as deprecations using warnings_treated_as_deprecation. This setting accepts an array of matchers (Regexps or objects responding to ===, such as Procs).

    To match all warnings, use an empty regex //.

    # Match all warnings
    DeprecationToolkit::Configuration.warnings_treated_as_deprecation = [//]
    
    # Match using a Proc
    DeprecationToolkit::Configuration.warnings_treated_as_deprecation = [
      ->(warning) { !warning.match?(/not a deprecation/) }
    ]
  10. Allow specific deprecations via `allowed_deprecations`

    main

    Use allowed_deprecations to whitelist specific deprecations so they are ignored by the toolkit. This setting accepts an array of Regexp or Procs.

    • Regexp: Matches against the deprecation message.
    • Proc: Receives the message and the stack (callstack). This is useful for whitelisting based on the specific method triggering the deprecation.
    # Ignore by message pattern
    DeprecationToolkit::Configuration.allowed_deprecations = [/Hello World/]
    
    # Ignore by message and callstack
    DeprecationToolkit::Configuration.allowed_deprecations = [
      ->(message, stack) { message =~ 'Foo' && stack.first.label == 'method_triggering_deprecation' }
    ]
  11. Configure deprecation file path format

    main

    You can customize how the .yml files are named and located using deprecation_file_path_format. This setting accepts a Proc that is called with the instance of the test.

    Default Formats:

    • Minitest: Uses the class name (e.g., test/deprecations/deprecation_toolkit/behaviors/raise_test.yml).
    • RSpec: Uses the file location with the spec prefix removed (e.g., /models/user.yml).
    # Custom format using a proc
    Configuration.deprecation_file_path_format = -> (test) do
      Kernel.const_source_location(test.class.name)[0].sub(%r{^./test/}, "").sub(/_test.rb$/, "")
    end