coverband 5.0.0.rc.3 → 5.0.0.rc.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/changes.md +9 -2
- data/coverband.gemspec +1 -0
- data/lib/coverband.rb +13 -1
- data/lib/coverband/adapters/base.rb +0 -1
- data/lib/coverband/adapters/file_store.rb +38 -6
- data/lib/coverband/adapters/stdout_store.rb +41 -0
- data/lib/coverband/adapters/web_service_store.rb +157 -0
- data/lib/coverband/collectors/delta.rb +10 -0
- data/lib/coverband/collectors/view_tracker_service.rb +59 -0
- data/lib/coverband/configuration.rb +78 -9
- data/lib/coverband/integrations/background.rb +3 -3
- data/lib/coverband/reporters/web.rb +2 -4
- data/lib/coverband/utils/railtie.rb +9 -4
- data/lib/coverband/utils/tasks.rb +1 -0
- data/lib/coverband/version.rb +1 -1
- data/test/coverband/adapters/file_store_test.rb +6 -6
- data/test/coverband/adapters/web_service_store_test.rb +56 -0
- data/test/coverband/collectors/delta_test.rb +1 -0
- data/test/coverband/integrations/background_test.rb +3 -3
- data/test/test_helper.rb +2 -0
- metadata +21 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c83a78e28c79671ef144ffa38e40ee061a873ef6a207437f01923188c17d3cf0
|
4
|
+
data.tar.gz: 80a3c0a46cfb55a5972139e034733d9fb6541f0d547caff1f004203b9ddb0484
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0b2f9965e62d911acc562c73df79f4ba13ca0df48bb18258a9069b71d3323f9167385b11fe25772d32d23575338e98e04f721dd57f6562ca40171e39f1cae591
|
7
|
+
data.tar.gz: 6ca2447bbddf8ac8bd6a92e4544a2e38b5a7bf84d49e9e02230ef3a2e0044436ce70d9907dad8a7d163cf909369ad4f089af49bb6a50da5b62982e3001aa48bd
|
data/changes.md
CHANGED
@@ -7,7 +7,7 @@
|
|
7
7
|
- [redis bitfield](https://stackoverflow.com/questions/47100606/optimal-way-to-store-array-of-integers-in-redis-database)
|
8
8
|
- Add support for [zadd](http://redis.io/topics/data-types-intro) so one could determine single call versus multiple calls on a line, letting us determine the most executed code in production.
|
9
9
|
|
10
|
-
### Coverband
|
10
|
+
### Coverband Future...
|
11
11
|
|
12
12
|
Will be the fully modern release that drops maintenance legacy support in favor of increased performance, ease of use, and maintainability.
|
13
13
|
|
@@ -60,7 +60,14 @@ Will be the fully modern release that drops maintenance legacy support in favor
|
|
60
60
|
- drops S3 support
|
61
61
|
- drops static report support
|
62
62
|
- drops gem support
|
63
|
-
-
|
63
|
+
- only loaded web reporter files when required
|
64
|
+
- improved load order allowing more time for ENV vars (better dotenv, figaro, rails secrets support)
|
65
|
+
- improved resque patching pattern
|
66
|
+
- improved default ignores
|
67
|
+
- additional adapters
|
68
|
+
- supports web-service adapter for http coverage collection
|
69
|
+
- support log/file adapter
|
70
|
+
- reduce logs / errors / alerts on bad startup configurations
|
64
71
|
|
65
72
|
# Released
|
66
73
|
|
data/coverband.gemspec
CHANGED
@@ -34,6 +34,7 @@ Gem::Specification.new do |spec|
|
|
34
34
|
|
35
35
|
spec.add_development_dependency "coveralls"
|
36
36
|
spec.add_development_dependency "minitest-profile"
|
37
|
+
spec.add_development_dependency "webmock"
|
37
38
|
|
38
39
|
# TODO: Remove when other production adapters exist
|
39
40
|
# because the default configuration of redis store, we really do require
|
data/lib/coverband.rb
CHANGED
@@ -12,9 +12,11 @@ require "coverband/adapters/base"
|
|
12
12
|
require "coverband/adapters/redis_store"
|
13
13
|
require "coverband/adapters/hash_redis_store"
|
14
14
|
require "coverband/adapters/file_store"
|
15
|
+
require "coverband/adapters/stdout_store"
|
15
16
|
require "coverband/utils/file_hasher"
|
16
17
|
require "coverband/collectors/coverage"
|
17
18
|
require "coverband/collectors/view_tracker"
|
19
|
+
require "coverband/collectors/view_tracker_service"
|
18
20
|
require "coverband/reporters/base"
|
19
21
|
require "coverband/reporters/console_report"
|
20
22
|
require "coverband/integrations/background"
|
@@ -25,6 +27,7 @@ Coverband::Adapters::RedisStore = Coverband::Adapters::HashRedisStore if ENV["CO
|
|
25
27
|
|
26
28
|
module Coverband
|
27
29
|
@@configured = false
|
30
|
+
SERVICE_CONFIG = "./config/coverband_service.rb"
|
28
31
|
CONFIG_FILE = "./config/coverband.rb"
|
29
32
|
RUNTIME_TYPE = :runtime
|
30
33
|
EAGER_TYPE = :eager_loading
|
@@ -33,7 +36,11 @@ module Coverband
|
|
33
36
|
ALL_TYPES = TYPES + [:merged]
|
34
37
|
|
35
38
|
def self.configure(file = nil)
|
36
|
-
configuration_file = file || ENV
|
39
|
+
configuration_file = file || ENV["COVERBAND_CONFIG"]
|
40
|
+
if configuration_file.nil?
|
41
|
+
configuration_file = coverband_service? ? SERVICE_CONFIG : CONFIG_FILE
|
42
|
+
end
|
43
|
+
|
37
44
|
configuration
|
38
45
|
if block_given?
|
39
46
|
yield(configuration)
|
@@ -46,6 +53,10 @@ module Coverband
|
|
46
53
|
coverage_instance.reset_instance
|
47
54
|
end
|
48
55
|
|
56
|
+
def self.coverband_service?
|
57
|
+
!!File.exist?(SERVICE_CONFIG)
|
58
|
+
end
|
59
|
+
|
49
60
|
def self.configured?
|
50
61
|
@@configured
|
51
62
|
end
|
@@ -87,6 +98,7 @@ module Coverband
|
|
87
98
|
private_class_method def self.coverage_instance
|
88
99
|
Coverband::Collectors::Coverage.instance
|
89
100
|
end
|
101
|
+
|
90
102
|
unless ENV["COVERBAND_DISABLE_AUTO_START"]
|
91
103
|
begin
|
92
104
|
# Coverband should be setup as early as possible
|
@@ -112,7 +112,6 @@ module Coverband
|
|
112
112
|
# transparently update from RUNTIME_TYPE = nil to RUNTIME_TYPE = :runtime
|
113
113
|
# transparent update for format coveband_3_2
|
114
114
|
old_report = coverage(nil, override_type: nil) if old_report.nil? && type == Coverband::RUNTIME_TYPE
|
115
|
-
|
116
115
|
new_report = expand_report(new_report) unless options[:skip_expansion]
|
117
116
|
keys = (new_report.keys + old_report.keys).uniq
|
118
117
|
keys.each do |file|
|
@@ -3,14 +3,38 @@
|
|
3
3
|
module Coverband
|
4
4
|
module Adapters
|
5
5
|
###
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
6
|
+
# FileStore store a merged coverage file to local disk
|
7
|
+
#
|
8
|
+
# Notes: Concurrency
|
9
|
+
# * threadsafe as the caller to save_report uses @semaphore.synchronize
|
10
|
+
# * file access process safe as each file written per process PID
|
11
|
+
#
|
12
|
+
# Usage:
|
13
|
+
# config.store = Coverband::Adapters::FileStore.new('log/coverage.log')
|
14
|
+
#
|
15
|
+
# View Reports:
|
16
|
+
# Using this assumes you are syncing the coverage files
|
17
|
+
# to some shared storage that is accessable outside of the production server
|
18
|
+
# download files to a system where you want to view the reports..
|
19
|
+
# When viewing coverage from the filestore adapter it merges all coverage
|
20
|
+
# files matching the path pattern, in this case `log/coverage.log.*`
|
21
|
+
#
|
22
|
+
# run: `bundle exec rake coverband:coverage_server`
|
23
|
+
# open http://localhost:1022/
|
24
|
+
#
|
25
|
+
# one could also build a report via code, the output is suitable to feed into SimpleCov
|
26
|
+
#
|
27
|
+
# ```
|
28
|
+
# coverband.configuration.store.merge_mode = true
|
29
|
+
# coverband.configuration.store.coverage
|
30
|
+
# ```
|
9
31
|
###
|
10
32
|
class FileStore < Base
|
33
|
+
attr_accessor :merge_mode
|
11
34
|
def initialize(path, _opts = {})
|
12
35
|
super()
|
13
|
-
@path = path
|
36
|
+
@path = "#{path}.#{::Process.pid}"
|
37
|
+
@merge_mode = false
|
14
38
|
|
15
39
|
config_dir = File.dirname(@path)
|
16
40
|
Dir.mkdir config_dir unless File.exist?(config_dir)
|
@@ -29,17 +53,25 @@ module Coverband
|
|
29
53
|
end
|
30
54
|
|
31
55
|
def coverage(_local_type = nil)
|
32
|
-
if
|
56
|
+
if merge_mode
|
57
|
+
data = {}
|
58
|
+
Dir[path.sub(/\.\d+/, ".*")].each do |path|
|
59
|
+
data = merge_reports(data, JSON.parse(File.read(path)), skip_expansion: true)
|
60
|
+
end
|
61
|
+
data
|
62
|
+
elsif File.exist?(path)
|
33
63
|
JSON.parse(File.read(path))
|
34
64
|
else
|
35
65
|
{}
|
36
66
|
end
|
67
|
+
rescue Errno::ENOENT
|
68
|
+
{}
|
37
69
|
end
|
38
70
|
|
39
71
|
def save_report(report)
|
40
72
|
data = report.dup
|
41
73
|
data = merge_reports(data, coverage)
|
42
|
-
File.
|
74
|
+
File.write(path, JSON.dump(data))
|
43
75
|
end
|
44
76
|
|
45
77
|
def raw_store
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coverband
|
4
|
+
module Adapters
|
5
|
+
###
|
6
|
+
# StdoutStore is for testing and development
|
7
|
+
#
|
8
|
+
# Usage:
|
9
|
+
# config.store = Coverband::Adapters::StdoutStore.new
|
10
|
+
###
|
11
|
+
class StdoutStore < Base
|
12
|
+
def initialize(_opts = {})
|
13
|
+
super()
|
14
|
+
end
|
15
|
+
|
16
|
+
def clear!
|
17
|
+
# NOOP
|
18
|
+
end
|
19
|
+
|
20
|
+
def size
|
21
|
+
0
|
22
|
+
end
|
23
|
+
|
24
|
+
def migrate!
|
25
|
+
raise NotImplementedError, "StdoutStore doesn't support migrations"
|
26
|
+
end
|
27
|
+
|
28
|
+
def coverage(_local_type = nil)
|
29
|
+
{}
|
30
|
+
end
|
31
|
+
|
32
|
+
def save_report(report)
|
33
|
+
$stdout.puts(report.to_json)
|
34
|
+
end
|
35
|
+
|
36
|
+
def raw_store
|
37
|
+
raise NotImplementedError, "StdoutStore doesn't support raw_store"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coverband
|
4
|
+
module Adapters
|
5
|
+
###
|
6
|
+
# WebServiceStore store a merged coverage file to local disk
|
7
|
+
# TODO: add webmock tests
|
8
|
+
###
|
9
|
+
class WebServiceStore < Base
|
10
|
+
attr_reader :coverband_url, :process_type, :runtime_env, :hostname, :pid
|
11
|
+
|
12
|
+
def initialize(coverband_url, opts = {})
|
13
|
+
super()
|
14
|
+
require "socket"
|
15
|
+
require "securerandom"
|
16
|
+
@coverband_url = coverband_url
|
17
|
+
@process_type = opts.fetch(:process_type) { $PROGRAM_NAME&.split("/")&.last || Coverband.configuration.process_type }
|
18
|
+
@hostname = opts.fetch(:hostname) { ENV["DYNO"] || Socket.gethostname.force_encoding("utf-8").encode }
|
19
|
+
@hostname = @hostname.delete("'", "").delete("’", "")
|
20
|
+
@runtime_env = opts.fetch(:runtime_env) { Coverband.configuration.coverband_env }
|
21
|
+
@failed_coverage_reports = []
|
22
|
+
end
|
23
|
+
|
24
|
+
def logger
|
25
|
+
Coverband.configuration.logger
|
26
|
+
end
|
27
|
+
|
28
|
+
def clear!
|
29
|
+
# done via service UI
|
30
|
+
raise "not supported via service"
|
31
|
+
end
|
32
|
+
|
33
|
+
def clear_file!(filename)
|
34
|
+
# done via service UI
|
35
|
+
raise "not supported via service"
|
36
|
+
end
|
37
|
+
|
38
|
+
# NOTE: Should support nil to mean not supported
|
39
|
+
# the size feature doesn't really makde sense for the service
|
40
|
+
def size
|
41
|
+
0
|
42
|
+
end
|
43
|
+
|
44
|
+
###
|
45
|
+
# Fetch coverband coverage via the API
|
46
|
+
# This would allow one to expore from the service and move back to the open source
|
47
|
+
# without having to reset coverage
|
48
|
+
###
|
49
|
+
def coverage(local_type = nil, opts = {})
|
50
|
+
return if Coverband.configuration.service_disabled_dev_test_env?
|
51
|
+
|
52
|
+
local_type ||= opts.key?(:override_type) ? opts[:override_type] : type
|
53
|
+
env_filter = opts.key?(:env_filter) ? opts[:env_filter] : "production"
|
54
|
+
uri = URI("#{coverband_url}/api/coverage?type=#{local_type}&env_filter=#{env_filter}")
|
55
|
+
req = Net::HTTP::Get.new(uri, "Content-Type" => "application/json", "Coverband-Token" => Coverband.configuration.api_key)
|
56
|
+
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
|
57
|
+
http.request(req)
|
58
|
+
end
|
59
|
+
coverage_data = JSON.parse(res.body)
|
60
|
+
coverage_data
|
61
|
+
rescue => e
|
62
|
+
logger&.error "Coverband: Error while retrieving coverage #{e}" if Coverband.configuration.verbose || Coverband.configuration.service_dev_mode
|
63
|
+
end
|
64
|
+
|
65
|
+
def save_report(report)
|
66
|
+
return if report.empty?
|
67
|
+
|
68
|
+
# We set here vs initialize to avoid setting on the primary process vs child processes
|
69
|
+
@pid ||= ::Process.pid
|
70
|
+
|
71
|
+
# TODO: do we need dup
|
72
|
+
# TODO: we don't need upstream timestamps, server will track first_seen
|
73
|
+
Thread.new do
|
74
|
+
data = expand_report(report.dup)
|
75
|
+
full_package = {
|
76
|
+
collection_type: "coverage_delta",
|
77
|
+
collection_data: {
|
78
|
+
tags: {
|
79
|
+
process_type: process_type,
|
80
|
+
app_loading: type == Coverband::EAGER_TYPE,
|
81
|
+
runtime_env: runtime_env,
|
82
|
+
pid: pid,
|
83
|
+
hostname: hostname
|
84
|
+
},
|
85
|
+
file_coverage: data
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
save_coverage(full_package)
|
90
|
+
retry_failed_reports
|
91
|
+
end&.join
|
92
|
+
end
|
93
|
+
|
94
|
+
def raw_store
|
95
|
+
raise "not supported via service"
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def retry_failed_reports
|
101
|
+
retries = []
|
102
|
+
@failed_coverage_reports.any? do
|
103
|
+
begin
|
104
|
+
report_body = @failed_coverage_reports.pop
|
105
|
+
send_report_body(report_body)
|
106
|
+
rescue
|
107
|
+
retries << report_body
|
108
|
+
end
|
109
|
+
end
|
110
|
+
retries.each do |report_body|
|
111
|
+
add_retry_message(report_body)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def add_retry_message(report_body)
|
116
|
+
if @failed_coverage_reports.length > 5
|
117
|
+
logger&.info "Coverband: The errored reporting queue has reached 5. Subsequent reports will not be transmitted"
|
118
|
+
else
|
119
|
+
@failed_coverage_reports << report_body
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def save_coverage(data)
|
124
|
+
if Coverband.configuration.api_key.nil?
|
125
|
+
puts "Coverband: Error: no Coverband API key was found!"
|
126
|
+
return
|
127
|
+
end
|
128
|
+
|
129
|
+
coverage_body = {remote_uuid: SecureRandom.uuid, data: data}.to_json
|
130
|
+
send_report_body(coverage_body)
|
131
|
+
rescue => e
|
132
|
+
add_retry_message(coverage_body)
|
133
|
+
logger&.info "Coverband: Error while saving coverage #{e}" if Coverband.configuration.verbose || Coverband.configuration.service_dev_mode
|
134
|
+
end
|
135
|
+
|
136
|
+
def send_report_body(coverage_body)
|
137
|
+
uri = URI("#{coverband_url}/api/collector")
|
138
|
+
req = ::Net::HTTP::Post.new(uri, "Content-Type" => "application/json", "Coverband-Token" => Coverband.configuration.api_key)
|
139
|
+
req.body = coverage_body
|
140
|
+
logger&.info "Coverband: saving (#{uri}) #{req.body}" if Coverband.configuration.verbose
|
141
|
+
res = ::Net::HTTP.start(
|
142
|
+
uri.hostname,
|
143
|
+
uri.port,
|
144
|
+
open_timeout: Coverband.configuration.coverband_timeout,
|
145
|
+
read_timeout: Coverband.configuration.coverband_timeout,
|
146
|
+
ssl_timeout: Coverband.configuration.coverband_timeout,
|
147
|
+
use_ssl: uri.scheme == "https"
|
148
|
+
) do |http|
|
149
|
+
http.request(req)
|
150
|
+
end
|
151
|
+
if res.code.to_i >= 500
|
152
|
+
add_retry_message(coverage_body)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -77,6 +77,16 @@ module Coverband
|
|
77
77
|
|
78
78
|
def transform_oneshot_lines_results(results)
|
79
79
|
results.each_with_object({}) do |(file, coverage), new_results|
|
80
|
+
###
|
81
|
+
# Eager filter:
|
82
|
+
# Normally I would break this out into additional methods
|
83
|
+
# and improve the readability but this is in a tight loop
|
84
|
+
# on the critical performance path, and any refactoring I come up with
|
85
|
+
# would slow down the performance.
|
86
|
+
###
|
87
|
+
next unless @@ignore_patterns.none? { |pattern| file.match(pattern) } &&
|
88
|
+
file.start_with?(@@project_directory)
|
89
|
+
|
80
90
|
@@stubs[file] ||= ::Coverage.line_stub(file)
|
81
91
|
transformed_line_counts = coverage[:oneshot_lines].each_with_object(@@stubs[file].dup) { |line_number, line_counts|
|
82
92
|
line_counts[line_number - 1] = 1
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coverband
|
4
|
+
module Collectors
|
5
|
+
###
|
6
|
+
# This class extends view tracker to support web service reporting
|
7
|
+
###
|
8
|
+
class ViewTrackerService < ViewTracker
|
9
|
+
def report_views_tracked
|
10
|
+
reported_time = Time.now.to_i
|
11
|
+
if views_to_record.any?
|
12
|
+
relative_views = views_to_record.map! do |view|
|
13
|
+
roots.each do |root|
|
14
|
+
view = view.gsub(/#{root}/, "")
|
15
|
+
end
|
16
|
+
view
|
17
|
+
end
|
18
|
+
save_tracked_views(views: relative_views, reported_time: reported_time)
|
19
|
+
end
|
20
|
+
self.views_to_record = []
|
21
|
+
rescue => e
|
22
|
+
# we don't want to raise errors if Coverband can't reach the service
|
23
|
+
logger&.error "Coverband: view_tracker failed to store, error #{e.class.name}" if Coverband.configuration.verbose || Coverband.configuration.service_dev_mode
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.supported_version?
|
27
|
+
defined?(Rails) && defined?(Rails::VERSION) && Rails::VERSION::STRING.split(".").first.to_i >= 4
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def logger
|
33
|
+
Coverband.configuration.logger
|
34
|
+
end
|
35
|
+
|
36
|
+
def save_tracked_views(views:, reported_time:)
|
37
|
+
uri = URI("#{Coverband.configuration.service_url}/api/collector")
|
38
|
+
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json", "Coverband-Token" => Coverband.configuration.api_key)
|
39
|
+
data = {
|
40
|
+
collection_type: "view_tracker_delta",
|
41
|
+
collection_data: {
|
42
|
+
tags: {
|
43
|
+
runtime_env: Coverband.configuration.coverband_env
|
44
|
+
},
|
45
|
+
collection_time: reported_time,
|
46
|
+
tracked_views: views
|
47
|
+
}
|
48
|
+
}
|
49
|
+
# puts "sending #{data}"
|
50
|
+
req.body = {remote_uuid: SecureRandom.uuid, data: data}.to_json
|
51
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
|
52
|
+
http.request(req)
|
53
|
+
end
|
54
|
+
rescue => e
|
55
|
+
logger&.error "Coverband: Error while saving coverage #{e}" if Coverband.configuration.verbose || Coverband.configuration.service_dev_mode
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -6,13 +6,14 @@ module Coverband
|
|
6
6
|
:additional_files, :verbose,
|
7
7
|
:reporter, :redis_namespace, :redis_ttl,
|
8
8
|
:background_reporting_enabled,
|
9
|
-
:
|
10
|
-
:
|
11
|
-
:
|
12
|
-
:reporting_wiggle
|
13
|
-
|
9
|
+
:test_env, :web_enable_clear, :gem_details, :web_debug, :report_on_exit,
|
10
|
+
:simulate_oneshot_lines_coverage,
|
11
|
+
:view_tracker
|
14
12
|
attr_writer :logger, :s3_region, :s3_bucket, :s3_access_key_id,
|
15
|
-
:s3_secret_access_key, :password
|
13
|
+
:s3_secret_access_key, :password, :api_key, :service_url, :coverband_timeout, :service_dev_mode,
|
14
|
+
:service_test_mode, :proces_type, :report_period, :track_views,
|
15
|
+
:background_reporting_sleep_seconds, :reporting_wiggle
|
16
|
+
|
16
17
|
attr_reader :track_gems, :ignore, :use_oneshot_lines_coverage
|
17
18
|
|
18
19
|
#####
|
@@ -61,12 +62,13 @@ module Coverband
|
|
61
62
|
@logger = nil
|
62
63
|
@store = nil
|
63
64
|
@background_reporting_enabled = true
|
64
|
-
@background_reporting_sleep_seconds =
|
65
|
+
@background_reporting_sleep_seconds = nil
|
65
66
|
@test_env = nil
|
66
67
|
@web_enable_clear = false
|
67
68
|
@track_gems = false
|
68
69
|
@gem_details = false
|
69
|
-
@track_views =
|
70
|
+
@track_views = true
|
71
|
+
@view_tracker = nil
|
70
72
|
@web_debug = false
|
71
73
|
@report_on_exit = true
|
72
74
|
@use_oneshot_lines_coverage = ENV["ONESHOT"] || false
|
@@ -76,6 +78,15 @@ module Coverband
|
|
76
78
|
@all_root_patterns = nil
|
77
79
|
@password = nil
|
78
80
|
|
81
|
+
# coverband service settings
|
82
|
+
@api_key = nil
|
83
|
+
@service_url = nil
|
84
|
+
@coverband_timeout = nil
|
85
|
+
@service_dev_mode = nil
|
86
|
+
@service_test_mode = nil
|
87
|
+
@proces_type = nil
|
88
|
+
@report_period = nil
|
89
|
+
|
79
90
|
# TODO: these are deprecated
|
80
91
|
@s3_region = nil
|
81
92
|
@s3_bucket = nil
|
@@ -115,8 +126,29 @@ module Coverband
|
|
115
126
|
puts "deprecated, s3 is no longer support"
|
116
127
|
end
|
117
128
|
|
129
|
+
def background_reporting_sleep_seconds
|
130
|
+
@background_reporting_sleep_seconds ||= if Coverband.coverband_service?
|
131
|
+
Coverband.configuration.coverband_env == "production" ? Coverband.configuration.report_period : 60
|
132
|
+
else
|
133
|
+
60
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def reporting_wiggle
|
138
|
+
@reporting_wiggle ||= 30
|
139
|
+
end
|
140
|
+
|
118
141
|
def store
|
119
|
-
@store ||= Coverband
|
142
|
+
@store ||= if Coverband.coverband_service?
|
143
|
+
require "coverband/adapters/web_service_store"
|
144
|
+
Coverband::Adapters::WebServiceStore.new(service_url)
|
145
|
+
else
|
146
|
+
Coverband::Adapters::RedisStore.new(Redis.new(url: redis_url), redis_store_options)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def track_views
|
151
|
+
@track_views ||= service_disabled_dev_test_env? ? false : true
|
120
152
|
end
|
121
153
|
|
122
154
|
def store=(store)
|
@@ -189,6 +221,43 @@ module Coverband
|
|
189
221
|
Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.6.0")
|
190
222
|
end
|
191
223
|
|
224
|
+
def api_key
|
225
|
+
@api_key ||= ENV["COVERBAND_API_KEY"]
|
226
|
+
end
|
227
|
+
|
228
|
+
def service_url
|
229
|
+
@service_url ||= ENV["COVERBAND_URL"] || "https://coverband.io"
|
230
|
+
end
|
231
|
+
|
232
|
+
def coverband_env
|
233
|
+
ENV["RACK_ENV"] || ENV["RAILS_ENV"] || (defined?(Rails) && Rails.respond_to?(:env) ? Rails.env : "unknown")
|
234
|
+
end
|
235
|
+
|
236
|
+
def coverband_timeout
|
237
|
+
@coverband_timeout ||= coverband_env == "development" ? 5 : 2
|
238
|
+
end
|
239
|
+
|
240
|
+
def service_dev_mode
|
241
|
+
@service_dev_mode ||= ENV["COVERBAND_ENABLE_DEV_MODE"] || false
|
242
|
+
end
|
243
|
+
|
244
|
+
def service_test_mode
|
245
|
+
@service_dev_mode ||= ENV["COVERBAND_ENABLE_TEST_MODE"] || false
|
246
|
+
end
|
247
|
+
|
248
|
+
def proces_type
|
249
|
+
@process_type ||= ENV["PROCESS_TYPE"] || "unknown"
|
250
|
+
end
|
251
|
+
|
252
|
+
def report_period
|
253
|
+
@process_type ||= (ENV["COVERBAND_REPORT_PERIOD"] || 600).to_i
|
254
|
+
end
|
255
|
+
|
256
|
+
def service_disabled_dev_test_env?
|
257
|
+
(coverband_env == "test" && !Coverband.configuration.service_test_mode) ||
|
258
|
+
(coverband_env == "development" && !Coverband.configuration.service_dev_mode)
|
259
|
+
end
|
260
|
+
|
192
261
|
private
|
193
262
|
|
194
263
|
def redis_url
|
@@ -28,18 +28,18 @@ module Coverband
|
|
28
28
|
return if running?
|
29
29
|
|
30
30
|
logger.debug("Coverband: Starting background reporting") if Coverband.configuration.verbose
|
31
|
-
sleep_seconds = Coverband.configuration.background_reporting_sleep_seconds
|
31
|
+
sleep_seconds = Coverband.configuration.background_reporting_sleep_seconds.to_i
|
32
32
|
@thread = Thread.new {
|
33
33
|
loop do
|
34
34
|
Coverband.report_coverage
|
35
35
|
Coverband.configuration.view_tracker&.report_views_tracked
|
36
36
|
if Coverband.configuration.reporting_wiggle
|
37
|
-
sleep_seconds = Coverband.configuration.background_reporting_sleep_seconds + rand(Coverband.configuration.reporting_wiggle.to_i)
|
37
|
+
sleep_seconds = Coverband.configuration.background_reporting_sleep_seconds.to_i + rand(Coverband.configuration.reporting_wiggle.to_i)
|
38
38
|
end
|
39
39
|
if Coverband.configuration.verbose
|
40
40
|
logger.debug("Coverband: background reporting coverage (#{Coverband.configuration.store.type}). Sleeping #{sleep_seconds}s")
|
41
41
|
end
|
42
|
-
sleep(sleep_seconds)
|
42
|
+
sleep(sleep_seconds.to_i)
|
43
43
|
end
|
44
44
|
}
|
45
45
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "coverband"
|
4
|
+
|
3
5
|
begin
|
4
6
|
require "rack"
|
5
7
|
rescue LoadError
|
@@ -11,10 +13,6 @@ module Coverband
|
|
11
13
|
class Web
|
12
14
|
attr_reader :request
|
13
15
|
|
14
|
-
def initialize
|
15
|
-
init_web
|
16
|
-
end
|
17
|
-
|
18
16
|
def init_web
|
19
17
|
full_path = Gem::Specification.find_by_name("coverband").full_gem_path
|
20
18
|
@static = Rack::Static.new(self,
|
@@ -15,11 +15,16 @@ module Coverband
|
|
15
15
|
app.middleware.use Coverband::BackgroundMiddleware
|
16
16
|
|
17
17
|
if Coverband.configuration.track_views
|
18
|
-
|
19
|
-
|
18
|
+
COVERBAND_VIEW_TRACKER = if Coverband.coverband_service?
|
19
|
+
Coverband::Collectors::ViewTrackerService.new
|
20
|
+
else
|
21
|
+
Coverband::Collectors::ViewTracker.new
|
22
|
+
end
|
23
|
+
|
24
|
+
Coverband.configuration.view_tracker = COVERBAND_VIEW_TRACKER
|
20
25
|
|
21
26
|
ActiveSupport::Notifications.subscribe(/render_partial.action_view|render_template.action_view/) do |name, start, finish, id, payload|
|
22
|
-
|
27
|
+
COVERBAND_VIEW_TRACKER.track_views(name, start, finish, id, payload) unless name.include?("!")
|
23
28
|
end
|
24
29
|
end
|
25
30
|
rescue Redis::CannotConnectError => error
|
@@ -30,6 +35,7 @@ module Coverband
|
|
30
35
|
|
31
36
|
config.after_initialize do
|
32
37
|
unless Coverband.tasks_to_ignore?
|
38
|
+
Coverband.configure
|
33
39
|
Coverband.eager_loading_coverage!
|
34
40
|
Coverband.report_coverage
|
35
41
|
Coverband.runtime_coverage!
|
@@ -39,7 +45,6 @@ module Coverband
|
|
39
45
|
config.before_configuration do
|
40
46
|
unless ENV["COVERBAND_DISABLE_AUTO_START"]
|
41
47
|
begin
|
42
|
-
Coverband.configure
|
43
48
|
Coverband.start
|
44
49
|
rescue Redis::CannotConnectError => error
|
45
50
|
Coverband.configuration.logger.info "Redis is not available (#{error}), Coverband not configured"
|
@@ -12,6 +12,7 @@ namespace :coverband do
|
|
12
12
|
desc "report runtime Coverband code coverage"
|
13
13
|
task :coverage_server do
|
14
14
|
Rake.application["environment"].invoke if Rake::Task.task_defined?("environment")
|
15
|
+
Coverband.configuration.store.merge_mode = true if Coverband.configuration.store.is_a?(Coverband::Adapters::FileStore)
|
15
16
|
Rack::Server.start app: Coverband::Reporters::Web.new, Port: ENV.fetch("COVERBAND_COVERAGE_PORT", 1022).to_i
|
16
17
|
end
|
17
18
|
|
data/lib/coverband/version.rb
CHANGED
@@ -13,14 +13,13 @@ class AdaptersFileStoreTest < Minitest::Test
|
|
13
13
|
def setup
|
14
14
|
super
|
15
15
|
@test_file_path = "/tmp/coverband_filestore_test_path.json"
|
16
|
-
|
16
|
+
previous_file_path = "#{@test_file_path}.#{::Process.pid}"
|
17
|
+
`rm #{@test_file_path}` if File.exist?(@test_file_path)
|
18
|
+
`rm #{previous_file_path}` if File.exist?(previous_file_path)
|
19
|
+
File.open(previous_file_path, "w") { |f| f.write(test_data.to_json) }
|
17
20
|
@store = Coverband::Adapters::FileStore.new(@test_file_path)
|
18
21
|
end
|
19
22
|
|
20
|
-
def test_size
|
21
|
-
assert @store.size > 1
|
22
|
-
end
|
23
|
-
|
24
23
|
def test_coverage
|
25
24
|
assert_equal @store.coverage["dog.rb"]["data"][0], 1
|
26
25
|
assert_equal @store.coverage["dog.rb"]["data"][1], 2
|
@@ -31,7 +30,7 @@ class AdaptersFileStoreTest < Minitest::Test
|
|
31
30
|
end
|
32
31
|
|
33
32
|
def test_covered_files
|
34
|
-
|
33
|
+
assert @store.covered_files.include?("dog.rb")
|
35
34
|
end
|
36
35
|
|
37
36
|
def test_clear
|
@@ -43,6 +42,7 @@ class AdaptersFileStoreTest < Minitest::Test
|
|
43
42
|
mock_file_hash
|
44
43
|
@store.send(:save_report, "cat.rb" => [0, 1])
|
45
44
|
assert_equal @store.coverage["cat.rb"]["data"][1], 1
|
45
|
+
assert @store.size > 1
|
46
46
|
end
|
47
47
|
|
48
48
|
def test_data
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require File.expand_path("../../test_helper", File.dirname(__FILE__))
|
4
|
+
require File.expand_path("../../../lib/coverband/adapters/web_service_store", File.dirname(__FILE__))
|
5
|
+
|
6
|
+
class WebServiceStoreTest < Minitest::Test
|
7
|
+
COVERBAND_SERVICE_URL = "http://localhost:12345"
|
8
|
+
FAKE_API_KEY = "12345"
|
9
|
+
|
10
|
+
def setup
|
11
|
+
WebMock.disable_net_connect!
|
12
|
+
super
|
13
|
+
@store = Coverband::Adapters::WebServiceStore.new(COVERBAND_SERVICE_URL)
|
14
|
+
Coverband.configuration.store = @store
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_coverage
|
18
|
+
Coverband.configuration.api_key = FAKE_API_KEY
|
19
|
+
stub_request(:post, "#{COVERBAND_SERVICE_URL}/api/collector").to_return(body: {status: "OK"}.to_json, status: 200)
|
20
|
+
mock_file_hash
|
21
|
+
@store.save_report(basic_coverage)
|
22
|
+
end
|
23
|
+
|
24
|
+
# TODO: sort out a retry test
|
25
|
+
# def test_retries
|
26
|
+
# Coverband.configuration.api_key = FAKE_API_KEY
|
27
|
+
# stub_request(:post, "#{COVERBAND_SERVICE_URL}/api/collector").to_return(body: {status: "OK"}.to_json, status: 200)
|
28
|
+
# mock_file_hash
|
29
|
+
# @store.save_report(basic_coverage)
|
30
|
+
# end
|
31
|
+
|
32
|
+
def test_no_webservice_call_without_api_key
|
33
|
+
Coverband.configuration.api_key = nil
|
34
|
+
mock_file_hash
|
35
|
+
@store.save_report(basic_coverage)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_clear
|
39
|
+
assert_raises RuntimeError do
|
40
|
+
@store.clear!
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_clear_file
|
45
|
+
assert_raises RuntimeError do
|
46
|
+
@store.clear_file!("app_path/dog.rb")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_size
|
51
|
+
mock_file_hash
|
52
|
+
@store.type = :eager_loading
|
53
|
+
@store.save_report("app_path/dog.rb" => [0, 1, 1])
|
54
|
+
assert @store.size, 1
|
55
|
+
end
|
56
|
+
end
|
@@ -114,6 +114,7 @@ class CollectorsDeltaTest < Minitest::Test
|
|
114
114
|
oneshot_lines: [2, 3]
|
115
115
|
}
|
116
116
|
}
|
117
|
+
Coverband::Collectors::Delta.class_variable_set(:@@project_directory, "dealership.rb")
|
117
118
|
::Coverage.expects(:line_stub).with("dealership.rb").returns([nil, 0, 0, nil])
|
118
119
|
results = Coverband::Collectors::Delta.results(mock_coverage(current_coverage))
|
119
120
|
expected = {
|
@@ -15,7 +15,7 @@ class BackgroundTest < Minitest::Test
|
|
15
15
|
def test_start
|
16
16
|
Thread.expects(:new).yields.returns(ThreadDouble.new(true))
|
17
17
|
Coverband::Background.expects(:loop).yields
|
18
|
-
Coverband::Background.expects(:sleep).with(
|
18
|
+
Coverband::Background.expects(:sleep).with(60)
|
19
19
|
Coverband::Collectors::Coverage.instance.expects(:report_coverage).once
|
20
20
|
2.times { Coverband::Background.start }
|
21
21
|
end
|
@@ -23,7 +23,7 @@ class BackgroundTest < Minitest::Test
|
|
23
23
|
def test_start_with_wiggle
|
24
24
|
Thread.expects(:new).yields.returns(ThreadDouble.new(true))
|
25
25
|
Coverband::Background.expects(:loop).yields
|
26
|
-
Coverband::Background.expects(:sleep).with(
|
26
|
+
Coverband::Background.expects(:sleep).with(65)
|
27
27
|
Coverband::Background.expects(:rand).with(10).returns(5)
|
28
28
|
Coverband.configuration.reporting_wiggle = 10
|
29
29
|
Coverband::Collectors::Coverage.instance.expects(:report_coverage).once
|
@@ -33,7 +33,7 @@ class BackgroundTest < Minitest::Test
|
|
33
33
|
def test_start_dead_thread
|
34
34
|
Thread.expects(:new).yields.returns(ThreadDouble.new(false)).twice
|
35
35
|
Coverband::Background.expects(:loop).yields.twice
|
36
|
-
Coverband::Background.expects(:sleep).with(
|
36
|
+
Coverband::Background.expects(:sleep).with(60).twice
|
37
37
|
Coverband::Collectors::Coverage.instance.expects(:report_coverage).twice
|
38
38
|
2.times { Coverband::Background.start }
|
39
39
|
end
|
data/test/test_helper.rb
CHANGED
@@ -21,6 +21,7 @@ require "coverband/utils/source_file"
|
|
21
21
|
require "coverband/utils/lines_classifier"
|
22
22
|
require "coverband/utils/results"
|
23
23
|
require "coverband/reporters/html_report"
|
24
|
+
require "webmock/minitest"
|
24
25
|
|
25
26
|
# require 'pry-byebug' unless ENV['CI'] # Ruby 2.3 on CI crashes on pry & JRuby doesn't support it
|
26
27
|
require_relative "unique_files"
|
@@ -51,6 +52,7 @@ module Coverband
|
|
51
52
|
Coverband::Collectors::Coverage.instance.reset_instance
|
52
53
|
Coverband::Utils::RelativeFileConverter.reset
|
53
54
|
Coverband::Utils::AbsoluteFileConverter.reset
|
55
|
+
Coverband.configuration.reporting_wiggle = 0
|
54
56
|
Coverband.configuration.redis_namespace = "coverband_test"
|
55
57
|
Coverband::Background.stop
|
56
58
|
Coverband.configuration.store.instance_variable_set(:@redis, redis)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: coverband
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.0.0.rc.
|
4
|
+
version: 5.0.0.rc.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dan Mayer
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2020-
|
12
|
+
date: 2020-08-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: benchmark-ips
|
@@ -207,6 +207,20 @@ dependencies:
|
|
207
207
|
- - ">="
|
208
208
|
- !ruby/object:Gem::Version
|
209
209
|
version: '0'
|
210
|
+
- !ruby/object:Gem::Dependency
|
211
|
+
name: webmock
|
212
|
+
requirement: !ruby/object:Gem::Requirement
|
213
|
+
requirements:
|
214
|
+
- - ">="
|
215
|
+
- !ruby/object:Gem::Version
|
216
|
+
version: '0'
|
217
|
+
type: :development
|
218
|
+
prerelease: false
|
219
|
+
version_requirements: !ruby/object:Gem::Requirement
|
220
|
+
requirements:
|
221
|
+
- - ">="
|
222
|
+
- !ruby/object:Gem::Version
|
223
|
+
version: '0'
|
210
224
|
- !ruby/object:Gem::Dependency
|
211
225
|
name: redis
|
212
226
|
requirement: !ruby/object:Gem::Requirement
|
@@ -249,10 +263,13 @@ files:
|
|
249
263
|
- lib/coverband/adapters/file_store.rb
|
250
264
|
- lib/coverband/adapters/hash_redis_store.rb
|
251
265
|
- lib/coverband/adapters/redis_store.rb
|
266
|
+
- lib/coverband/adapters/stdout_store.rb
|
267
|
+
- lib/coverband/adapters/web_service_store.rb
|
252
268
|
- lib/coverband/at_exit.rb
|
253
269
|
- lib/coverband/collectors/coverage.rb
|
254
270
|
- lib/coverband/collectors/delta.rb
|
255
271
|
- lib/coverband/collectors/view_tracker.rb
|
272
|
+
- lib/coverband/collectors/view_tracker_service.rb
|
256
273
|
- lib/coverband/configuration.rb
|
257
274
|
- lib/coverband/integrations/background.rb
|
258
275
|
- lib/coverband/integrations/background_middleware.rb
|
@@ -319,6 +336,7 @@ files:
|
|
319
336
|
- test/coverband/adapters/file_store_test.rb
|
320
337
|
- test/coverband/adapters/hash_redis_store_test.rb
|
321
338
|
- test/coverband/adapters/redis_store_test.rb
|
339
|
+
- test/coverband/adapters/web_service_store_test.rb
|
322
340
|
- test/coverband/at_exit_test.rb
|
323
341
|
- test/coverband/collectors/coverage_test.rb
|
324
342
|
- test/coverband/collectors/delta_test.rb
|
@@ -436,6 +454,7 @@ test_files:
|
|
436
454
|
- test/coverband/adapters/file_store_test.rb
|
437
455
|
- test/coverband/adapters/hash_redis_store_test.rb
|
438
456
|
- test/coverband/adapters/redis_store_test.rb
|
457
|
+
- test/coverband/adapters/web_service_store_test.rb
|
439
458
|
- test/coverband/at_exit_test.rb
|
440
459
|
- test/coverband/collectors/coverage_test.rb
|
441
460
|
- test/coverband/collectors/delta_test.rb
|