class Covet::CLI

Attributes

options[RW]

Public Class Methods

new(argv) click to toggle source
# File lib/covet/cli.rb, line 12
def initialize(argv)
  @argv = argv
end

Public Instance Methods

run() click to toggle source

TODO: process cmdline options for

- specify VCS [ ]
- specify test seed (ordering)
- stats (show filtered files, files to run vs files to ignore, etc.)
# File lib/covet/cli.rb, line 20
def run
  options = nil
  begin
    options = Options.parse!(@argv)
    self.class.options = options
  rescue OptionParser::InvalidArgument, OptionParser::InvalidOption => e
    Kernel.abort "Error: #{e.message}"
  end

  if options[:collect_cmdline] && !options[:collect_cmdline].empty?
    cmd = options[:collect_cmdline]
    env_options = { 'COVET_COLLECT' => '1' }
    if options[:collect_gem_whitelist].any?
      env_options['COVET_GEM_WHITELIST'] = %Q("#{options[:collect_gem_whitelist].join(',')}")
    end
    if options[:test_runner] != Options::DEFAULTS[:test_runner]
      env_options['COVET_TEST_RUNNER'] = %Q("#{options[:test_runner]}")
    end
    env_list_str = env_options.to_a.map { |ary| ary[0] + '=' + ary[1] }.join(' ')
    cmd = %Q(#{env_list_str} #{cmd})
    puts cmd
    puts "Collecting coverage information for each test method..."
    Kernel.exec cmd
  end

  revision = options[:revision]
  line_changes = nil # establish scope
  begin
    line_changes = LineChangesVCS.changes_since(revision)
  rescue Rugged::RepositoryError
    Kernel.abort "Error: #{Dir.pwd} is not a git repository. " \
      "Make sure you're in the project root."
  rescue Rugged::Error, Rugged::InvalidError, TypeError
    Kernel.abort "Error: #{options[:revision]} is not a valid revision reference in #{options[:VCS]}"
  end
  if line_changes.empty?
    if revision.to_s == 'last_commit'
      revision = "last commit" # prettier output below
    end
    puts "# No changes since #{revision}. You can specify the #{options[:VCS]} revision using the --revision option."
    Kernel.exit
  end

  cov_map = Hash.new { |h, file| h[file] = Hash.new { |i, line| i[line] = [] } }
  logfile = LogFile.new(:mode => 'r')

  if logfile.file_exists?

    run_stats = {}
    # Read in the coverage info
    logfile.load_each_buf! do |buf|
      buf.each do |args|
        if args[0] == 'base' # first value logged
          run_options = args.last
          if run_options['version'] != Covet::VERSION
            warn "Warning - the run log was created with another version of covet " \
            "(#{run_options['version']}), which is not guaranteed to be compatible " \
            "with this version of covet (#{Covet::VERSION}). Please run 'covet -c' again."
          end
          next
        end

        if args[0] == 'stats' # last value logged
          run_stats.update(args.last)
          next
        end

        desc = args[0]
        delta = args[1]
        next if delta.nil? # no coverage difference
        #stats = args[2]

        delta.each_pair do |fname, lines_hash|
          file_map = cov_map[fname]
          lines_hash.each do |line, _executions|
            # add the test name to the map. Multiple tests can execute the same
            # line, so we need to use an array.
            file_map[line.to_i] << desc
          end
        end
      end
    end

    git_repo = VCS::Git.find_git_repo_path!

    to_run = []
    line_changes.each do |(file, line)|
      full_path = File.join(git_repo, file)
      relative_to_pwd = file
      if git_repo != Dir.pwd
        relative_to_pwd = full_path.sub(Dir.pwd, '').sub(File::SEPARATOR, '')
      end
      # NOTE: here, `file` is a filename starting from the GIT path (not necessarily `Dir.pwd`)
      # if the actual test files changed, then we need to run the whole file again.
      if relative_to_pwd.start_with?(*Covet.test_directories)
        if relative_to_pwd.start_with?("test#{File::SEPARATOR}") && relative_to_pwd.end_with?('_test.rb', '_spec.rb')
          to_run << [file, full_path] unless to_run.include?([file, full_path])
          # We have to disable the method filter in this case because we
          # don't know the method names of all these methods in this file.
          # TODO: save this information in the coverage log file and use it here.
          options[:disable_test_method_filter] = true
        elsif relative_to_pwd.start_with?("spec#{File::SEPARATOR}") && relative_to_pwd.end_with?('_test.rb', '_spec.rb')
          to_run << [file, full_path] unless to_run.include?([file, full_path])
          # We have to disable the method filter in this case because we
          # don't know the method names of all these methods in this file.
          # TODO: save this information in the coverage log file and use it here.
          options[:disable_test_method_filter] = true
        end
        next
      end
      # library code changes
      cov_map[full_path][line].each do |desc|
        to_run << [file, desc] unless to_run.include?([file, desc])
      end
    end
    if ENV['COVET_INVERT_RUN_LIST'] == '1' # NOTE: for debugging covet only
      to_run_fnames = to_run.map { |(_file, desc)| desc.split('#').first }.flatten.uniq
      all_fnames = Dir.glob("{#{Covet.test_directories.join(',')}}/**/*_{test,spec}.rb").to_a.map { |fname| File.expand_path(fname, Dir.pwd) }
      to_run = (all_fnames - to_run_fnames).map { |fname| [fname, "#{fname}##{fname}"] }.sort_by do |ary|
        ary[1].split('#').first
      end
    end
    if options[:exec_run_list]
      if to_run.empty?
        puts "# No test cases to run"
      else
        cmdline = Covet.cmdline_for_run_list(to_run)
        puts cmdline
        Kernel.exec cmdline
      end
    elsif options[:print_run_list]
      if to_run.empty?
        puts "# No test cases to run"
      else
        if options[:print_run_list_format] == :"test-runner"
          puts Covet.cmdline_for_run_list(to_run)
        else
          # TODO: show not just the files but also the methods in each file
          puts "You need to run:"
          to_run.uniq! { |(_file, desc)| desc.split('#').first }
          to_run.each do |(_file, desc)|
            puts " - #{desc.split('#').first}"
          end
        end
      end
    end
  else
    Kernel.abort "Error: The coverage log file doesn't exist.\n" \
      "You need to collect info first with 'covet -c $TEST_CMD'\n" \
      "Ex: covet -c \"rake test\""
  end
end