Created
July 18, 2024 05:03
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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