coverband 2.0.3 → 3.0.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
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