Class: Covet::CLI

Inherits:
Object
  • Object
show all
Defined in:
lib/covet/cli.rb

Class Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(argv) ⇒ CLI

Returns a new instance of CLI.



12
13
14
# File 'lib/covet/cli.rb', line 12

def initialize(argv)
  @argv = argv
end

Class Attribute Details

.optionsObject

Returns the value of attribute options.



9
10
11
# File 'lib/covet/cli.rb', line 9

def options
  @options
end

Instance Method Details

#runObject

TODO: process cmdline options for

- specify VCS [ ]
- specify test seed (ordering)
- stats (show filtered files, files to run vs files to ignore, etc.)


20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# 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