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
data/changes.md
CHANGED
|
@@ -16,6 +16,8 @@ Will be the fully modern release that drops maintenance legacy support in favor
|
|
|
16
16
|
- add built-in support for easy loading via Railties
|
|
17
17
|
- built in support for activejob, sidekiq, and other common frameworks
|
|
18
18
|
- reduced configuration options
|
|
19
|
+
- support oneshot
|
|
20
|
+
- drop middleware figure out a way to kick off background without middelware
|
|
19
21
|
- options on reporting
|
|
20
22
|
- background reporting
|
|
21
23
|
- or middleware reporting
|
|
@@ -47,6 +49,7 @@ Will be the fully modern release that drops maintenance legacy support in favor
|
|
|
47
49
|
- move all code to work with relative paths leaving only stdlib Coverage working on full paths
|
|
48
50
|
- add gem_safe_lists to track only some gems
|
|
49
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
|
|
50
53
|
|
|
51
54
|
### Coverband_jam_session
|
|
52
55
|
|
|
@@ -54,32 +57,104 @@ This is a possible gem to host experimental or more complex features, which woul
|
|
|
54
57
|
|
|
55
58
|
Feature Ideas:
|
|
56
59
|
|
|
60
|
+
- per request coverage implemented via Ruby 2.6.0 coverage.clear https://bugs.ruby-lang.org/issues/15022
|
|
57
61
|
- statsd adapters (it would allow passing in date ranges on usage)
|
|
58
|
-
- move to SimpleCov console out, or make similar console tabular output
|
|
59
62
|
- Possibly add ability to record code run for a given route
|
|
60
63
|
- integrate recording with deploy tag or deploy timestamp
|
|
61
64
|
- diff code usage across deployed versions
|
|
62
65
|
- what methods increased usage or decreased
|
|
63
|
-
- Improve client code api, around manual usage of sampling (like event usage)
|
|
64
66
|
- ability to change the Coverband config at runtime by changing the config pushed to the Redis hash. In memory cache around the changes to only make that call periodically.
|
|
65
67
|
- Opposed to just showing code usage on a route allow 'tagging' events which would record line coverage for that tag (this would allow tagging all code that modified an ActiveRecord model for example
|
|
66
68
|
- additional adapters (tracepoint, ruby-profiler, etc)
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
69
|
+
- tagged coverage reports
|
|
70
|
+
- code route tracing (entry point to all code executed for example /some_path -> code coverage of that path)
|
|
71
|
+
- deploy git hash tagging of reported Coverage
|
|
72
|
+
- allow only to collect coverage based on route (limiting or scoped coverage)
|
|
73
|
+
- coverage over some other variable like a set of alpha users
|
|
71
74
|
- document how to use this on staging / selenium test suite runs
|
|
72
75
|
- possible add API to pull report at end of run
|
|
73
76
|
|
|
74
|
-
# Alpha
|
|
77
|
+
# Alpha / Beta / Release Candidates
|
|
75
78
|
|
|
76
|
-
### Coverband 4.
|
|
79
|
+
### Coverband 4.2.5
|
|
77
80
|
|
|
78
|
-
- further improvements on eager_loading detection
|
|
79
81
|
- ?
|
|
80
82
|
|
|
81
83
|
# Released
|
|
82
84
|
|
|
85
|
+
### Coverband 4.2.4
|
|
86
|
+
|
|
87
|
+
- fixes related to startup without Redis, skipping Coverband on common rake tasks (assets:precompile), etc
|
|
88
|
+
- avoid calls to redis if coverage not updated
|
|
89
|
+
- split out lua work and added testing around lua scripts use for the hash redis store
|
|
90
|
+
- improvements to hash redis store resulting in 25% faster perf
|
|
91
|
+
- avoid duplicate hash calculations
|
|
92
|
+
- debug data now includes md5_hashes
|
|
93
|
+
- added support to download coverage and view data in JSON format
|
|
94
|
+
- documentation about working with environment variables
|
|
95
|
+
- add cache wiggle to avoid Redis CPU spikes (cache stampede on Redis server)
|
|
96
|
+
- make the nocov consistant on the data download and html view
|
|
97
|
+
- small performance improvements
|
|
98
|
+
|
|
99
|
+
### Coverband 4.2.3
|
|
100
|
+
|
|
101
|
+
- readme fixes
|
|
102
|
+
- improved defaults for the next redis hash adapter
|
|
103
|
+
- add tooltips
|
|
104
|
+
- improved messaging around non-loaded files
|
|
105
|
+
- fix on last updated nil issue
|
|
106
|
+
- view tracker improvements
|
|
107
|
+
- clear all
|
|
108
|
+
- reset individual file
|
|
109
|
+
- timestamps on last seen activity
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
### Coverband 4.2.2
|
|
113
|
+
|
|
114
|
+
- new experimental hash redis store for high volume collection
|
|
115
|
+
(hundreds of clients), thanks @kbaum
|
|
116
|
+
- view_tracker supports tracking view layer files like `.html.erb`
|
|
117
|
+
- documentation improvements, thanks @brossetti1, @jjb, @kbaum
|
|
118
|
+
- we now have discordapp for discussions, thanks @kbaum,
|
|
119
|
+
https://discordapp.com/channels/609509533999562753/609509533999562756
|
|
120
|
+
- perf fix on Rails initialization, thanks @skangg
|
|
121
|
+
- simplified logging
|
|
122
|
+
|
|
123
|
+
### Coverband 4.2.1
|
|
124
|
+
|
|
125
|
+
- larger changes
|
|
126
|
+
- reduce memory usage
|
|
127
|
+
- fix issue where reports didn't include files with 0 activity
|
|
128
|
+
- updated runtime relavent lines and runtime percentages
|
|
129
|
+
- add Oneshot coverage support for Ruby 2.6.0 thanks @zwalker
|
|
130
|
+
- I would consider this our test oneshot release, please report any issues
|
|
131
|
+
- improved control over memory vs functionality
|
|
132
|
+
- oneshot support is the best memory and speed if you are on Ruby 2.6.\*
|
|
133
|
+
- 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
|
|
134
|
+
- full hit tracking (The previous and current default), this uses more memory than other options, but provides full robust data
|
|
135
|
+
|
|
136
|
+
* small changes fixes
|
|
137
|
+
- further improvements on eager_loading detection, thanks @kbaum
|
|
138
|
+
- fix on ignore configuration options
|
|
139
|
+
- fix broken static server
|
|
140
|
+
- fix issue where clear and coverage trigger coverage reports
|
|
141
|
+
- improved logging
|
|
142
|
+
- fix on gem runtime code coverage support, thanks @kbaum
|
|
143
|
+
- fix sorting of runtime coverage
|
|
144
|
+
- add runtime coverage to gem summary pages
|
|
145
|
+
- documented redis TTL, thanks @jjb
|
|
146
|
+
- resolve regression on coverband rake tasks recording coverage
|
|
147
|
+
- improved runtime / eager loading tracking
|
|
148
|
+
- fix on small memory leaks
|
|
149
|
+
- added warnings that gem tracking isn't production ready
|
|
150
|
+
- built in support for basic auth on the web rack app, thanks @jjb
|
|
151
|
+
- don't clobber rake environment method definition, thanks @shioyama
|
|
152
|
+
- readme improvements, thanks @yuriyalekseyev
|
|
153
|
+
- fix duplicate requires thanks @yuriyalekseyev
|
|
154
|
+
- various cleanups and improvements, thanks @kbaum
|
|
155
|
+
- additional benchmarks to ensure proper memory handling, thanks @kbaum
|
|
156
|
+
- default redis TTL, thanks @jjb
|
|
157
|
+
|
|
83
158
|
### Coverband 4.2.0
|
|
84
159
|
|
|
85
160
|
**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.
|
|
@@ -96,7 +171,7 @@ Feature Ideas:
|
|
|
96
171
|
- move from thread based reporter instance to process based instance
|
|
97
172
|
- thanks Karl Baum for much of the above mentioned work
|
|
98
173
|
- clear coverage on individual files
|
|
99
|
-
- we have a logo, thanks Dave Woodall
|
|
174
|
+
- we have a logo, thanks Dave Woodall, http://davewoodall.com
|
|
100
175
|
|
|
101
176
|
### Coverband 4.1.1
|
|
102
177
|
|
data/coverband.gemspec
CHANGED
|
@@ -28,17 +28,15 @@ Gem::Specification.new do |spec|
|
|
|
28
28
|
spec.add_development_dependency 'm'
|
|
29
29
|
spec.add_development_dependency 'memory_profiler'
|
|
30
30
|
spec.add_development_dependency 'minitest'
|
|
31
|
+
spec.add_development_dependency 'minitest-fork_executor'
|
|
31
32
|
spec.add_development_dependency 'mocha', '~> 1.7.0'
|
|
32
33
|
spec.add_development_dependency 'rack'
|
|
33
34
|
spec.add_development_dependency 'rack-test'
|
|
34
35
|
spec.add_development_dependency 'rake'
|
|
35
36
|
spec.add_development_dependency 'resque'
|
|
36
|
-
|
|
37
|
-
spec.add_development_dependency '
|
|
37
|
+
spec.add_development_dependency 'rubocop'
|
|
38
|
+
spec.add_development_dependency 'rubocop-performance'
|
|
38
39
|
|
|
39
|
-
# TODO: used for benchmarking and tests I think we have other better benchmarking
|
|
40
|
-
# perhaps time to drop this and refactor.
|
|
41
|
-
spec.add_development_dependency 'classifier-reborn'
|
|
42
40
|
spec.add_development_dependency 'coveralls'
|
|
43
41
|
# add when debugging
|
|
44
42
|
# require 'byebug'; byebug
|
|
@@ -3,45 +3,48 @@
|
|
|
3
3
|
module Coverband
|
|
4
4
|
module Adapters
|
|
5
5
|
class Base
|
|
6
|
-
|
|
6
|
+
DATA_KEY = 'data'
|
|
7
|
+
FIRST_UPDATED_KEY = 'first_updated_at'
|
|
8
|
+
LAST_UPDATED_KEY = 'last_updated_at'
|
|
9
|
+
FILE_HASH = 'file_hash'
|
|
10
|
+
ABSTRACT_KEY = 'abstract'
|
|
11
|
+
|
|
7
12
|
attr_accessor :type
|
|
8
13
|
|
|
9
14
|
def initialize
|
|
10
|
-
@
|
|
15
|
+
@type = Coverband::RUNTIME_TYPE
|
|
11
16
|
end
|
|
12
17
|
|
|
13
18
|
def clear!
|
|
14
|
-
raise
|
|
19
|
+
raise ABSTRACT_KEY
|
|
15
20
|
end
|
|
16
21
|
|
|
17
|
-
def clear_file!(_file)
|
|
18
|
-
raise
|
|
22
|
+
def clear_file!(_file = nil)
|
|
23
|
+
raise ABSTRACT_KEY
|
|
19
24
|
end
|
|
20
25
|
|
|
21
26
|
def migrate!
|
|
22
|
-
raise
|
|
27
|
+
raise ABSTRACT_KEY
|
|
23
28
|
end
|
|
24
29
|
|
|
25
30
|
def size
|
|
26
|
-
raise
|
|
31
|
+
raise ABSTRACT_KEY
|
|
27
32
|
end
|
|
28
33
|
|
|
29
|
-
def
|
|
30
|
-
|
|
34
|
+
def save_coverage
|
|
35
|
+
raise ABSTRACT_KEY
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def coverage(_local_type = nil)
|
|
39
|
+
raise ABSTRACT_KEY
|
|
31
40
|
end
|
|
32
41
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
# the Coverband 2 had the same issue,
|
|
36
|
-
# and the tradeoff has always been acceptable
|
|
37
|
-
def save_report(report)
|
|
38
|
-
data = report.dup
|
|
39
|
-
data = merge_reports(data, get_report)
|
|
40
|
-
save_coverage(data)
|
|
42
|
+
def size_in_mib
|
|
43
|
+
format('%<size>.2f', size: (size.to_f / 2**20))
|
|
41
44
|
end
|
|
42
45
|
|
|
43
|
-
def
|
|
44
|
-
|
|
46
|
+
def save_report(_report)
|
|
47
|
+
raise 'abstract'
|
|
45
48
|
end
|
|
46
49
|
|
|
47
50
|
def get_coverage_report
|
|
@@ -53,60 +56,69 @@ module Coverband
|
|
|
53
56
|
coverage.keys || []
|
|
54
57
|
end
|
|
55
58
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
Array(coverage.dig(file, 'data'))
|
|
59
|
+
def raw_store
|
|
60
|
+
raise ABSTRACT_KEY
|
|
59
61
|
end
|
|
60
62
|
|
|
61
63
|
protected
|
|
62
64
|
|
|
63
65
|
def split_coverage(types)
|
|
64
66
|
types.reduce({}) do |data, type|
|
|
65
|
-
|
|
67
|
+
if type == Coverband::RUNTIME_TYPE && Coverband.configuration.simulate_oneshot_lines_coverage
|
|
68
|
+
data.update(type => simulated_runtime_coverage)
|
|
69
|
+
else
|
|
70
|
+
data.update(type => coverage(type))
|
|
71
|
+
end
|
|
66
72
|
end
|
|
67
73
|
end
|
|
68
74
|
|
|
69
|
-
def
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
def simulated_runtime_coverage
|
|
76
|
+
runtime_data = coverage(Coverband::RUNTIME_TYPE)
|
|
77
|
+
eager_data = coverage(Coverband::EAGER_TYPE)
|
|
78
|
+
eager_data.values do |vals|
|
|
79
|
+
vals['data'].map! { |line_coverage| line_coverage ? (0 - line_coverage) : line_coverage }
|
|
72
80
|
end
|
|
81
|
+
merge_reports(runtime_data, eager_data, skip_expansion: true)
|
|
73
82
|
end
|
|
74
83
|
|
|
75
|
-
def
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def get_report
|
|
80
|
-
raise 'abstract'
|
|
84
|
+
def merged_coverage(types)
|
|
85
|
+
types.reduce({}) do |data, type|
|
|
86
|
+
merge_reports(data, coverage(type), skip_expansion: true)
|
|
87
|
+
end
|
|
81
88
|
end
|
|
82
89
|
|
|
83
90
|
def file_hash(file)
|
|
84
|
-
|
|
91
|
+
Coverband::Utils::FileHasher.hash(file)
|
|
85
92
|
end
|
|
86
93
|
|
|
94
|
+
# TODO: modify to extend report inline?
|
|
87
95
|
def expand_report(report)
|
|
88
96
|
expanded = {}
|
|
89
97
|
report_time = Time.now.to_i
|
|
90
|
-
updated_time =
|
|
98
|
+
updated_time = type == Coverband::EAGER_TYPE ? nil : report_time
|
|
91
99
|
report.each_pair do |key, line_data|
|
|
92
100
|
extended_data = {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
101
|
+
FIRST_UPDATED_KEY => report_time,
|
|
102
|
+
LAST_UPDATED_KEY => updated_time,
|
|
103
|
+
FILE_HASH => file_hash(key),
|
|
104
|
+
DATA_KEY => line_data
|
|
97
105
|
}
|
|
98
|
-
expanded[
|
|
106
|
+
expanded[Utils::RelativeFileConverter.convert(key)] = extended_data
|
|
99
107
|
end
|
|
100
108
|
expanded
|
|
101
109
|
end
|
|
102
110
|
|
|
103
111
|
def merge_reports(new_report, old_report, options = {})
|
|
112
|
+
# transparently update from RUNTIME_TYPE = nil to RUNTIME_TYPE = :runtime
|
|
113
|
+
# transparent update for format coveband_3_2
|
|
114
|
+
old_report = coverage(nil, override_type: nil) if old_report.nil? && type == Coverband::RUNTIME_TYPE
|
|
115
|
+
|
|
104
116
|
new_report = expand_report(new_report) unless options[:skip_expansion]
|
|
105
117
|
keys = (new_report.keys + old_report.keys).uniq
|
|
106
118
|
keys.each do |file|
|
|
107
119
|
new_report[file] = if new_report[file] &&
|
|
108
120
|
old_report[file] &&
|
|
109
|
-
new_report[file][
|
|
121
|
+
new_report[file][FILE_HASH] == old_report[file][FILE_HASH]
|
|
110
122
|
merge_expanded_data(new_report[file], old_report[file])
|
|
111
123
|
elsif new_report[file]
|
|
112
124
|
new_report[file]
|
|
@@ -119,15 +131,22 @@ module Coverband
|
|
|
119
131
|
|
|
120
132
|
def merge_expanded_data(new_expanded, old_expanded)
|
|
121
133
|
{
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
134
|
+
FIRST_UPDATED_KEY => old_expanded[FIRST_UPDATED_KEY],
|
|
135
|
+
LAST_UPDATED_KEY => new_expanded[LAST_UPDATED_KEY],
|
|
136
|
+
FILE_HASH => new_expanded[FILE_HASH],
|
|
137
|
+
DATA_KEY => array_add(new_expanded[DATA_KEY], old_expanded[DATA_KEY])
|
|
126
138
|
}
|
|
127
139
|
end
|
|
128
140
|
|
|
141
|
+
# TODO: This should only be 2 cases get our dup / not dups aligned
|
|
129
142
|
def array_add(latest, original)
|
|
130
|
-
|
|
143
|
+
if Coverband.configuration.use_oneshot_lines_coverage
|
|
144
|
+
latest.map!.with_index { |v, i| (v + original[i] >= 1 ? 1 : 0) if v && original[i] }
|
|
145
|
+
elsif Coverband.configuration.simulate_oneshot_lines_coverage
|
|
146
|
+
latest.map.with_index { |v, i| (v + original[i] >= 1 ? 1 : 0) if v && original[i] }
|
|
147
|
+
else
|
|
148
|
+
latest.map.with_index { |v, i| (v && original[i]) ? v + original[i] : nil }
|
|
149
|
+
end
|
|
131
150
|
end
|
|
132
151
|
end
|
|
133
152
|
end
|
|
@@ -28,21 +28,27 @@ module Coverband
|
|
|
28
28
|
raise NotImplementedError, "FileStore doesn't support migrations"
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
attr_accessor :path
|
|
34
|
-
|
|
35
|
-
def save_coverage(report)
|
|
36
|
-
File.open(path, 'w') { |f| f.write(report.to_json) }
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def get_report
|
|
31
|
+
def coverage(_local_type = nil)
|
|
40
32
|
if File.exist?(path)
|
|
41
33
|
JSON.parse(File.read(path))
|
|
42
34
|
else
|
|
43
35
|
{}
|
|
44
36
|
end
|
|
45
37
|
end
|
|
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
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
attr_accessor :path
|
|
46
52
|
end
|
|
47
53
|
end
|
|
48
54
|
end
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Coverband
|
|
6
|
+
module Adapters
|
|
7
|
+
class HashRedisStore < Base
|
|
8
|
+
FILE_KEY = 'file'
|
|
9
|
+
FILE_LENGTH_KEY = 'file_length'
|
|
10
|
+
META_DATA_KEYS = [DATA_KEY, FIRST_UPDATED_KEY, LAST_UPDATED_KEY, FILE_HASH].freeze
|
|
11
|
+
###
|
|
12
|
+
# This key isn't related to the coverband version, but to the interal format
|
|
13
|
+
# used to store data to redis. It is changed only when breaking changes to our
|
|
14
|
+
# redis format are required.
|
|
15
|
+
###
|
|
16
|
+
REDIS_STORAGE_FORMAT_VERSION = 'coverband_hash_3_3'
|
|
17
|
+
|
|
18
|
+
JSON_PAYLOAD_EXPIRATION = 5 * 60
|
|
19
|
+
|
|
20
|
+
attr_reader :redis_namespace
|
|
21
|
+
|
|
22
|
+
def initialize(redis, opts = {})
|
|
23
|
+
super()
|
|
24
|
+
@redis_namespace = opts[:redis_namespace]
|
|
25
|
+
@save_report_batch_size = opts[:save_report_batch_size] || 100
|
|
26
|
+
@format_version = REDIS_STORAGE_FORMAT_VERSION
|
|
27
|
+
@redis = redis
|
|
28
|
+
raise 'HashRedisStore requires redis >= 2.6.0' unless supported?
|
|
29
|
+
|
|
30
|
+
@ttl = opts[:ttl]
|
|
31
|
+
@relative_file_converter = opts[:relative_file_converter] || Utils::RelativeFileConverter
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def supported?
|
|
35
|
+
Gem::Version.new(@redis.info['redis_version']) >= Gem::Version.new('2.6.0')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def clear!
|
|
39
|
+
old_type = type
|
|
40
|
+
Coverband::TYPES.each do |type|
|
|
41
|
+
self.type = type
|
|
42
|
+
file_keys = files_set
|
|
43
|
+
@redis.del(*file_keys) if file_keys.any?
|
|
44
|
+
@redis.del(files_key)
|
|
45
|
+
end
|
|
46
|
+
self.type = old_type
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def clear_file!(file)
|
|
50
|
+
file_hash = file_hash(file)
|
|
51
|
+
relative_path_file = @relative_file_converter.convert(file)
|
|
52
|
+
Coverband::TYPES.each do |type|
|
|
53
|
+
@redis.del(key(relative_path_file, type, file_hash: file_hash))
|
|
54
|
+
end
|
|
55
|
+
@redis.srem(files_key, relative_path_file)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def save_report(report)
|
|
59
|
+
report_time = Time.now.to_i
|
|
60
|
+
updated_time = type == Coverband::EAGER_TYPE ? nil : report_time
|
|
61
|
+
keys = []
|
|
62
|
+
report.each_slice(@save_report_batch_size) do |slice|
|
|
63
|
+
files_data = slice.map do |(file, data)|
|
|
64
|
+
relative_file = @relative_file_converter.convert(file)
|
|
65
|
+
file_hash = file_hash(relative_file)
|
|
66
|
+
key = key(relative_file, file_hash: file_hash)
|
|
67
|
+
keys << key
|
|
68
|
+
script_input(
|
|
69
|
+
key: key,
|
|
70
|
+
file: relative_file,
|
|
71
|
+
file_hash: file_hash,
|
|
72
|
+
data: data,
|
|
73
|
+
report_time: report_time,
|
|
74
|
+
updated_time: updated_time
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
next unless files_data.any?
|
|
78
|
+
|
|
79
|
+
arguments_key = [@redis_namespace, SecureRandom.uuid].compact.join('.')
|
|
80
|
+
@redis.set(arguments_key, { ttl: @ttl, files_data: files_data }.to_json, ex: JSON_PAYLOAD_EXPIRATION)
|
|
81
|
+
@redis.evalsha(hash_incr_script, [arguments_key])
|
|
82
|
+
end
|
|
83
|
+
@redis.sadd(files_key, keys) if keys.any?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def coverage(local_type = nil)
|
|
87
|
+
files_set = files_set(local_type)
|
|
88
|
+
@redis.pipelined do
|
|
89
|
+
files_set.map do |key|
|
|
90
|
+
@redis.hgetall(key)
|
|
91
|
+
end
|
|
92
|
+
end.each_with_object({}) do |data_from_redis, hash|
|
|
93
|
+
add_coverage_for_file(data_from_redis, hash)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def raw_store
|
|
98
|
+
@redis
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def size
|
|
102
|
+
'not available'
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def size_in_mib
|
|
106
|
+
'not available'
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def add_coverage_for_file(data_from_redis, hash)
|
|
112
|
+
return if data_from_redis.empty?
|
|
113
|
+
|
|
114
|
+
file = data_from_redis[FILE_KEY]
|
|
115
|
+
return unless file_hash(file) == data_from_redis[FILE_HASH]
|
|
116
|
+
|
|
117
|
+
data = coverage_data_from_redis(data_from_redis)
|
|
118
|
+
hash[file] = data_from_redis.select { |meta_data_key, _value| META_DATA_KEYS.include?(meta_data_key) }.merge!('data' => data)
|
|
119
|
+
hash[file][LAST_UPDATED_KEY] = hash[file][LAST_UPDATED_KEY].blank? ? nil : hash[file][LAST_UPDATED_KEY].to_i
|
|
120
|
+
hash[file].merge!(LAST_UPDATED_KEY => hash[file][LAST_UPDATED_KEY], FIRST_UPDATED_KEY => hash[file][FIRST_UPDATED_KEY].to_i)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def coverage_data_from_redis(data_from_redis)
|
|
124
|
+
max = data_from_redis[FILE_LENGTH_KEY].to_i - 1
|
|
125
|
+
Array.new(max + 1) do |index|
|
|
126
|
+
line_coverage = data_from_redis[index.to_s]
|
|
127
|
+
line_coverage.nil? ? nil : line_coverage.to_i
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def script_input(key:, file:, file_hash:, data:, report_time:, updated_time:)
|
|
132
|
+
coverage_data = data.each_with_index.each_with_object({}) do |(coverage, index), hash|
|
|
133
|
+
hash[index] = coverage if coverage
|
|
134
|
+
end
|
|
135
|
+
meta = {
|
|
136
|
+
first_updated_at: report_time,
|
|
137
|
+
file: file,
|
|
138
|
+
file_hash: file_hash,
|
|
139
|
+
file_length: data.length,
|
|
140
|
+
hash_key: key
|
|
141
|
+
}
|
|
142
|
+
meta[:last_updated_at] = updated_time if updated_time
|
|
143
|
+
{
|
|
144
|
+
hash_key: key,
|
|
145
|
+
meta: meta,
|
|
146
|
+
coverage: coverage_data
|
|
147
|
+
}
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def hash_incr_script
|
|
151
|
+
@hash_incr_script ||= @redis.script(:load, lua_script_content)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def lua_script_content
|
|
155
|
+
File.read(File.join(
|
|
156
|
+
File.dirname(__FILE__), '../../../lua/lib/persist-coverage.lua'
|
|
157
|
+
))
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def values_from_redis(local_type, files)
|
|
161
|
+
return files if files.empty?
|
|
162
|
+
|
|
163
|
+
@redis.mget(*files.map { |file| key(file, local_type) }).map do |value|
|
|
164
|
+
value.nil? ? {} : JSON.parse(value)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def relative_paths(files)
|
|
169
|
+
files&.map! { |file| full_path_to_relative(file) }
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def files_set(local_type = nil)
|
|
173
|
+
@redis.smembers(files_key(local_type))
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def files_key(local_type = nil)
|
|
177
|
+
"#{key_prefix(local_type)}.files"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def key(file, local_type = nil, file_hash:)
|
|
181
|
+
[key_prefix(local_type), file, file_hash].join('.')
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def key_prefix(local_type = nil)
|
|
185
|
+
local_type ||= type
|
|
186
|
+
[@format_version, @redis_namespace, local_type].compact.join('.')
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -13,23 +13,31 @@ module Coverband
|
|
|
13
13
|
###
|
|
14
14
|
REDIS_STORAGE_FORMAT_VERSION = 'coverband_3_2'
|
|
15
15
|
|
|
16
|
+
attr_reader :redis_namespace
|
|
17
|
+
|
|
16
18
|
def initialize(redis, opts = {})
|
|
17
19
|
super()
|
|
18
20
|
@redis = redis
|
|
19
21
|
@ttl = opts[:ttl]
|
|
20
22
|
@redis_namespace = opts[:redis_namespace]
|
|
21
23
|
@format_version = REDIS_STORAGE_FORMAT_VERSION
|
|
24
|
+
@keys = {}
|
|
25
|
+
Coverband::TYPES.each do |type|
|
|
26
|
+
@keys[type] = [@format_version, @redis_namespace, type].compact.join('.')
|
|
27
|
+
end
|
|
22
28
|
end
|
|
23
29
|
|
|
24
30
|
def clear!
|
|
25
31
|
Coverband::TYPES.each do |type|
|
|
26
32
|
@redis.del(type_base_key(type))
|
|
27
33
|
end
|
|
34
|
+
# temporarily clear the old namespace of coverband_3_2
|
|
35
|
+
@redis.del(type_base_key(nil))
|
|
28
36
|
end
|
|
29
37
|
|
|
30
38
|
def clear_file!(filename)
|
|
31
39
|
Coverband::TYPES.each do |type|
|
|
32
|
-
data =
|
|
40
|
+
data = coverage(type)
|
|
33
41
|
data.delete(filename)
|
|
34
42
|
save_coverage(data, type)
|
|
35
43
|
end
|
|
@@ -47,18 +55,18 @@ module Coverband
|
|
|
47
55
|
def migrate!
|
|
48
56
|
reset_base_key
|
|
49
57
|
@format_version = 'coverband3_1'
|
|
50
|
-
previous_data =
|
|
58
|
+
previous_data = coverage
|
|
51
59
|
if previous_data.empty?
|
|
52
60
|
puts 'no previous data to migrate found'
|
|
53
61
|
exit 0
|
|
54
62
|
end
|
|
55
63
|
relative_path_report = previous_data.each_with_object({}) do |(key, vals), fixed_report|
|
|
56
|
-
fixed_report[
|
|
64
|
+
fixed_report[Utils::RelativeFileConverter.convert(key)] = vals
|
|
57
65
|
end
|
|
58
66
|
clear!
|
|
59
67
|
reset_base_key
|
|
60
68
|
@format_version = REDIS_STORAGE_FORMAT_VERSION
|
|
61
|
-
save_coverage(merge_reports(
|
|
69
|
+
save_coverage(merge_reports(coverage, relative_path_report, skip_expansion: true))
|
|
62
70
|
end
|
|
63
71
|
|
|
64
72
|
def type=(type)
|
|
@@ -66,6 +74,28 @@ module Coverband
|
|
|
66
74
|
reset_base_key
|
|
67
75
|
end
|
|
68
76
|
|
|
77
|
+
def coverage(local_type = nil, opts = {})
|
|
78
|
+
local_type ||= opts.key?(:override_type) ? opts[:override_type] : type
|
|
79
|
+
data = redis.get type_base_key(local_type)
|
|
80
|
+
data = data ? JSON.parse(data) : {}
|
|
81
|
+
data.delete_if { |file_path, file_data| file_hash(file_path) != file_data['file_hash'] } unless opts[:skip_hash_check]
|
|
82
|
+
data
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Note: This could lead to slight race on redis
|
|
86
|
+
# where multiple processes pull the old coverage and add to it then push
|
|
87
|
+
# the Coverband 2 had the same issue,
|
|
88
|
+
# and the tradeoff has always been acceptable
|
|
89
|
+
def save_report(report)
|
|
90
|
+
data = report.dup
|
|
91
|
+
data = merge_reports(data, coverage(nil, skip_hash_check: true))
|
|
92
|
+
save_coverage(data)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def raw_store
|
|
96
|
+
@redis
|
|
97
|
+
end
|
|
98
|
+
|
|
69
99
|
private
|
|
70
100
|
|
|
71
101
|
attr_reader :redis
|
|
@@ -79,7 +109,7 @@ module Coverband
|
|
|
79
109
|
end
|
|
80
110
|
|
|
81
111
|
def type_base_key(local_type)
|
|
82
|
-
[
|
|
112
|
+
@keys[local_type]
|
|
83
113
|
end
|
|
84
114
|
|
|
85
115
|
def save_coverage(data, local_type = nil)
|
|
@@ -87,12 +117,6 @@ module Coverband
|
|
|
87
117
|
redis.set type_base_key(local_type), data.to_json
|
|
88
118
|
redis.expire(type_base_key(local_type), @ttl) if @ttl
|
|
89
119
|
end
|
|
90
|
-
|
|
91
|
-
def get_report(local_type = nil)
|
|
92
|
-
local_type ||= type
|
|
93
|
-
data = redis.get type_base_key(local_type)
|
|
94
|
-
data ? JSON.parse(data) : {}
|
|
95
|
-
end
|
|
96
120
|
end
|
|
97
121
|
end
|
|
98
122
|
end
|