module Maxitest::Testrbl
def all_test_files_in(folder)
def all_test_files_in(folder) Dir[File.join(folder, "{**/,}*_{test,spec}.rb")].uniq end
def block_start_from_line(line)
def block_start_from_line(line) if line =~ /^(\s*).* do( \|.*\|)?$/ [$1, nil] end end
def changed_files
def changed_files changed_files = sh("git status -s").split("\n").map { |l| l.strip.split(/\s+/, 2)[1] } if changed_files.empty? # user wants to test last commit and not current diff changed_files = sh("git show --name-only").split("\n\n").last.split("\n") end # we only want test files that were added or changed (not deleted) changed_files.select { |f| f =~ /_(test|spec)\.rb$/ && File.exist?(f) } end
def filter_duplicate_final(patterns)
def filter_duplicate_final(patterns) found_final = 0 patterns.reject { |p| p.end_with?("$") and (found_final += 1) > 1 } end
def line_pattern_option(file, line)
def line_pattern_option(file, line) [file, "-n", "/#{pattern_from_file(File.readlines(file), line)}/"] end
def localize(file)
def localize(file) file =~ /^[-a-z\d_]/ ? "./#{file}" : file end
def partition_argv(argv)
def partition_argv(argv) next_is_option = false argv.partition do |arg| if next_is_option next_is_option = false else if arg =~ /^-.$/ or arg =~ /^--/ # single letter option followed by argument like -I test or long options like --verbose next_is_option = true if OPTION_WITH_ARGUMENT.include?(arg) false elsif arg =~ /^-/ # multi letter option like -Itest false else true end end end end
def partition_options(options)
def partition_options(options) next_is_before = false options.partition do |option| if next_is_before next_is_before = false true else if option =~ /^-(r|I)/ next_is_before = (option.size == 2) true else false end end end end
def pattern_from_file(lines, line)
def pattern_from_file(lines, line) possible_lines = lines[0..(line.to_i-1)].reverse found = possible_lines.map { |line| test_pattern_from_line(line) || block_start_from_line(line) }.compact # pattern and the groups it is nested under (like describe - describe - it) last_spaces = " " * 100 patterns = found.select do |spaces, name| last_spaces = spaces if spaces.size < last_spaces.size end.map(&:last).compact return filter_duplicate_final(patterns).reverse.join(".*") if found.size > 0 raise "no test found before line #{line}" end
def ruby
def ruby if File.file?("Gemfile") ["ruby", "-rbundler/setup"] # faster then bundle exec ruby else ["ruby"] end end
def run(command)
def run(command) puts command.join(" ") STDOUT.flush # if exec fails horribly we at least see some output Kernel.exec *command end
def run_from_cli(argv)
def run_from_cli(argv) files, options = partition_argv(argv) files.concat(changed_files) if options.delete("--changed") files = ["test"] if files.empty? files = files.map { |f| localize(f) } load_options, options = partition_options(options) if files.size == 1 and files.first =~ /^(\S+):(\d+)$/ file = $1 line = $2 run(ruby + load_options + line_pattern_option(file, line) + options) else if files.size == 1 and File.file?(files.first) run(ruby + load_options + files + options) elsif options.none? { |arg| arg =~ /^-n/ } seed = if seed = options.index("--seed") ["--"] + options.slice!(seed, 2) else [] end files = files.map { |f| File.directory?(f) ? all_test_files_in(f) : f }.flatten run(ruby + load_options + files.map { |f| "-r#{f}" } + options + ["-e", ""] + seed) else # pass though # no bundle exec: projects with mini and unit-test do not run well via bundle exec testrb run ["testrb"] + argv end end end
def sh(command)
def sh(command) result = `#{command}` raise "Failed: #{command} -> #{result}" unless $?.success? result end
def test_pattern_from_line(line)
def test_pattern_from_line(line) PATTERNS.each do |r| next unless line =~ r whitespace, method, test_name = $1, $2, $4 return [whitespace, test_pattern_from_match(method, test_name)] end nil end
def test_pattern_from_match(method, test_name)
def test_pattern_from_match(method, test_name) regex = Regexp.escape(test_name).gsub("\\ "," ").gsub(INTERPOLATION, ".*") regex = if method == "test" # test "xxx -_ yyy" # test-unit: "test: xxx -_ yyy" # activesupport: "test_xxx_-__yyy" "^test(: |_)#{regex.gsub(" ", ".")}$" elsif method == "describe" || (method == "context" && !via_shoulda?) "#{regex}(::)?" elsif method == "should" && via_shoulda? optional_test_name = "(?:\(.*\))?" "#{method} #{regex}\. #{optional_test_name}$" elsif ["it", "should"].include?(method) # minitest aliases for shoulda "#test_\\d+_#{regex}$" else regex end regex.gsub("'", ".") end
def via_shoulda?
def via_shoulda? return @via_shoulda if defined?(@via_shoulda) @via_shoulda = !File.exist?("Gemfile.lock") || File.read("Gemfile.lock").include?(" shoulda-context ") end