Created
September 7, 2020 05:32
-
-
Save jemmyw/9e44fefe61984b7dc43b95262980c5ce to your computer and use it in GitHub Desktop.
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
#!/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