Skip to content

Instantly share code, notes, and snippets.

@costa
Created July 18, 2024 05:03
Show Gist options
  • Save costa/f9ae7184ca254b25f98872ae7a170358 to your computer and use it in GitHub Desktop.
Save costa/f9ae7184ca254b25f98872ae7a170358 to your computer and use it in GitHub Desktop.
Git-based "report-skip-retry" RSpec helper for detailing code version releases and more efficient testing -- just `require` it in your spec_helper.rb
require 'git'
module GitHelper
class << self
attr_accessor :logger
attr_accessor :git_logger
def record!(example, result, time:)
if dirty?
warn "Not actually recording\n#{example.full_description}: #{result} (#{time}s)"
else
record.track! example, result, time
record.save!
end
end
def passed?(example)
!dirty? && passed_include?(example)
end
def dirty!
@dirty = true
end
private
def dirty?
@dirty || !record
end
def record
return @record if defined? @record
@record = Record.open(File.expand_path '.')
end
def passed_include?(example)
!!record.passed[example.id]
end
end
def run(example_group_instance, reporter)
start_t = Time.now
GitHelper.passed?(self) ||
super(example_group_instance, reporter).tap do |result|
GitHelper.record! self, result, time: (Time.now - start_t)
end
rescue Exception
GitHelper.record! self, $!, time: (Time.now - start_t)
raise
end
class Record
class << self
def open(work_dir)
g = Git.open(work_dir, log: GitHelper.git_logger)
return nil unless g.diff.size == 0
new git: g, logger: GitHelper.logger
end
end
attr_reader :failed, :passed
def initialize(git:, logger: nil)
@g = git
@logger = logger
@spec_commit = last_commit!
data = load
@logger.debug{"IN:\n#{data}"} if @logger
if data.respond_to?(:keys) && ([:failed, :passed] - data.keys).empty?
@logger.info "git helper here: will amend the last commit message YAML with spec results" if @logger
data
else
if data
@logger.warn "git helper here: found some weird YAML with the last commit message, will overwrite" if @logger
else
@logger.info "git helper here: the work dir is clean, will amend the last commit message with spec results YAML" if @logger
end
@spec_commit = nil
{failed: {}, passed: {}}
end.tap do |data|
@failed = data[:failed]
@passed = data[:passed]
end
end
def track!(example, result, time)
if !result || result.is_a?(Exception)
[@failed, @passed]
else
[@passed, @failed]
end.tap do |to_insert, to_delete|
to_insert[example.id] = [example.full_description, time]
to_delete.delete example.id
end
end
def save!(retries: 3)
data = {total: @failed.size + @passed.size, failed: @failed, passed: @passed}
# TODO! what if it's someone's else's commit?? make an empty one of our own's on save!!
@g.commit "#{@message}\n\n#{data.to_yaml}", allow_empty: true, amend: true
@spec_commit = last_commit!
@logger.debug{"OUT:\n#{data}"} if @logger
rescue Git::FailedError
raise unless
(retries -= 1) >= 0
sleep 3
retry
end
private
def last_commit!
@g.object('HEAD')
end
def load
@message = @spec_commit.message
yaml_start = @message.lines.index{|l| l.strip == '---'}
if yaml_start
begin
YAML.load @message.lines[yaml_start..-1].join
rescue # TODO be more specific here
@logger.warn "git helper here: failed to parse the last commit message YAML, will overwrite" if @logger
nil
end.tap do
@message = @message.lines[0..(yaml_start - 1)].join.strip
end
end
end
end
end
GitHelper.logger = Logger.new(STDERR).tap do |logger|
logger.level = ENV['GIT_HELPER_LOGGER_LEVEL'] || 'INFO'
if logger.level < Logger::INFO # TODO a proxy level-lowering logger instead
GitHelper.git_logger = logger
end
end
# TODO couldn't get it working with an `around` hook (playing nicely with other hooks), had to resort to monkey patching
class RSpec::Core::Example
prepend GitHelper
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment