Class: SimpleCov::SourceFile
Relationships & Source Files | |
Namespace Children | |
Classes:
| |
Inherits: | Object |
Defined in: | lib/simplecov/source_file.rb, lib/simplecov/source_file/branch.rb, lib/simplecov/source_file/line.rb |
Overview
Representation of a source file including it’s coverage data, source code, source lines and featuring helpers to interpret that data.
Constant Summary
-
RUBY_FILE_ENCODING_MAGIC_COMMENT_REGEX =
# File 'lib/simplecov/source_file.rb', line 205/\A#\s*(?:-\*-)?\s*(?:en)?coding:\s*(\S+)\s*(?:-\*-)?\s*\z/.freeze
-
SHEBANG_REGEX =
# File 'lib/simplecov/source_file.rb', line 193/\A#!/.freeze
Class Method Summary
- .new(filename, coverage_data) ⇒ SourceFile constructor
Instance Attribute Summary
-
#coverage_data
readonly
The array of coverage data received from the
Coverage.result
-
#filename
readonly
The full path to this source file (e.g.
- #no_branches? ⇒ Boolean readonly
- #no_lines? ⇒ Boolean readonly
Instance Method Summary
-
#branches
Return all the branches inside current source file.
- #branches_coverage_percent
- #branches_for_line(line_number)
-
#branches_report
Return hash with key of line number and branch coverage count as value.
- #coverage_statistics
-
#covered_branches ⇒ Array
Select the covered branches Here we user tree schema because some conditions like case may have additional else that is not in declared inside the code but given by default by coverage report.
-
#covered_lines
Returns all covered lines as
Line
-
#covered_percent
The coverage for this file in percent.
- #covered_strength
-
#line(number)
Access
Line
source lines by line number. -
#line_with_missed_branch?(line_number) ⇒ Boolean
Check if any branches missing on given line number.
-
#lines
(also: #source_lines)
Returns all source lines for this file as instances of
Line
, and thus including coverage data. -
#lines_of_code
Returns the number of relevant lines (covered + missed).
-
#missed_branches ⇒ Array
Select the missed branches with coverage equal to zero.
-
#missed_lines
Returns all lines that should have been, but were not covered as instances of
Line
-
#never_lines
Returns all lines that are not relevant for coverage as
Line
instances. -
#project_filename
The path to this source file relative to the projects directory.
- #relevant_lines
-
#skipped_lines
Returns all lines that were skipped as
Line
instances. -
#source
Alias for #src.
-
#source_lines
Alias for #lines.
-
#src
(also: #source)
The source code for this file.
-
#total_branches
Return the relevant branches to source file.
- #branch_coverage_statistics private
- #build_branch(branch_data, hit_count, condition_start_line) private
-
#build_branches ⇒ Array
private
Call recursive method that transform our static hash to array of objects.
- #build_branches_from(condition, branches) private
-
#build_branches_report ⇒ Hash
private
Build full branches report Root branches represent the wrapper of all condition state that have inside the branches.
- #build_lines private
- #build_no_cov_chunks private
-
#coverage_exceeding_source_warn
private
Warning to identify condition from Issue #56.
- #ensure_remove_undefs(file_lines) private
- #line_coverage_statistics private
- #lines_strength private
- #load_source private
-
#no_cov_chunks
private
no_cov_chunks is zero indexed to work directly with the array holding the lines.
- #process_skipped_branches(branches) private
- #process_skipped_lines(lines) private
- #read_lines(file, lines, current_line) private
-
#restore_ruby_data_structure(structure)
private
Since we are dumping to and loading from JSON, and we have arrays as keys those don’t make their way back to us intact e.g. just as a string.
- #set_encoding_based_on_magic_comment(file, line) private
- #shebang?(line) ⇒ Boolean private
Constructor Details
.new(filename, coverage_data) ⇒ SourceFile
# File 'lib/simplecov/source_file.rb', line 14
def initialize(filename, coverage_data) @filename = filename @coverage_data = coverage_data end
Instance Attribute Details
#coverage_data (readonly)
The array of coverage data received from the Coverage.result
# File 'lib/simplecov/source_file.rb', line 12
attr_reader :coverage_data
#filename (readonly)
The full path to this source file (e.g. /User/colszowka/projects/simplecov/lib/simplecov/source_file.rb)
# File 'lib/simplecov/source_file.rb', line 10
attr_reader :filename
#no_branches? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'lib/simplecov/source_file.rb', line 102
def no_branches? total_branches.empty? end
#no_lines? ⇒ Boolean
(readonly)
[ GitHub ]
# File 'lib/simplecov/source_file.rb', line 88
def no_lines? lines.empty? || (lines.length == never_lines.size) end
Instance Method Details
#branch_coverage_statistics (private)
[ GitHub ]# File 'lib/simplecov/source_file.rb', line 346
def branch_coverage_statistics { branch: CoverageStatistics.new( covered: covered_branches.size, missed: missed_branches.size ) } end
#branches
Return all the branches inside current source file
# File 'lib/simplecov/source_file.rb', line 98
def branches @branches ||= build_branches end
#branches_coverage_percent
[ GitHub ]# File 'lib/simplecov/source_file.rb', line 106
def branches_coverage_percent coverage_statistics[:branch]&.percent end
#branches_for_line(line_number)
[ GitHub ]# File 'lib/simplecov/source_file.rb', line 142
def branches_for_line(line_number) branches_report.fetch(line_number, []) end
#branches_report
Return hash with key of line number and branch coverage count as value
# File 'lib/simplecov/source_file.rb', line 118
def branches_report @branches_report ||= build_branches_report end
#build_branch(branch_data, hit_count, condition_start_line) (private)
[ GitHub ]# File 'lib/simplecov/source_file.rb', line 324
def build_branch(branch_data, hit_count, condition_start_line) type, _id, start_line, _start_col, end_line, _end_col = branch_data SourceFile::Branch.new( start_line: start_line, end_line: end_line, coverage: hit_count, inline: start_line == condition_start_line, type: type ) end
#build_branches ⇒ Array
(private)
Call recursive method that transform our static hash to array of objects
# File 'lib/simplecov/source_file.rb', line 273
def build_branches coverage_branch_data = coverage_data.fetch("branches", {}) branches = coverage_branch_data.flat_map do |condition, coverage_branches| build_branches_from(condition, coverage_branches) end process_skipped_branches(branches) end
#build_branches_from(condition, branches) (private)
[ GitHub ]# File 'lib/simplecov/source_file.rb', line 310
def build_branches_from(condition, branches) # the format handed in from the coverage data is like this: # # [:then, 4, 6, 6, 6, 10] # # which is [type, id, start_line, start_col, end_line, end_col] _condition_type, _condition_id, condition_start_line, * = restore_ruby_data_structure(condition) branches.map do |branch_data, hit_count| branch_data = restore_ruby_data_structure(branch_data) build_branch(branch_data, hit_count, condition_start_line) end end
#build_branches_report ⇒ Hash
(private)
Build full branches report Root branches represent the wrapper of all condition state that have inside the branches
# File 'lib/simplecov/source_file.rb', line 262
def build_branches_report branches.reject(&:skipped?).each_with_object({}) do |branch, coverage_statistics| coverage_statistics[branch.report_line] ||= [] coverage_statistics[branch.report_line] << branch.report end end
#build_lines (private)
[ GitHub ]# File 'lib/simplecov/source_file.rb', line 229
def build_lines coverage_exceeding_source_warn if coverage_data["lines"].size > src.size lines = src.map.with_index(1) do |src, i| SimpleCov::SourceFile::Line.new(src, i, coverage_data["lines"][i - 1]) end process_skipped_lines(lines) end
#build_no_cov_chunks (private)
[ GitHub ]# File 'lib/simplecov/source_file.rb', line 164
def build_no_cov_chunks no_cov_lines = src.map.with_index(1).select { |line_src, _index| LinesClassifier.no_cov_line?(line_src) } # if we have an uneven number of nocovs we assume they go to the # end of the file, the source doesn't really matter # Can't deal with this within the each_slice due to differing # behavior in JRuby: jruby/jruby#6048 no_cov_lines << ["", src.size] if no_cov_lines.size.odd? no_cov_lines.each_slice(2).map do |(_line_src_start, index_start), (_line_src_end, index_end)| index_start..index_end end end
#coverage_exceeding_source_warn (private)
Warning to identify condition from Issue #56
# File 'lib/simplecov/source_file.rb', line 251
def coverage_exceeding_source_warn warn "Warning: coverage data provided by Coverage [#{coverage_data['lines'].size}] exceeds number of lines in #{filename} [#{src.size}]" end
#coverage_statistics
[ GitHub ]# File 'lib/simplecov/source_file.rb', line 32
def coverage_statistics @coverage_statistics ||= { **line_coverage_statistics, **branch_coverage_statistics } end
#covered_branches ⇒ Array
Select the covered branches Here we user tree schema because some conditions like case may have additional else that is not in declared inside the code but given by default by coverage report
# File 'lib/simplecov/source_file.rb', line 129
def covered_branches @covered_branches ||= branches.select(&:covered?) end
#covered_lines
Returns all covered lines as SourceFile::Line
# File 'lib/simplecov/source_file.rb', line 48
def covered_lines @covered_lines ||= lines.select(&:covered?) end
#covered_percent
The coverage for this file in percent. 0 if the file has no coverage lines
# File 'lib/simplecov/source_file.rb', line 80
def covered_percent coverage_statistics[:line]&.percent end
#covered_strength
[ GitHub ]# File 'lib/simplecov/source_file.rb', line 84
def covered_strength coverage_statistics[:line]&.strength end
#ensure_remove_undefs(file_lines) (private)
[ GitHub ]# File 'lib/simplecov/source_file.rb', line 214
def ensure_remove_undefs(file_lines) # invalid/undef replace are technically not really necessary but nice to # have and work around a JRuby incompatibility. Also moved here from # simplecov-html to have encoding shenaningans in one place. See #866 # also setting these option on `file.set_encoding` doesn't seem to work # properly so it has to be done here. file_lines.each do |line| if line.encoding == Encoding::UTF_8 line else line.encode!("UTF-8", invalid: :replace, undef: :replace) end end end
#line(number)
Access SourceFile::Line
source lines by line number
# File 'lib/simplecov/source_file.rb', line 75
def line(number) lines[number - 1] end
#line_coverage_statistics (private)
[ GitHub ]# File 'lib/simplecov/source_file.rb', line 336
def line_coverage_statistics { line: CoverageStatistics.new( total_strength: lines_strength, covered: covered_lines.size, missed: missed_lines.size ) } end
#line_with_missed_branch?(line_number) ⇒ Boolean
Check if any branches missing on given line number
# File 'lib/simplecov/source_file.rb', line 153
def line_with_missed_branch?(line_number) branches_for_line(line_number).any? { |_type, count| count.zero? } end
#lines Also known as: #source_lines
Returns all source lines for this file as instances of SourceFile::Line
, and thus including coverage data. Aliased as :source_lines
# File 'lib/simplecov/source_file.rb', line 42
def lines @lines ||= build_lines end
#lines_of_code
Returns the number of relevant lines (covered + missed)
# File 'lib/simplecov/source_file.rb', line 70
def lines_of_code coverage_statistics[:line]&.total end
#lines_strength (private)
[ GitHub ]#load_source (private)
[ GitHub ]
#missed_branches ⇒ Array
Select the missed branches with coverage equal to zero
# File 'lib/simplecov/source_file.rb', line 138
def missed_branches @missed_branches ||= branches.select(&:missed?) end
#missed_lines
Returns all lines that should have been, but were not covered as instances of SourceFile::Line
# File 'lib/simplecov/source_file.rb', line 54
def missed_lines @missed_lines ||= lines.select(&:missed?) end
#never_lines
Returns all lines that are not relevant for coverage as SourceFile::Line
instances
# File 'lib/simplecov/source_file.rb', line 60
def never_lines @never_lines ||= lines.select(&:never?) end
#no_cov_chunks (private)
no_cov_chunks is zero indexed to work directly with the array holding the lines
# File 'lib/simplecov/source_file.rb', line 160
def no_cov_chunks @no_cov_chunks ||= build_no_cov_chunks end
#process_skipped_branches(branches) (private)
[ GitHub ]# File 'lib/simplecov/source_file.rb', line 282
def process_skipped_branches(branches) return branches if no_cov_chunks.empty? branches.each do |branch| branch.skipped! if no_cov_chunks.any? { |no_cov_chunk| branch.overlaps_with?(no_cov_chunk) } end branches end
#process_skipped_lines(lines) (private)
[ GitHub ]# File 'lib/simplecov/source_file.rb', line 237
def process_skipped_lines(lines) # the array the lines are kept in is 0-based whereas the line numbers in the nocov # chunks are 1-based and are expected to be like this in other parts (and it's also # arguably more understandable) no_cov_chunks.each { |chunk| lines[(chunk.begin - 1)..(chunk.end - 1)].each(&:skipped!) } lines end
#project_filename
The path to this source file relative to the projects directory
#read_lines(file, lines, current_line) (private)
[ GitHub ]# File 'lib/simplecov/source_file.rb', line 198
def read_lines(file, lines, current_line) return lines unless current_line set_encoding_based_on_magic_comment(file, current_line) lines.concat([current_line], ensure_remove_undefs(file.readlines)) end
#relevant_lines
[ GitHub ]# File 'lib/simplecov/source_file.rb', line 92
def relevant_lines lines.size - never_lines.size - skipped_lines.size end
#restore_ruby_data_structure(structure) (private)
Since we are dumping to and loading from JSON, and we have arrays as keys those don’t make their way back to us intact e.g. just as a string
We should probably do something different here, but as it stands these are our data structures that we write so eval isn’t too bad.
See #801
# File 'lib/simplecov/source_file.rb', line 300
def restore_ruby_data_structure(structure) # Tests use the real data structures (except for integration tests) so no need to # put them through here. return structure if structure.is_a?(Array) # rubocop:disable Security/Eval eval structure # rubocop:enable Security/Eval end
#set_encoding_based_on_magic_comment(file, line) (private)
[ GitHub ]# File 'lib/simplecov/source_file.rb', line 206
def set_encoding_based_on_magic_comment(file, line) # Check for encoding magic comment # Encoding magic comment must be placed at first line except for shebang if (match = RUBY_FILE_ENCODING_MAGIC_COMMENT_REGEX.match(line)) file.set_encoding(match[1], "UTF-8") end end
#shebang?(line) ⇒ Boolean
(private)
# File 'lib/simplecov/source_file.rb', line 194
def shebang?(line) SHEBANG_REGEX.match?(line) end
#skipped_lines
Returns all lines that were skipped as SourceFile::Line
instances
# File 'lib/simplecov/source_file.rb', line 65
def skipped_lines @skipped_lines ||= lines.select(&:skipped?) end
#source
Alias for #src.
# File 'lib/simplecov/source_file.rb', line 30
alias source src
#source_lines
Alias for #lines.
# File 'lib/simplecov/source_file.rb', line 45
alias source_lines lines
#src Also known as: #source
The source code for this file. Aliased as :source
# File 'lib/simplecov/source_file.rb', line 25
def src # We intentionally read source code lazily to # suppress reading unused source code. @src ||= load_source end
#total_branches
Return the relevant branches to source file
# File 'lib/simplecov/source_file.rb', line 112
def total_branches @total_branches ||= covered_branches + missed_branches end