#!/usr/bin/ruby -ws $d ||= false $d ||= ENV["DELETE"] $t ||= false $t ||= ENV["DELETE_TIMEOUT"] $m ||= false $m ||= ENV["MOVE_TIMEOUT"] $q ||= false $q ||= ENV["QUIET"] $v ||= ENV["V"] || "20" $s ||= ENV["SPEED"] || false require 'rubygems' require 'ruby_parser' require 'fileutils' $parser_class = case $v when "18" then Ruby18Parser when "19" then Ruby19Parser when "20" then Ruby20Parser else abort "Unknown version #{$v.inspect}. Needs to be 18, 19, or 20" end class IO RUBY19 = "<3".respond_to? :encoding class << self alias :binread :read unless RUBY19 end end ARGV.push "-" if ARGV.empty? class Racc::Parser def extract_defs ss = lexer.ss raise "can't access source. possible encoding issue" unless ss src = ss.string pre_error = src[0...ss.pos] defs = pre_error.grep(/^ *(?:def|it)/) raise "can't figure out where the bad code starts" unless defs.last last_def_indent = defs.last[/^ */] post_error = src[ss.pos..-1] idx = post_error =~ /^#{last_def_indent}end.*/ raise "can't figure out where the bad code ends" unless idx src = pre_error + post_error[0..idx+$&.length] src.scan(/^(( *)(?:def|it) .*?^\2end)/m) end def retest_for_errors defs parser = self.class.new parser.process(defs.join("\n\n")) rescue SyntaxError, StandardError nil end end def expand path if File.directory? path then require 'find' files = [] Find.find(*Dir[path]) do |f| files << f if File.file? f end files.sort else Dir.glob path end end def process_error parser defs = parser.extract_defs if parser.retest_for_errors defs then warn "Can't reproduce error with just methods, punting..." return end catch :extract_done do (1..defs.size).each do |perm_size| defs.combination(perm_size).each do |trial| unless parser.retest_for_errors trial then puts trial.join "\n" throw :extract_done end end end end rescue RuntimeError, Racc::ParseError => e warn "# process error: #{e.message.strip}" end def process file ruby = file == "-" ? $stdin.binread : File.binread(file) time = (ENV["RP_TIMEOUT"] || 10).to_i $stderr.print "# Validating #{file}: " parser = $parser_class.new t0 = Time.now if $s parser.process(ruby, file, time) if $s then warn "good: #{Time.now - t0}" else warn "good" end File.unlink file if $d rescue Timeout::Error $exit = 1 warn "TIMEOUT parsing #{file}. Skipping." if $m then dir = File.join $m, File.dirname(file) FileUtils.mkdir_p dir FileUtils.move file, dir elsif $t then File.unlink file end rescue StandardError, SyntaxError, Racc::ParseError => e $exit = 1 warn "" warn "# error: #{e.message.strip}" unless $q warn "" return if $q process_error parser end $exit = 0 $stdout.sync = true ARGV.each do |path| expand(path).each do |file| next unless File.file? file # omg... why would you name a dir support.rb? process file end end exit $exit