simplecov 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +65 -1425
- data/lib/simplecov/atomic_file.rb +70 -0
- data/lib/simplecov/cli/clean.rb +43 -3
- data/lib/simplecov/cli/command_helpers.rb +55 -0
- data/lib/simplecov/cli/coverage.rb +21 -28
- data/lib/simplecov/cli/coverage_file.rb +65 -0
- data/lib/simplecov/cli/diff.rb +43 -48
- data/lib/simplecov/cli/dotfile.rb +12 -6
- data/lib/simplecov/cli/merge.rb +12 -10
- data/lib/simplecov/cli/open.rb +3 -5
- data/lib/simplecov/cli/report.rb +31 -23
- data/lib/simplecov/cli/serve/report_preparer.rb +29 -0
- data/lib/simplecov/cli/serve/static_file_handler.rb +122 -0
- data/lib/simplecov/cli/serve.rb +30 -94
- data/lib/simplecov/cli/uncovered.rb +20 -25
- data/lib/simplecov/cli.rb +15 -2
- data/lib/simplecov/combine/branches_combiner.rb +43 -19
- data/lib/simplecov/combine/coverage_accumulator.rb +268 -0
- data/lib/simplecov/combine/identity_interner.rb +30 -0
- data/lib/simplecov/combine/interned_counts.rb +30 -0
- data/lib/simplecov/combine/lines_combiner.rb +48 -20
- data/lib/simplecov/combine/methods_combiner.rb +46 -21
- data/lib/simplecov/combine/results_combiner.rb +12 -37
- data/lib/simplecov/combine.rb +5 -23
- data/lib/simplecov/command_guesser.rb +66 -9
- data/lib/simplecov/configuration/coverage.rb +12 -15
- data/lib/simplecov/configuration/coverage_criteria.rb +34 -38
- data/lib/simplecov/configuration/eval_coverage.rb +41 -0
- data/lib/simplecov/configuration/filters.rb +11 -44
- data/lib/simplecov/configuration/formatting.rb +18 -9
- data/lib/simplecov/configuration/groups.rb +42 -0
- data/lib/simplecov/configuration/merging.rb +8 -7
- data/lib/simplecov/configuration/thresholds.rb +14 -13
- data/lib/simplecov/configuration.rb +13 -43
- data/lib/simplecov/coverage_json.rb +24 -0
- data/lib/simplecov/coverage_statistics.rb +1 -1
- data/lib/simplecov/coverage_violations.rb +15 -5
- data/lib/simplecov/defaults.rb +7 -3
- data/lib/simplecov/directive.rb +1 -1
- data/lib/simplecov/exit_codes/check.rb +33 -0
- data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +11 -20
- data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +3 -16
- data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +14 -23
- data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +14 -25
- data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +4 -17
- data/lib/simplecov/exit_codes.rb +1 -0
- data/lib/simplecov/exit_handling.rb +11 -41
- data/lib/simplecov/file_list.rb +5 -10
- data/lib/simplecov/filter.rb +43 -9
- data/lib/simplecov/formatter/base.rb +11 -0
- data/lib/simplecov/formatter/coverage_json_writer.rb +97 -0
- data/lib/simplecov/formatter/html_formatter/public/index.html +35 -5
- data/lib/simplecov/formatter/html_formatter/viewer_data_validator.rb +96 -0
- data/lib/simplecov/formatter/html_formatter.rb +67 -47
- data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +54 -56
- data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +78 -88
- data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +70 -76
- data/lib/simplecov/formatter/json_formatter.rb +5 -48
- data/lib/simplecov/formatter/multi_formatter.rb +1 -1
- data/lib/simplecov/formatter/simple_formatter.rb +8 -5
- data/lib/simplecov/formatter.rb +12 -0
- data/lib/simplecov/group_names.rb +32 -0
- data/lib/simplecov/last_run.rb +16 -9
- data/lib/simplecov/lines_classifier.rb +29 -8
- data/lib/simplecov/load_global_config.rb +5 -2
- data/lib/simplecov/parallel_adapters/base.rb +17 -0
- data/lib/simplecov/parallel_adapters/generic.rb +2 -2
- data/lib/simplecov/parallel_adapters/parallel_tests.rb +2 -2
- data/lib/simplecov/parallel_coordination.rb +6 -1
- data/lib/simplecov/parallel_result_merger.rb +230 -0
- data/lib/simplecov/report_deferral.rb +49 -0
- data/lib/simplecov/report_stamp.rb +28 -0
- data/lib/simplecov/result.rb +40 -10
- data/lib/simplecov/result_adapter.rb +48 -16
- data/lib/simplecov/result_merger/resultset_file.rb +43 -7
- data/lib/simplecov/result_merger/resultset_run_identity.rb +67 -0
- data/lib/simplecov/result_merger/resultset_store.rb +15 -12
- data/lib/simplecov/result_merger/unloaded_files.rb +103 -0
- data/lib/simplecov/result_merger.rb +69 -42
- data/lib/simplecov/result_processing.rb +82 -43
- data/lib/simplecov/run_identity.rb +77 -0
- data/lib/simplecov/simulate_coverage.rb +35 -11
- data/lib/simplecov/source_file/method.rb +7 -1
- data/lib/simplecov/source_file/ruby_data_parser.rb +25 -3
- data/lib/simplecov/source_file/skip_chunks.rb +7 -10
- data/lib/simplecov/source_file/source_loader.rb +23 -7
- data/lib/simplecov/source_file/statistics.rb +24 -16
- data/lib/simplecov/static_coverage_extractor/condition_folding.rb +203 -13
- data/lib/simplecov/static_coverage_extractor/location_conventions.rb +19 -30
- data/lib/simplecov/static_coverage_extractor/method_collector.rb +7 -0
- data/lib/simplecov/static_coverage_extractor/prism_compat.rb +55 -0
- data/lib/simplecov/static_coverage_extractor/value_position.rb +6 -14
- data/lib/simplecov/static_coverage_extractor/visitor.rb +23 -36
- data/lib/simplecov/unloaded_file_injector.rb +75 -0
- data/lib/simplecov/version.rb +1 -1
- data/lib/simplecov.rb +11 -5
- data/schemas/coverage-v1.0.schema.json +2 -2
- data/schemas/coverage.schema.json +2 -2
- data/sig/simplecov.rbs +206 -67
- metadata +28 -14
- data/doc/alternate-formatters.md +0 -66
- data/doc/commercial-services.md +0 -25
- data/doc/editor-integration.md +0 -18
- data/lib/simplecov/combine/files_combiner.rb +0 -70
- data/lib/simplecov/formatter/html_formatter/public/application.css +0 -1
- data/lib/simplecov/formatter/html_formatter/public/application.js +0 -18
- data/lib/simplecov/formatter/html_formatter/public/favicon_green.png +0 -0
- data/lib/simplecov/formatter/html_formatter/public/favicon_red.png +0 -0
- data/lib/simplecov/formatter/html_formatter/public/favicon_yellow.png +0 -0
|
@@ -3,47 +3,56 @@
|
|
|
3
3
|
require "fileutils"
|
|
4
4
|
require "json"
|
|
5
5
|
require_relative "base"
|
|
6
|
+
require_relative "../atomic_file"
|
|
7
|
+
require_relative "../coverage_json"
|
|
6
8
|
require_relative "json_formatter"
|
|
7
9
|
|
|
8
10
|
module SimpleCov
|
|
9
11
|
module Formatter
|
|
10
|
-
# Generates
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
12
|
+
# Generates a single self-contained HTML coverage report. The compiled
|
|
13
|
+
# template (public/index.html) already carries the viewer app's JS and
|
|
14
|
+
# CSS inline; format substitutes the coverage JSON at the data marker
|
|
15
|
+
# and writes one index.html, so the report can be mailed, uploaded as
|
|
16
|
+
# a single CI artifact, or opened from anywhere without sibling files.
|
|
17
|
+
# A coverage.json is written alongside from the same in-memory hash.
|
|
14
18
|
class HTMLFormatter < Base
|
|
15
|
-
|
|
19
|
+
# Placeholder in the compiled template where the report's data goes.
|
|
20
|
+
DATA_MARKER = "<!-- SIMPLECOV_COVERAGE_DATA -->"
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
FileUtils.mkdir_p(output_path)
|
|
26
|
-
viewer_json = JSON.pretty_generate(JSONFormatter.build_hash(result, include_source: true))
|
|
27
|
-
coverage_json = SimpleCov.source_in_json ? viewer_json : JSON.pretty_generate(JSONFormatter.build_hash(result))
|
|
28
|
-
|
|
29
|
-
atomic_write(File.join(output_path, JSONFormatter::FILENAME), coverage_json)
|
|
30
|
-
atomic_write(File.join(output_path, DATA_FILENAME), "window.SIMPLECOV_DATA = #{viewer_json};\n")
|
|
22
|
+
# The sibling files a 1.0.0 through 1.0.3 report wrote next to
|
|
23
|
+
# index.html. They are stale the moment a single-file report lands
|
|
24
|
+
# on top of them — `simplecov serve` would happily keep serving the
|
|
25
|
+
# old coverage_data.js — so formatting removes the known names.
|
|
26
|
+
LEGACY_REPORT_FILES = %w[
|
|
27
|
+
coverage_data.js application.js application.css
|
|
28
|
+
favicon_green.png favicon_red.png favicon_yellow.png
|
|
29
|
+
].freeze
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
# `
|
|
37
|
-
|
|
31
|
+
def format(result)
|
|
32
|
+
# The inlined report data feeds the client-side viewer, which
|
|
33
|
+
# renders source from the embedded array — it always needs
|
|
34
|
+
# `source`, regardless of `SimpleCov.source_in_json`. The
|
|
35
|
+
# side-file `coverage.json` honors the setting so downstream
|
|
36
|
+
# tools that read source from disk can opt into a smaller
|
|
37
|
+
# payload. The embedded copy is serialized compactly (pretty
|
|
38
|
+
# printing puts every element of every per-line coverage array
|
|
39
|
+
# on its own indented line, roughly doubling the payload);
|
|
40
|
+
# coverage.json stays pretty-printed for human readers.
|
|
41
|
+
viewer_hash = JSONFormatter.build_hash(result, include_source: true)
|
|
42
|
+
json_hash = SimpleCov.source_in_json ? viewer_hash : source_less_hash(viewer_hash)
|
|
43
|
+
write_report_files(json_hash, viewer_hash, result)
|
|
44
|
+
emit_status(result)
|
|
38
45
|
end
|
|
39
46
|
|
|
40
47
|
# Generate HTML from a pre-existing coverage.json file without
|
|
41
48
|
# needing a live SimpleCov::Result or even a running test suite.
|
|
49
|
+
# The round-trip through parse/generate compacts the (typically
|
|
50
|
+
# pretty-printed) input and rejects invalid JSON here rather than
|
|
51
|
+
# embedding it and failing at view time.
|
|
42
52
|
def format_from_json(json_path, output_dir)
|
|
43
|
-
|
|
44
|
-
json =
|
|
45
|
-
|
|
46
|
-
copy_static_assets(output_dir)
|
|
53
|
+
data = ViewerDataValidator.call(SimpleCov::CoverageJSON.load(json_path))
|
|
54
|
+
json = JSON.generate(data)
|
|
55
|
+
AtomicFile.write(File.join(output_dir, "index.html"), render_report(json), binary: true)
|
|
47
56
|
end
|
|
48
57
|
|
|
49
58
|
private
|
|
@@ -52,27 +61,36 @@ module SimpleCov
|
|
|
52
61
|
"index.html"
|
|
53
62
|
end
|
|
54
63
|
|
|
55
|
-
def
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
end
|
|
64
|
+
def source_less_hash(hash)
|
|
65
|
+
coverage = hash.fetch(:coverage).transform_values { |file| file.except(:source) }
|
|
66
|
+
hash.merge(coverage: coverage)
|
|
59
67
|
end
|
|
60
68
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
#
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
69
|
+
def write_report_files(json_hash, viewer_hash, result)
|
|
70
|
+
CoverageJSONWriter.write(output_path, json_hash, result)
|
|
71
|
+
AtomicFile.write(File.join(output_path, "index.html"), render_report(JSON.generate(viewer_hash)), binary: true)
|
|
72
|
+
FileUtils.rm_f(LEGACY_REPORT_FILES.map { |name| File.join(output_path, name) })
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Substitute the coverage JSON into the compiled template. `<` is
|
|
76
|
+
# escaped as \u003c — valid JSON, since `<` can only occur inside
|
|
77
|
+
# strings — so embedded source text containing "</script>" or
|
|
78
|
+
# "<!--" cannot terminate the surrounding <script> element. Block
|
|
79
|
+
# forms keep gsub/sub from interpreting backslashes in the JSON as
|
|
80
|
+
# replacement-string back-references.
|
|
81
|
+
def render_report(json)
|
|
82
|
+
# UTF-8 explicitly, not the locale's `Encoding.default_external`:
|
|
83
|
+
# the JSON payload is UTF-8, and substituting it into a template
|
|
84
|
+
# tagged US-ASCII (or a Windows code page) raises
|
|
85
|
+
# Encoding::CompatibilityError the moment either side carries a
|
|
86
|
+
# non-ASCII character.
|
|
87
|
+
template = File.read(File.join(public_dir, "index.html"), encoding: Encoding::UTF_8)
|
|
88
|
+
unless template.include?(DATA_MARKER)
|
|
89
|
+
raise "SimpleCov's HTML template is missing its #{DATA_MARKER.inspect} marker"
|
|
75
90
|
end
|
|
91
|
+
|
|
92
|
+
data_script = "<script>window.SIMPLECOV_DATA = #{json.gsub('<') { '\u003c' }};</script>"
|
|
93
|
+
template.sub(DATA_MARKER) { data_script }
|
|
76
94
|
end
|
|
77
95
|
|
|
78
96
|
def public_dir
|
|
@@ -83,3 +101,5 @@ module SimpleCov
|
|
|
83
101
|
end
|
|
84
102
|
end
|
|
85
103
|
end
|
|
104
|
+
|
|
105
|
+
require_relative "html_formatter/viewer_data_validator"
|
|
@@ -12,77 +12,75 @@ module SimpleCov
|
|
|
12
12
|
CRITERION_KEYS = {line: :lines, branch: :branches, method: :methods}.freeze
|
|
13
13
|
private_constant :CRITERION_KEYS
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
format_maximum_drop
|
|
26
|
-
@errors
|
|
27
|
-
end
|
|
15
|
+
class << self
|
|
16
|
+
def call(result)
|
|
17
|
+
errors = {} #: Hash[Symbol, Hash[untyped, untyped]]
|
|
18
|
+
format_minimum_overall(result, errors)
|
|
19
|
+
format_minimum_by_file(result, errors)
|
|
20
|
+
format_minimum_by_group(result, errors)
|
|
21
|
+
format_maximum_overall(result, errors)
|
|
22
|
+
format_maximum_drop(result, errors)
|
|
23
|
+
errors
|
|
24
|
+
end
|
|
28
25
|
|
|
29
|
-
|
|
26
|
+
private
|
|
30
27
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
def format_minimum_overall(result, errors)
|
|
29
|
+
SimpleCov::CoverageViolations.minimum_overall(result, SimpleCov.minimum_coverage).each do |violation|
|
|
30
|
+
bucket(errors, :minimum_coverage)[key_for(violation)] = expected_actual(violation)
|
|
31
|
+
end
|
|
34
32
|
end
|
|
35
|
-
end
|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
def format_minimum_by_file(result, errors)
|
|
35
|
+
violations = SimpleCov::CoverageViolations.minimum_by_file(
|
|
36
|
+
result, SimpleCov.minimum_coverage_by_file, SimpleCov.minimum_coverage_by_file_overrides
|
|
37
|
+
)
|
|
38
|
+
violations.each { |violation| record_by_file(violation, errors) }
|
|
39
|
+
end
|
|
43
40
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
def record_by_file(violation, errors)
|
|
42
|
+
by_file = bucket(errors, :minimum_coverage_by_file)
|
|
43
|
+
file_bucket = by_file[violation.fetch(:project_filename)] ||= {} #: Hash[untyped, untyped]
|
|
44
|
+
file_bucket[key_for(violation)] = expected_actual(violation)
|
|
45
|
+
end
|
|
49
46
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
def format_minimum_by_group(result, errors)
|
|
48
|
+
violations = SimpleCov::CoverageViolations.minimum_by_group(result, SimpleCov.minimum_coverage_by_group)
|
|
49
|
+
return if violations.empty?
|
|
53
50
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
51
|
+
# `bucket` lazily creates the errors key, so only touch it when
|
|
52
|
+
# there is a violation to record.
|
|
53
|
+
by_group = bucket(errors, :minimum_coverage_by_group)
|
|
54
|
+
violations.each do |violation|
|
|
55
|
+
group_bucket = by_group[violation.fetch(:group_name)] ||= {} #: Hash[untyped, untyped]
|
|
56
|
+
group_bucket[key_for(violation)] = expected_actual(violation)
|
|
57
|
+
end
|
|
60
58
|
end
|
|
61
|
-
end
|
|
62
59
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
def format_maximum_overall(result, errors)
|
|
61
|
+
SimpleCov::CoverageViolations.maximum_overall(result, SimpleCov.maximum_coverage).each do |violation|
|
|
62
|
+
bucket(errors, :maximum_coverage)[key_for(violation)] = expected_actual(violation)
|
|
63
|
+
end
|
|
66
64
|
end
|
|
67
|
-
end
|
|
68
65
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
def format_maximum_drop(result, errors)
|
|
67
|
+
SimpleCov::CoverageViolations.maximum_drop(result, SimpleCov.maximum_coverage_drop).each do |violation|
|
|
68
|
+
bucket(errors, :maximum_coverage_drop)[key_for(violation)] =
|
|
69
|
+
{maximum: violation.fetch(:maximum), actual: violation.fetch(:actual)}
|
|
70
|
+
end
|
|
73
71
|
end
|
|
74
|
-
end
|
|
75
72
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
def bucket(errors, name)
|
|
74
|
+
errors[name] ||= {} #: Hash[untyped, untyped]
|
|
75
|
+
end
|
|
79
76
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
77
|
+
def key_for(violation)
|
|
78
|
+
CRITERION_KEYS.fetch(SimpleCov.coverage_statistics_key(violation.fetch(:criterion)))
|
|
79
|
+
end
|
|
83
80
|
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
def expected_actual(violation)
|
|
82
|
+
{expected: violation.fetch(:expected), actual: violation.fetch(:actual)}
|
|
83
|
+
end
|
|
86
84
|
end
|
|
87
85
|
end
|
|
88
86
|
end
|
|
@@ -22,105 +22,95 @@ module SimpleCov
|
|
|
22
22
|
SCHEMA_URL = "https://raw.githubusercontent.com/simplecov-ruby/simplecov/main/schemas/coverage-v#{SCHEMA_VERSION}.schema.json".freeze
|
|
23
23
|
private_constant :SCHEMA_VERSION, :SCHEMA_URL
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
:groups => format_groups,
|
|
37
|
-
:errors => ErrorsFormatter.new(@result).call
|
|
38
|
-
}
|
|
39
|
-
end
|
|
25
|
+
class << self
|
|
26
|
+
def format(result, include_source: true)
|
|
27
|
+
{
|
|
28
|
+
:$schema => SCHEMA_URL,
|
|
29
|
+
:meta => format_meta(result),
|
|
30
|
+
:total => format_coverage_statistics(result.coverage_statistics),
|
|
31
|
+
:coverage => format_files(result, include_source: include_source),
|
|
32
|
+
:groups => format_groups(result),
|
|
33
|
+
:errors => ErrorsFormatter.call(result)
|
|
34
|
+
}
|
|
35
|
+
end
|
|
40
36
|
|
|
41
|
-
|
|
37
|
+
private
|
|
42
38
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
def format_files(result, include_source:)
|
|
40
|
+
result.files.to_h do |source_file|
|
|
41
|
+
[source_file.project_filename, SourceFileFormatter.call(source_file, include_source: include_source)]
|
|
42
|
+
end
|
|
46
43
|
end
|
|
47
|
-
end
|
|
48
44
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
def format_groups(result)
|
|
46
|
+
result.groups.to_h do |name, file_list|
|
|
47
|
+
stats = format_coverage_statistics(file_list.coverage_statistics)
|
|
48
|
+
[name, stats.merge(files: file_list.map(&:project_filename))]
|
|
49
|
+
end
|
|
53
50
|
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def format_meta
|
|
57
|
-
{
|
|
58
|
-
schema_version: SCHEMA_VERSION,
|
|
59
|
-
simplecov_version: SimpleCov::VERSION,
|
|
60
|
-
command_name: @result.command_name,
|
|
61
|
-
project_name: SimpleCov.project_name,
|
|
62
|
-
timestamp: @result.created_at.iso8601(3),
|
|
63
|
-
root: SimpleCov.root,
|
|
64
|
-
commit: git_commit,
|
|
65
|
-
primary_coverage: SimpleCov.primary_coverage.to_s
|
|
66
|
-
}.merge!(coverage_flags)
|
|
67
|
-
end
|
|
68
51
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
52
|
+
def format_meta(result)
|
|
53
|
+
{
|
|
54
|
+
schema_version: SCHEMA_VERSION,
|
|
55
|
+
simplecov_version: SimpleCov::VERSION,
|
|
56
|
+
command_name: result.command_name,
|
|
57
|
+
project_name: SimpleCov.project_name,
|
|
58
|
+
timestamp: result.created_at.iso8601(3),
|
|
59
|
+
root: SimpleCov.root,
|
|
60
|
+
commit: git_commit,
|
|
61
|
+
primary_coverage: SimpleCov.primary_coverage.to_s
|
|
62
|
+
}.merge!(coverage_flags)
|
|
63
|
+
end
|
|
81
64
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
65
|
+
# Full git commit SHA of `SimpleCov.root`'s HEAD, or nil when the
|
|
66
|
+
# project isn't a git checkout or git isn't on PATH. Recorded so tools
|
|
67
|
+
# can recover the exact source a report was generated against, which
|
|
68
|
+
# matters most when `source_in_json false` drops the source text from
|
|
69
|
+
# coverage.json. stderr is captured (not forwarded) so a non-git project
|
|
70
|
+
# doesn't print git's diagnostics to the build.
|
|
71
|
+
def git_commit
|
|
72
|
+
output, status = Open3.capture2e("git", "-C", SimpleCov.root.to_s, "rev-parse", "HEAD")
|
|
73
|
+
status.success? ? output.strip : nil
|
|
74
|
+
rescue StandardError
|
|
75
|
+
nil
|
|
76
|
+
end
|
|
89
77
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
78
|
+
def coverage_flags
|
|
79
|
+
{
|
|
80
|
+
line_coverage: SimpleCov.line_coverage?,
|
|
81
|
+
branch_coverage: SimpleCov.branch_coverage?,
|
|
82
|
+
method_coverage: SimpleCov.method_coverage?
|
|
83
|
+
}
|
|
84
|
+
end
|
|
96
85
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
86
|
+
def format_coverage_statistics(statistics)
|
|
87
|
+
result = {} #: Hash[Symbol, untyped]
|
|
88
|
+
result[:lines] = format_line_statistic(statistics[:line]) if statistics[:line]
|
|
89
|
+
result[:branches] = format_single_statistic(statistics[:branch]) if statistics[:branch]
|
|
90
|
+
result[:methods] = format_single_statistic(statistics[:method]) if statistics[:method]
|
|
91
|
+
result
|
|
92
|
+
end
|
|
104
93
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
94
|
+
def format_line_statistic(stat)
|
|
95
|
+
{
|
|
96
|
+
covered: stat.covered,
|
|
97
|
+
missed: stat.missed,
|
|
98
|
+
omitted: stat.omitted,
|
|
99
|
+
total: stat.total,
|
|
100
|
+
percent: stat.percent,
|
|
101
|
+
strength: stat.strength
|
|
102
|
+
}
|
|
103
|
+
end
|
|
115
104
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
105
|
+
def format_single_statistic(stat)
|
|
106
|
+
{
|
|
107
|
+
covered: stat.covered,
|
|
108
|
+
missed: stat.missed,
|
|
109
|
+
total: stat.total,
|
|
110
|
+
percent: stat.percent,
|
|
111
|
+
strength: stat.strength
|
|
112
|
+
}
|
|
113
|
+
end
|
|
124
114
|
end
|
|
125
115
|
end
|
|
126
116
|
end
|
|
@@ -7,91 +7,85 @@ module SimpleCov
|
|
|
7
7
|
# in coverage.json: source code plus per-enabled-criterion arrays
|
|
8
8
|
# and totals.
|
|
9
9
|
class SourceFileFormatter
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
result.merge!(branch_coverage_section) if SimpleCov.branch_coverage?
|
|
19
|
-
result.merge!(method_coverage_section) if SimpleCov.method_coverage?
|
|
20
|
-
result
|
|
21
|
-
end
|
|
10
|
+
class << self
|
|
11
|
+
def call(source_file, include_source: true)
|
|
12
|
+
result = include_source ? format_source_code(source_file) : {} #: Hash[Symbol, untyped]
|
|
13
|
+
result.merge!(line_coverage_section(source_file)) if SimpleCov.line_coverage?
|
|
14
|
+
result.merge!(branch_coverage_section(source_file)) if SimpleCov.branch_coverage?
|
|
15
|
+
result.merge!(method_coverage_section(source_file)) if SimpleCov.method_coverage?
|
|
16
|
+
result
|
|
17
|
+
end
|
|
22
18
|
|
|
23
|
-
|
|
19
|
+
private
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
{source: @source_file.lines.map { |line| ensure_utf8(line.src.chomp) }}
|
|
34
|
-
end
|
|
21
|
+
# No per-line encoding conversion here: SourceLoader guarantees
|
|
22
|
+
# every line leaves it as valid UTF-8 (transcoding declared
|
|
23
|
+
# encodings, scrubbing invalid bytes), and the converter copy
|
|
24
|
+
# this used to make per line was the single largest allocation
|
|
25
|
+
# source in formatting a large report.
|
|
26
|
+
def format_source_code(source_file)
|
|
27
|
+
{source: source_file.lines.map { |line| line.src.chomp }}
|
|
28
|
+
end
|
|
35
29
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
def line_coverage_section(source_file)
|
|
31
|
+
covered = source_file.covered_lines.size
|
|
32
|
+
missed = source_file.missed_lines.size
|
|
33
|
+
{
|
|
34
|
+
lines: source_file.lines.map { |line| format_line(line) },
|
|
35
|
+
lines_covered_percent: source_file.covered_percent,
|
|
36
|
+
covered_lines: covered,
|
|
37
|
+
missed_lines: missed,
|
|
38
|
+
omitted_lines: source_file.never_lines.size,
|
|
39
|
+
total_lines: covered + missed
|
|
40
|
+
}
|
|
41
|
+
end
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
total_lines: covered + missed
|
|
50
|
-
}
|
|
51
|
-
end
|
|
43
|
+
def branch_coverage_section(source_file)
|
|
44
|
+
{
|
|
45
|
+
branches: source_file.branches.map { |branch| format_branch(branch) },
|
|
46
|
+
branches_covered_percent: source_file.covered_percent(:branch),
|
|
47
|
+
covered_branches: source_file.covered_branches.size,
|
|
48
|
+
missed_branches: source_file.missed_branches.size,
|
|
49
|
+
total_branches: source_file.total_branches.size
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
branches_covered_percent: @source_file.covered_percent(:branch),
|
|
57
|
-
covered_branches: @source_file.covered_branches.size,
|
|
58
|
-
missed_branches: @source_file.missed_branches.size,
|
|
59
|
-
total_branches: @source_file.total_branches.size
|
|
60
|
-
}
|
|
61
|
-
end
|
|
53
|
+
def method_coverage_section(source_file)
|
|
54
|
+
covered = source_file.covered_methods.size
|
|
55
|
+
missed = source_file.missed_methods.size
|
|
62
56
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
end
|
|
57
|
+
{
|
|
58
|
+
methods: source_file.methods.map { |method| format_method(method) },
|
|
59
|
+
methods_covered_percent: source_file.covered_percent(:method),
|
|
60
|
+
covered_methods: covered,
|
|
61
|
+
missed_methods: missed,
|
|
62
|
+
total_methods: covered + missed
|
|
63
|
+
}
|
|
64
|
+
end
|
|
72
65
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
def format_line(line)
|
|
67
|
+
line.skipped? ? "ignored" : line.coverage
|
|
68
|
+
end
|
|
76
69
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
70
|
+
def format_branch(branch)
|
|
71
|
+
{
|
|
72
|
+
type: branch.type,
|
|
73
|
+
start_line: branch.start_line,
|
|
74
|
+
end_line: branch.end_line,
|
|
75
|
+
coverage: format_line(branch),
|
|
76
|
+
inline: branch.inline?,
|
|
77
|
+
report_line: branch.report_line
|
|
78
|
+
}
|
|
79
|
+
end
|
|
87
80
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
81
|
+
def format_method(method)
|
|
82
|
+
{
|
|
83
|
+
name: method.to_s,
|
|
84
|
+
start_line: method.start_line,
|
|
85
|
+
end_line: method.end_line,
|
|
86
|
+
coverage: format_line(method)
|
|
87
|
+
}
|
|
88
|
+
end
|
|
95
89
|
end
|
|
96
90
|
end
|
|
97
91
|
end
|