Class: DeprecationTracker

Inherits:
Object
  • Object
show all
Includes:
KernelWarnTracker
Defined in:
lib/deprecation_tracker.rb

Overview

A shitlist for deprecation warnings during test runs. It has two modes: “save” and “compare”

DEPRECATION_TRACKER=save Record deprecation warnings, grouped by spec file. After the test run, save to a file.

DEPRECATION_TRACKER=compare Tracks deprecation warnings, grouped by spec file. After the test run, compare against shitlist of expected deprecation warnings. If anything is added or removed, raise an error with a diff of the changes.

Defined Under Namespace

Modules: KernelWarnTracker

Constant Summary collapse

UnexpectedDeprecations =
Class.new(StandardError)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from KernelWarnTracker

callbacks, #warn

Constructor Details

#initialize(shitlist_path, transform_message = nil) ⇒ DeprecationTracker

Returns a new instance of DeprecationTracker.



74
75
76
77
78
# File 'lib/deprecation_tracker.rb', line 74

def initialize(shitlist_path, transform_message = nil)
  @shitlist_path = shitlist_path
  @transform_message = transform_message || -> (message) { message }
  @deprecation_messages = {}
end

Instance Attribute Details

#bucketObject

Returns the value of attribute bucket.



72
73
74
# File 'lib/deprecation_tracker.rb', line 72

def bucket
  @bucket
end

#deprecation_messagesObject (readonly)

Returns the value of attribute deprecation_messages.



71
72
73
# File 'lib/deprecation_tracker.rb', line 71

def deprecation_messages
  @deprecation_messages
end

#shitlist_pathObject (readonly)

Returns the value of attribute shitlist_path.



71
72
73
# File 'lib/deprecation_tracker.rb', line 71

def shitlist_path
  @shitlist_path
end

#transform_messageObject (readonly)

Returns the value of attribute transform_message.



71
72
73
# File 'lib/deprecation_tracker.rb', line 71

def transform_message
  @transform_message
end

Class Method Details

.track_rspec(rspec_config, opts = {}) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/deprecation_tracker.rb', line 42

def self.track_rspec(rspec_config, opts = {})
  shitlist_path = opts[:shitlist_path]
  mode = opts[:mode]
  transform_message = opts[:transform_message]
  deprecation_tracker = DeprecationTracker.new(shitlist_path, transform_message)
  if defined?(ActiveSupport)
    ActiveSupport::Deprecation.behavior << -> (message, _callstack, _deprecation_horizon, _gem_name) { deprecation_tracker.add(message) }
  end
  KernelWarnTracker.callbacks << -> (message) { deprecation_tracker.add(message) }

  rspec_config.around do |example|
    deprecation_tracker.bucket = example..fetch(:rerun_file_path)

    begin
      example.run
    ensure
      deprecation_tracker.bucket = nil
    end
  end

  rspec_config.after(:suite) do
    if mode == "save"
      deprecation_tracker.save
    elsif mode == "compare"
      deprecation_tracker.compare
    end
  end
end

Instance Method Details

#add(message) ⇒ Object



80
81
82
83
84
# File 'lib/deprecation_tracker.rb', line 80

def add(message)
  return if bucket.nil?

  @deprecation_messages[bucket] << transform_message.(message)
end

#compareObject



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/deprecation_tracker.rb', line 91

def compare
  shitlist = read_shitlist

  changed_buckets = []
  normalized_deprecation_messages.each do |bucket, messages|
    if shitlist[bucket] != messages
      changed_buckets << bucket
    end
  end

  if changed_buckets.length > 0
    message = Rainbow(<<-MESSAGE).red
      ⚠️  Deprecation warnings have changed!

      Code called by the following spec files is now generating different deprecation warnings:

      #{changed_buckets.join("\n")}

      To check your failures locally, you can run:

      DEPRECATION_TRACKER=compare bundle exec rspec #{changed_buckets.join(" ")}

      Here is a diff between what is expected and what was generated by this process:

      #{diff}

      See \e[4;37mdev-docs/testing/deprecation_tracker.md\e[0;31m for more information.
    MESSAGE

    raise UnexpectedDeprecations, message
  end
end

#create_temp_shitlistObject



138
139
140
141
142
143
144
# File 'lib/deprecation_tracker.rb', line 138

def create_temp_shitlist
  temp_file = Tempfile.new("temp-deprecation-tracker-shitlist")
  temp_file.write(JSON.pretty_generate(normalized_deprecation_messages))
  temp_file.flush

  temp_file
end

#diffObject



124
125
126
127
128
129
# File 'lib/deprecation_tracker.rb', line 124

def diff
  new_shitlist = create_temp_shitlist
  `git diff --no-index #{shitlist_path} #{new_shitlist.path}`
ensure
  new_shitlist.delete
end

#normalized_deprecation_messagesObject

Normalize deprecation messages to reduce noise from file output and test files to be tracked with separate test runs



147
148
149
150
151
152
153
# File 'lib/deprecation_tracker.rb', line 147

def normalized_deprecation_messages
  normalized = read_shitlist.merge(deprecation_messages).each_with_object({}) do |(bucket, messages), hash|
    hash[bucket] = messages.sort
  end

  normalized.reject {|_key, value| value.empty? }.sort_by {|key, _value| key }.to_h
end

#read_shitlistObject



155
156
157
158
159
160
# File 'lib/deprecation_tracker.rb', line 155

def read_shitlist
  return {} unless File.exist?(shitlist_path)
  JSON.parse(File.read(shitlist_path))
rescue JSON::ParserError => e
  raise "#{shitlist_path} is not valid JSON: #{e.message}"
end

#saveObject



131
132
133
134
135
136
# File 'lib/deprecation_tracker.rb', line 131

def save
  new_shitlist = create_temp_shitlist
  FileUtils.cp(new_shitlist.path, shitlist_path)
ensure
  new_shitlist.delete if new_shitlist
end