coverband 4.2.1 → 4.2.2.rc.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -9
- data/CODE_OF_CONDUCT.md +76 -0
- data/README.md +7 -22
- data/changes.md +13 -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 +174 -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 +24 -33
- data/lib/coverband/utils/file_path_helper.rb +0 -68
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca39325a3a42ab0820e60c4162eb0d9aa22d9f2cd0a085cd315aff45445a06a7
|
4
|
+
data.tar.gz: 2695638a09c7e6c673cfaba58cd94f59e8997cdf92ca8067bb565f25d8fa907d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 830ddef421419a22aa741d53f78b169e9d1d52fe6b30c72757993e09008bc0a07dd733d67e0381b2eea951410e7217fdb6046e6c19901e44fab211334f84859c
|
7
|
+
data.tar.gz: 1df3167a89d98df21658b21b06a7d7c3472b9ccd2eed53b95dc3edf243ee2b066cb780fd9885f823998a09e2deb501a502a3fb73284306318be0539614f63996
|
data/.travis.yml
CHANGED
@@ -11,18 +11,11 @@ services:
|
|
11
11
|
- redis-server
|
12
12
|
script:
|
13
13
|
- bundle exec rake rubocop
|
14
|
+
- COVERBAND_HASH_REDIS_STORE=t bundle exec rake
|
15
|
+
- COVERBAND_HASH_REDIS_STORE=t bundle exec rake forked_tests
|
14
16
|
- bundle exec rake
|
15
17
|
- bundle exec rake forked_tests
|
16
18
|
# remove this for now as it is flaky
|
17
19
|
# passes locally on 2.3.5 on travis passes sometimes on 2.4 and always on 2.6.1
|
18
20
|
#- bundle exec rake benchmarks:memory
|
19
21
|
- bundle exec rake benchmarks
|
20
|
-
before_install:
|
21
|
-
- echo 'this is a hack to clear default bundler and force bundler 1.17.3'
|
22
|
-
- ls /home/travis/.rvm/gems/
|
23
|
-
- rm /home/travis/.rvm/gems/ruby-2.3.7@global/specifications/bundler-2.0.1.gemspec || true
|
24
|
-
- rm /home/travis/.rvm/gems/ruby-2.4.6@global/specifications/bundler-2.0.1.gemspec || true
|
25
|
-
- rm /home/travis/.rvm/gems/ruby-2.5.5@global/specifications/bundler-2.0.1.gemspec || true
|
26
|
-
- gem uninstall bundler || true
|
27
|
-
- gem install bundler -v '1.17.3'
|
28
|
-
- bundler --version
|
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
9
|
+
level of experience, education, socio-economic status, nationality, personal
|
10
|
+
appearance, race, religion, or sexual identity and orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at danmayer@gmail.com. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
72
|
+
|
73
|
+
[homepage]: https://www.contributor-covenant.org
|
74
|
+
|
75
|
+
For answers to common questions about this code of conduct, see
|
76
|
+
https://www.contributor-covenant.org/faq
|
data/README.md
CHANGED
@@ -12,7 +12,8 @@
|
|
12
12
|
<a href="#coverage-report">Coverage Report</a> •
|
13
13
|
<a href="#advanced-config">Advanced Config</a> •
|
14
14
|
<a href="#license">License</a> •
|
15
|
-
<a href="/changes.md">Change Log / Roadmap</a>
|
15
|
+
<a href="/changes.md">Change Log / Roadmap</a> •
|
16
|
+
<a href="/CODE_OF_CONDUCT.md">Code of Conduct</a>
|
16
17
|
</p>
|
17
18
|
|
18
19
|
A gem to measure production code usage, showing a counter for the number of times each line of code that is executed. Coverband allows easy configuration to collect and report on production code usage. It reports in the background via a thread or can be used as Rack middleware, or manually configured to meet any need.
|
@@ -161,10 +162,10 @@ Coverband.configure do |config|
|
|
161
162
|
config.s3_access_key_id = ENV['AWS_ACCESS_KEY_ID']
|
162
163
|
config.s3_secret_access_key = ENV['AWS_SECRET_ACCESS_KEY']
|
163
164
|
|
164
|
-
# config options false, true
|
165
|
+
# config options false, true. (defaults to false)
|
165
166
|
# true and debug can give helpful and interesting code usage information
|
166
|
-
#
|
167
|
-
#
|
167
|
+
# and is safe to use if one is investigating issues in production, but it will slightly
|
168
|
+
# hit perf.
|
168
169
|
config.verbose = false
|
169
170
|
end
|
170
171
|
```
|
@@ -307,27 +308,11 @@ Coverband.start
|
|
307
308
|
|
308
309
|
### Verbose Debug / Development Mode
|
309
310
|
|
310
|
-
Note: To debug issues getting Coverband working. I recommend running in development mode, by turning verbose logging on `config.verbose = true` and passing in the Rails.logger `config.logger = Rails.logger` to the Coverband config. This makes it easy to follow in development mode. Be careful to not leave these on in production as they will affect performance.
|
311
|
+
Note: To debug issues getting Coverband working. I recommend running in development mode, by turning verbose logging on `config.verbose = true` and passing in the Rails.logger `config.logger = Rails.logger` to the Coverband config. We respect the log level, and I would recommend log level info generally, but if you are investigating a prolbem Coverband logs additional data at the `debug` level. This makes it easy to follow in development mode. Be careful to not leave these on in production as they will affect performance.
|
311
312
|
|
312
313
|
---
|
313
314
|
|
314
|
-
If you are trying to debug locally wondering what code is being run during a request. The verbose modes `config.verbose = true`
|
315
|
-
|
316
|
-
config.verbose = 'debug'
|
317
|
-
|
318
|
-
coverband file usage:
|
319
|
-
[["/Users/danmayer/projects/app_name/lib/facebook.rb", 6],
|
320
|
-
["/Users/danmayer/projects/app_name/app/models/some_modules.rb", 9],
|
321
|
-
...
|
322
|
-
["/Users/danmayer/projects/app_name/app/models/user.rb", 2606],
|
323
|
-
["/Users/danmayer/projects/app_name/app/helpers/application_helper.rb",
|
324
|
-
43150]]
|
325
|
-
|
326
|
-
file:
|
327
|
-
/Users/danmayer/projects/app_name/app/helpers/application_helper.rb =>
|
328
|
-
[[448, 1], [202, 1],
|
329
|
-
...
|
330
|
-
[517, 1617], [516, 38577]]
|
315
|
+
If you are trying to debug locally wondering what code is being run during a request. The verbose modes `config.verbose = true` && `Rails.logger.level = :debug`. With true set it will output the number of lines executed per file, to the passed in log.
|
331
316
|
|
332
317
|
# Prerequisites
|
333
318
|
|
data/changes.md
CHANGED
@@ -49,7 +49,7 @@ Will be the fully modern release that drops maintenance legacy support in favor
|
|
49
49
|
- move all code to work with relative paths leaving only stdlib Coverage working on full paths
|
50
50
|
- add gem_safe_lists to track only some gems
|
51
51
|
- add gem_details_safe list to report on details on some gems
|
52
|
-
- - display gems that are in loaded with 0 coverage, thanks @kbaum
|
52
|
+
- - display gems that are in loaded with 0 coverage, thanks @kbaum
|
53
53
|
|
54
54
|
### Coverband_jam_session
|
55
55
|
|
@@ -76,6 +76,12 @@ Feature Ideas:
|
|
76
76
|
|
77
77
|
# Alpha / Beta / Release Candidates
|
78
78
|
|
79
|
+
### Coverband 4.2.2
|
80
|
+
|
81
|
+
- ?
|
82
|
+
|
83
|
+
# Released
|
84
|
+
|
79
85
|
### Coverband 4.2.1
|
80
86
|
|
81
87
|
- larger changes
|
@@ -83,14 +89,13 @@ Feature Ideas:
|
|
83
89
|
- fix issue where reports didn't include files with 0 activity
|
84
90
|
- updated runtime relavent lines and runtime percentages
|
85
91
|
- add Oneshot coverage support for Ruby 2.6.0 thanks @zwalker
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
92
|
+
- I would consider this our test oneshot release, please report any issues
|
93
|
+
- improved control over memory vs functionality
|
94
|
+
- oneshot support is the best memory and speed if you are on Ruby 2.6.\*
|
95
|
+
- simulated_oneshot works for Ruby prior to 2.6.\*, keeps lower runtime memory, at a trade of only being able to detect eager_load or runtime hits not both
|
96
|
+
- full hit tracking (The previous and current default), this uses more memory than other options, but provides full robust data
|
91
97
|
|
92
|
-
|
93
|
-
- small changes fixes
|
98
|
+
* small changes fixes
|
94
99
|
- further improvements on eager_loading detection, thanks @kbaum
|
95
100
|
- fix on ignore configuration options
|
96
101
|
- fix broken static server
|
@@ -112,9 +117,6 @@ Feature Ideas:
|
|
112
117
|
- additional benchmarks to ensure proper memory handling, thanks @kbaum
|
113
118
|
- default redis TTL, thanks @jjb
|
114
119
|
|
115
|
-
|
116
|
-
# Released
|
117
|
-
|
118
120
|
### Coverband 4.2.0
|
119
121
|
|
120
122
|
**NOTE:** This release introduces load time and runtime Coverage. To get the full benifit of this feature you need to reset you coverage data (`rake coverband:clear` or clear via the web UI). Until you clear the combined result is still correct, but your runtime data will be mixed with previous data. This is due to an additive change in how we store coverage. We didn't reset by default as the coverage history for some folks may be more important than runtime breakdown.
|
data/coverband.gemspec
CHANGED
@@ -35,12 +35,7 @@ Gem::Specification.new do |spec|
|
|
35
35
|
spec.add_development_dependency 'rake'
|
36
36
|
spec.add_development_dependency 'resque'
|
37
37
|
spec.add_development_dependency 'rubocop'
|
38
|
-
# temporarily needed to run tests for classifier-reborn as part of benchmarks
|
39
|
-
spec.add_development_dependency 'minitest-reporters'
|
40
38
|
|
41
|
-
# TODO: used for benchmarking and tests I think we have other better benchmarking
|
42
|
-
# perhaps time to drop this and refactor.
|
43
|
-
spec.add_development_dependency 'classifier-reborn'
|
44
39
|
spec.add_development_dependency 'coveralls'
|
45
40
|
# add when debugging
|
46
41
|
# require 'byebug'; byebug
|
@@ -3,8 +3,6 @@
|
|
3
3
|
module Coverband
|
4
4
|
module Adapters
|
5
5
|
class Base
|
6
|
-
include Coverband::Utils::FilePathHelper
|
7
|
-
|
8
6
|
DATA_KEY = 'data'
|
9
7
|
FIRST_UPDATED_KEY = 'first_updated_at'
|
10
8
|
LAST_UPDATED_KEY = 'last_updated_at'
|
@@ -14,7 +12,6 @@ module Coverband
|
|
14
12
|
attr_accessor :type
|
15
13
|
|
16
14
|
def initialize
|
17
|
-
@file_hash_cache = {}
|
18
15
|
@type = Coverband::RUNTIME_TYPE
|
19
16
|
end
|
20
17
|
|
@@ -46,14 +43,8 @@ module Coverband
|
|
46
43
|
format('%.2f', (size.to_f / 2**20))
|
47
44
|
end
|
48
45
|
|
49
|
-
|
50
|
-
|
51
|
-
# the Coverband 2 had the same issue,
|
52
|
-
# and the tradeoff has always been acceptable
|
53
|
-
def save_report(report)
|
54
|
-
data = report.dup
|
55
|
-
data = merge_reports(data, coverage)
|
56
|
-
save_coverage(data)
|
46
|
+
def save_report(_report)
|
47
|
+
raise 'abstract'
|
57
48
|
end
|
58
49
|
|
59
50
|
def get_coverage_report
|
@@ -65,6 +56,10 @@ module Coverband
|
|
65
56
|
coverage.keys || []
|
66
57
|
end
|
67
58
|
|
59
|
+
def raw_store
|
60
|
+
raise ABSTRACT_KEY
|
61
|
+
end
|
62
|
+
|
68
63
|
protected
|
69
64
|
|
70
65
|
def split_coverage(types)
|
@@ -93,7 +88,7 @@ module Coverband
|
|
93
88
|
end
|
94
89
|
|
95
90
|
def file_hash(file)
|
96
|
-
|
91
|
+
Coverband::Utils::FileHasher.hash(file)
|
97
92
|
end
|
98
93
|
|
99
94
|
# TODO: modify to extend report inline?
|
@@ -108,7 +103,7 @@ module Coverband
|
|
108
103
|
FILE_HASH => file_hash(key),
|
109
104
|
DATA_KEY => line_data
|
110
105
|
}
|
111
|
-
expanded[
|
106
|
+
expanded[Utils::RelativeFileConverter.convert(key)] = extended_data
|
112
107
|
end
|
113
108
|
expanded
|
114
109
|
end
|
@@ -36,13 +36,19 @@ module Coverband
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
+
def save_report(report)
|
40
|
+
data = report.dup
|
41
|
+
data = merge_reports(data, coverage)
|
42
|
+
File.open(path, 'w') { |f| f.write(data.to_json) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def raw_store
|
46
|
+
raise NotImplementedError, "FileStore doesn't support raw_store"
|
47
|
+
end
|
48
|
+
|
39
49
|
private
|
40
50
|
|
41
51
|
attr_accessor :path
|
42
|
-
|
43
|
-
def save_coverage(report)
|
44
|
-
File.open(path, 'w') { |f| f.write(report.to_json) }
|
45
|
-
end
|
46
52
|
end
|
47
53
|
end
|
48
54
|
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coverband
|
4
|
+
module Adapters
|
5
|
+
class HashRedisStore < Base
|
6
|
+
FILE_KEY = 'file'
|
7
|
+
FILE_LENGTH_KEY = 'file_length'
|
8
|
+
META_DATA_KEYS = [DATA_KEY, FIRST_UPDATED_KEY, LAST_UPDATED_KEY].freeze
|
9
|
+
###
|
10
|
+
# This key isn't related to the coverband version, but to the interal format
|
11
|
+
# used to store data to redis. It is changed only when breaking changes to our
|
12
|
+
# redis format are required.
|
13
|
+
###
|
14
|
+
REDIS_STORAGE_FORMAT_VERSION = 'coverband_hash_3_3'
|
15
|
+
|
16
|
+
attr_reader :redis_namespace
|
17
|
+
|
18
|
+
def initialize(redis, opts = {})
|
19
|
+
super()
|
20
|
+
@redis_namespace = opts[:redis_namespace]
|
21
|
+
@format_version = REDIS_STORAGE_FORMAT_VERSION
|
22
|
+
@redis = redis
|
23
|
+
raise 'HashRedisStore requires redis >= 2.6.0' unless supported?
|
24
|
+
|
25
|
+
@ttl = opts[:ttl] || -1
|
26
|
+
@relative_file_converter = opts[:relative_file_converter] || Utils::RelativeFileConverter
|
27
|
+
end
|
28
|
+
|
29
|
+
def supported?
|
30
|
+
Gem::Version.new(@redis.info['redis_version']) >= Gem::Version.new('2.6.0')
|
31
|
+
end
|
32
|
+
|
33
|
+
def clear!
|
34
|
+
old_type = type
|
35
|
+
Coverband::TYPES.each do |type|
|
36
|
+
self.type = type
|
37
|
+
file_keys = files_set
|
38
|
+
@redis.del(*file_keys) if file_keys.any?
|
39
|
+
@redis.del(files_key)
|
40
|
+
end
|
41
|
+
self.type = old_type
|
42
|
+
end
|
43
|
+
|
44
|
+
def clear_file!(file)
|
45
|
+
file_hash = file_hash(file)
|
46
|
+
relative_path_file = @relative_file_converter.convert(file)
|
47
|
+
Coverband::TYPES.each do |type|
|
48
|
+
@redis.del(key(relative_path_file, type, file_hash: file_hash))
|
49
|
+
end
|
50
|
+
@redis.srem(files_key, relative_path_file)
|
51
|
+
end
|
52
|
+
|
53
|
+
def save_report(report)
|
54
|
+
report_time = Time.now.to_i
|
55
|
+
updated_time = type == Coverband::EAGER_TYPE ? nil : report_time
|
56
|
+
script_id = hash_incr_script
|
57
|
+
@redis.pipelined do
|
58
|
+
keys = report.map do |file, data|
|
59
|
+
relative_file = @relative_file_converter.convert(file)
|
60
|
+
file_hash = file_hash(relative_file)
|
61
|
+
key = key(relative_file, file_hash: file_hash)
|
62
|
+
script_input = save_report_script_input(
|
63
|
+
key: key,
|
64
|
+
file: relative_file,
|
65
|
+
file_hash: file_hash,
|
66
|
+
data: data,
|
67
|
+
report_time: report_time,
|
68
|
+
updated_time: updated_time
|
69
|
+
)
|
70
|
+
@redis.evalsha(script_id, script_input[:keys], script_input[:args])
|
71
|
+
key
|
72
|
+
end
|
73
|
+
@redis.sadd(files_key, keys) if keys.any?
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def coverage(local_type = nil)
|
78
|
+
files_set(local_type).each_with_object({}) do |key, hash|
|
79
|
+
add_coverage_for_file(key, hash)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def raw_store
|
84
|
+
@redis
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def add_coverage_for_file(key, hash)
|
90
|
+
data_from_redis = @redis.hgetall(key)
|
91
|
+
|
92
|
+
return if data_from_redis.empty?
|
93
|
+
|
94
|
+
file = data_from_redis[FILE_KEY]
|
95
|
+
return unless file_hash(file) == data_from_redis[FILE_HASH]
|
96
|
+
|
97
|
+
data = coverage_data_from_redis(data_from_redis)
|
98
|
+
hash[file] = data_from_redis.select { |meta_data_key, _value| META_DATA_KEYS.include?(meta_data_key) }.merge!('data' => data)
|
99
|
+
hash[file].merge!(LAST_UPDATED_KEY => hash[file][LAST_UPDATED_KEY].to_i, FIRST_UPDATED_KEY => hash[file][FIRST_UPDATED_KEY].to_i)
|
100
|
+
end
|
101
|
+
|
102
|
+
def coverage_data_from_redis(data_from_redis)
|
103
|
+
max = data_from_redis[FILE_LENGTH_KEY].to_i - 1
|
104
|
+
Array.new(max + 1) do |index|
|
105
|
+
line_coverage = data_from_redis[index.to_s]
|
106
|
+
line_coverage.nil? ? nil : line_coverage.to_i
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def save_report_script_input(key:, file:, file_hash:, data:, report_time:, updated_time:)
|
111
|
+
data.each_with_index
|
112
|
+
.each_with_object(keys: [key], args: [report_time, updated_time, file, file_hash, @ttl, data.length]) do |(coverage, index), hash|
|
113
|
+
if coverage
|
114
|
+
hash[:keys] << index
|
115
|
+
hash[:args] << coverage
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def hash_incr_script
|
121
|
+
@hash_incr_script ||= @redis.script(:load, <<~LUA)
|
122
|
+
local first_updated_at = table.remove(ARGV, 1)
|
123
|
+
local last_updated_at = table.remove(ARGV, 1)
|
124
|
+
local file = table.remove(ARGV, 1)
|
125
|
+
local file_hash = table.remove(ARGV, 1)
|
126
|
+
local ttl = table.remove(ARGV, 1)
|
127
|
+
local file_length = table.remove(ARGV, 1)
|
128
|
+
local hash_key = table.remove(KEYS, 1)
|
129
|
+
redis.call('HMSET', hash_key, '#{LAST_UPDATED_KEY}', last_updated_at, '#{FILE_KEY}', file, '#{FILE_HASH}', file_hash, '#{FILE_LENGTH_KEY}', file_length)
|
130
|
+
redis.call('HSETNX', hash_key, '#{FIRST_UPDATED_KEY}', first_updated_at)
|
131
|
+
for i, key in ipairs(KEYS) do
|
132
|
+
if ARGV[i] == '-1' then
|
133
|
+
redis.call("HSET", hash_key, key, ARGV[i])
|
134
|
+
else
|
135
|
+
redis.call("HINCRBY", hash_key, key, ARGV[i])
|
136
|
+
end
|
137
|
+
end
|
138
|
+
if ttl ~= '-1' then
|
139
|
+
redis.call("EXPIRE", hash_key, ttl)
|
140
|
+
end
|
141
|
+
LUA
|
142
|
+
end
|
143
|
+
|
144
|
+
def values_from_redis(local_type, files)
|
145
|
+
return files if files.empty?
|
146
|
+
|
147
|
+
@redis.mget(*files.map { |file| key(file, local_type) }).map do |value|
|
148
|
+
value.nil? ? {} : JSON.parse(value)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def relative_paths(files)
|
153
|
+
files&.map! { |file| full_path_to_relative(file) }
|
154
|
+
end
|
155
|
+
|
156
|
+
def files_set(local_type = nil)
|
157
|
+
@redis.smembers(files_key(local_type))
|
158
|
+
end
|
159
|
+
|
160
|
+
def files_key(local_type = nil)
|
161
|
+
"#{key_prefix(local_type)}.files"
|
162
|
+
end
|
163
|
+
|
164
|
+
def key(file, local_type = nil, file_hash:)
|
165
|
+
[key_prefix(local_type), file, file_hash].join('.')
|
166
|
+
end
|
167
|
+
|
168
|
+
def key_prefix(local_type = nil)
|
169
|
+
local_type ||= type
|
170
|
+
[@format_version, @redis_namespace, local_type].compact.join('.')
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -61,7 +61,7 @@ module Coverband
|
|
61
61
|
exit 0
|
62
62
|
end
|
63
63
|
relative_path_report = previous_data.each_with_object({}) do |(key, vals), fixed_report|
|
64
|
-
fixed_report[
|
64
|
+
fixed_report[Utils::RelativeFileConverter.convert(key)] = vals
|
65
65
|
end
|
66
66
|
clear!
|
67
67
|
reset_base_key
|
@@ -80,6 +80,20 @@ module Coverband
|
|
80
80
|
data ? JSON.parse(data) : {}
|
81
81
|
end
|
82
82
|
|
83
|
+
# Note: This could lead to slight race on redis
|
84
|
+
# where multiple processes pull the old coverage and add to it then push
|
85
|
+
# the Coverband 2 had the same issue,
|
86
|
+
# and the tradeoff has always been acceptable
|
87
|
+
def save_report(report)
|
88
|
+
data = report.dup
|
89
|
+
data = merge_reports(data, coverage)
|
90
|
+
save_coverage(data)
|
91
|
+
end
|
92
|
+
|
93
|
+
def raw_store
|
94
|
+
@redis
|
95
|
+
end
|
96
|
+
|
83
97
|
private
|
84
98
|
|
85
99
|
attr_reader :redis
|
@@ -22,8 +22,8 @@ module Coverband
|
|
22
22
|
@project_directory = File.expand_path(Coverband.configuration.root)
|
23
23
|
@ignore_patterns = Coverband.configuration.ignore
|
24
24
|
@store = Coverband.configuration.store
|
25
|
-
@verbose
|
26
|
-
@logger
|
25
|
+
@verbose = Coverband.configuration.verbose
|
26
|
+
@logger = Coverband.configuration.logger
|
27
27
|
@test_env = Coverband.configuration.test_env
|
28
28
|
@track_gems = Coverband.configuration.track_gems
|
29
29
|
Delta.reset
|
@@ -55,11 +55,9 @@ module Coverband
|
|
55
55
|
@store.save_report(files_with_line_usage)
|
56
56
|
end
|
57
57
|
rescue StandardError => e
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
@logger.error e.backtrace
|
62
|
-
end
|
58
|
+
@logger&.error 'coverage failed to store'
|
59
|
+
@logger&.error "Coverband Error: #{e.inspect} #{e.message}"
|
60
|
+
e.backtrace.each { |line| @logger&.error line } if @verbose
|
63
61
|
raise e if @test_env
|
64
62
|
end
|
65
63
|
|
@@ -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
|