Class: Rcov::FileStatistics

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

Overview

A FileStatistics object associates a filename to:

  1. its source code

  2. the per-line coverage information after correction using rcov’s heuristics

  3. the per-line execution counts

A FileStatistics object can be therefore be built given the filename, the associated source code, and an array holding execution counts (i.e. how many times each line has been executed).

FileStatistics is relatively intelligent: it handles normal comments, =begin/=end, heredocs, many multiline-expressions… It uses a number of heuristics to determine what is code and what is a comment, and to refine the initial (incomplete) coverage information.

Basic usage is as follows:

sf = FileStatistics.new("foo.rb", ["puts 1", "if true &&", "   false", 
                               "puts 2", "end"],  [1, 1, 0, 0, 0])
sf.num_lines        # => 5
sf.num_code_lines   # => 5
sf.coverage[2]      # => true
sf.coverage[3]      # => :inferred
sf.code_coverage    # => 0.6

The array of strings representing the source code and the array of execution counts would normally be obtained from a Rcov::CodeCoverageAnalyzer.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, lines, counts, comments_run_by_default = false) ⇒ FileStatistics

Returns a new instance of FileStatistics.



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/rcov.rb', line 95

def initialize(name, lines, counts, comments_run_by_default = false)
  @name = name
  @lines = lines
  initial_coverage = counts.map{|x| (x || 0) > 0 ? true : false }
  @coverage = CoverageInfo.new initial_coverage
  @counts = counts
  @is_begin_comment = nil
  # points to the line defining the heredoc identifier
  # but only if it was marked (we don't care otherwise)
  @heredoc_start = Array.new(lines.size, false)
  @multiline_string_start = Array.new(lines.size, false)
  extend_heredocs
  find_multiline_strings
  precompute_coverage comments_run_by_default
end

Instance Attribute Details

#countsObject (readonly)

Returns the value of attribute counts.



94
95
96
# File 'lib/rcov.rb', line 94

def counts
  @counts
end

#coverageObject (readonly)

Returns the value of attribute coverage.



94
95
96
# File 'lib/rcov.rb', line 94

def coverage
  @coverage
end

#linesObject (readonly)

Returns the value of attribute lines.



94
95
96
# File 'lib/rcov.rb', line 94

def lines
  @lines
end

#nameObject (readonly)

Returns the value of attribute name.



94
95
96
# File 'lib/rcov.rb', line 94

def name
  @name
end

Instance Method Details

#code_coverageObject

Code coverage rate: fraction of lines of code executed, relative to the total amount of lines of code (loc). Returns a float from 0 to 1.0.



145
146
147
148
149
150
151
# File 'lib/rcov.rb', line 145

def code_coverage
  indices = (0...@lines.size).select{|i| is_code? i }
  return 0 if indices.size == 0
  count = 0
  indices.each {|i| count += 1 if @coverage[i] }
  1.0 * count / indices.size
end

#code_coverage_for_reportObject



153
154
155
# File 'lib/rcov.rb', line 153

def code_coverage_for_report
  code_coverage * 100
end

#is_code?(lineno) ⇒ Boolean

Returns true if the given line number corresponds to code, as opposed to a comment (either # or =begin/=end blocks).

Returns:

  • (Boolean)


173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/rcov.rb', line 173

def is_code?(lineno)
  unless @is_begin_comment
    @is_begin_comment = Array.new(@lines.size, false)
    pending = []
    state = :code
    @lines.each_with_index do |line, index|
      case state
      when :code
        if /^=begin\b/ =~ line
          state = :comment
          pending << index
        end
      when :comment
        pending << index
        if /^=end\b/ =~ line
          state = :code
          pending.each{|idx| @is_begin_comment[idx] = true}
          pending.clear
        end
      end
    end
  end
  @lines[lineno] && !@is_begin_comment[lineno] && 
    @lines[lineno] !~ /^\s*(#|$)/ 
end

#merge(lines, coverage, counts) ⇒ Object

Merge code coverage and execution count information. As for code coverage, a line will be considered

  • covered for sure (true) if it is covered in either self or in the coverage array

  • considered :inferred if the neither self nor the coverage array indicate that it was definitely executed, but it was inferred in either one

  • not covered (false) if it was uncovered in both

Execution counts are just summated on a per-line basis.



121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/rcov.rb', line 121

def merge(lines, coverage, counts)
  coverage.each_with_index do |v, idx|
    case @coverage[idx]
    when :inferred 
      @coverage[idx] = v || @coverage[idx]
    when false 
      @coverage[idx] ||= v
    end
  end
  counts.each_with_index{|v, idx| @counts[idx] += v }
  precompute_coverage false
end

#num_code_linesObject

Number of lines of code (loc).



162
163
164
# File 'lib/rcov.rb', line 162

def num_code_lines
  (0...@lines.size).select{|i| is_code? i}.size
end

#num_linesObject

Total number of lines.



167
168
169
# File 'lib/rcov.rb', line 167

def num_lines
  @lines.size
end

#total_coverageObject

Total coverage rate if comments are also considered “executable”, given as a fraction, i.e. from 0 to 1.0. A comment is attached to the code following it (RDoc-style): it will be considered executed if the the next statement was executed.



138
139
140
141
# File 'lib/rcov.rb', line 138

def total_coverage
  return 0 if @coverage.size == 0
  @coverage.inject(0.0) {|s,a| s + (a ? 1:0) } / @coverage.size
end

#total_coverage_for_reportObject



157
158
159
# File 'lib/rcov.rb', line 157

def total_coverage_for_report
  total_coverage * 100
end