Module: TestProf::AnyFixture

Extended by:
Logging
Defined in:
lib/test_prof/any_fixture.rb,
lib/test_prof/any_fixture/dsl.rb,
lib/test_prof/any_fixture/dump.rb,
lib/test_prof/any_fixture/dump/digest.rb,
lib/test_prof/any_fixture/dump/sqlite.rb,
lib/test_prof/any_fixture/dump/postgresql.rb,
lib/test_prof/any_fixture/dump/base_adapter.rb

Overview

Make DB fixtures from blocks.

Defined Under Namespace

Modules: DSL Classes: Cache, Configuration, Dump

Constant Summary collapse

INSERT_RXP =
/^INSERT INTO (\S+)/.freeze
MODIFY_RXP =
/^(INSERT INTO|UPDATE|DELETE FROM) (\S+)/i.freeze
ANY_FIXTURE_RXP =
/(\/\*|--).*\bany_fixture:dump/.freeze
ANY_FIXTURE_IGNORE_RXP =
/(\/\*|--).*\bany_fixture:ignore/.freeze

Constants included from Logging

Logging::COLORS

Class Method Summary collapse

Methods included from Logging

log

Class Method Details

.after_fixtures_reset(&block) ⇒ Object



193
194
195
# File 'lib/test_prof/any_fixture.rb', line 193

def after_fixtures_reset(&block)
  callbacks[:after_fixtures_reset] << block
end

.before_fixtures_reset(&block) ⇒ Object



189
190
191
# File 'lib/test_prof/any_fixture.rb', line 189

def before_fixtures_reset(&block)
  callbacks[:before_fixtures_reset] << block
end

.cached(id, &block) ⇒ Object



119
120
121
122
123
# File 'lib/test_prof/any_fixture.rb', line 119

def cached(id, &block)
  return (block_given? ? yield : nil) if @disabled

  cache.fetch(id, &block)
end

.cleanObject

Clean all affected tables (but do not reset cache)



159
160
161
162
163
164
165
166
167
# File 'lib/test_prof/any_fixture.rb', line 159

def clean
  disable_referential_integrity do
    tables_cache.keys.reverse_each do |table|
      ActiveRecord::Base.connection.execute %(
        DELETE FROM #{table}
      )
    end
  end
end

.configObject



97
98
99
# File 'lib/test_prof/any_fixture.rb', line 97

def config
  @config ||= Configuration.new
end

.configure {|config| ... } ⇒ Object

Yields:



101
102
103
# File 'lib/test_prof/any_fixture.rb', line 101

def configure
  yield config
end

.disable!Object



181
182
183
# File 'lib/test_prof/any_fixture.rb', line 181

def disable!
  @disabled = true
end

.enable!Object



185
186
187
# File 'lib/test_prof/any_fixture.rb', line 185

def enable!
  @disabled = false
end

.register(id) ⇒ Object

Register a block of code as a fixture, returns the result of the block execution



107
108
109
110
111
112
113
114
115
116
117
# File 'lib/test_prof/any_fixture.rb', line 107

def register(id)
  cached(id) do
    raise "No fixture named #{id} has been registered" unless block_given?

    next yield if @disabled

    ActiveSupport::Notifications.subscribed(method(:subscriber), "sql.active_record") do
      yield
    end
  end
end

.register_dump(name, clean: true, **options) ⇒ Object

Create and register new SQL dump. Use ‘watch` to provide additional paths to watch for dump re-generation



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/test_prof/any_fixture.rb', line 128

def register_dump(name, clean: true, **options)
  called_from = caller_locations(1, 1).first.path
  watch = options.delete(:watch) || [called_from]
  cache_key = options.delete(:cache_key)
  skip = options.delete(:skip_if)

  id = "sql/#{name}"

  register_method = clean ? :register : :cached

  public_send(register_method, id) do
    dump = Dump.new(name, watch: watch, cache_key: cache_key)

    unless dump.force?
      next if skip&.call(dump: dump)

      next dump.within_prepared_env(import: true, **options) { dump.load } if dump.exists?
    end

    subscriber = ActiveSupport::Notifications.subscribe("sql.active_record", dump.subscriber)
    res = dump.within_prepared_env(**options) { yield }

    dump.commit!

    res
  ensure
    ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
  end
end

.report_statsObject



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/test_prof/any_fixture.rb', line 208

def report_stats
  if cache.stats.empty?
    log :info, "AnyFixture has not been used"
    return
  end

  msgs = []

  msgs <<
    <<~MSG
      AnyFixture usage stats:
    MSG

  first_column = cache.stats.keys.map(&:size).max + 2

  msgs << format(
    "%#{first_column}s  %12s  %9s  %12s",
    "key", "build time", "hit count", "saved time"
  )

  msgs << ""

  total_spent = 0.0
  total_saved = 0.0
  total_miss = 0.0

  cache.stats.to_a.sort_by { |(_, v)| -v[:hit] }.each do |(key, stats)|
    total_spent += stats[:time]

    saved = stats[:time] * stats[:hit]

    total_saved += saved

    total_miss += stats[:time] if stats[:hit].zero?

    msgs << format(
      "%#{first_column}s  %12s  %9d  %12s",
      key, stats[:time].duration, stats[:hit],
      saved.duration
    )
  end

  msgs <<
    <<~MSG

      Total time spent: #{total_spent.duration}
      Total time saved: #{total_saved.duration}
      Total time wasted: #{total_miss.duration}
    MSG

  log :info, msgs.join("\n")
end

.resetObject

Reset all information and clean tables



170
171
172
173
174
175
176
177
178
179
# File 'lib/test_prof/any_fixture.rb', line 170

def reset
  callbacks[:before_fixtures_reset].each(&:call)

  clean
  tables_cache.clear
  cache.clear

  callbacks[:after_fixtures_reset].each(&:call)
  callbacks.clear
end

.subscriber(_event, _start, _finish, _id, data) ⇒ Object



197
198
199
200
201
202
203
204
205
206
# File 'lib/test_prof/any_fixture.rb', line 197

def subscriber(_event, _start, _finish, _id, data)
  matches = data.fetch(:sql).match(INSERT_RXP)
  return unless matches

  table_name = matches[1]

  return if /sqlite_sequence/.match?(table_name)

  tables_cache[table_name] = true
end