Skip to content

Instantly share code, notes, and snippets.

@jemmyw
Created September 7, 2020 05:32
Show Gist options
  • Save jemmyw/9e44fefe61984b7dc43b95262980c5ce to your computer and use it in GitHub Desktop.
Save jemmyw/9e44fefe61984b7dc43b95262980c5ce to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby
require "open3"
ANSI_REGEX = Regexp.new('\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
SPEC_EXT = "_spec.rb"
BRANCHED_FROM = "master"
RSPEC_ENV = {
"FULL_CHROME" => "0",
"DISABLE_SPRING" => "",
"DISABLE_COV" => "1"
}
`which entr`
unless $?.to_i == 0
puts "Must install entr first. brew install entr or something"
exit 1
end
Failure =
Struct.new(:location, :msg, :file) do
include Comparable
def ==(other)
location == other.location
end
def <=>(other)
location <=> other.location
end
def inspect
"#{location} @ #{file} # #{msg}"
end
end
@failures = []
@worker = nil
def run_specs(spec_files)
if spec_files.none?
puts "No spec files given"
return []
end
spec_files = spec_files.map { |sp| sp.is_a?(String) ? sp : sp.location }
cmd =
"bundle exec spring rspec --force-color --no-profile --format progress #{
spec_files.join(" ")
}"
puts cmd
buffer = ""
Open3.popen3(RSPEC_ENV, cmd) do |i, o, e, t|
i.close
until [o, e].all?(&:eof)
rs, = IO.select([o, e])
rs.each do |f|
next if f.eof
out = f.read_nonblock(1024)
$stdout.write(out) if f == o
buffer << out
end
end
end
lines = buffer.gsub(ANSI_REGEX, "").chomp.lines.map(&:strip)
lines.reduce([]) do |acc, line|
if line =~ /rspec ((?:'[^']+')|(?:[^\s]+:\d+)).*# (.*)$/
loc = $1
msg = $2
fil = failure_to_file_line(lines, loc, msg)
acc << Failure.new(loc, msg, fil)
end
acc
end
end
def spec_files_for(spec_files, file)
if file.end_with?("_spec.rb")
[file]
else
spec_file = File.basename(file, File.extname(file))
other_files = File.join(File.basename(File.dirname(file)), File.basename(file, File.extname(file)))
r = %r{(#{spec_file}#{SPEC_EXT}$)|(#{other_files}.*#{SPEC_EXT}$)}
spec_files.grep(r)
end.reject { |sp| sp =~ %r{\/features\/} }
end
def failure_to_file_line(lines, loc, msg)
if loc =~ /'([^\[]+)\[/
spec_file = $1
starting_line = lines.find_index { |line| line =~ /\s*\d+\) #{Regexp.escape(msg)}/ }
return loc unless starting_line
lines[starting_line..-1].detect do |line|
line =~ %r{\s*#\s+\.\/spec[^\s]+:(\d+)}
end.yield_self { |line| line =~ /([^\s]+:\d+)/ ? $1.to_s : loc }
else
loc
end
end
def stop_spring
`bundle exec spring stop`
end
def start_spring
stop_spring
r, w = IO.pipe
if pid = fork
w.close
puts "sd spring"
puts r.gets
r.close
Process.detach(pid)
else
r.close
Open3.popen2e("bundle exec spring server") do |i, oe, t|
i.close
oe.each { |line| w << line if line =~ /started on/ }
end
exit!
end
end
def output_failures(failures)
failures.each { |failure| puts "sd failure #{failure.inspect}" }
end
def create_worker
Thread.new do
puts "\nsd Starting"
spec_files = `git ls-files|grep '_spec\.rb'`.chomp.lines.map(&:strip)
changed_files =
`git diff --name-status --no-renames #{BRANCHED_FROM}...|grep -E '\''^(A|M)'\'.*\\.rb'|awk '\''{print $2}'\''|xargs ls -t`.chomp
.lines.map(&:strip)
puts "Last changed file: #{changed_files.first}"
if @failures.any?
# first pick out failures in the top changed spec file
top_changed = spec_files_for(spec_files, changed_files.first)
top_failures =
@failures.select { |failure| top_changed.any? { |sp| failure.location.include?(sp) } }
new_failures = top_failures.any? ? run_specs(top_failures) : []
# then other failures
new_failures += run_specs(@failures - top_failures)
# then the whole top changed spec file
new_failures += run_specs(top_changed)
new_failures.uniq!
if new_failures.none?
puts "All fixed!"
else
still_broken = @failures & new_failures
unseen = new_failures - @failures
fixed = @failures - new_failures
puts "#{fixed.length} fixed, #{still_broken.length} still broken, #{
unseen.length
} new failures"
end
@failures = new_failures
else
top_files = spec_files_for(spec_files, changed_files.first)
top_failures = run_specs(top_files)
output_failures(top_failures)
rest_files = changed_files.map { |f| spec_files_for(spec_files, f) }.flatten.uniq - top_files
rest_failures = run_specs(rest_files)
output_failures(rest_failures)
@failures = top_failures + rest_failures
puts "#{@failures.length} failures"
end
stop_spring
puts "sd Finished"
end
end
start_spring
while true
puts "sd Waiting for changes"
`git ls-files|grep -E '\\.rb$'|entr -nzdp echo 1`
if @worker
@worker.kill
@worker.join
else
# Restart spring each full start
start_spring
end
@worker = create_worker
sleep 1
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment