coverband 4.2.7 → 5.0.0.rc.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/.standard.yml +26 -0
  3. data/.travis.yml +2 -1
  4. data/Gemfile +5 -7
  5. data/Gemfile.rails4 +0 -3
  6. data/Gemfile.rails6 +0 -3
  7. data/README.md +8 -77
  8. data/Rakefile +17 -17
  9. data/changes.md +30 -28
  10. data/config.ru +1 -1
  11. data/coverband.gemspec +31 -34
  12. data/lib/coverband.rb +68 -44
  13. data/lib/coverband/adapters/base.rb +17 -18
  14. data/lib/coverband/adapters/file_store.rb +38 -6
  15. data/lib/coverband/adapters/hash_redis_store.rb +24 -21
  16. data/lib/coverband/adapters/redis_store.rb +12 -12
  17. data/lib/coverband/adapters/stdout_store.rb +41 -0
  18. data/lib/coverband/adapters/web_service_store.rb +155 -0
  19. data/lib/coverband/at_exit.rb +1 -1
  20. data/lib/coverband/collectors/coverage.rb +15 -27
  21. data/lib/coverband/collectors/delta.rb +29 -9
  22. data/lib/coverband/collectors/view_tracker.rb +10 -10
  23. data/lib/coverband/collectors/view_tracker_service.rb +59 -0
  24. data/lib/coverband/configuration.rb +149 -112
  25. data/lib/coverband/integrations/background.rb +6 -6
  26. data/lib/coverband/integrations/rack_server_check.rb +3 -3
  27. data/lib/coverband/reporters/base.rb +11 -12
  28. data/lib/coverband/reporters/console_report.rb +1 -1
  29. data/lib/coverband/reporters/html_report.rb +10 -30
  30. data/lib/coverband/reporters/web.rb +55 -50
  31. data/lib/coverband/utils/absolute_file_converter.rb +6 -6
  32. data/lib/coverband/utils/html_formatter.rb +32 -61
  33. data/lib/coverband/utils/railtie.rb +23 -6
  34. data/lib/coverband/utils/relative_file_converter.rb +2 -2
  35. data/lib/coverband/utils/result.rb +6 -11
  36. data/lib/coverband/utils/results.rb +0 -10
  37. data/lib/coverband/utils/source_file.rb +21 -30
  38. data/lib/coverband/utils/tasks.rb +8 -11
  39. data/lib/coverband/version.rb +1 -1
  40. data/public/application.js +0 -30
  41. data/test/benchmarks/benchmark.rake +97 -92
  42. data/test/benchmarks/dog.rb +1 -1
  43. data/test/benchmarks/init_rails.rake +4 -4
  44. data/test/coverband/adapters/base_test.rb +29 -30
  45. data/test/coverband/adapters/file_store_test.rb +19 -20
  46. data/test/coverband/adapters/hash_redis_store_test.rb +57 -57
  47. data/test/coverband/adapters/redis_store_test.rb +26 -26
  48. data/test/coverband/adapters/web_service_store_test.rb +56 -0
  49. data/test/coverband/at_exit_test.rb +2 -2
  50. data/test/coverband/collectors/coverage_test.rb +33 -47
  51. data/test/coverband/collectors/delta_test.rb +52 -23
  52. data/test/coverband/collectors/view_tracker_test.rb +35 -35
  53. data/test/coverband/configuration_test.rb +49 -36
  54. data/test/coverband/coverband_test.rb +11 -11
  55. data/test/coverband/integrations/background_middleware_test.rb +10 -10
  56. data/test/coverband/integrations/background_test.rb +17 -5
  57. data/test/coverband/integrations/rack_server_check_test.rb +7 -7
  58. data/test/coverband/integrations/report_middleware_test.rb +9 -9
  59. data/test/coverband/integrations/resque_worker_test.rb +9 -9
  60. data/test/coverband/integrations/test_resque_job.rb +1 -1
  61. data/test/coverband/reporters/base_test.rb +9 -9
  62. data/test/coverband/reporters/console_test.rb +6 -6
  63. data/test/coverband/reporters/html_test.rb +36 -48
  64. data/test/coverband/reporters/web_test.rb +16 -18
  65. data/test/coverband/utils/absolute_file_converter_test.rb +22 -22
  66. data/test/coverband/utils/file_hasher_test.rb +6 -12
  67. data/test/coverband/utils/file_list_test.rb +13 -13
  68. data/test/coverband/utils/html_formatter_test.rb +9 -23
  69. data/test/coverband/utils/lines_classifier_test.rb +29 -29
  70. data/test/coverband/utils/relative_file_converter_test.rb +13 -13
  71. data/test/coverband/utils/result_test.rb +18 -18
  72. data/test/coverband/utils/results_test.rb +17 -17
  73. data/test/coverband/utils/source_file_line_test.rb +46 -46
  74. data/test/coverband/utils/source_file_test.rb +38 -88
  75. data/test/dog.rb +1 -1
  76. data/test/fake_app/basic_rack.rb +2 -2
  77. data/test/fixtures/app/controllers/sample_controller.rb +1 -1
  78. data/test/fixtures/app/models/user.rb +1 -1
  79. data/test/fixtures/sample.rb +1 -1
  80. data/test/fixtures/utf-8.rb +0 -2
  81. data/test/forked/rails_full_stack_test.rb +24 -27
  82. data/test/forked/rails_rake_full_stack_test.rb +7 -26
  83. data/test/integration/full_stack_test.rb +11 -22
  84. data/test/jruby_check.rb +2 -3
  85. data/test/rails4_dummy/Rakefile +1 -1
  86. data/test/rails4_dummy/config.ru +1 -1
  87. data/test/rails4_dummy/config/application.rb +4 -4
  88. data/test/rails4_dummy/config/boot.rb +2 -2
  89. data/test/rails4_dummy/config/coverband.rb +1 -1
  90. data/test/rails4_dummy/config/coverband_missing_redis.rb +1 -1
  91. data/test/rails4_dummy/config/environment.rb +1 -1
  92. data/test/rails4_dummy/config/routes.rb +2 -2
  93. data/test/rails5_dummy/Rakefile +1 -1
  94. data/test/rails5_dummy/config.ru +1 -1
  95. data/test/rails5_dummy/config/application.rb +3 -3
  96. data/test/rails5_dummy/config/coverband.rb +8 -8
  97. data/test/rails5_dummy/config/coverband_missing_redis.rb +8 -8
  98. data/test/rails5_dummy/config/environment.rb +1 -1
  99. data/test/rails5_dummy/config/routes.rb +2 -2
  100. data/test/rails6_dummy/Rakefile +1 -1
  101. data/test/rails6_dummy/config.ru +1 -1
  102. data/test/rails6_dummy/config/application.rb +4 -4
  103. data/test/rails6_dummy/config/boot.rb +2 -2
  104. data/test/rails6_dummy/config/coverband.rb +1 -1
  105. data/test/rails6_dummy/config/coverband_missing_redis.rb +1 -1
  106. data/test/rails6_dummy/config/environment.rb +1 -1
  107. data/test/rails6_dummy/config/routes.rb +2 -2
  108. data/test/rails_test_helper.rb +11 -11
  109. data/test/test_helper.rb +44 -34
  110. data/test/unique_files.rb +10 -10
  111. data/views/layout.erb +2 -12
  112. metadata +28 -34
  113. data/.rubocop.yml +0 -86
  114. data/lib/coverband/integrations/bundler.rb +0 -8
  115. data/lib/coverband/utils/file_groups.rb +0 -53
  116. data/lib/coverband/utils/gem_list.rb +0 -31
  117. data/lib/coverband/utils/s3_report.rb +0 -105
  118. data/test/coverband/utils/file_groups_test.rb +0 -61
  119. data/test/coverband/utils/gem_list_test.rb +0 -48
  120. data/test/coverband/utils/s3_report_test.rb +0 -44
  121. data/views/gem_list.erb +0 -63
@@ -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,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coverband
4
+ module Adapters
5
+ ###
6
+ # WebServiceStore: store a checkpoint of coverage to a remote service
7
+ ###
8
+ class WebServiceStore < Base
9
+ attr_reader :coverband_url, :process_type, :runtime_env, :hostname, :pid
10
+
11
+ def initialize(coverband_url, opts = {})
12
+ super()
13
+ require "socket"
14
+ require "securerandom"
15
+ @coverband_url = coverband_url
16
+ @process_type = opts.fetch(:process_type) { $PROGRAM_NAME&.split("/")&.last || Coverband.configuration.process_type }
17
+ @hostname = opts.fetch(:hostname) { ENV["DYNO"] || Socket.gethostname.force_encoding("utf-8").encode }
18
+ @hostname = @hostname.delete("'", "").delete("’", "")
19
+ @runtime_env = opts.fetch(:runtime_env) { Coverband.configuration.coverband_env }
20
+ @failed_coverage_reports = []
21
+ end
22
+
23
+ def logger
24
+ Coverband.configuration.logger
25
+ end
26
+
27
+ def clear!
28
+ # done via service UI
29
+ raise "not supported via service"
30
+ end
31
+
32
+ def clear_file!(filename)
33
+ # done via service UI
34
+ raise "not supported via service"
35
+ end
36
+
37
+ # NOTE: Should support nil to mean not supported
38
+ # the size feature doesn't really makde sense for the service
39
+ def size
40
+ 0
41
+ end
42
+
43
+ ###
44
+ # Fetch coverband coverage via the API
45
+ # This would allow one to expore from the service and move back to the open source
46
+ # without having to reset coverage
47
+ ###
48
+ def coverage(local_type = nil, opts = {})
49
+ return if Coverband.configuration.service_disabled_dev_test_env?
50
+
51
+ local_type ||= opts.key?(:override_type) ? opts[:override_type] : type
52
+ env_filter = opts.key?(:env_filter) ? opts[:env_filter] : "production"
53
+ uri = URI("#{coverband_url}/api/coverage?type=#{local_type}&env_filter=#{env_filter}")
54
+ req = Net::HTTP::Get.new(uri, "Content-Type" => "application/json", "Coverband-Token" => Coverband.configuration.api_key)
55
+ res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
56
+ http.request(req)
57
+ end
58
+ JSON.parse(res.body)
59
+ rescue => e
60
+ logger&.error "Coverband: Error while retrieving coverage #{e}" if Coverband.configuration.verbose || Coverband.configuration.service_dev_mode
61
+ end
62
+
63
+ def save_report(report)
64
+ return if report.empty?
65
+
66
+ # We set here vs initialize to avoid setting on the primary process vs child processes
67
+ @pid ||= ::Process.pid
68
+
69
+ # TODO: do we need dup
70
+ # TODO: we don't need upstream timestamps, server will track first_seen
71
+ Thread.new do
72
+ data = expand_report(report.dup)
73
+ full_package = {
74
+ collection_type: "coverage_delta",
75
+ collection_data: {
76
+ tags: {
77
+ process_type: process_type,
78
+ app_loading: type == Coverband::EAGER_TYPE,
79
+ runtime_env: runtime_env,
80
+ pid: pid,
81
+ hostname: hostname
82
+ },
83
+ file_coverage: data
84
+ }
85
+ }
86
+
87
+ save_coverage(full_package)
88
+ retry_failed_reports
89
+ end&.join
90
+ end
91
+
92
+ def raw_store
93
+ raise "not supported via service"
94
+ end
95
+
96
+ private
97
+
98
+ def retry_failed_reports
99
+ retries = []
100
+ @failed_coverage_reports.any? do
101
+ begin
102
+ report_body = @failed_coverage_reports.pop
103
+ send_report_body(report_body)
104
+ rescue
105
+ retries << report_body
106
+ end
107
+ end
108
+ retries.each do |report_body|
109
+ add_retry_message(report_body)
110
+ end
111
+ end
112
+
113
+ def add_retry_message(report_body)
114
+ if @failed_coverage_reports.length > 5
115
+ logger&.info "Coverband: The errored reporting queue has reached 5. Subsequent reports will not be transmitted"
116
+ else
117
+ @failed_coverage_reports << report_body
118
+ end
119
+ end
120
+
121
+ def save_coverage(data)
122
+ if Coverband.configuration.api_key.nil?
123
+ puts "Coverband: Error: no Coverband API key was found!"
124
+ return
125
+ end
126
+
127
+ coverage_body = {remote_uuid: SecureRandom.uuid, data: data}.to_json
128
+ send_report_body(coverage_body)
129
+ rescue => e
130
+ add_retry_message(coverage_body)
131
+ logger&.info "Coverband: Error while saving coverage #{e}" if Coverband.configuration.verbose || Coverband.configuration.service_dev_mode
132
+ end
133
+
134
+ def send_report_body(coverage_body)
135
+ uri = URI("#{coverband_url}/api/collector")
136
+ req = ::Net::HTTP::Post.new(uri, "Content-Type" => "application/json", "Coverband-Token" => Coverband.configuration.api_key)
137
+ req.body = coverage_body
138
+ logger&.info "Coverband: saving (#{uri}) #{req.body}" if Coverband.configuration.verbose
139
+ res = ::Net::HTTP.start(
140
+ uri.hostname,
141
+ uri.port,
142
+ open_timeout: Coverband.configuration.coverband_timeout,
143
+ read_timeout: Coverband.configuration.coverband_timeout,
144
+ ssl_timeout: Coverband.configuration.coverband_timeout,
145
+ use_ssl: uri.scheme == "https"
146
+ ) do |http|
147
+ http.request(req)
148
+ end
149
+ if res.code.to_i >= 500
150
+ add_retry_message(coverage_body)
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -6,7 +6,7 @@ module Coverband
6
6
 
7
7
  @at_exit_registered = nil
8
8
  def self.register
9
- return if ENV['COVERBAND_DISABLE_AT_EXIT']
9
+ return if ENV["COVERBAND_DISABLE_AT_EXIT"]
10
10
  return if @at_exit_registered
11
11
 
12
12
  @semaphore.synchronize do
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'singleton'
4
- require_relative 'delta'
3
+ require "singleton"
4
+ require_relative "delta"
5
5
 
6
6
  module Coverband
7
7
  module Collectors
@@ -25,7 +25,6 @@ module Coverband
25
25
  @verbose = Coverband.configuration.verbose
26
26
  @logger = Coverband.configuration.logger
27
27
  @test_env = Coverband.configuration.test_env
28
- @track_gems = Coverband.configuration.track_gems
29
28
  Delta.reset
30
29
  self
31
30
  end
@@ -49,53 +48,42 @@ module Coverband
49
48
 
50
49
  def report_coverage
51
50
  @semaphore.synchronize do
52
- raise 'no Coverband store set' unless @store
51
+ raise "no Coverband store set" unless @store
53
52
 
54
53
  files_with_line_usage = filtered_files(Delta.results)
55
54
  @store.save_report(files_with_line_usage)
56
55
  end
57
- rescue StandardError => e
58
- @logger&.error 'coverage failed to store'
56
+ rescue => e
57
+ @logger&.error "coverage failed to store"
59
58
  @logger&.error "Coverband Error: #{e.inspect} #{e.message}"
60
59
  e.backtrace.each { |line| @logger&.error line } if @verbose
61
60
  raise e if @test_env
62
61
  end
63
62
 
64
- protected
65
-
66
- ###
67
- # Normally I would break this out into additional methods
68
- # and improve the readability but this is in a tight loop
69
- # on the critical performance path, and any refactoring I come up with
70
- # would slow down the performance.
71
- ###
72
- def track_file?(file)
73
- @ignore_patterns.none? do |pattern|
74
- file.match(pattern)
75
- end && (file.start_with?(@project_directory) ||
76
- (@track_gems &&
77
- Coverband.configuration.gem_paths.any? { |path| file.start_with?(path) }))
78
- end
79
-
80
63
  private
81
64
 
82
65
  def filtered_files(new_results)
83
- new_results.select! { |file, coverage| track_file?(file) && coverage.any? { |value| value&.nonzero? } }
66
+ new_results.select! { |_file, coverage| coverage.any? { |value| value&.nonzero? } }
84
67
  new_results
85
68
  end
86
69
 
87
70
  def initialize
88
71
  @semaphore = Mutex.new
89
- raise NotImplementedError, 'Coverage needs Ruby > 2.3.0' if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3.0')
72
+ raise NotImplementedError, "Coverage needs Ruby > 2.3.0" if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.3.0")
90
73
 
91
- require 'coverage'
74
+ require "coverage"
75
+ if RUBY_PLATFORM == "java"
76
+ unless ::Coverage.respond_to?(:line_stub)
77
+ require "coverband/utils/jruby_ext"
78
+ end
79
+ end
92
80
  if defined?(SimpleCov) && defined?(Rails) && defined?(Rails.env) && Rails.env.test?
93
81
  puts "Coverband: detected SimpleCov in test Env, allowing it to start Coverage"
94
82
  puts "Coverband: to ensure no error logs or missing Coverage call `SimpleCov.start` prior to requiring Coverband"
95
83
  else
96
- if Coverage.ruby_version_greater_than_or_equal_to?('2.6.0')
84
+ if Coverage.ruby_version_greater_than_or_equal_to?("2.6.0")
97
85
  ::Coverage.start(oneshot_lines: Coverband.configuration.use_oneshot_lines_coverage) unless ::Coverage.running?
98
- elsif Coverage.ruby_version_greater_than_or_equal_to?('2.5.0')
86
+ elsif Coverage.ruby_version_greater_than_or_equal_to?("2.5.0")
99
87
  ::Coverage.start unless ::Coverage.running?
100
88
  else
101
89
  ::Coverage.start
@@ -39,23 +39,33 @@ module Coverband
39
39
 
40
40
  def self.reset
41
41
  @@previous_coverage = {}
42
+ @@project_directory = File.expand_path(Coverband.configuration.root)
43
+ @@ignore_patterns = Coverband.configuration.ignore
42
44
  end
43
45
 
44
46
  private
45
47
 
46
48
  def generate
47
- # TODO: if we filtered before doing this we would avoid calculating the line diff on a ton of files
48
- # This would be a fairly noticeable perf win
49
49
  current_coverage.each_with_object({}) do |(file, line_counts), new_results|
50
+ ###
51
+ # Eager filter:
52
+ # Normally I would break this out into additional methods
53
+ # and improve the readability but this is in a tight loop
54
+ # on the critical performance path, and any refactoring I come up with
55
+ # would slow down the performance.
56
+ ###
57
+ next unless @@ignore_patterns.none? { |pattern| file.match(pattern) } &&
58
+ file.start_with?(@@project_directory)
59
+
50
60
  # This handles Coverage branch support, setup by default in
51
61
  # simplecov 0.18.x
52
62
  arr_line_counts = line_counts.is_a?(Hash) ? line_counts[:lines] : line_counts
53
63
  new_results[file] = if @@previous_coverage && @@previous_coverage[file]
54
- prev_line_counts = @@previous_coverage[file].is_a?(Hash) ? @@previous_coverage[file][:lines] : @@previous_coverage[file]
55
- array_diff(arr_line_counts, prev_line_counts)
56
- else
57
- arr_line_counts
58
- end
64
+ prev_line_counts = @@previous_coverage[file].is_a?(Hash) ? @@previous_coverage[file][:lines] : @@previous_coverage[file]
65
+ array_diff(arr_line_counts, prev_line_counts)
66
+ else
67
+ arr_line_counts
68
+ end
59
69
  end
60
70
  end
61
71
 
@@ -67,10 +77,20 @@ module Coverband
67
77
 
68
78
  def transform_oneshot_lines_results(results)
69
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
+
70
90
  @@stubs[file] ||= ::Coverage.line_stub(file)
71
- transformed_line_counts = coverage[:oneshot_lines].each_with_object(@@stubs[file].dup) do |line_number, line_counts|
91
+ transformed_line_counts = coverage[:oneshot_lines].each_with_object(@@stubs[file].dup) { |line_number, line_counts|
72
92
  line_counts[line_number - 1] = 1
73
- end
93
+ }
74
94
  new_results[file] = transformed_line_counts
75
95
  end
76
96
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'singleton'
3
+ require "singleton"
4
4
 
5
5
  module Coverband
6
6
  module Collectors
@@ -14,12 +14,12 @@ module Coverband
14
14
  # TODO: test and ensure slim, haml, and other support
15
15
  ###
16
16
  class ViewTracker
17
- DEFAULT_TARGET = Dir.glob('app/views/**/*.html.erb').reject { |file| file.match(/(_mailer)/) }
17
+ DEFAULT_TARGET = Dir.glob("app/views/**/*.html.erb").reject { |file| file.match(/(_mailer)/) }
18
18
  attr_accessor :target, :logged_views, :views_to_record
19
19
  attr_reader :logger, :roots, :store, :ignore_patterns
20
20
 
21
21
  def initialize(options = {})
22
- raise NotImplementedError, 'View Tracker requires Rails 4 or greater' unless self.class.supported_version?
22
+ raise NotImplementedError, "View Tracker requires Rails 4 or greater" unless self.class.supported_version?
23
23
 
24
24
  @project_directory = File.expand_path(Coverband.configuration.root)
25
25
  @ignore_patterns = Coverband.configuration.ignore
@@ -28,7 +28,7 @@ module Coverband
28
28
  @target = options.fetch(:target) { DEFAULT_TARGET }
29
29
 
30
30
  @roots = options.fetch(:roots) { Coverband.configuration.all_root_patterns }
31
- @roots = @roots.split(',') if @roots.is_a?(String)
31
+ @roots = @roots.split(",") if @roots.is_a?(String)
32
32
  @one_time_timestamp = false
33
33
 
34
34
  @logged_views = []
@@ -65,7 +65,7 @@ module Coverband
65
65
  normalized_views = {}
66
66
  views.each_pair do |view, time|
67
67
  roots.each do |root|
68
- view = view.gsub(/#{root}/, '')
68
+ view = view.gsub(/#{root}/, "")
69
69
  end
70
70
  normalized_views[view] = time
71
71
  end
@@ -90,7 +90,7 @@ module Coverband
90
90
  if (tracking_time = redis_store.get(tracker_time_key))
91
91
  Time.at(tracking_time.to_i).iso8601
92
92
  else
93
- 'N/A'
93
+ "N/A"
94
94
  end
95
95
  end
96
96
 
@@ -115,14 +115,14 @@ module Coverband
115
115
  redis_store.hset(tracker_key, file, reported_time)
116
116
  end
117
117
  self.views_to_record = []
118
- rescue StandardError => e
118
+ rescue => e
119
119
  # we don't want to raise errors if Coverband can't reach redis.
120
120
  # This is a nice to have not a bring the system down
121
121
  logger&.error "Coverband: view_tracker failed to store, error #{e.class.name}"
122
122
  end
123
123
 
124
124
  def self.supported_version?
125
- defined?(Rails) && defined?(Rails::VERSION) && Rails::VERSION::STRING.split('.').first.to_i >= 4
125
+ defined?(Rails) && defined?(Rails::VERSION) && Rails::VERSION::STRING.split(".").first.to_i >= 4
126
126
  end
127
127
 
128
128
  protected
@@ -154,11 +154,11 @@ module Coverband
154
154
  end
155
155
 
156
156
  def tracker_key
157
- 'render_tracker_2'
157
+ "render_tracker_2"
158
158
  end
159
159
 
160
160
  def tracker_time_key
161
- 'render_tracker_time'
161
+ "render_tracker_time"
162
162
  end
163
163
  end
164
164
  end
@@ -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