coverband 5.0.0.rc.3 → 5.0.0.rc.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|