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 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