coverband 4.2.0 → 4.2.4
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/.rubocop.yml +29 -10
- data/.travis.yml +16 -8
- data/CODE_OF_CONDUCT.md +76 -0
- data/Gemfile +3 -1
- data/Gemfile.rails4 +1 -0
- data/README.md +108 -59
- data/Rakefile +19 -3
- data/changes.md +85 -10
- data/coverband.gemspec +3 -5
- data/lib/coverband/adapters/base.rb +64 -45
- data/lib/coverband/adapters/file_store.rb +15 -9
- data/lib/coverband/adapters/hash_redis_store.rb +190 -0
- data/lib/coverband/adapters/redis_store.rb +35 -11
- data/lib/coverband/at_exit.rb +2 -11
- data/lib/coverband/collectors/coverage.rb +35 -41
- data/lib/coverband/collectors/delta.rb +34 -28
- data/lib/coverband/collectors/view_tracker.rb +157 -0
- data/lib/coverband/configuration.rb +115 -33
- data/lib/coverband/integrations/background.rb +12 -4
- data/lib/coverband/integrations/{middleware.rb → background_middleware.rb} +2 -6
- data/lib/coverband/integrations/bundler.rb +8 -0
- data/lib/coverband/integrations/report_middleware.rb +15 -0
- data/lib/coverband/integrations/resque.rb +3 -5
- data/lib/coverband/reporters/base.rb +40 -16
- data/lib/coverband/reporters/console_report.rb +1 -0
- data/lib/coverband/reporters/html_report.rb +31 -27
- data/lib/coverband/reporters/web.rb +63 -18
- data/lib/coverband/utils/absolute_file_converter.rb +47 -0
- data/lib/coverband/utils/file_hasher.rb +16 -0
- data/lib/coverband/utils/file_list.rb +16 -5
- data/lib/coverband/utils/html_formatter.rb +45 -8
- data/lib/coverband/utils/lines_classifier.rb +5 -0
- data/lib/coverband/utils/railtie.rb +28 -10
- data/lib/coverband/utils/relative_file_converter.rb +41 -0
- data/lib/coverband/utils/result.rb +6 -39
- data/lib/coverband/utils/results.rb +51 -0
- data/lib/coverband/utils/s3_report.rb +2 -3
- data/lib/coverband/utils/source_file.rb +34 -8
- data/lib/coverband/utils/tasks.rb +10 -10
- data/lib/coverband/version.rb +5 -1
- data/lib/coverband.rb +49 -27
- data/lua/install.sh +15 -0
- data/lua/lib/persist-coverage.lua +28 -0
- data/lua/test/bootstrap.lua +5 -0
- data/lua/test/harness.lua +19 -0
- data/lua/test/redis-call.lua +55 -0
- data/lua/test/test-persist-coverage.lua +132 -0
- data/public/application.css +14 -6
- data/public/application.js +27 -0
- data/test/benchmarks/benchmark.rake +167 -73
- data/test/benchmarks/init_rails.rake +10 -0
- data/test/coverband/adapters/base_test.rb +73 -42
- data/test/coverband/adapters/file_store_test.rb +48 -37
- data/test/coverband/adapters/hash_redis_store_test.rb +195 -0
- data/test/coverband/adapters/redis_store_test.rb +102 -59
- data/test/coverband/at_exit_test.rb +0 -2
- data/test/coverband/collectors/coverage_test.rb +71 -13
- data/test/coverband/collectors/delta_test.rb +36 -6
- data/test/coverband/collectors/view_tracker_test.rb +113 -0
- data/test/coverband/configuration_test.rb +47 -7
- data/test/coverband/coverband_test.rb +14 -2
- data/test/coverband/integrations/background_middleware_test.rb +44 -0
- data/test/coverband/integrations/background_test.rb +11 -3
- data/test/coverband/integrations/report_middleware_test.rb +44 -0
- data/test/coverband/integrations/resque_worker_test.rb +5 -6
- data/test/coverband/integrations/test_resque_job.rb +3 -1
- data/test/coverband/reporters/base_test.rb +5 -81
- data/test/coverband/reporters/console_test.rb +3 -10
- data/test/coverband/reporters/html_test.rb +71 -21
- data/test/coverband/reporters/web_test.rb +0 -10
- data/test/coverband/utils/absolute_file_converter_test.rb +56 -0
- data/test/coverband/utils/file_groups_test.rb +11 -5
- data/test/coverband/utils/file_hasher_test.rb +29 -0
- data/test/coverband/utils/file_list_test.rb +5 -5
- data/test/coverband/utils/html_formatter_test.rb +41 -0
- data/test/coverband/utils/relative_file_converter_test.rb +39 -0
- data/test/coverband/utils/result_test.rb +6 -47
- data/test/coverband/utils/results_test.rb +54 -0
- data/test/coverband/utils/s3_report_test.rb +2 -0
- data/test/coverband/utils/source_file_test.rb +54 -0
- data/test/dog.rb.erb +12 -0
- data/test/forked/rails_full_stack_test.rb +106 -0
- data/test/forked/rails_rake_full_stack_test.rb +58 -0
- data/test/integration/full_stack_test.rb +15 -16
- data/test/rails4_dummy/Rakefile +6 -0
- data/test/rails4_dummy/config/application.rb +8 -9
- data/test/rails4_dummy/config/coverband.rb +3 -11
- data/test/rails4_dummy/config/coverband_missing_redis.rb +3 -0
- data/test/rails5_dummy/Rakefile +6 -0
- data/test/rails5_dummy/config/application.rb +6 -10
- data/test/rails5_dummy/config/coverband.rb +7 -3
- data/test/rails5_dummy/config/coverband_missing_redis.rb +15 -0
- data/test/rails_test_helper.rb +22 -5
- data/test/test_helper.rb +52 -11
- data/test/unique_files.rb +17 -9
- data/views/data.erb +1 -0
- data/views/file_list.erb +13 -12
- data/views/gem_list.erb +10 -1
- data/views/layout.erb +6 -18
- data/views/nav.erb +36 -0
- data/views/source_file.erb +20 -4
- data/views/source_file_loader.erb +1 -1
- data/views/view_tracker.erb +52 -0
- metadata +71 -12
- data/lib/coverband/utils/file_path_helper.rb +0 -57
- data/test/coverband/integrations/middleware_test.rb +0 -96
- data/test/integration/rails_full_stack_test.rb +0 -95
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Coverband
|
|
4
|
-
class
|
|
4
|
+
class BackgroundMiddleware
|
|
5
5
|
def initialize(app)
|
|
6
6
|
@app = app
|
|
7
7
|
end
|
|
@@ -10,11 +10,7 @@ module Coverband
|
|
|
10
10
|
@app.call(env)
|
|
11
11
|
ensure
|
|
12
12
|
AtExit.register
|
|
13
|
-
if Coverband.configuration.background_reporting_enabled
|
|
14
|
-
Background.start
|
|
15
|
-
else
|
|
16
|
-
Collectors::Coverage.instance.report_coverage
|
|
17
|
-
end
|
|
13
|
+
Background.start if Coverband.configuration.background_reporting_enabled
|
|
18
14
|
end
|
|
19
15
|
end
|
|
20
16
|
end
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
Resque.after_fork do |
|
|
3
|
+
Resque.after_fork do |_job|
|
|
4
4
|
Coverband.start
|
|
5
5
|
Coverband.runtime_coverage!
|
|
6
|
-
# no reason to miss coverage on a first resque job
|
|
7
|
-
Coverband::Collectors::Delta.set_default_results
|
|
8
6
|
end
|
|
9
7
|
|
|
10
8
|
Resque.before_first_fork do
|
|
11
9
|
Coverband.eager_loading_coverage!
|
|
12
10
|
Coverband.configuration.background_reporting_enabled = false
|
|
13
11
|
Coverband::Background.stop
|
|
14
|
-
Coverband
|
|
12
|
+
Coverband.report_coverage
|
|
15
13
|
end
|
|
16
14
|
|
|
17
15
|
module Coverband
|
|
@@ -19,7 +17,7 @@ module Coverband
|
|
|
19
17
|
def perform
|
|
20
18
|
super
|
|
21
19
|
ensure
|
|
22
|
-
Coverband.report_coverage
|
|
20
|
+
Coverband.report_coverage
|
|
23
21
|
end
|
|
24
22
|
end
|
|
25
23
|
end
|
|
@@ -8,37 +8,61 @@ module Coverband
|
|
|
8
8
|
###
|
|
9
9
|
class Base
|
|
10
10
|
class << self
|
|
11
|
-
|
|
11
|
+
DATA_KEY = 'data'
|
|
12
|
+
|
|
12
13
|
def report(store, _options = {})
|
|
13
14
|
all_roots = Coverband.configuration.all_root_paths
|
|
14
15
|
scov_style_report = get_current_scov_data_imp(store, all_roots)
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
# These are extremelhy verbose but useful during coverband development, not generally for users
|
|
18
|
+
# Only avaiable by uncommenting this mode is never released
|
|
19
|
+
# if Coverband.configuration.verbose
|
|
20
|
+
# # msg = "report:\n #{scov_style_report.inspect}"
|
|
21
|
+
# # Coverband.configuration.logger.debug msg
|
|
22
|
+
# end
|
|
20
23
|
scov_style_report
|
|
21
24
|
end
|
|
22
25
|
|
|
26
|
+
###
|
|
27
|
+
# Add back files that exist in the project but have no Coverage
|
|
28
|
+
# This makes it easy to find and delete files with no references
|
|
29
|
+
###
|
|
30
|
+
def fix_reports(reports)
|
|
31
|
+
# list all files, even if not tracked by Coverband (0% coverage)
|
|
32
|
+
tracked_glob = Coverband.configuration.tracked_search_paths
|
|
33
|
+
filtered_report_files = {}
|
|
34
|
+
|
|
35
|
+
reports.each_pair do |report_name, report_data|
|
|
36
|
+
filtered_report_files[report_name] = {}
|
|
37
|
+
report_files = Coverband::Utils::Result.add_not_loaded_files(report_data, tracked_glob)
|
|
38
|
+
|
|
39
|
+
# apply coverband filters
|
|
40
|
+
report_files.each_pair do |file, data|
|
|
41
|
+
next if Coverband.configuration.ignore.any? { |i| file.match(i) }
|
|
42
|
+
|
|
43
|
+
filtered_report_files[report_name][file] = data
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
filtered_report_files
|
|
47
|
+
end
|
|
48
|
+
|
|
23
49
|
protected
|
|
24
50
|
|
|
25
51
|
def fix_file_names(report_hash, roots)
|
|
26
|
-
if Coverband.configuration.verbose
|
|
27
|
-
Coverband.configuration.logger.info "fixing root: #{roots.join(', ')}"
|
|
28
|
-
end
|
|
52
|
+
Coverband.configuration.logger.debug "fixing root: #{roots.join(', ')}" if Coverband.configuration.verbose
|
|
29
53
|
|
|
30
54
|
# normalize names across servers
|
|
31
55
|
report_hash.each_with_object({}) do |(name, report), fixed_report|
|
|
32
56
|
fixed_report[name] = {}
|
|
33
57
|
report.each_pair do |key, vals|
|
|
34
|
-
filename =
|
|
35
|
-
fixed_report[name][filename] = if fixed_report[name].key?(filename) && fixed_report[name][filename][
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
58
|
+
filename = Coverband::Utils::AbsoluteFileConverter.convert(key)
|
|
59
|
+
fixed_report[name][filename] = if fixed_report[name].key?(filename) && fixed_report[name][filename][DATA_KEY] && vals[DATA_KEY]
|
|
60
|
+
merged_data = merge_arrays(fixed_report[name][filename][DATA_KEY], vals[DATA_KEY])
|
|
61
|
+
vals[DATA_KEY] = merged_data
|
|
62
|
+
vals
|
|
63
|
+
else
|
|
64
|
+
vals
|
|
65
|
+
end
|
|
42
66
|
end
|
|
43
67
|
end
|
|
44
68
|
end
|
|
@@ -10,6 +10,7 @@ module Coverband
|
|
|
10
10
|
scov_style_report = super(store, options)
|
|
11
11
|
|
|
12
12
|
scov_style_report.each_pair do |file, usage|
|
|
13
|
+
# TODO: In Coverband 5 deprecate none hash format
|
|
13
14
|
if usage.is_a?(Hash)
|
|
14
15
|
Coverband.configuration.logger.info "#{file}: #{usage['data']}"
|
|
15
16
|
else
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
module Coverband
|
|
4
4
|
module Reporters
|
|
5
5
|
class HTMLReport < Base
|
|
6
|
-
attr_accessor :filtered_report_files, :open_report, :
|
|
6
|
+
attr_accessor :filtered_report_files, :open_report, :static, :notice,
|
|
7
7
|
:base_path, :filename
|
|
8
8
|
|
|
9
9
|
def initialize(store, options = {})
|
|
10
10
|
coverband_reports = Coverband::Reporters::Base.report(store, options)
|
|
11
11
|
self.open_report = options.fetch(:open_report) { true }
|
|
12
|
-
self.
|
|
12
|
+
self.static = options.fetch(:static) { true }
|
|
13
13
|
# TODO: refactor notice out to top level of web only
|
|
14
14
|
self.notice = options.fetch(:notice) { nil }
|
|
15
15
|
self.base_path = options.fetch(:base_path) { nil }
|
|
@@ -25,40 +25,44 @@ module Coverband
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def report
|
|
28
|
-
if
|
|
29
|
-
|
|
30
|
-
base_path: base_path,
|
|
31
|
-
notice: notice).format_html!
|
|
28
|
+
if static?
|
|
29
|
+
report_static_site
|
|
32
30
|
else
|
|
33
|
-
|
|
34
|
-
if open_report
|
|
35
|
-
`open #{Coverband.configuration.root}/coverage/index.html`
|
|
36
|
-
else
|
|
37
|
-
Coverband.configuration.logger.info 'report is ready and viewable: open coverage/index.html'
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
Coverband::Utils::S3Report.instance.persist! if Coverband.configuration.s3_bucket
|
|
31
|
+
report_dynamic_html
|
|
41
32
|
end
|
|
42
33
|
end
|
|
43
34
|
|
|
35
|
+
def report_data
|
|
36
|
+
report_dynamic_data
|
|
37
|
+
end
|
|
38
|
+
|
|
44
39
|
private
|
|
45
40
|
|
|
46
|
-
def
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
41
|
+
def static?
|
|
42
|
+
static
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def report_static_site
|
|
46
|
+
Coverband::Utils::HTMLFormatter.new(filtered_report_files).format_static_html!
|
|
47
|
+
if open_report
|
|
48
|
+
`open #{Coverband.configuration.root}/coverage/index.html`
|
|
49
|
+
else
|
|
50
|
+
Coverband.configuration.logger.info 'report is ready and viewable: open coverage/index.html'
|
|
51
|
+
end
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
report_files = Coverband::Utils::Result.add_not_loaded_files(report_data, tracked_glob)
|
|
53
|
+
Coverband::Utils::S3Report.instance.persist! if Coverband.configuration.s3_bucket
|
|
54
|
+
end
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
def report_dynamic_html
|
|
57
|
+
Coverband::Utils::HTMLFormatter.new(filtered_report_files,
|
|
58
|
+
base_path: base_path,
|
|
59
|
+
notice: notice).format_dynamic_html!
|
|
60
|
+
end
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
def report_dynamic_data
|
|
63
|
+
Coverband::Utils::HTMLFormatter.new(filtered_report_files,
|
|
64
|
+
base_path: base_path,
|
|
65
|
+
notice: notice).format_dynamic_data!
|
|
62
66
|
end
|
|
63
67
|
end
|
|
64
68
|
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
begin
|
|
4
4
|
require 'rack'
|
|
5
5
|
rescue LoadError
|
|
6
|
+
puts 'error loading Coverband web reporter as Rack is not available'
|
|
6
7
|
end
|
|
7
8
|
|
|
8
9
|
module Coverband
|
|
@@ -17,19 +18,30 @@ module Coverband
|
|
|
17
18
|
urls: [/.*\.css/, /.*\.js/, /.*\.gif/, /.*\.png/])
|
|
18
19
|
end
|
|
19
20
|
|
|
21
|
+
def check_auth
|
|
22
|
+
return true unless Coverband.configuration.password
|
|
23
|
+
|
|
24
|
+
auth_header = request.get_header('HTTP_AUTHORIZATION')
|
|
25
|
+
return unless auth_header
|
|
26
|
+
|
|
27
|
+
Coverband.configuration.password == Base64.decode64(auth_header.split[1]).split(':')[1]
|
|
28
|
+
end
|
|
29
|
+
|
|
20
30
|
def call(env)
|
|
21
31
|
@request = Rack::Request.new(env)
|
|
22
32
|
|
|
33
|
+
return [401, { 'www-authenticate' => 'Basic realm=""' }, ['']] unless check_auth
|
|
34
|
+
|
|
23
35
|
if request.post?
|
|
24
36
|
case request.path_info
|
|
37
|
+
when %r{\/clear_view_tracking_file}
|
|
38
|
+
clear_view_tracking_file
|
|
39
|
+
when %r{\/clear_view_tracking}
|
|
40
|
+
clear_view_tracking
|
|
25
41
|
when %r{\/clear_file}
|
|
26
42
|
clear_file
|
|
27
43
|
when %r{\/clear}
|
|
28
44
|
clear
|
|
29
|
-
when %r{\/collect_coverage}
|
|
30
|
-
collect_coverage
|
|
31
|
-
when %r{\/reload_files}
|
|
32
|
-
reload_files
|
|
33
45
|
else
|
|
34
46
|
[404, { 'Content-Type' => 'text/html' }, ['404 error!']]
|
|
35
47
|
end
|
|
@@ -39,6 +51,12 @@ module Coverband
|
|
|
39
51
|
@static.call(env)
|
|
40
52
|
when %r{\/settings}
|
|
41
53
|
[200, { 'Content-Type' => 'text/html' }, [settings]]
|
|
54
|
+
when %r{\/view_tracker_data}
|
|
55
|
+
[200, { 'Content-Type' => 'text/json' }, [view_tracker_data]]
|
|
56
|
+
when %r{\/view_tracker}
|
|
57
|
+
[200, { 'Content-Type' => 'text/html' }, [view_tracker]]
|
|
58
|
+
when %r{\/enriched_debug_data}
|
|
59
|
+
[200, { 'Content-Type' => 'text/json' }, [enriched_debug_data]]
|
|
42
60
|
when %r{\/debug_data}
|
|
43
61
|
[200, { 'Content-Type' => 'text/json' }, [debug_data]]
|
|
44
62
|
when %r{\/load_file_details}
|
|
@@ -55,7 +73,7 @@ module Coverband
|
|
|
55
73
|
notice = "<strong>Notice:</strong> #{Rack::Utils.escape_html(request.params['notice'])}<br/>"
|
|
56
74
|
notice = request.params['notice'] ? notice : ''
|
|
57
75
|
Coverband::Reporters::HTMLReport.new(Coverband.configuration.store,
|
|
58
|
-
|
|
76
|
+
static: false,
|
|
59
77
|
base_path: base_path,
|
|
60
78
|
notice: notice,
|
|
61
79
|
open_report: false).report
|
|
@@ -65,10 +83,30 @@ module Coverband
|
|
|
65
83
|
Coverband::Utils::HTMLFormatter.new(nil, base_path: base_path).format_settings!
|
|
66
84
|
end
|
|
67
85
|
|
|
86
|
+
def view_tracker
|
|
87
|
+
notice = "<strong>Notice:</strong> #{Rack::Utils.escape_html(request.params['notice'])}<br/>"
|
|
88
|
+
notice = request.params['notice'] ? notice : ''
|
|
89
|
+
Coverband::Utils::HTMLFormatter.new(nil,
|
|
90
|
+
notice: notice,
|
|
91
|
+
base_path: base_path).format_view_tracker!
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def view_tracker_data
|
|
95
|
+
Coverband::Collectors::ViewTracker.new(store: Coverband.configuration.store).as_json
|
|
96
|
+
end
|
|
97
|
+
|
|
68
98
|
def debug_data
|
|
69
99
|
Coverband.configuration.store.get_coverage_report.to_json
|
|
70
100
|
end
|
|
71
101
|
|
|
102
|
+
def enriched_debug_data
|
|
103
|
+
Coverband::Reporters::HTMLReport.new(Coverband.configuration.store,
|
|
104
|
+
static: false,
|
|
105
|
+
base_path: base_path,
|
|
106
|
+
notice: '',
|
|
107
|
+
open_report: false).report_data
|
|
108
|
+
end
|
|
109
|
+
|
|
72
110
|
def load_file_details
|
|
73
111
|
filename = request.params['filename']
|
|
74
112
|
Coverband::Reporters::HTMLReport.new(Coverband.configuration.store,
|
|
@@ -77,12 +115,6 @@ module Coverband
|
|
|
77
115
|
open_report: false).file_details
|
|
78
116
|
end
|
|
79
117
|
|
|
80
|
-
def collect_coverage
|
|
81
|
-
Coverband.report_coverage(true)
|
|
82
|
-
notice = 'coverband coverage collected'
|
|
83
|
-
[301, { 'Location' => "#{base_path}?notice=#{notice}" }, []]
|
|
84
|
-
end
|
|
85
|
-
|
|
86
118
|
def clear
|
|
87
119
|
if Coverband.configuration.web_enable_clear
|
|
88
120
|
Coverband.configuration.store.clear!
|
|
@@ -104,14 +136,27 @@ module Coverband
|
|
|
104
136
|
[301, { 'Location' => "#{base_path}?notice=#{notice}" }, []]
|
|
105
137
|
end
|
|
106
138
|
|
|
107
|
-
def
|
|
108
|
-
Coverband.configuration
|
|
109
|
-
|
|
139
|
+
def clear_view_tracking
|
|
140
|
+
if Coverband.configuration.web_enable_clear
|
|
141
|
+
tracker = Coverband::Collectors::ViewTracker.new(store: Coverband.configuration.store)
|
|
142
|
+
tracker.reset_recordings
|
|
143
|
+
notice = 'view tracking reset'
|
|
144
|
+
else
|
|
145
|
+
notice = 'web_enable_clear isnt enabled in your configuration'
|
|
110
146
|
end
|
|
111
|
-
#
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
147
|
+
[301, { 'Location' => "#{base_path}/view_tracker?notice=#{notice}" }, []]
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def clear_view_tracking_file
|
|
151
|
+
if Coverband.configuration.web_enable_clear
|
|
152
|
+
tracker = Coverband::Collectors::ViewTracker.new(store: Coverband.configuration.store)
|
|
153
|
+
filename = request.params['filename']
|
|
154
|
+
tracker.clear_file!(filename)
|
|
155
|
+
notice = "coverage for file #{filename} cleared"
|
|
156
|
+
else
|
|
157
|
+
notice = 'web_enable_clear isnt enabled in your configuration'
|
|
158
|
+
end
|
|
159
|
+
[301, { 'Location' => "#{base_path}/view_tracker?notice=#{notice}" }, []]
|
|
115
160
|
end
|
|
116
161
|
|
|
117
162
|
private
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coverband
|
|
4
|
+
module Utils
|
|
5
|
+
class AbsoluteFileConverter
|
|
6
|
+
def initialize(roots)
|
|
7
|
+
@cache = {}
|
|
8
|
+
@roots = roots.map { |root| "#{File.expand_path(root)}/" }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.instance
|
|
12
|
+
@instance ||= new(Coverband.configuration.all_root_paths)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.reset
|
|
16
|
+
@instance = nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.convert(relative_path)
|
|
20
|
+
instance.convert(relative_path)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def convert(relative_path)
|
|
24
|
+
@cache[relative_path] ||= begin
|
|
25
|
+
relative_filename = relative_path
|
|
26
|
+
local_filename = relative_filename
|
|
27
|
+
@roots.each do |root|
|
|
28
|
+
relative_filename = relative_filename.sub(/^#{root}/, './')
|
|
29
|
+
# once we have a relative path break out of the loop
|
|
30
|
+
break if relative_filename.start_with? './'
|
|
31
|
+
end
|
|
32
|
+
# the filename for our reports is expected to be a full path.
|
|
33
|
+
# roots.last should be roots << current_root}/
|
|
34
|
+
# a fully expanded path of config.root
|
|
35
|
+
# filename = filename.gsub('./', roots.last)
|
|
36
|
+
# above only works for app files
|
|
37
|
+
# we need to rethink some of this logic
|
|
38
|
+
# gems aren't at project root and can have multiple locations
|
|
39
|
+
local_root = @roots.find do |root|
|
|
40
|
+
File.exist?(relative_filename.gsub('./', root))
|
|
41
|
+
end
|
|
42
|
+
local_root ? relative_filename.gsub('./', local_root) : local_filename
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coverband
|
|
4
|
+
module Utils
|
|
5
|
+
class FileHasher
|
|
6
|
+
@cache = {}
|
|
7
|
+
|
|
8
|
+
def self.hash(file, path_converter: AbsoluteFileConverter.instance)
|
|
9
|
+
@cache[file] ||= begin
|
|
10
|
+
file = path_converter.convert(file)
|
|
11
|
+
Digest::MD5.file(file).hexdigest if File.exist?(file)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -13,24 +13,28 @@ module Coverband
|
|
|
13
13
|
# Returns the count of lines that have coverage
|
|
14
14
|
def covered_lines
|
|
15
15
|
return 0.0 if empty?
|
|
16
|
+
|
|
16
17
|
map { |f| f.covered_lines.count }.inject(:+)
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
# Returns the count of lines that have been missed
|
|
20
21
|
def missed_lines
|
|
21
22
|
return 0.0 if empty?
|
|
23
|
+
|
|
22
24
|
map { |f| f.missed_lines.count }.inject(:+)
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
# Returns the count of lines that are not relevant for coverage
|
|
26
28
|
def never_lines
|
|
27
29
|
return 0.0 if empty?
|
|
30
|
+
|
|
28
31
|
map { |f| f.never_lines.count }.inject(:+)
|
|
29
32
|
end
|
|
30
33
|
|
|
31
34
|
# Returns the count of skipped lines
|
|
32
35
|
def skipped_lines
|
|
33
36
|
return 0.0 if empty?
|
|
37
|
+
|
|
34
38
|
map { |f| f.skipped_lines.count }.inject(:+)
|
|
35
39
|
end
|
|
36
40
|
|
|
@@ -40,11 +44,6 @@ module Coverband
|
|
|
40
44
|
map(&:covered_percent)
|
|
41
45
|
end
|
|
42
46
|
|
|
43
|
-
# Finds the least covered file and returns that file's name
|
|
44
|
-
def least_covered_file
|
|
45
|
-
sort_by(&:covered_percent).first.filename
|
|
46
|
-
end
|
|
47
|
-
|
|
48
47
|
# Returns the overall amount of relevant lines of code across all files in this list
|
|
49
48
|
def lines_of_code
|
|
50
49
|
covered_lines + missed_lines
|
|
@@ -54,15 +53,27 @@ module Coverband
|
|
|
54
53
|
# @return [Float]
|
|
55
54
|
def covered_percent
|
|
56
55
|
return 100.0 if empty? || lines_of_code.zero?
|
|
56
|
+
|
|
57
57
|
Float(covered_lines * 100.0 / lines_of_code)
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
+
# Computes the coverage based upon lines covered and lines missed, formatted
|
|
61
|
+
# @return [Float]
|
|
62
|
+
def formatted_covered_percent
|
|
63
|
+
covered_percent.round(2)
|
|
64
|
+
end
|
|
65
|
+
|
|
60
66
|
# Computes the strength (hits / line) based upon lines covered and lines missed
|
|
61
67
|
# @return [Float]
|
|
62
68
|
def covered_strength
|
|
63
69
|
return 0.0 if empty? || lines_of_code.zero?
|
|
70
|
+
|
|
64
71
|
Float(map { |f| f.covered_strength * f.lines_of_code }.inject(:+) / lines_of_code)
|
|
65
72
|
end
|
|
73
|
+
|
|
74
|
+
def first_seen_at
|
|
75
|
+
map(&:first_updated_at).reject { |el| el.is_a?(String) }.min
|
|
76
|
+
end
|
|
66
77
|
end
|
|
67
78
|
end
|
|
68
79
|
end
|
|
@@ -21,22 +21,34 @@ module Coverband
|
|
|
21
21
|
@coverage_result = Coverband::Utils::Results.new(report) if report
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
def
|
|
24
|
+
def format_static_html!
|
|
25
25
|
format(@coverage_result)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
def
|
|
28
|
+
def format_dynamic_html!
|
|
29
29
|
format_html(@coverage_result)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
def format_dynamic_data!
|
|
33
|
+
format_data(@coverage_result)
|
|
34
|
+
end
|
|
35
|
+
|
|
32
36
|
def format_settings!
|
|
33
37
|
format_settings
|
|
34
38
|
end
|
|
35
39
|
|
|
40
|
+
def format_view_tracker!
|
|
41
|
+
format_view_tracker
|
|
42
|
+
end
|
|
43
|
+
|
|
36
44
|
def format_source_file!(filename)
|
|
37
45
|
source_file = @coverage_result.file_from_path_with_type(filename)
|
|
38
|
-
|
|
39
|
-
|
|
46
|
+
|
|
47
|
+
if source_file
|
|
48
|
+
formatted_source_file(@coverage_result, source_file)
|
|
49
|
+
else
|
|
50
|
+
'File No Longer Available'
|
|
51
|
+
end
|
|
40
52
|
end
|
|
41
53
|
|
|
42
54
|
private
|
|
@@ -45,6 +57,10 @@ module Coverband
|
|
|
45
57
|
template('settings').result(binding)
|
|
46
58
|
end
|
|
47
59
|
|
|
60
|
+
def format_view_tracker
|
|
61
|
+
template('view_tracker').result(binding)
|
|
62
|
+
end
|
|
63
|
+
|
|
48
64
|
def format(result)
|
|
49
65
|
Dir[File.join(File.dirname(__FILE__), '../../../public/*')].each do |path|
|
|
50
66
|
FileUtils.cp_r(path, asset_output_path)
|
|
@@ -59,6 +75,10 @@ module Coverband
|
|
|
59
75
|
template('layout').result(binding)
|
|
60
76
|
end
|
|
61
77
|
|
|
78
|
+
def format_data(result)
|
|
79
|
+
template('data').result(binding)
|
|
80
|
+
end
|
|
81
|
+
|
|
62
82
|
# Returns the an erb instance for the template of given name
|
|
63
83
|
def template(name)
|
|
64
84
|
ERB.new(File.read(File.join(File.dirname(__FILE__), '../../../views/', "#{name}.erb")))
|
|
@@ -70,16 +90,25 @@ module Coverband
|
|
|
70
90
|
|
|
71
91
|
def asset_output_path
|
|
72
92
|
return @asset_output_path if defined?(@asset_output_path) && @asset_output_path
|
|
93
|
+
|
|
73
94
|
@asset_output_path = File.join(output_path)
|
|
74
95
|
FileUtils.mkdir_p(@asset_output_path)
|
|
75
96
|
@asset_output_path
|
|
76
97
|
end
|
|
77
98
|
|
|
99
|
+
def served_html?
|
|
100
|
+
!static_html?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def static_html?
|
|
104
|
+
base_path.nil?
|
|
105
|
+
end
|
|
106
|
+
|
|
78
107
|
def assets_path(name)
|
|
79
|
-
if
|
|
80
|
-
File.join(base_path, name)
|
|
81
|
-
else
|
|
108
|
+
if static_html?
|
|
82
109
|
File.join(name)
|
|
110
|
+
else
|
|
111
|
+
File.join(base_path, name)
|
|
83
112
|
end
|
|
84
113
|
end
|
|
85
114
|
|
|
@@ -91,6 +120,10 @@ module Coverband
|
|
|
91
120
|
button + '</form>'
|
|
92
121
|
end
|
|
93
122
|
|
|
123
|
+
def display_nav(nav_options = {})
|
|
124
|
+
template('nav').result(binding)
|
|
125
|
+
end
|
|
126
|
+
|
|
94
127
|
# Returns the html for the given source_file
|
|
95
128
|
def formatted_source_file(result, source_file)
|
|
96
129
|
template('source_file').result(binding)
|
|
@@ -157,7 +190,11 @@ module Coverband
|
|
|
157
190
|
end
|
|
158
191
|
|
|
159
192
|
def timeago(time)
|
|
160
|
-
|
|
193
|
+
if time
|
|
194
|
+
"<abbr class=\"timeago\" title=\"#{time.iso8601}\">#{time.iso8601}</abbr>"
|
|
195
|
+
else
|
|
196
|
+
'Not Available'
|
|
197
|
+
end
|
|
161
198
|
end
|
|
162
199
|
|
|
163
200
|
def shortened_filename(source_file)
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
####
|
|
4
|
+
#
|
|
5
|
+
# NOTE: with Ruby 2.6.0 and beyond we can replace this classifier with
|
|
6
|
+
# ::Coverage.line_stub
|
|
7
|
+
# https://ruby-doc.org/stdlib-2.6.1/libdoc/coverage/rdoc/Coverage.html#method-c-line_stub
|
|
8
|
+
#
|
|
4
9
|
# Thanks for all the help SimpleCov https://github.com/colszowka/simplecov-html
|
|
5
10
|
# initial version pulled into Coverband from Simplecov 12/04/2018
|
|
6
11
|
#
|