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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c59cff2fa063db7a0c165e9cfefa8607151ee39699bf57cbb6bb6fce849abe97
4
- data.tar.gz: 6fedd5cc2efd695370b3136f293209184b8c633e093ae75ac2f7d083c1eec13d
3
+ metadata.gz: c83a78e28c79671ef144ffa38e40ee061a873ef6a207437f01923188c17d3cf0
4
+ data.tar.gz: 80a3c0a46cfb55a5972139e034733d9fb6541f0d547caff1f004203b9ddb0484
5
5
  SHA512:
6
- metadata.gz: 75740ba292febb787cbb40beaa47b1a355d9303b23b43fc949d77f918848497fca538e080e86baf5d898b4a5001a9a838ed64846cb5a039a6aaa2a300f39edf0
7
- data.tar.gz: 6ed03c519ee7a4a5d24f6399c8a1a747d2de58d0a6370575f72bed528a18cd4d24c941c6e7f60902ced21883e4f664158a91881b3518577722323cb5cc5b904c
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 4.X
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
 
@@ -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
@@ -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.fetch("COVERBAND_CONFIG", CONFIG_FILE)
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
- # FilesStore store a merged coverage file to local disk
7
- # Generally this is for testing and development
8
- # Not recommended for production deployment, as it doesn't handle concurrency
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 File.exist?(path)
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.open(path, "w") { |f| f.write(data.to_json) }
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
- :background_reporting_sleep_seconds, :test_env,
10
- :web_enable_clear, :gem_details, :web_debug, :report_on_exit,
11
- :simulate_oneshot_lines_coverage, :track_views, :view_tracker,
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 = 30
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 = false
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::Adapters::RedisStore.new(Redis.new(url: redis_url), redis_store_options)
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
- CoverbandViewTracker = Coverband::Collectors::ViewTracker.new
19
- Coverband.configuration.view_tracker = CoverbandViewTracker
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
- CoverbandViewTracker.track_views(name, start, finish, id, payload) unless name.include?("!")
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
 
@@ -5,5 +5,5 @@
5
5
  # use format '4.2.1.rc.1' ~> 4.2.1.rc to prerelease versions like v4.2.1.rc.2 and v4.2.1.rc.3
6
6
  ###
7
7
  module Coverband
8
- VERSION = "5.0.0.rc.3"
8
+ VERSION = "5.0.0.rc.4"
9
9
  end
@@ -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
- File.open(@test_file_path, "w") { |f| f.write(test_data.to_json) }
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
- assert_equal @store.covered_files, ["dog.rb"]
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(30)
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(35)
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(30).twice
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
@@ -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.3
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-07-30 00:00:00.000000000 Z
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