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.



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/rcov.rb', line 82

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.



81
82
83
# File 'lib/rcov.rb', line 81

def counts
  @counts
end

#coverageObject (readonly)

Returns the value of attribute coverage.



81
82
83
# File 'lib/rcov.rb', line 81

def coverage
  @coverage
end

#linesObject (readonly)

Returns the value of attribute lines.



81
82
83
# File 'lib/rcov.rb', line 81

def lines
  @lines
end

#nameObject (readonly)

Returns the value of attribute name.



81
82
83
# File 'lib/rcov.rb', line 81

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.



132
133
134
135
136
137
138
# File 'lib/rcov.rb', line 132

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

#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)


152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/rcov.rb', line 152

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.



108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/rcov.rb', line 108

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).



141
142
143
# File 'lib/rcov.rb', line 141

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

#num_linesObject

Total number of lines.



146
147
148
# File 'lib/rcov.rb', line 146

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.



125
126
127
128
# File 'lib/rcov.rb', line 125

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