coverband 6.1.7 → 6.1.8
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/.github/workflows/main.yml +10 -3
- data/Gemfile +3 -0
- data/Gemfile.rails7.0 +3 -0
- data/Gemfile.rails7.1 +4 -1
- data/Gemfile.rails7.2 +3 -0
- data/Gemfile.rails8.0 +3 -0
- data/changes.md +14 -0
- data/lib/coverband/adapters/base.rb +21 -6
- data/lib/coverband/adapters/file_store.rb +1 -2
- data/lib/coverband/adapters/hash_redis_store.rb +2 -1
- data/lib/coverband/adapters/redis_store.rb +1 -2
- data/lib/coverband/adapters/web_service_store.rb +1 -2
- data/lib/coverband/collectors/abstract_tracker.rb +4 -4
- data/lib/coverband/collectors/delta.rb +4 -4
- data/lib/coverband/collectors/view_tracker.rb +12 -2
- data/lib/coverband/mcp/tools/get_file_coverage.rb +2 -0
- data/lib/coverband/reporters/json_report.rb +7 -0
- data/lib/coverband/utils/absolute_file_converter.rb +37 -7
- data/lib/coverband/utils/file_list.rb +15 -7
- data/lib/coverband/utils/html_formatter.rb +5 -2
- data/lib/coverband/utils/relative_file_converter.rb +22 -7
- data/lib/coverband/utils/result.rb +0 -2
- data/lib/coverband/utils/source_file/line.rb +84 -0
- data/lib/coverband/utils/source_file.rb +33 -81
- data/lib/coverband/version.rb +1 -1
- data/test/benchmarks/benchmark.rake +4 -0
- data/test/benchmarks/benchmark_delta.rb +54 -0
- data/test/benchmarks/benchmark_file_list.rb +58 -0
- data/test/benchmarks/benchmark_unused_keys.rb +52 -0
- data/test/coverband/collectors/route_tracker_test.rb +3 -3
- data/test/coverband/collectors/translation_tracker_test.rb +1 -1
- data/test/coverband/collectors/view_tracker_test.rb +2 -2
- data/test/coverband/mcp/tools/get_file_coverage_test.rb +44 -0
- data/test/coverband/reporters/console_test.rb +1 -0
- data/test/coverband/reporters/json_test.rb +3 -3
- data/test/coverband/utils/absolute_file_converter_test.rb +35 -0
- data/test/coverband/utils/relative_file_converter_test.rb +26 -0
- data/test/coverband/utils/result_test.rb +19 -0
- data/test/coverband/utils/{source_file_line_test.rb → source_file/line_test.rb} +1 -1
- metadata +6 -3
- data/coverband/log.272267 +0 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '01680ffc24d42fce1b45f90fff31a9d190b029af8a83b0a1405ed81d2785bc10'
|
|
4
|
+
data.tar.gz: 04daf91b9d7f64849d217075e71ff00d54cac2e64fd1b5c7fdd7e0b27943b56e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0de107048e072e97c1bded932a59ac9b6be2c6ac94b045118977aae38282a109772b183bed1b7ca99d7867d3fc694f326a53e2e93174bbf372e7f0ba16e8bd13
|
|
7
|
+
data.tar.gz: 57d8e1921056acb7f13e47b02caeb6207e635984a461c1952aafd0dbf4e445519d99ca8b369b60d11c3121b86da6f5f3d3d2a4e6aaf6d68fd2d3a8aa155d14fb
|
data/.github/workflows/main.yml
CHANGED
|
@@ -28,11 +28,18 @@ jobs:
|
|
|
28
28
|
- gemfile: 'rails_8.0'
|
|
29
29
|
ruby: '3.1'
|
|
30
30
|
runs-on: ${{ matrix.os }}-latest
|
|
31
|
+
services:
|
|
32
|
+
redis:
|
|
33
|
+
image: redis:${{ matrix.redis-version }}
|
|
34
|
+
ports:
|
|
35
|
+
- 6379:6379
|
|
36
|
+
options: >-
|
|
37
|
+
--health-cmd "redis-cli ping"
|
|
38
|
+
--health-interval 10s
|
|
39
|
+
--health-timeout 5s
|
|
40
|
+
--health-retries 5
|
|
31
41
|
steps:
|
|
32
42
|
- uses: actions/checkout@v6
|
|
33
|
-
- uses: supercharge/redis-github-action@v2
|
|
34
|
-
with:
|
|
35
|
-
redis-version: ${{ matrix.redis-version }}
|
|
36
43
|
- uses: ruby/setup-ruby@v1
|
|
37
44
|
with:
|
|
38
45
|
ruby-version: ${{ matrix.ruby }}
|
data/Gemfile
CHANGED
data/Gemfile.rails7.0
CHANGED
data/Gemfile.rails7.1
CHANGED
data/Gemfile.rails7.2
CHANGED
data/Gemfile.rails8.0
CHANGED
data/changes.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
### 6.1.8
|
|
2
|
+
|
|
3
|
+
* Feature: Added temporal data to GetFileCoverage tool for MCP by @fabienpiette (#626)
|
|
4
|
+
* Optimization: Significant performance improvements to `FileList` avoiding array allocations and reducing redundant iterations (#619, #620, #622, #623)
|
|
5
|
+
* Optimization: Optimize `HTMLFormatter` with template caching (#616, #618)
|
|
6
|
+
* Optimization: Optimize memory usage by removing redundant `dup` calls across `Coverband::Utils::Result` (#617, #621)
|
|
7
|
+
* Optimization: Optimize `array_add` logic with in-place modification avoiding overhead in newer Ruby (#615)
|
|
8
|
+
* Optimization: Optimize `ViewTracker` unused keys and path normalization (#608, #609, #613)
|
|
9
|
+
* Optimization: Optimize `AbstractTracker` to use bulk Redis hset and direct Hash lookups (#610, #612)
|
|
10
|
+
* Optimization: Cache Regexp in `short_name` and optimize `RelativeFileConverter` with `Regexp.union` (#606, #607)
|
|
11
|
+
* Optimization: Optimize delta collector ignore check order (#611)
|
|
12
|
+
* Refactoring: Extract Line class to `Coverband::Utils::SourceFile::Line` (#605, #614)
|
|
13
|
+
* Fix: Improve symlink handling in file converters (#603)
|
|
14
|
+
|
|
1
15
|
### Coverband 6.1.7
|
|
2
16
|
|
|
3
17
|
* add back a running demo site
|
|
@@ -125,14 +125,29 @@ module Coverband
|
|
|
125
125
|
}
|
|
126
126
|
end
|
|
127
127
|
|
|
128
|
-
# TODO: This should have cases reduced
|
|
129
128
|
def array_add(latest, original)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
129
|
+
use_oneshot = Coverband.configuration.use_oneshot_lines_coverage
|
|
130
|
+
if latest.frozen?
|
|
131
|
+
latest.map.with_index do |v, i|
|
|
132
|
+
if v && original[i]
|
|
133
|
+
if use_oneshot
|
|
134
|
+
(v + original[i] >= 1) ? 1 : 0
|
|
135
|
+
else
|
|
136
|
+
v + original[i]
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
134
140
|
else
|
|
135
|
-
latest.
|
|
141
|
+
latest.each_with_index do |v, i|
|
|
142
|
+
latest[i] = if v && original[i]
|
|
143
|
+
if use_oneshot
|
|
144
|
+
(v + original[i] >= 1) ? 1 : 0
|
|
145
|
+
else
|
|
146
|
+
v + original[i]
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
latest
|
|
136
151
|
end
|
|
137
152
|
end
|
|
138
153
|
end
|
|
@@ -29,6 +29,7 @@ module Coverband
|
|
|
29
29
|
|
|
30
30
|
@ttl = opts[:ttl]
|
|
31
31
|
@relative_file_converter = opts[:relative_file_converter] || Utils::RelativeFileConverter
|
|
32
|
+
@short_name_regex = /^#{Coverband.configuration.root}/
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
def supported?
|
|
@@ -171,7 +172,7 @@ module Coverband
|
|
|
171
172
|
end
|
|
172
173
|
|
|
173
174
|
def short_name(filename)
|
|
174
|
-
filename.sub(
|
|
175
|
+
filename.sub(@short_name_regex, ".")
|
|
175
176
|
.gsub(%r{^\./}, "")
|
|
176
177
|
end
|
|
177
178
|
|
|
@@ -63,8 +63,7 @@ module Coverband
|
|
|
63
63
|
# the Coverband 2 had the same issue,
|
|
64
64
|
# and the tradeoff has always been acceptable
|
|
65
65
|
def save_report(report)
|
|
66
|
-
data = report
|
|
67
|
-
data = merge_reports(data, coverage(nil, skip_hash_check: true))
|
|
66
|
+
data = merge_reports(report, coverage(nil, skip_hash_check: true))
|
|
68
67
|
save_coverage(data)
|
|
69
68
|
end
|
|
70
69
|
|
|
@@ -66,10 +66,9 @@ module Coverband
|
|
|
66
66
|
# We set here vs initialize to avoid setting on the primary process vs child processes
|
|
67
67
|
@pid ||= ::Process.pid
|
|
68
68
|
|
|
69
|
-
# TODO: do we need dup
|
|
70
69
|
# TODO: we don't need upstream timestamps, server will track first_seen
|
|
71
70
|
Thread.new do
|
|
72
|
-
data = expand_report(report
|
|
71
|
+
data = expand_report(report)
|
|
73
72
|
full_package = {
|
|
74
73
|
collection_type: "coverage_delta",
|
|
75
74
|
collection_data: {
|
|
@@ -61,8 +61,8 @@ module Coverband
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
def unused_keys(used_keys = nil)
|
|
64
|
-
recently_used_keys =
|
|
65
|
-
all_keys.reject { |k| recently_used_keys.
|
|
64
|
+
recently_used_keys = used_keys || self.used_keys
|
|
65
|
+
all_keys.reject { |k| recently_used_keys.key?(k.to_s) }
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def as_json
|
|
@@ -97,8 +97,8 @@ module Coverband
|
|
|
97
97
|
redis_store.set(tracker_time_key, Time.now.to_i) unless @one_time_timestamp || tracker_time_key_exists?
|
|
98
98
|
@one_time_timestamp = true
|
|
99
99
|
reported_time = Time.now.to_i
|
|
100
|
-
@keys_to_record.
|
|
101
|
-
redis_store.hset(tracker_key, key.to_s
|
|
100
|
+
if @keys_to_record.any?
|
|
101
|
+
redis_store.hset(tracker_key, @keys_to_record.each_with_object({}) { |key, h| h[key.to_s] = reported_time })
|
|
102
102
|
end
|
|
103
103
|
@keys_to_record.clear
|
|
104
104
|
rescue => e
|
|
@@ -54,8 +54,8 @@ module Coverband
|
|
|
54
54
|
# on the critical performance path, and any refactoring I come up with
|
|
55
55
|
# would slow down the performance.
|
|
56
56
|
###
|
|
57
|
-
next unless
|
|
58
|
-
file.
|
|
57
|
+
next unless file.start_with?(@@project_directory) &&
|
|
58
|
+
@@ignore_patterns.none? { |pattern| file.match(pattern) }
|
|
59
59
|
|
|
60
60
|
# This handles Coverage branch support, setup by default in
|
|
61
61
|
# simplecov 0.18.x
|
|
@@ -84,8 +84,8 @@ module Coverband
|
|
|
84
84
|
# on the critical performance path, and any refactoring I come up with
|
|
85
85
|
# would slow down the performance.
|
|
86
86
|
###
|
|
87
|
-
next unless
|
|
88
|
-
file.
|
|
87
|
+
next unless file.start_with?(@@project_directory) &&
|
|
88
|
+
@@ignore_patterns.none? { |pattern| file.match(pattern) }
|
|
89
89
|
|
|
90
90
|
@@stubs[file] ||= ::Coverage.line_stub(file)
|
|
91
91
|
transformed_line_counts = coverage[:oneshot_lines].each_with_object(@@stubs[file].dup) { |line_number, line_counts|
|
|
@@ -78,8 +78,17 @@ module Coverband
|
|
|
78
78
|
recently_used_views = used_keys.keys
|
|
79
79
|
unused_views = all_keys - recently_used_views
|
|
80
80
|
# since layouts don't include format we count them used if they match with ANY formats
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
potential_layout_references = recently_used_views.reject { |v| v.end_with?(".erb", ".haml", ".slim") }
|
|
82
|
+
|
|
83
|
+
layout_matcher = if potential_layout_references.any?
|
|
84
|
+
Regexp.union(potential_layout_references)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
unused_views.reject! do |view|
|
|
88
|
+
(layout_matcher && view.include?("/layouts/") && view.match?(layout_matcher)) ||
|
|
89
|
+
@ignore_patterns.any? { |pattern| view.match?(pattern) }
|
|
90
|
+
end
|
|
91
|
+
unused_views
|
|
83
92
|
end
|
|
84
93
|
|
|
85
94
|
def clear_key!(filename)
|
|
@@ -103,6 +112,7 @@ module Coverband
|
|
|
103
112
|
|
|
104
113
|
roots.each do |root|
|
|
105
114
|
normalized = normalized.gsub(root, "")
|
|
115
|
+
break if normalized.length < original_length
|
|
106
116
|
end
|
|
107
117
|
|
|
108
118
|
# Only remove leading slash if we actually modified the path by removing a root
|
|
@@ -52,6 +52,8 @@ module Coverband
|
|
|
52
52
|
lines_missed: file_data["lines_missed"],
|
|
53
53
|
runtime_percentage: file_data["runtime_percentage"],
|
|
54
54
|
never_loaded: file_data["never_loaded"],
|
|
55
|
+
first_updated_at: file_data["first_updated_at"],
|
|
56
|
+
last_updated_at: file_data["last_updated_at"],
|
|
55
57
|
coverage: file_data["coverage"]
|
|
56
58
|
}
|
|
57
59
|
end
|
|
@@ -72,6 +72,11 @@ module Coverband
|
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
+
def format_timestamp(timestamp)
|
|
76
|
+
return nil if timestamp.nil? || timestamp == Coverband::Utils::SourceFile::NOT_AVAILABLE
|
|
77
|
+
timestamp.is_a?(Time) ? timestamp.iso8601 : timestamp.to_s
|
|
78
|
+
end
|
|
79
|
+
|
|
75
80
|
def report_as_json
|
|
76
81
|
return filtered_report_files.to_json if for_merged_report
|
|
77
82
|
|
|
@@ -133,6 +138,8 @@ module Coverband
|
|
|
133
138
|
filename: source_file.filename,
|
|
134
139
|
hash: Digest::SHA1.hexdigest(source_file.filename),
|
|
135
140
|
never_loaded: source_file.never_loaded,
|
|
141
|
+
first_updated_at: format_timestamp(source_file.first_updated_at),
|
|
142
|
+
last_updated_at: format_timestamp(source_file.last_updated_at),
|
|
136
143
|
runtime_percentage: result.runtime_relevant_coverage(source_file),
|
|
137
144
|
lines_of_code: source_file.lines.count,
|
|
138
145
|
lines_covered: source_file.covered_lines.count,
|
|
@@ -5,7 +5,7 @@ module Coverband
|
|
|
5
5
|
class AbsoluteFileConverter
|
|
6
6
|
def initialize(roots)
|
|
7
7
|
@cache = {}
|
|
8
|
-
@roots = roots
|
|
8
|
+
@roots = convert_roots(roots)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def self.instance
|
|
@@ -24,11 +24,25 @@ module Coverband
|
|
|
24
24
|
@cache[relative_path] ||= begin
|
|
25
25
|
relative_filename = relative_path
|
|
26
26
|
local_filename = relative_filename
|
|
27
|
-
@roots.each do |root|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
@roots.each do |root, root_regexp|
|
|
28
|
+
if relative_filename.match?(root_regexp)
|
|
29
|
+
relative_filename = relative_filename.sub(root_regexp, "./")
|
|
30
|
+
# once we have a relative path break out of the loop
|
|
31
|
+
break
|
|
32
|
+
end
|
|
31
33
|
end
|
|
34
|
+
|
|
35
|
+
if relative_filename == local_filename && File.exist?(local_filename)
|
|
36
|
+
real_filename = File.realpath(local_filename)
|
|
37
|
+
@roots.each do |root, root_regexp|
|
|
38
|
+
if real_filename.match?(root_regexp)
|
|
39
|
+
relative_filename = real_filename.sub(root_regexp, "./")
|
|
40
|
+
# once we have a relative path break out of the loop
|
|
41
|
+
break
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
32
46
|
# the filename for our reports is expected to be a full path.
|
|
33
47
|
# roots.last should be roots << current_root}/
|
|
34
48
|
# a fully expanded path of config.root
|
|
@@ -36,12 +50,28 @@ module Coverband
|
|
|
36
50
|
# above only works for app files
|
|
37
51
|
# we need to rethink some of this logic
|
|
38
52
|
# gems aren't at project root and can have multiple locations
|
|
39
|
-
local_root = @roots.find { |root|
|
|
53
|
+
local_root = @roots.find { |root, _root_regexp|
|
|
40
54
|
File.exist?(relative_filename.gsub("./", root))
|
|
41
|
-
}
|
|
55
|
+
}&.first
|
|
42
56
|
local_root ? relative_filename.gsub("./", local_root) : local_filename
|
|
43
57
|
end
|
|
44
58
|
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def convert_roots(roots)
|
|
63
|
+
roots.flat_map { |root|
|
|
64
|
+
items = []
|
|
65
|
+
expanded = "#{File.expand_path(root)}/"
|
|
66
|
+
items << [expanded, /^#{expanded}/]
|
|
67
|
+
|
|
68
|
+
if File.exist?(root)
|
|
69
|
+
real = "#{File.realpath(root)}/"
|
|
70
|
+
items << [real, /^#{Regexp.escape(real)}/]
|
|
71
|
+
end
|
|
72
|
+
items
|
|
73
|
+
}.uniq
|
|
74
|
+
end
|
|
45
75
|
end
|
|
46
76
|
end
|
|
47
77
|
end
|
|
@@ -14,28 +14,28 @@ module Coverband
|
|
|
14
14
|
def covered_lines
|
|
15
15
|
return 0.0 if empty?
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
@covered_lines ||= sum(&:covered_lines_count)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
# Returns the count of lines that have been missed
|
|
21
21
|
def missed_lines
|
|
22
22
|
return 0.0 if empty?
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
@missed_lines ||= sum(&:missed_lines_count)
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
# Returns the count of lines that are not relevant for coverage
|
|
28
28
|
def never_lines
|
|
29
29
|
return 0.0 if empty?
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
@never_lines ||= sum(&:never_lines_count)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# Returns the count of skipped lines
|
|
35
35
|
def skipped_lines
|
|
36
36
|
return 0.0 if empty?
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
@skipped_lines ||= sum(&:skipped_lines_count)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
# Computes the coverage based upon lines covered and lines missed for each file
|
|
@@ -46,7 +46,9 @@ module Coverband
|
|
|
46
46
|
|
|
47
47
|
# Returns the overall amount of relevant lines of code across all files in this list
|
|
48
48
|
def lines_of_code
|
|
49
|
-
|
|
49
|
+
return 0.0 if empty?
|
|
50
|
+
|
|
51
|
+
@lines_of_code ||= sum(&:lines_of_code)
|
|
50
52
|
end
|
|
51
53
|
|
|
52
54
|
# Computes the coverage based upon lines covered and lines missed
|
|
@@ -68,11 +70,17 @@ module Coverband
|
|
|
68
70
|
def covered_strength
|
|
69
71
|
return 0.0 if empty? || lines_of_code.zero?
|
|
70
72
|
|
|
71
|
-
Float(
|
|
73
|
+
Float(sum { |f| f.covered_strength * f.lines_of_code } / lines_of_code)
|
|
72
74
|
end
|
|
73
75
|
|
|
74
76
|
def first_seen_at
|
|
75
|
-
|
|
77
|
+
min = nil
|
|
78
|
+
each do |f|
|
|
79
|
+
val = f.first_updated_at
|
|
80
|
+
next if val.is_a?(String)
|
|
81
|
+
min = val if min.nil? || val < min
|
|
82
|
+
end
|
|
83
|
+
min
|
|
76
84
|
end
|
|
77
85
|
end
|
|
78
86
|
end
|
|
@@ -73,9 +73,12 @@ module Coverband
|
|
|
73
73
|
template("data").result(binding)
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
+
TEMPLATE_CACHE = {}
|
|
77
|
+
private_constant :TEMPLATE_CACHE
|
|
78
|
+
|
|
76
79
|
# Returns the an erb instance for the template of given name
|
|
77
80
|
def template(name)
|
|
78
|
-
ERB.new(File.read(File.
|
|
81
|
+
TEMPLATE_CACHE[name] ||= ERB.new(File.read(File.expand_path("../../../views/#{name}.erb", __dir__)))
|
|
79
82
|
end
|
|
80
83
|
|
|
81
84
|
def output_path
|
|
@@ -119,7 +122,7 @@ module Coverband
|
|
|
119
122
|
|
|
120
123
|
# Returns a table containing the given source files
|
|
121
124
|
def formatted_file_list(title, result, source_files, options = {})
|
|
122
|
-
title_id = title.gsub(/^[^a-zA-Z]+/, "").gsub(/[^a-zA-Z0-9
|
|
125
|
+
title_id = title.gsub(/^[^a-zA-Z]+/, "").gsub(/[^a-zA-Z0-9\-_]/, "")
|
|
123
126
|
# Silence a warning by using the following variable to assign to `_`:
|
|
124
127
|
# "warning: possibly useless use of a variable in void context"
|
|
125
128
|
# The variable is used by ERB via binding.
|
|
@@ -17,24 +17,39 @@ module Coverband
|
|
|
17
17
|
|
|
18
18
|
def initialize(roots)
|
|
19
19
|
@cache = {}
|
|
20
|
-
@
|
|
20
|
+
@roots_regexp = Regexp.union(convert_roots(roots))
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def convert(file)
|
|
24
24
|
@cache[file] ||= begin
|
|
25
|
-
relative_file = file
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
relative_file = file.sub(@roots_regexp, "./")
|
|
26
|
+
|
|
27
|
+
if relative_file == file && !file.start_with?(".") && File.exist?(file)
|
|
28
|
+
real_file = File.realpath(file)
|
|
29
|
+
new_relative_file = real_file.sub(@roots_regexp, "./")
|
|
30
|
+
relative_file = ((new_relative_file == real_file) ? file : new_relative_file)
|
|
29
31
|
end
|
|
32
|
+
|
|
30
33
|
relative_file
|
|
31
34
|
end
|
|
32
35
|
end
|
|
33
36
|
|
|
34
37
|
private
|
|
35
38
|
|
|
36
|
-
def
|
|
37
|
-
|
|
39
|
+
def convert_roots(roots)
|
|
40
|
+
roots.flat_map { |root|
|
|
41
|
+
items = []
|
|
42
|
+
expanded = File.expand_path(root)
|
|
43
|
+
expanded += "/" unless expanded.end_with?("/")
|
|
44
|
+
items << /^#{Regexp.escape(expanded)}/
|
|
45
|
+
|
|
46
|
+
if File.exist?(root)
|
|
47
|
+
real = File.realpath(root)
|
|
48
|
+
real += "/" unless real.end_with?("/")
|
|
49
|
+
items << /^#{Regexp.escape(real)}/
|
|
50
|
+
end
|
|
51
|
+
items
|
|
52
|
+
}.uniq
|
|
38
53
|
end
|
|
39
54
|
end
|
|
40
55
|
end
|
|
@@ -51,8 +51,6 @@ module Coverband
|
|
|
51
51
|
# the line-by-line coverage to zero (if relevant) or nil (comments / whitespace etc).
|
|
52
52
|
def self.add_not_loaded_files(result, tracked_files)
|
|
53
53
|
if tracked_files
|
|
54
|
-
# TODO: Can we get rid of this dup it wastes memory
|
|
55
|
-
result = result.dup
|
|
56
54
|
Dir[tracked_files].each do |file|
|
|
57
55
|
absolute = File.expand_path(file)
|
|
58
56
|
result[absolute] ||= {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
####
|
|
4
|
+
# Thanks for all the help SimpleCov https://github.com/colszowka/simplecov
|
|
5
|
+
# initial version pulled into Coverband from Simplecov 12/04/2018
|
|
6
|
+
#
|
|
7
|
+
# Representation of a source file including it's coverage data, source code,
|
|
8
|
+
# source lines and featuring helpers to interpret that data.
|
|
9
|
+
####
|
|
10
|
+
module Coverband
|
|
11
|
+
module Utils
|
|
12
|
+
class SourceFile
|
|
13
|
+
# Representation of a single line in a source file including
|
|
14
|
+
# this specific line's source code, line_number and code coverage,
|
|
15
|
+
# with the coverage being either nil (coverage not applicable, e.g. comment
|
|
16
|
+
# line), 0 (line not covered) or >1 (the amount of times the line was
|
|
17
|
+
# executed)
|
|
18
|
+
class Line
|
|
19
|
+
# The source code for this line. Aliased as :source
|
|
20
|
+
attr_reader :src
|
|
21
|
+
# The line number in the source file. Aliased as :line, :number
|
|
22
|
+
attr_reader :line_number
|
|
23
|
+
# The coverage data for this line: either nil (never), 0 (missed) or >=1 (times covered)
|
|
24
|
+
attr_reader :coverage
|
|
25
|
+
# Whether this line was skipped
|
|
26
|
+
attr_reader :skipped
|
|
27
|
+
# The coverage data posted time for this line: either nil (never), nil (missed) or Time instance (last posted)
|
|
28
|
+
attr_reader :coverage_posted
|
|
29
|
+
|
|
30
|
+
# Lets grab some fancy aliases, shall we?
|
|
31
|
+
alias_method :source, :src
|
|
32
|
+
alias_method :line, :line_number
|
|
33
|
+
alias_method :number, :line_number
|
|
34
|
+
|
|
35
|
+
def initialize(src, line_number, coverage, coverage_posted = nil)
|
|
36
|
+
raise ArgumentError, "Only String accepted for source" unless src.is_a?(String)
|
|
37
|
+
raise ArgumentError, "Only Integer accepted for line_number" unless line_number.is_a?(Integer)
|
|
38
|
+
raise ArgumentError, "Only Integer and nil accepted for coverage" unless coverage.is_a?(Integer) || coverage.nil?
|
|
39
|
+
|
|
40
|
+
@src = src
|
|
41
|
+
@line_number = line_number
|
|
42
|
+
@coverage = coverage
|
|
43
|
+
@skipped = false
|
|
44
|
+
@coverage_posted = coverage_posted
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns true if this is a line that should have been covered, but was not
|
|
48
|
+
def missed?
|
|
49
|
+
!never? && !skipped? && coverage.zero?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Returns true if this is a line that has been covered
|
|
53
|
+
def covered?
|
|
54
|
+
!never? && !skipped? && coverage.positive?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Returns true if this line is not relevant for coverage
|
|
58
|
+
def never?
|
|
59
|
+
!skipped? && coverage.nil?
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Flags this line as skipped
|
|
63
|
+
def skipped!
|
|
64
|
+
@skipped = true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Returns true if this line was skipped, false otherwise. Lines are skipped if they are wrapped with
|
|
68
|
+
# # :nocov: comment lines.
|
|
69
|
+
def skipped?
|
|
70
|
+
skipped
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# The status of this line - either covered, missed, skipped or never. Useful i.e. for direct use
|
|
74
|
+
# as a css class in report generation
|
|
75
|
+
def status
|
|
76
|
+
return "skipped" if skipped?
|
|
77
|
+
return "never" if never?
|
|
78
|
+
return "missed" if missed?
|
|
79
|
+
"covered" if covered?
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|