coverband 4.2.1 → 4.2.2
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/.travis.yml +2 -9
- data/CODE_OF_CONDUCT.md +76 -0
- data/README.md +32 -28
- data/changes.md +31 -11
- data/coverband.gemspec +0 -5
- data/lib/coverband/adapters/base.rb +8 -13
- data/lib/coverband/adapters/file_store.rb +10 -4
- data/lib/coverband/adapters/hash_redis_store.rb +182 -0
- data/lib/coverband/adapters/redis_store.rb +15 -1
- data/lib/coverband/collectors/coverage.rb +5 -7
- data/lib/coverband/collectors/view_tracker.rb +124 -0
- data/lib/coverband/configuration.rb +21 -1
- data/lib/coverband/integrations/background.rb +2 -1
- data/lib/coverband/reporters/base.rb +4 -5
- data/lib/coverband/reporters/console_report.rb +1 -0
- data/lib/coverband/reporters/web.rb +6 -0
- data/lib/coverband/utils/absolute_file_converter.rb +47 -0
- data/lib/coverband/utils/file_hasher.rb +16 -0
- data/lib/coverband/utils/html_formatter.rb +12 -0
- data/lib/coverband/utils/railtie.rb +15 -3
- data/lib/coverband/utils/relative_file_converter.rb +41 -0
- data/lib/coverband/utils/s3_report.rb +2 -2
- data/lib/coverband/utils/source_file.rb +1 -3
- data/lib/coverband/utils/tasks.rb +1 -0
- data/lib/coverband/version.rb +5 -1
- data/lib/coverband.rb +7 -1
- data/public/application.css +6 -6
- data/test/benchmarks/benchmark.rake +25 -49
- data/test/benchmarks/init_rails.rake +10 -0
- data/test/coverband/adapters/hash_redis_store_test.rb +190 -0
- data/test/coverband/adapters/redis_store_test.rb +90 -88
- data/test/coverband/collectors/coverage_test.rb +6 -2
- data/test/coverband/collectors/view_tracker_test.rb +77 -0
- data/test/coverband/integrations/resque_worker_test.rb +2 -3
- data/test/coverband/reporters/base_test.rb +1 -78
- data/test/coverband/reporters/console_test.rb +1 -4
- data/test/coverband/reporters/html_test.rb +9 -9
- data/test/coverband/utils/absolute_file_converter_test.rb +56 -0
- data/test/coverband/utils/file_hasher_test.rb +29 -0
- data/test/coverband/utils/relative_file_converter_test.rb +39 -0
- data/test/forked/rails_rake_full_stack_test.rb +9 -1
- data/test/integration/full_stack_test.rb +1 -2
- data/test/test_helper.rb +6 -7
- data/test/unique_files.rb +1 -1
- data/views/layout.erb +1 -20
- data/views/nav.erb +35 -0
- data/views/view_tracker.erb +37 -0
- metadata +22 -31
- data/lib/coverband/utils/file_path_helper.rb +0 -68
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'singleton'
|
|
4
|
+
|
|
5
|
+
module Coverband
|
|
6
|
+
module Collectors
|
|
7
|
+
###
|
|
8
|
+
# This class tracks view file usage via ActiveSupport::Notifications
|
|
9
|
+
#
|
|
10
|
+
# This is a port of Flatfoot, a project I open sourced years ago, but am now rolling into Coverband
|
|
11
|
+
# https://github.com/livingsocial/flatfoot
|
|
12
|
+
#
|
|
13
|
+
# TODO: test and ensure slim, haml, and other support
|
|
14
|
+
###
|
|
15
|
+
class ViewTracker
|
|
16
|
+
DEFAULT_TARGET = Dir.glob('app/views/**/*.html.erb').reject { |file| file.match(/(_mailer)/) }
|
|
17
|
+
attr_accessor :target, :logged_views, :views_to_record
|
|
18
|
+
attr_reader :logger, :roots, :store, :ignore_patterns
|
|
19
|
+
|
|
20
|
+
def initialize(options = {})
|
|
21
|
+
raise NotImplementedError, 'View Tracker requires Rails 4 or greater' unless self.class.supported_version?
|
|
22
|
+
|
|
23
|
+
@project_directory = File.expand_path(Coverband.configuration.root)
|
|
24
|
+
@ignore_patterns = Coverband.configuration.ignore
|
|
25
|
+
@store = options.fetch(:store) { Coverband.configuration.store }
|
|
26
|
+
@logger = options.fetch(:logger) { Coverband.configuration.logger }
|
|
27
|
+
@target = options.fetch(:target) { DEFAULT_TARGET }
|
|
28
|
+
|
|
29
|
+
@roots = options.fetch(:roots) { Coverband.configuration.all_root_patterns }
|
|
30
|
+
@roots = @roots.split(',') if @roots.is_a?(String)
|
|
31
|
+
|
|
32
|
+
@logged_views = []
|
|
33
|
+
@views_to_record = []
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
###
|
|
37
|
+
# This method is called on every render call, so we try to reduce method calls
|
|
38
|
+
# and ensure high performance
|
|
39
|
+
###
|
|
40
|
+
def track_views(_name, _start, _finish, _id, payload)
|
|
41
|
+
if (file = payload[:identifier])
|
|
42
|
+
if newly_seen_file?(file)
|
|
43
|
+
logged_views << file
|
|
44
|
+
views_to_record << file if track_file?(file)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
###
|
|
49
|
+
# Annoyingly while you get full path for templates
|
|
50
|
+
# notifications only pass part of the path for layouts dropping any format info
|
|
51
|
+
# such as .html.erb or .js.erb
|
|
52
|
+
# http://edgeguides.rubyonrails.org/active_support_instrumentation.html#render_partial-action_view
|
|
53
|
+
###
|
|
54
|
+
return unless (layout_file = payload[:layout])
|
|
55
|
+
return unless newly_seen_file?(layout_file)
|
|
56
|
+
|
|
57
|
+
logged_views << layout_file
|
|
58
|
+
views_to_record << layout_file if track_file?(layout_file, layout: true)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def used_views
|
|
62
|
+
views = redis_store.smembers(tracker_key)
|
|
63
|
+
normalized_views = []
|
|
64
|
+
views.each do |view|
|
|
65
|
+
roots.each do |root|
|
|
66
|
+
view = view.gsub(/#{root}/, '')
|
|
67
|
+
end
|
|
68
|
+
normalized_views << view
|
|
69
|
+
end
|
|
70
|
+
normalized_views
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def unused_views
|
|
74
|
+
recently_used_views = used_views
|
|
75
|
+
all_views = target.reject { |view| recently_used_views.include?(view) }
|
|
76
|
+
# since layouts don't include format we count them used if they match with ANY formats
|
|
77
|
+
all_views.reject { |view| view.match(/\/layouts\//) && recently_used_views.any? { |used_view| view.include?(used_view) } }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def reset_recordings
|
|
81
|
+
redis_store.del(tracker_key)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def self.supported_version?
|
|
85
|
+
defined?(Rails) && defined?(Rails::VERSION) && Rails::VERSION::STRING.split('.').first.to_i >= 4
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def report_views_tracked
|
|
89
|
+
views_to_record.each do |file|
|
|
90
|
+
redis_store.sadd(tracker_key, file)
|
|
91
|
+
end
|
|
92
|
+
self.views_to_record = []
|
|
93
|
+
rescue StandardError => e
|
|
94
|
+
# we don't want to raise errors if Coverband can't reach redis.
|
|
95
|
+
# This is a nice to have not a bring the system down
|
|
96
|
+
logger&.error "Coverband: view_tracker failed to store, error #{e.class.name}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
protected
|
|
100
|
+
|
|
101
|
+
def newly_seen_file?(file)
|
|
102
|
+
return false if logged_views.include?(file)
|
|
103
|
+
|
|
104
|
+
true
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def track_file?(file, options = {})
|
|
108
|
+
@ignore_patterns.none? do |pattern|
|
|
109
|
+
file.include?(pattern)
|
|
110
|
+
end && (file.start_with?(@project_directory) || options[:layout])
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
def redis_store
|
|
116
|
+
store.raw_store
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def tracker_key
|
|
120
|
+
'render_tracker'
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -8,7 +8,7 @@ module Coverband
|
|
|
8
8
|
:background_reporting_enabled,
|
|
9
9
|
:background_reporting_sleep_seconds, :test_env,
|
|
10
10
|
:web_enable_clear, :gem_details, :web_debug, :report_on_exit,
|
|
11
|
-
:simulate_oneshot_lines_coverage
|
|
11
|
+
:simulate_oneshot_lines_coverage, :track_views, :view_tracker
|
|
12
12
|
|
|
13
13
|
attr_writer :logger, :s3_region, :s3_bucket, :s3_access_key_id, :s3_secret_access_key, :password
|
|
14
14
|
attr_reader :track_gems, :ignore, :use_oneshot_lines_coverage
|
|
@@ -30,6 +30,10 @@ module Coverband
|
|
|
30
30
|
# heroku asset compilation
|
|
31
31
|
IGNORE_DEFAULTS = %w[vendor .erb$ .slim$ /tmp internal:prelude schema.rb]
|
|
32
32
|
|
|
33
|
+
# Add in missing files which were never loaded
|
|
34
|
+
# we need to know what all paths to check for unloaded files
|
|
35
|
+
TRACKED_DEFAULT_PATHS = %w[app lib config]
|
|
36
|
+
|
|
33
37
|
def initialize
|
|
34
38
|
reset
|
|
35
39
|
end
|
|
@@ -38,6 +42,7 @@ module Coverband
|
|
|
38
42
|
@root = Dir.pwd
|
|
39
43
|
@root_paths = []
|
|
40
44
|
@ignore = IGNORE_DEFAULTS.dup
|
|
45
|
+
@seach_paths = TRACKED_DEFAULT_PATHS.dup
|
|
41
46
|
@additional_files = []
|
|
42
47
|
@verbose = false
|
|
43
48
|
@reporter = 'scov'
|
|
@@ -49,6 +54,7 @@ module Coverband
|
|
|
49
54
|
@web_enable_clear = false
|
|
50
55
|
@track_gems = false
|
|
51
56
|
@gem_details = false
|
|
57
|
+
@track_views = false
|
|
52
58
|
@groups = {}
|
|
53
59
|
@web_debug = false
|
|
54
60
|
@report_on_exit = true
|
|
@@ -106,6 +112,20 @@ module Coverband
|
|
|
106
112
|
@store = store
|
|
107
113
|
end
|
|
108
114
|
|
|
115
|
+
###
|
|
116
|
+
# Search Paths
|
|
117
|
+
###
|
|
118
|
+
def tracked_search_paths
|
|
119
|
+
"#{Coverband.configuration.current_root}/{#{@seach_paths.join(',')}}/**/*.{rb}"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
###
|
|
123
|
+
# Don't allow the to override defaults
|
|
124
|
+
###
|
|
125
|
+
def search_paths=(path_array)
|
|
126
|
+
@seach_paths = (@seach_paths + path_array).uniq
|
|
127
|
+
end
|
|
128
|
+
|
|
109
129
|
###
|
|
110
130
|
# Don't allow the ignore to override things like gem tracking
|
|
111
131
|
###
|
|
@@ -27,11 +27,12 @@ module Coverband
|
|
|
27
27
|
@semaphore.synchronize do
|
|
28
28
|
return if running?
|
|
29
29
|
|
|
30
|
-
logger.debug('Coverband: Starting background reporting')
|
|
30
|
+
logger.debug('Coverband: Starting background reporting') if Coverband.configuration.verbose
|
|
31
31
|
sleep_seconds = Coverband.configuration.background_reporting_sleep_seconds
|
|
32
32
|
@thread = Thread.new do
|
|
33
33
|
loop do
|
|
34
34
|
Coverband.report_coverage
|
|
35
|
+
Coverband.configuration.view_tracker&.report_views_tracked
|
|
35
36
|
if Coverband.configuration.verbose
|
|
36
37
|
logger.debug("Coverband: background reporting coverage (#{Coverband.configuration.store.type}). Sleeping #{sleep_seconds}s")
|
|
37
38
|
end
|
|
@@ -8,8 +8,6 @@ module Coverband
|
|
|
8
8
|
###
|
|
9
9
|
class Base
|
|
10
10
|
class << self
|
|
11
|
-
include Coverband::Utils::FilePathHelper
|
|
12
|
-
|
|
13
11
|
DATA_KEY = 'data'
|
|
14
12
|
|
|
15
13
|
def report(store, _options = {})
|
|
@@ -17,6 +15,7 @@ module Coverband
|
|
|
17
15
|
scov_style_report = get_current_scov_data_imp(store, all_roots)
|
|
18
16
|
|
|
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
|
|
20
19
|
# if Coverband.configuration.verbose
|
|
21
20
|
# # msg = "report:\n #{scov_style_report.inspect}"
|
|
22
21
|
# # Coverband.configuration.logger.debug msg
|
|
@@ -30,7 +29,7 @@ module Coverband
|
|
|
30
29
|
###
|
|
31
30
|
def fix_reports(reports)
|
|
32
31
|
# list all files, even if not tracked by Coverband (0% coverage)
|
|
33
|
-
tracked_glob =
|
|
32
|
+
tracked_glob = Coverband.configuration.tracked_search_paths
|
|
34
33
|
filtered_report_files = {}
|
|
35
34
|
|
|
36
35
|
reports.each_pair do |report_name, report_data|
|
|
@@ -50,13 +49,13 @@ module Coverband
|
|
|
50
49
|
protected
|
|
51
50
|
|
|
52
51
|
def fix_file_names(report_hash, roots)
|
|
53
|
-
Coverband.configuration.logger.
|
|
52
|
+
Coverband.configuration.logger.debug "fixing root: #{roots.join(', ')}" if Coverband.configuration.verbose
|
|
54
53
|
|
|
55
54
|
# normalize names across servers
|
|
56
55
|
report_hash.each_with_object({}) do |(name, report), fixed_report|
|
|
57
56
|
fixed_report[name] = {}
|
|
58
57
|
report.each_pair do |key, vals|
|
|
59
|
-
filename =
|
|
58
|
+
filename = Coverband::Utils::AbsoluteFileConverter.convert(key)
|
|
60
59
|
fixed_report[name][filename] = if fixed_report[name].key?(filename) && fixed_report[name][filename][DATA_KEY] && vals[DATA_KEY]
|
|
61
60
|
merged_data = merge_arrays(fixed_report[name][filename][DATA_KEY], vals[DATA_KEY])
|
|
62
61
|
vals[DATA_KEY] = merged_data
|
|
@@ -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
|
|
@@ -47,6 +47,8 @@ module Coverband
|
|
|
47
47
|
@static.call(env)
|
|
48
48
|
when %r{\/settings}
|
|
49
49
|
[200, { 'Content-Type' => 'text/html' }, [settings]]
|
|
50
|
+
when %r{\/view_tracker}
|
|
51
|
+
[200, { 'Content-Type' => 'text/html' }, [view_tracker]]
|
|
50
52
|
when %r{\/debug_data}
|
|
51
53
|
[200, { 'Content-Type' => 'text/json' }, [debug_data]]
|
|
52
54
|
when %r{\/load_file_details}
|
|
@@ -73,6 +75,10 @@ module Coverband
|
|
|
73
75
|
Coverband::Utils::HTMLFormatter.new(nil, base_path: base_path).format_settings!
|
|
74
76
|
end
|
|
75
77
|
|
|
78
|
+
def view_tracker
|
|
79
|
+
Coverband::Utils::HTMLFormatter.new(nil, base_path: base_path).format_view_tracker!
|
|
80
|
+
end
|
|
81
|
+
|
|
76
82
|
def debug_data
|
|
77
83
|
Coverband.configuration.store.get_coverage_report.to_json
|
|
78
84
|
end
|
|
@@ -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
|
|
@@ -33,6 +33,10 @@ module Coverband
|
|
|
33
33
|
format_settings
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
def format_view_tracker!
|
|
37
|
+
format_view_tracker
|
|
38
|
+
end
|
|
39
|
+
|
|
36
40
|
def format_source_file!(filename)
|
|
37
41
|
source_file = @coverage_result.file_from_path_with_type(filename)
|
|
38
42
|
|
|
@@ -49,6 +53,10 @@ module Coverband
|
|
|
49
53
|
template('settings').result(binding)
|
|
50
54
|
end
|
|
51
55
|
|
|
56
|
+
def format_view_tracker
|
|
57
|
+
template('view_tracker').result(binding)
|
|
58
|
+
end
|
|
59
|
+
|
|
52
60
|
def format(result)
|
|
53
61
|
Dir[File.join(File.dirname(__FILE__), '../../../public/*')].each do |path|
|
|
54
62
|
FileUtils.cp_r(path, asset_output_path)
|
|
@@ -104,6 +112,10 @@ module Coverband
|
|
|
104
112
|
button + '</form>'
|
|
105
113
|
end
|
|
106
114
|
|
|
115
|
+
def display_nav(nav_options = {})
|
|
116
|
+
template('nav').result(binding)
|
|
117
|
+
end
|
|
118
|
+
|
|
107
119
|
# Returns the html for the given source_file
|
|
108
120
|
def formatted_source_file(result, source_file)
|
|
109
121
|
template('source_file').result(binding)
|
|
@@ -5,9 +5,6 @@ module Coverband
|
|
|
5
5
|
def eager_load!
|
|
6
6
|
Coverband.eager_loading_coverage!
|
|
7
7
|
super
|
|
8
|
-
ensure
|
|
9
|
-
Coverband.report_coverage
|
|
10
|
-
Coverband.runtime_coverage!
|
|
11
8
|
end
|
|
12
9
|
end
|
|
13
10
|
Rails::Engine.prepend(RailsEagerLoad)
|
|
@@ -15,6 +12,21 @@ module Coverband
|
|
|
15
12
|
class Railtie < Rails::Railtie
|
|
16
13
|
initializer 'coverband.configure' do |app|
|
|
17
14
|
app.middleware.use Coverband::BackgroundMiddleware
|
|
15
|
+
|
|
16
|
+
if Coverband.configuration.track_views
|
|
17
|
+
CoverbandViewTracker = Coverband::Collectors::ViewTracker.new
|
|
18
|
+
Coverband.configuration.view_tracker = CoverbandViewTracker
|
|
19
|
+
|
|
20
|
+
ActiveSupport::Notifications.subscribe(/render_partial.action_view|render_template.action_view/) do |name, start, finish, id, payload|
|
|
21
|
+
CoverbandViewTracker.track_views(name, start, finish, id, payload) unless name.include?('!')
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
config.after_initialize do
|
|
27
|
+
Coverband.eager_loading_coverage!
|
|
28
|
+
Coverband.report_coverage
|
|
29
|
+
Coverband.runtime_coverage!
|
|
18
30
|
end
|
|
19
31
|
|
|
20
32
|
rake_tasks do
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coverband
|
|
4
|
+
module Utils
|
|
5
|
+
class RelativeFileConverter
|
|
6
|
+
def self.instance
|
|
7
|
+
@instance ||= new(Coverband.configuration.all_root_paths)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.reset
|
|
11
|
+
@instance = nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.convert(file)
|
|
15
|
+
instance.convert(file)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize(roots)
|
|
19
|
+
@cache = {}
|
|
20
|
+
@roots = normalize(roots)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def convert(file)
|
|
24
|
+
@cache[file] ||= begin
|
|
25
|
+
relative_file = file
|
|
26
|
+
@roots.each do |root|
|
|
27
|
+
relative_file = file.gsub(/^#{root}/, '.')
|
|
28
|
+
break relative_file if relative_file.start_with?('.')
|
|
29
|
+
end
|
|
30
|
+
relative_file
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def normalize(paths)
|
|
37
|
+
paths.map { |root| File.expand_path(root) }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -26,8 +26,8 @@ module Coverband
|
|
|
26
26
|
begin
|
|
27
27
|
require 'aws-sdk-s3'
|
|
28
28
|
rescue StandardError
|
|
29
|
-
err_msg = '
|
|
30
|
-
Coverband.configuration.logger
|
|
29
|
+
err_msg = 'Coverband requires aws-sdk in order use S3Report.'
|
|
30
|
+
Coverband.configuration.logger&.error err_msg
|
|
31
31
|
return
|
|
32
32
|
end
|
|
33
33
|
end
|
|
@@ -10,8 +10,6 @@
|
|
|
10
10
|
module Coverband
|
|
11
11
|
module Utils
|
|
12
12
|
class SourceFile
|
|
13
|
-
include Coverband::Utils::FilePathHelper
|
|
14
|
-
|
|
15
13
|
# TODO: Refactor Line into its own file
|
|
16
14
|
# Representation of a single line in a source file including
|
|
17
15
|
# this specific line's source code, line_number and code coverage,
|
|
@@ -246,7 +244,7 @@ module Coverband
|
|
|
246
244
|
end
|
|
247
245
|
|
|
248
246
|
def relative_path
|
|
249
|
-
|
|
247
|
+
RelativeFileConverter.convert(filename)
|
|
250
248
|
end
|
|
251
249
|
|
|
252
250
|
def gem?
|
|
@@ -15,6 +15,7 @@ namespace :coverband do
|
|
|
15
15
|
|
|
16
16
|
desc 'report runtime Coverband code coverage'
|
|
17
17
|
task :coverage_server do
|
|
18
|
+
Rake.application['environment'].invoke if Rake::Task.task_defined?('environment')
|
|
18
19
|
Rack::Server.start app: Coverband::Reporters::Web.new, Port: ENV.fetch('COVERBAND_COVERAGE_PORT', 1022).to_i
|
|
19
20
|
end
|
|
20
21
|
|
data/lib/coverband/version.rb
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
###
|
|
4
|
+
# ensure we properly do release candidate versioning; https://github.com/danmayer/coverband/issues/288
|
|
5
|
+
# use format '4.2.1.rc.1' ~> 4.2.1.rc to prerelease versions like v4.2.1.rc.2 and v4.2.1.rc.3
|
|
6
|
+
###
|
|
3
7
|
module Coverband
|
|
4
|
-
VERSION = '4.2.
|
|
8
|
+
VERSION = '4.2.2'
|
|
5
9
|
end
|
data/lib/coverband.rb
CHANGED
|
@@ -6,10 +6,13 @@ require 'redis'
|
|
|
6
6
|
require 'coverband/version'
|
|
7
7
|
require 'coverband/at_exit'
|
|
8
8
|
require 'coverband/configuration'
|
|
9
|
-
require 'coverband/utils/
|
|
9
|
+
require 'coverband/utils/relative_file_converter'
|
|
10
|
+
require 'coverband/utils/absolute_file_converter'
|
|
10
11
|
require 'coverband/adapters/base'
|
|
11
12
|
require 'coverband/adapters/redis_store'
|
|
13
|
+
require 'coverband/adapters/hash_redis_store'
|
|
12
14
|
require 'coverband/adapters/file_store'
|
|
15
|
+
require 'coverband/utils/file_hasher'
|
|
13
16
|
require 'coverband/utils/s3_report'
|
|
14
17
|
require 'coverband/utils/html_formatter'
|
|
15
18
|
require 'coverband/utils/result'
|
|
@@ -20,6 +23,7 @@ require 'coverband/utils/file_groups'
|
|
|
20
23
|
require 'coverband/utils/lines_classifier'
|
|
21
24
|
require 'coverband/utils/results'
|
|
22
25
|
require 'coverband/collectors/coverage'
|
|
26
|
+
require 'coverband/collectors/view_tracker'
|
|
23
27
|
require 'coverband/reporters/base'
|
|
24
28
|
require 'coverband/reporters/html_report'
|
|
25
29
|
require 'coverband/reporters/console_report'
|
|
@@ -28,6 +32,8 @@ require 'coverband/integrations/background'
|
|
|
28
32
|
require 'coverband/integrations/background_middleware'
|
|
29
33
|
require 'coverband/integrations/rack_server_check'
|
|
30
34
|
|
|
35
|
+
Coverband::Adapters::RedisStore = Coverband::Adapters::HashRedisStore if ENV['COVERBAND_HASH_REDIS_STORE']
|
|
36
|
+
|
|
31
37
|
module Coverband
|
|
32
38
|
@@configured = false
|
|
33
39
|
CONFIG_FILE = './config/coverband.rb'
|
data/public/application.css
CHANGED
|
@@ -634,15 +634,15 @@ abbr.timeago {
|
|
|
634
634
|
float: right;
|
|
635
635
|
color: #dddddd; }
|
|
636
636
|
|
|
637
|
-
.group_tabs {
|
|
637
|
+
.group_tabs, .extra_tabs {
|
|
638
638
|
list-style: none;
|
|
639
639
|
float: left;
|
|
640
640
|
margin: 0;
|
|
641
641
|
padding: 0; }
|
|
642
|
-
.group_tabs li {
|
|
642
|
+
.group_tabs li, .extra_tabs li {
|
|
643
643
|
display: inline;
|
|
644
644
|
float: left; }
|
|
645
|
-
.group_tabs li a {
|
|
645
|
+
.group_tabs li a, .extra_tabs li a {
|
|
646
646
|
font-family: Helvetica, Arial, sans-serif;
|
|
647
647
|
display: block;
|
|
648
648
|
float: left;
|
|
@@ -664,15 +664,15 @@ abbr.timeago {
|
|
|
664
664
|
-moz-border-radius-topright: 2px;
|
|
665
665
|
border-top-left-radius: 2px;
|
|
666
666
|
border-top-right-radius: 2px; }
|
|
667
|
-
.group_tabs li a:hover {
|
|
667
|
+
.group_tabs li a:hover, .extra_tabs li a:hover {
|
|
668
668
|
background-color: #cccccc;
|
|
669
669
|
background: -webkit-gradient(linear, 0 0, 0 bottom, from(#eeeeee), to(#aaaaaa));
|
|
670
670
|
background: -moz-linear-gradient(#eeeeee, #aaaaaa);
|
|
671
671
|
background: linear-gradient(#eeeeee, #aaaaaa); }
|
|
672
|
-
.group_tabs li a:active {
|
|
672
|
+
.group_tabs li a:active, .extra_tabs li a:active {
|
|
673
673
|
padding-top: 5px;
|
|
674
674
|
padding-bottom: 3px; }
|
|
675
|
-
.group_tabs li.active a {
|
|
675
|
+
.group_tabs li.active a, .extra_tabs li.active a {
|
|
676
676
|
color: black;
|
|
677
677
|
text-shadow: white 1px 1px 0px;
|
|
678
678
|
background-color: #dddddd;
|
|
@@ -27,30 +27,11 @@ namespace :benchmarks do
|
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
def classifier_dir
|
|
31
|
-
File.join(File.dirname(__FILE__), 'classifier-reborn')
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def clone_classifier
|
|
35
|
-
system "git clone https://github.com/jekyll/classifier-reborn.git #{classifier_dir}" unless Dir.exist? classifier_dir
|
|
36
|
-
end
|
|
37
|
-
|
|
38
30
|
# desc 'setup standard benchmark'
|
|
39
31
|
task :setup do
|
|
40
|
-
clone_classifier
|
|
41
|
-
$LOAD_PATH.unshift(File.join(classifier_dir, 'lib'))
|
|
42
32
|
require 'benchmark'
|
|
43
33
|
require 'benchmark/ips'
|
|
44
34
|
|
|
45
|
-
# TODO: ok this is interesting and weird
|
|
46
|
-
# basically the earlier I require coverage and
|
|
47
|
-
# then require files the larger perf impact
|
|
48
|
-
# this is somewhat expected but can lead to significant perf diffs
|
|
49
|
-
# for example moving `require 'classifier-reborn'` below the coverage.start
|
|
50
|
-
# results in 1.5x slower vs "difference falls within error"
|
|
51
|
-
# moving from 5 second of time to 12 still shows slower based on when classifier is required
|
|
52
|
-
# make sure to be plugged in while benchmarking ;) Otherwise you get very unreliable results
|
|
53
|
-
require 'classifier-reborn'
|
|
54
35
|
if ENV['COVERAGE'] || ENV['ONESHOT']
|
|
55
36
|
require 'coverage'
|
|
56
37
|
::Coverage.start(oneshot_lines: !!ENV['ONESHOT'])
|
|
@@ -91,32 +72,7 @@ namespace :benchmarks do
|
|
|
91
72
|
end
|
|
92
73
|
end
|
|
93
74
|
|
|
94
|
-
def bayes_classification
|
|
95
|
-
b = ClassifierReborn::Bayes.new 'Interesting', 'Uninteresting'
|
|
96
|
-
b.train_interesting 'here are some good words. I hope you love them'
|
|
97
|
-
b.train_uninteresting 'here are some bad words, I hate you'
|
|
98
|
-
b.classify 'I hate bad words and you' # returns 'Uninteresting'
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def lsi_classification
|
|
102
|
-
lsi = ClassifierReborn::LSI.new
|
|
103
|
-
strings = [['This text deals with dogs. Dogs.', :dog],
|
|
104
|
-
['This text involves dogs too. Dogs! ', :dog],
|
|
105
|
-
['This text revolves around cats. Cats.', :cat],
|
|
106
|
-
['This text also involves cats. Cats!', :cat],
|
|
107
|
-
['This text involves birds. Birds.', :bird]]
|
|
108
|
-
strings.each { |x| lsi.add_item x.first, x.last }
|
|
109
|
-
lsi.search('dog', 3)
|
|
110
|
-
lsi.find_related(strings[2], 2)
|
|
111
|
-
lsi.classify 'This text is also about dogs!'
|
|
112
|
-
end
|
|
113
|
-
|
|
114
75
|
def work
|
|
115
|
-
5.times do
|
|
116
|
-
bayes_classification
|
|
117
|
-
lsi_classification
|
|
118
|
-
end
|
|
119
|
-
|
|
120
76
|
# simulate many calls to the same line
|
|
121
77
|
10_000.times { Dog.new.bark }
|
|
122
78
|
end
|
|
@@ -140,9 +96,11 @@ namespace :benchmarks do
|
|
|
140
96
|
Coverband::Collectors::Coverage.instance.reset_instance
|
|
141
97
|
end
|
|
142
98
|
|
|
99
|
+
LINES = 45
|
|
100
|
+
NON_NIL_LINES = 18
|
|
143
101
|
def fake_line_numbers
|
|
144
|
-
|
|
145
|
-
|
|
102
|
+
LINES.times.map do |line|
|
|
103
|
+
coverage = (line < NON_NIL_LINES) ? rand(5) : nil
|
|
146
104
|
end
|
|
147
105
|
end
|
|
148
106
|
|
|
@@ -165,8 +123,8 @@ namespace :benchmarks do
|
|
|
165
123
|
###
|
|
166
124
|
# this is a hack because in the benchmark we don't have real files
|
|
167
125
|
###
|
|
168
|
-
def store.file_hash(
|
|
169
|
-
@
|
|
126
|
+
def store.file_hash(_file)
|
|
127
|
+
@file_hash ||= Digest::MD5.file(__FILE__).hexdigest
|
|
170
128
|
end
|
|
171
129
|
|
|
172
130
|
def store.full_path_to_relative(file)
|
|
@@ -183,7 +141,13 @@ namespace :benchmarks do
|
|
|
183
141
|
5.times { store.save_report(report) }
|
|
184
142
|
Benchmark.ips do |x|
|
|
185
143
|
x.config(time: 15, warmup: 5)
|
|
186
|
-
x.report('
|
|
144
|
+
x.report('store_reports_all') { store.save_report(report) }
|
|
145
|
+
end
|
|
146
|
+
keys_subset = report.keys.first(100)
|
|
147
|
+
report_subset = report.select { |key, _value| keys_subset.include?(key) }
|
|
148
|
+
Benchmark.ips do |x|
|
|
149
|
+
x.config(time: 20, warmup: 5)
|
|
150
|
+
x.report('store_reports_subset') { store.save_report(report_subset) }
|
|
187
151
|
end
|
|
188
152
|
end
|
|
189
153
|
|
|
@@ -415,6 +379,18 @@ namespace :benchmarks do
|
|
|
415
379
|
`open tmp/timeseries.jpg`
|
|
416
380
|
end
|
|
417
381
|
|
|
382
|
+
desc 'benchmark initialization of rails'
|
|
383
|
+
task :init_rails do
|
|
384
|
+
require 'benchmark'
|
|
385
|
+
require 'benchmark/ips'
|
|
386
|
+
Benchmark.ips do |x|
|
|
387
|
+
x.config(time: 60, warmup: 0)
|
|
388
|
+
x.report('init_rails') do
|
|
389
|
+
system('bundle exec rake init_rails -f ./test/benchmarks/init_rails.rake')
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
|
|
418
394
|
desc 'compare Coverband Ruby Coverage with Filestore with normal Ruby'
|
|
419
395
|
task :compare_file do
|
|
420
396
|
puts 'comparing Coverage loaded/not, this takes some time for output...'
|