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, MinitestExtension

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, mode = :save) ⇒ DeprecationTracker

Returns a new instance of DeprecationTracker.



116
117
118
119
120
121
# File 'lib/deprecation_tracker.rb', line 116

def initialize(shitlist_path, transform_message = nil, mode = :save)
  @shitlist_path = shitlist_path
  @transform_message = transform_message || -> (message) { message }
  @deprecation_messages = {}
  @mode = mode.to_sym
end

Instance Attribute Details

#bucketObject

Returns the value of attribute bucket.



114
115
116
# File 'lib/deprecation_tracker.rb', line 114

def bucket
  @bucket
end

#deprecation_messagesObject (readonly)

Returns the value of attribute deprecation_messages.



114
115
116
# File 'lib/deprecation_tracker.rb', line 114

def deprecation_messages
  @deprecation_messages
end

#modeObject (readonly)

Returns the value of attribute mode.



114
115
116
# File 'lib/deprecation_tracker.rb', line 114

def mode
  @mode
end

#shitlist_pathObject (readonly)

Returns the value of attribute shitlist_path.



114
115
116
# File 'lib/deprecation_tracker.rb', line 114

def shitlist_path
  @shitlist_path
end

#transform_messageObject (readonly)

Returns the value of attribute transform_message.



114
115
116
# File 'lib/deprecation_tracker.rb', line 114

def transform_message
  @transform_message
end

Class Method Details

.init_tracker(opts = {}) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/deprecation_tracker.rb', line 71

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

  deprecation_tracker
end

.track_minitest(opts = {}) ⇒ Object



104
105
106
107
108
109
110
111
112
# File 'lib/deprecation_tracker.rb', line 104

def self.track_minitest(opts = {})
  tracker = init_tracker(opts)

  Minitest.after_run do
    tracker.after_run
  end

  ActiveSupport::TestCase.include(MinitestExtension.new(tracker))
end

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



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/deprecation_tracker.rb', line 86

def self.track_rspec(rspec_config, opts = {})
  deprecation_tracker = init_tracker(opts)

  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
    deprecation_tracker.after_run
  end
end

Instance Method Details

#add(message) ⇒ Object



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

def add(message)
  return if bucket.nil?

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

#after_runObject



134
135
136
137
138
139
140
# File 'lib/deprecation_tracker.rb', line 134

def after_run
  if mode == :save
    save
  elsif mode == :compare
    compare
  end
end

#compareObject



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/deprecation_tracker.rb', line 142

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 = <<-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_if_shitlist_path_does_not_existObject



190
191
192
193
194
195
# File 'lib/deprecation_tracker.rb', line 190

def create_if_shitlist_path_does_not_exist
  dirname = File.dirname(shitlist_path)
  unless File.directory?(dirname)
    FileUtils.mkdir_p(dirname)
  end
end

#create_temp_shitlistObject



197
198
199
200
201
202
203
# File 'lib/deprecation_tracker.rb', line 197

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



175
176
177
178
179
180
# File 'lib/deprecation_tracker.rb', line 175

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



206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/deprecation_tracker.rb', line 206

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

  # not using `to_h` here to support older ruby versions
  {}.tap do |h|
    normalized.reject {|_key, value| value.empty? }.sort_by {|key, _value| key }.each do |k ,v|
      h[k] = v
    end
  end
end

#read_shitlistObject



219
220
221
222
223
224
# File 'lib/deprecation_tracker.rb', line 219

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



182
183
184
185
186
187
188
# File 'lib/deprecation_tracker.rb', line 182

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