Class: Rcov::FileStatistics

Inherits:
Object
  • Object
show all
Defined in:
lib/rcov/file_statistics.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.



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/rcov/file_statistics.rb', line 29

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.



28
29
30
# File 'lib/rcov/file_statistics.rb', line 28

def counts
  @counts
end

#coverageObject (readonly)

Returns the value of attribute coverage.



28
29
30
# File 'lib/rcov/file_statistics.rb', line 28

def coverage
  @coverage
end

#linesObject (readonly)

Returns the value of attribute lines.



28
29
30
# File 'lib/rcov/file_statistics.rb', line 28

def lines
  @lines
end

#nameObject (readonly)

Returns the value of attribute name.



28
29
30
# File 'lib/rcov/file_statistics.rb', line 28

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.



79
80
81
82
83
84
85
# File 'lib/rcov/file_statistics.rb', line 79

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



87
88
89
# File 'lib/rcov/file_statistics.rb', line 87

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)


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
# File 'lib/rcov/file_statistics.rb', line 107

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|
      line.force_encoding("utf-8") if line.respond_to?(:force_encoding)
      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.



55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/rcov/file_statistics.rb', line 55

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



96
97
98
# File 'lib/rcov/file_statistics.rb', line 96

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

#num_linesObject

Total number of lines.



101
102
103
# File 'lib/rcov/file_statistics.rb', line 101

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.



72
73
74
75
# File 'lib/rcov/file_statistics.rb', line 72

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



91
92
93
# File 'lib/rcov/file_statistics.rb', line 91

def total_coverage_for_report
  total_coverage * 100
end