coverband 2.0.3 → 3.0.0.alpha

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/.rubocop.yml +73 -0
  4. data/.travis.yml +0 -3
  5. data/README.md +3 -3
  6. data/changes.md +65 -49
  7. data/coverband.gemspec +1 -1
  8. data/lib/coverband.rb +9 -6
  9. data/lib/coverband/adapters/base.rb +22 -2
  10. data/lib/coverband/adapters/file_store.rb +11 -11
  11. data/lib/coverband/adapters/redis_store.rb +22 -57
  12. data/lib/coverband/collectors/coverage.rb +57 -53
  13. data/lib/coverband/configuration.rb +6 -14
  14. data/lib/coverband/integrations/background.rb +7 -0
  15. data/lib/coverband/{middleware.rb → integrations/middleware.rb} +1 -3
  16. data/lib/coverband/reporters/base.rb +37 -82
  17. data/lib/coverband/reporters/console_report.rb +3 -0
  18. data/lib/coverband/reporters/simple_cov_report.rb +4 -3
  19. data/lib/coverband/reporters/web.rb +38 -35
  20. data/lib/coverband/utils/s3_report_writer.rb +59 -0
  21. data/lib/coverband/{tasks.rb → utils/tasks.rb} +0 -0
  22. data/lib/coverband/version.rb +1 -1
  23. data/test/benchmarks/benchmark.rake +3 -3
  24. data/test/test_helper.rb +18 -17
  25. data/test/unit/adapters_base_test.rb +29 -0
  26. data/test/unit/adapters_file_store_test.rb +2 -2
  27. data/test/unit/adapters_redis_store_test.rb +14 -51
  28. data/test/unit/collectors_coverage_test.rb +3 -107
  29. data/test/unit/configuration_test.rb +2 -9
  30. data/test/unit/full_stack_test.rb +47 -0
  31. data/test/unit/middleware_test.rb +21 -57
  32. data/test/unit/reports_base_test.rb +12 -71
  33. data/test/unit/reports_console_test.rb +9 -22
  34. data/test/unit/reports_simple_cov_test.rb +3 -37
  35. data/test/unit/reports_web_test.rb +4 -0
  36. data/test/unit/{s3_report_writer_test.rb → utils_s3_report_writer_test.rb} +1 -1
  37. metadata +29 -18
  38. data/lib/coverband/adapters/memory_cache_store.rb +0 -53
  39. data/lib/coverband/collectors/base.rb +0 -126
  40. data/lib/coverband/collectors/trace.rb +0 -122
  41. data/lib/coverband/s3_report_writer.rb +0 -49
  42. data/test/unit/adapters_memory_cache_store_test.rb +0 -66
  43. data/test/unit/collectors_base_test.rb +0 -104
  44. data/test/unit/collectors_trace_test.rb +0 -106
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- ###
4
- # TODO current benchmarks aren't showing much advantage from this wrapped cache approach
5
- # re-evaluate before 2.0.0 release
6
- ###
7
- module Coverband
8
- module Adapters
9
- class MemoryCacheStore < Base
10
- attr_accessor :store
11
-
12
- @@files_cache = {}
13
-
14
- def initialize(store)
15
- @store = store
16
- end
17
-
18
- def self.clear!
19
- @@files_cache.clear
20
- end
21
-
22
- def clear!
23
- self.class.clear!
24
- end
25
-
26
- def save_report(files)
27
- filtered_files = filter(files)
28
- store.save_report(filtered_files) if filtered_files.any?
29
- end
30
-
31
- private
32
-
33
- def files_cache
34
- @@files_cache
35
- end
36
-
37
- def filter(files)
38
- files.each_with_object({}) do |(file, covered_lines), filtered_file_hash|
39
- if covered_lines != cached_file(file)
40
- files_cache[file] = covered_lines
41
- filtered_file_hash[file] = covered_lines
42
- end
43
- end
44
- end
45
-
46
- def cached_file(file)
47
- files_cache[file] ||= store.covered_lines_for_file(file).each_with_object({}) do |(line_number, value), hash|
48
- hash[line_number.to_i] = value.to_i
49
- end
50
- end
51
- end
52
- end
53
- end
@@ -1,126 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- ####
4
- # TODO refactor this along with the coverage and trace collector
5
- ####
6
- module Coverband
7
- module Collectors
8
- class Base
9
- def self.instance
10
- if Coverband.configuration.collector == 'trace'
11
- Thread.current[:coverband_instance] ||= Coverband::Collectors::Trace.new
12
- elsif Coverband.configuration.collector == 'coverage'
13
- Thread.current[:coverband_instance] ||= Coverband::Collectors::Coverage.new
14
- else
15
- raise 'select valid collector [trace, coverage]'
16
- end
17
- end
18
-
19
- def start
20
- @enabled = true
21
- record_coverage
22
- end
23
-
24
- def stop
25
- @enabled = false
26
- stop_coverage
27
- end
28
-
29
- def sample
30
- configure_sampling
31
- record_coverage
32
- result = yield
33
- report_coverage
34
- result
35
- end
36
-
37
- def save
38
- @enabled = true
39
- report_coverage
40
- @enabled = false
41
- end
42
-
43
- def reset_instance
44
- @project_directory = File.expand_path(Coverband.configuration.root)
45
- @enabled = false
46
- @disable_on_failure_for = Coverband.configuration.disable_on_failure_for
47
- @file_line_usage = {}
48
- @ignored_files = Set.new
49
- @startup_delay = Coverband.configuration.startup_delay
50
- @ignore_patterns = Coverband.configuration.ignore + ['internal:prelude', 'schema.rb']
51
- @ignore_patterns += ['gems'] unless Coverband.configuration.include_gems
52
- @sample_percentage = Coverband.configuration.percentage
53
- @store = Coverband.configuration.store
54
- @store = Coverband::Adapters::MemoryCacheStore.new(@store) if Coverband.configuration.memory_caching
55
- @verbose = Coverband.configuration.verbose
56
- @logger = Coverband.configuration.logger
57
- @current_thread = Thread.current
58
- Thread.current[:coverband_instance] = nil
59
- self
60
- end
61
-
62
- def configure_sampling
63
- if @startup_delay != 0 || (rand * 100.0) > @sample_percentage
64
- @startup_delay -= 1 if @startup_delay > 0
65
- @enabled = false
66
- else
67
- @enabled = true
68
- end
69
- end
70
-
71
- def record_coverage
72
- raise 'abstract'
73
- end
74
-
75
- def stop_coverage
76
- raise 'abstract'
77
- end
78
-
79
- def report_coverage
80
- raise 'abstract'
81
- end
82
-
83
- protected
84
-
85
- def track_file?(file)
86
- @ignore_patterns.none? { |pattern| file.include?(pattern) } && file.start_with?(@project_directory)
87
- end
88
-
89
- def output_file_line_usage
90
- @logger.debug 'coverband debug coverband file:line usage:'
91
- @file_line_usage.sort_by { |_key, value| value.length }.each do |pair|
92
- file = pair.first
93
- lines = pair.last
94
- @logger.info "file: #{file} => #{lines.sort_by { |_key, value| value }}"
95
- end
96
- end
97
-
98
- private
99
-
100
- def failed_at_thread_key
101
- "__#{self.class.name}__failed_at"
102
- end
103
-
104
- def failed_at
105
- Thread.current[failed_at_thread_key]
106
- end
107
-
108
- def failed_at=(time)
109
- Thread.current[failed_at_thread_key] = time
110
- end
111
-
112
- def failed!
113
- self.failed_at = Time.now
114
- end
115
-
116
- def failed_recently?
117
- return false unless @disable_on_failure_for && failed_at
118
- failed_at + @disable_on_failure_for > Time.now
119
- end
120
-
121
- def initialize
122
- reset_instance
123
- end
124
- end
125
- end
126
- end
@@ -1,122 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Coverband
4
- module Collectors
5
- ###
6
- # NOTE: While this still works it is slower than Coverage.
7
- # I recommend using the Coverage adapter.
8
- # As baseline is removed the Trace collector also doesn't have a good way
9
- # to collect initial code usage during app boot up.
10
- #
11
- # I am leaving Trace around as I believe there are some interesting use cases
12
- # also, it illustrates an alternative collector, and I have some others I would like to implement
13
- ###
14
- class Trace < Base
15
- def reset_instance
16
- super
17
- @tracer_set = false
18
- @trace_point_events = [:line]
19
- @trace = create_trace_point
20
- self
21
- end
22
-
23
- def record_coverage
24
- if @enabled && !failed_recently?
25
- set_tracer
26
- else
27
- unset_tracer
28
- end
29
- rescue RuntimeError => err
30
- failed!
31
- if @verbose
32
- @logger.error 'error stating recording coverage'
33
- @logger.error "error: #{err.inspect} #{err.message}"
34
- @logger.error err.backtrace
35
- end
36
- end
37
-
38
- def stop_coverage
39
- unset_tracer
40
- end
41
-
42
- def report_coverage
43
- unless @enabled
44
- @logger.info 'coverage disabled' if @verbose
45
- return
46
- end
47
-
48
- unset_tracer
49
-
50
- if failed_recently?
51
- @logger.error 'coverage reporting standing-by because of recent failure' if @verbose
52
- return
53
- end
54
-
55
- if @verbose
56
- @logger.debug "coverband file usage: #{file_usage.inspect}"
57
- output_file_line_usage if @verbose == 'debug'
58
- end
59
-
60
- if @store
61
- @store.save_report(@file_line_usage)
62
- @file_line_usage.clear
63
- elsif @verbose
64
- @logger.info 'coverage report: '
65
- @logger.info @file_line_usage.inspect
66
- end
67
- rescue RuntimeError => err
68
- failed!
69
- if @verbose
70
- @logger.error 'coverage missing'
71
- @logger.error "error: #{err.inspect} #{err.message}"
72
- @logger.error err.backtrace
73
- end
74
- end
75
-
76
- protected
77
-
78
- def set_tracer
79
- unless @tracer_set
80
- @trace.enable
81
- @tracer_set = true
82
- end
83
- end
84
-
85
- def unset_tracer
86
- @trace.disable
87
- @tracer_set = false
88
- end
89
-
90
- private
91
-
92
- def add_file(file, line)
93
- @file_line_usage[file] = Hash.new(0) unless @file_line_usage.include?(file)
94
- @file_line_usage[file][line] += 1
95
- end
96
-
97
- def file_usage
98
- hash = {}
99
- @file_line_usage.each do |file, lines|
100
- hash[file] = lines.values.inject(0, :+)
101
- end
102
- hash.sort_by { |_key, value| value }
103
- end
104
-
105
- def create_trace_point
106
- TracePoint.new(*@trace_point_events) do |tp|
107
- if Thread.current == @current_thread
108
- file = tp.path
109
-
110
- unless @ignored_files.include?(file)
111
- if track_file?(file)
112
- add_file(file, tp.lineno)
113
- else
114
- @ignored_files << file
115
- end
116
- end
117
- end
118
- end
119
- end
120
- end
121
- end
122
- end
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # possibly move to / make an adapter or more likely a reporter
4
- class S3ReportWriter
5
- def initialize(bucket_name, options = {})
6
- @bucket_name = bucket_name
7
- @region = options[:region]
8
- @access_key_id = options[:access_key_id]
9
- @secret_access_key = options[:secret_access_key]
10
- begin
11
- require 'aws-sdk'
12
- rescue StandardError
13
- Coverband.configuration.logger.error "coverband requires 'aws-sdk' in order use S3ReportWriter."
14
- return
15
- end
16
- end
17
-
18
- def persist!
19
- object.put(body: coverage_content)
20
- end
21
-
22
- private
23
-
24
- def coverage_content
25
- version = Gem::Specification.find_by_name('simplecov-html').version.version
26
- File.read("#{SimpleCov.coverage_dir}/index.html").gsub("./assets/#{version}/", '')
27
- rescue StandardError
28
- File.read("#{SimpleCov.coverage_dir}/index.html").to_s.gsub('./assets/0.10.1/', '')
29
- end
30
-
31
- def object
32
- bucket.object('coverband/index.html')
33
- end
34
-
35
- def s3
36
- client_options = {
37
- region: @region,
38
- access_key_id: @access_key_id,
39
- secret_access_key: @secret_access_key
40
- }
41
- resource_options = { client: Aws::S3::Client.new(client_options) }
42
- resource_options = {} if client_options.values.any?(&:nil?)
43
- Aws::S3::Resource.new(resource_options)
44
- end
45
-
46
- def bucket
47
- s3.bucket(@bucket_name)
48
- end
49
- end
@@ -1,66 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require File.expand_path('../test_helper', File.dirname(__FILE__))
4
-
5
- module Coverband
6
- class MemoryCacheStoreTest < Test::Unit::TestCase
7
- def setup
8
- Adapters::MemoryCacheStore.clear!
9
- @redis = Redis.new
10
- @store = Coverband::Adapters::RedisStore.new(@redis)
11
- @store.clear!
12
- @memory_store = Adapters::MemoryCacheStore.new(@store)
13
- end
14
-
15
- test 'it passes data into store' do
16
- data = {
17
- 'file1' => { 1 => 0, 2 => 1, 3 => 0 },
18
- 'file2' => { 1 => 5, 2 => 2 }
19
- }
20
- @store.expects(:save_report).with data
21
- @memory_store.save_report data
22
- end
23
-
24
-
25
- test 'it filters coverage with same exact data' do
26
- data = {
27
- 'file1' => { 1 => 0, 2 => 1, 3 => 0 },
28
- 'file2' => { 1 => 5, 2 => 2 }
29
- }
30
- @store.expects(:save_report).once.with data
31
- 2.times { @memory_store.save_report data }
32
- end
33
-
34
- test 'it filters coverage for files with same exact data' do
35
-
36
- report_first_request = {
37
- 'file1' => { 1 => 0, 2 => 1, 3 => 0 },
38
- 'file2' => { 1 => 5, 2 => 2 }
39
- }
40
-
41
- report_second_request = {
42
- 'file1' => { 1 => 0, 2 => 1, 3 => 0 },
43
- 'file2' => { 1 => 5, 2 => 3 }
44
- }
45
- @store.expects(:save_report).with({
46
- 'file1' => { 1 => 0, 2 => 1, 3 => 0 },
47
- 'file2' => { 1 => 5, 2 => 2 }
48
- })
49
- @store.expects(:save_report).with({
50
- 'file2' => { 1 => 5, 2 => 3 }
51
- })
52
- @memory_store.save_report(report_first_request)
53
- @memory_store.save_report(report_second_request)
54
- end
55
-
56
- test 'it initializes cache with what is in store' do
57
- data = {
58
- 'file1' => { 1 => 0, 2 => 1, 3 => 0 },
59
- 'file2' => { 1 => 5, 2 => 2 }
60
- }
61
- Coverband::Adapters::RedisStore.new(@redis).save_report(data)
62
- @store.expects(:save_report).never
63
- @memory_store.save_report(data)
64
- end
65
- end
66
- end
@@ -1,104 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require File.expand_path('../test_helper', File.dirname(__FILE__))
4
- require File.expand_path('./dog', File.dirname(__FILE__))
5
-
6
- class CollectorsBaseTest < Test::Unit::TestCase
7
- def setup
8
- Coverband.configure do |config|
9
- config.collector = 'trace'
10
- end
11
- end
12
-
13
- test 'defaults to a redis store' do
14
- coverband = Coverband::Collectors::Base.instance.reset_instance
15
- assert_equal Coverband::Adapters::RedisStore, coverband.instance_variable_get('@store').class
16
- end
17
-
18
- test 'configure memory caching' do
19
- Coverband.configuration.memory_caching = true
20
- coverband = Coverband::Collectors::Base.instance.reset_instance
21
- assert_equal Coverband::Adapters::MemoryCacheStore, coverband.instance_variable_get('@store').class
22
- Coverband.configuration.memory_caching = false
23
- end
24
-
25
- test 'start should enable coverage' do
26
- coverband = Coverband::Collectors::Base.instance.reset_instance
27
- assert_equal false, coverband.instance_variable_get('@enabled')
28
- coverband.expects(:record_coverage).once
29
- coverband.start
30
- assert_equal true, coverband.instance_variable_get('@enabled')
31
- end
32
-
33
- test 'stop should disable coverage' do
34
- coverband = Coverband::Collectors::Base.instance.reset_instance
35
- assert_equal false, coverband.instance_variable_get('@enabled')
36
- coverband.expects(:record_coverage).once
37
- coverband.start
38
- assert_equal true, coverband.instance_variable_get('@enabled')
39
- coverband.stop
40
- assert_equal false, coverband.instance_variable_get('@enabled')
41
- end
42
-
43
- test 'allow for sampling with a block and enable when 100 percent sample' do
44
- logger = Logger.new(STDOUT)
45
- coverband = Coverband::Collectors::Base.instance.reset_instance
46
- coverband.instance_variable_set('@sample_percentage', 100.0)
47
- coverband.instance_variable_set('@verbose', true)
48
- coverband.instance_variable_set('@logger', logger)
49
- coverband.instance_variable_set('@store', nil)
50
- assert_equal false, coverband.instance_variable_get('@enabled')
51
- logger.expects(:info).at_least_once
52
- logger.stubs('debug')
53
- coverband.sample { 1 + 1 }
54
- assert_equal true, coverband.instance_variable_get('@enabled')
55
- end
56
-
57
- test 'allow reporting with start stop save' do
58
- logger = Logger.new(STDOUT)
59
- coverband = Coverband::Collectors::Base.instance.reset_instance
60
- coverband.instance_variable_set('@sample_percentage', 100.0)
61
- coverband.instance_variable_set('@verbose', true)
62
- coverband.instance_variable_set('@logger', logger)
63
- coverband.instance_variable_set('@store', nil)
64
- assert_equal false, coverband.instance_variable_get('@enabled')
65
- logger.expects(:info).at_least_once
66
- logger.stubs('debug')
67
- coverband.start
68
- coverband.stop
69
- coverband.save
70
- end
71
-
72
- test 'allow reporting to redis start stop save' do
73
- dog_file = File.expand_path('./dog.rb', File.dirname(__FILE__))
74
- coverband = Coverband::Collectors::Base.instance.reset_instance
75
- coverband.instance_variable_set('@sample_percentage', 100.0)
76
- coverband.instance_variable_set('@verbose', true)
77
- Coverband.configuration.logger.stubs('info')
78
- Coverband.configuration.logger.stubs('debug')
79
- store = Coverband::Adapters::RedisStore.new(Redis.new)
80
- coverband.instance_variable_set('@store', store)
81
- store.expects(:save_report).once.with(has_entries(dog_file => {5 => 5}))
82
- assert_equal false, coverband.instance_variable_get('@enabled')
83
- coverband.start
84
- 5.times { Dog.new.bark }
85
- coverband.stop
86
- coverband.save
87
- end
88
-
89
- test 'tracer should count line numbers' do
90
- dog_file = File.expand_path('./dog.rb', File.dirname(__FILE__))
91
- coverband = Coverband::Collectors::Base.instance.reset_instance
92
- coverband.start
93
- 100.times { Dog.new.bark }
94
- Coverband::Collectors::Base.instance
95
- assert_equal 100, coverband.instance_variable_get('@file_line_usage')[dog_file][5]
96
- coverband.stop
97
- coverband.save
98
- end
99
-
100
- test 'sample should return the result of the block' do
101
- coverband = Coverband::Collectors::Base.instance.reset_instance
102
- assert_equal 2, coverband.sample { 1 + 1 }
103
- end
104
- end