coverband 1.5.4 → 2.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.travis.yml +3 -0
  4. data/Gemfile +2 -1
  5. data/Rakefile +5 -3
  6. data/coverband.gemspec +27 -23
  7. data/lib/coverband.rb +10 -14
  8. data/lib/coverband/adapters/file_store.rb +6 -7
  9. data/lib/coverband/adapters/memory_cache_store.rb +8 -5
  10. data/lib/coverband/adapters/redis_store.rb +10 -50
  11. data/lib/coverband/baseline.rb +5 -5
  12. data/lib/coverband/collectors/base.rb +140 -0
  13. data/lib/coverband/collectors/coverage.rb +148 -0
  14. data/lib/coverband/collectors/trace.rb +100 -0
  15. data/lib/coverband/configuration.rb +8 -10
  16. data/lib/coverband/middleware.rb +5 -5
  17. data/lib/coverband/reporters/base.rb +26 -31
  18. data/lib/coverband/reporters/console_report.rb +2 -3
  19. data/lib/coverband/reporters/simple_cov_report.rb +5 -6
  20. data/lib/coverband/s3_report_writer.rb +7 -8
  21. data/lib/coverband/s3_web.rb +3 -5
  22. data/lib/coverband/tasks.rb +23 -26
  23. data/lib/coverband/version.rb +3 -1
  24. data/test/benchmarks/benchmark.rake +38 -32
  25. data/test/benchmarks/dog.rb +3 -3
  26. data/test/fake_app/basic_rack.rb +4 -2
  27. data/test/test_helper.rb +17 -11
  28. data/test/unit/adapters_file_store_test.rb +12 -11
  29. data/test/unit/adapters_memory_cache_store_test.rb +3 -4
  30. data/test/unit/adapters_redis_store_test.rb +42 -118
  31. data/test/unit/baseline_test.rb +17 -20
  32. data/test/unit/collectors_base_test.rb +96 -0
  33. data/test/unit/collectors_coverage_test.rb +137 -0
  34. data/test/unit/collectors_trace_test.rb +96 -0
  35. data/test/unit/configuration_test.rb +8 -8
  36. data/test/unit/dog.rb +3 -1
  37. data/test/unit/middleware_test.rb +70 -61
  38. data/test/unit/reports_base_test.rb +62 -62
  39. data/test/unit/reports_console_test.rb +18 -21
  40. data/test/unit/reports_simple_cov_test.rb +23 -26
  41. data/test/unit/s3_report_writer_test.rb +6 -8
  42. data/test/unit/s3_web_test.rb +2 -1
  43. metadata +45 -25
  44. data/lib/coverband/base.rb +0 -210
  45. data/test/unit/base_test.rb +0 -100
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fb86a59ade80400f2e95ffbdbcbc2d8b343d07b2
4
- data.tar.gz: 2f43c4ea1d6288262d660ba163061a2448f6e454
3
+ metadata.gz: 578fbbc18bb6563f7d6568ba1b62ca49957f08e9
4
+ data.tar.gz: 0b4fd748dfedd42479f770bf9d853910d589d0f4
5
5
  SHA512:
6
- metadata.gz: 293e0b120e6bb341c5fabf1f91f65939e521d4dcb883488ea1de7aa1a4f2cc27e206dbcbbac40327ac8c7f809ade9adeb729a0578d9555715ea58554952549b7
7
- data.tar.gz: d4bfc1ed199234e09464cbd71676a1db2bac1e9a0ef78cc53f89cace8fd65b9feb8b303d15a32778dfd336b9075b560203f67cb603fd74c069b74f9654932d9b
6
+ metadata.gz: 3cfc0686746b6100abeae7345fa536028d8ba7c9a4168fbfee5b62f66f71e4185bb0dfa9f93d10d97543898b1b4a16ffacd3e7413c009dff645ca012df37fdce
7
+ data.tar.gz: 3d5573fc51c3cb2efbbf7ea79cd4711bde18950d18324a4f2d72cb3f51f1a3a68934d29ac6a60154a9d2548f8661c47982f92af39dde49e742f7cb128f046d1e
data/.gitignore CHANGED
@@ -4,6 +4,8 @@
4
4
  .config
5
5
  .yardoc
6
6
  .idea/
7
+ .ruby-version
8
+ .rubocop.yml
7
9
  Gemfile.lock
8
10
  InstalledFiles
9
11
  _yardoc
@@ -16,3 +18,4 @@ spec/reports
16
18
  test/tmp
17
19
  test/version_tmp
18
20
  tmp
21
+ .byebug_history
@@ -3,6 +3,9 @@ rvm:
3
3
  - "2.0"
4
4
  - "2.1"
5
5
  - "2.2"
6
+ - "2.3"
7
+ - "2.4"
8
+ - "2.5"
6
9
  services:
7
10
  - redis-server
8
11
  before_install:
data/Gemfile CHANGED
@@ -1,5 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in coverband.gemspec
4
6
  gemspec
5
-
data/Rakefile CHANGED
@@ -1,13 +1,15 @@
1
- require "bundler/gem_tasks"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
2
4
 
3
5
  import 'test/benchmarks/benchmark.rake'
4
6
 
5
- task :default => :test
7
+ task default: :test
6
8
  require 'rake/testtask'
7
9
  Rake::TestTask.new(:test) do |test|
8
10
  test.libs << 'lib' << 'test'
9
11
  # exclude benchmark from the tests as the way it functions resets code coverage during executions
10
- #test.pattern = 'test/unit/*_test.rb'
12
+ # test.pattern = 'test/unit/*_test.rb'
11
13
  # using test files opposed to pattern as it outputs which files are run
12
14
  test.test_files = FileList['test/unit/*_test.rb']
13
15
  test.verbose = true
@@ -1,34 +1,38 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'coverband/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
- spec.name = "coverband"
8
+ spec.name = 'coverband'
8
9
  spec.version = Coverband::VERSION
9
- spec.authors = ["Dan Mayer"]
10
- spec.email = ["dan@mayerdan.com"]
11
- spec.description = %q{Rack middleware to help measure production code usage (LOC runtime usage)}
12
- spec.summary = %q{Rack middleware to help measure production code usage (LOC runtime usage)}
13
- spec.homepage = "https://github.com/danmayer/coverband"
14
- spec.license = "MIT"
10
+ spec.authors = ['Dan Mayer']
11
+ spec.email = ['dan@mayerdan.com']
12
+ spec.description = 'Rack middleware to help measure production code usage (LOC runtime usage)'
13
+ spec.summary = 'Rack middleware to help measure production code usage (LOC runtime usage)'
14
+ spec.homepage = 'https://github.com/danmayer/coverband'
15
+ spec.license = 'MIT'
15
16
 
16
- spec.files = `git ls-files`.split($/)
17
+ spec.files = `git ls-files`.split("\n")
17
18
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
20
+ spec.require_paths = ['lib']
20
21
 
21
- spec.add_development_dependency "bundler", "~> 1.3"
22
- spec.add_development_dependency "rake"
23
- spec.add_development_dependency "mocha", "~> 0.14.0"
24
- spec.add_development_dependency "rack"
25
- spec.add_development_dependency "rack-test"
26
- spec.add_development_dependency "test-unit"
27
- spec.add_development_dependency 'sinatra'
28
- spec.add_development_dependency 'classifier-reborn'
29
22
  spec.add_development_dependency 'aws-sdk', '~> 2'
30
- spec.add_runtime_dependency "simplecov", '> 0.11.1'
31
- spec.add_runtime_dependency "json"
32
- # TODO make redis optional dependancy as we add additional adapters
33
- spec.add_runtime_dependency "redis"
23
+ spec.add_development_dependency 'bundler', '~> 1.3'
24
+ spec.add_development_dependency 'classifier-reborn'
25
+ spec.add_development_dependency 'mocha', '~> 0.14.0'
26
+ spec.add_development_dependency 'rack'
27
+ spec.add_development_dependency 'rack-test'
28
+ spec.add_development_dependency 'rake'
29
+ spec.add_development_dependency 'sinatra'
30
+ spec.add_development_dependency 'test-unit'
31
+ # add when debugging
32
+ # require 'byebug'; byebug
33
+ spec.add_development_dependency 'byebug'
34
+ spec.add_runtime_dependency 'json'
35
+ spec.add_runtime_dependency 'simplecov', '> 0.11.1'
36
+ # TODO: make redis optional dependancy as we add additional adapters
37
+ spec.add_runtime_dependency 'redis'
34
38
  end
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'logger'
2
4
  require 'json'
3
- # todo move to only be request if using redis store
5
+ # TODO: move to only be request if using redis store
4
6
  require 'redis'
5
7
 
6
8
  require 'coverband/version'
@@ -8,7 +10,9 @@ require 'coverband/configuration'
8
10
  require 'coverband/adapters/redis_store'
9
11
  require 'coverband/adapters/memory_cache_store'
10
12
  require 'coverband/adapters/file_store'
11
- require 'coverband/base'
13
+ require 'coverband/collectors/base'
14
+ require 'coverband/collectors/trace'
15
+ require 'coverband/collectors/coverage'
12
16
  require 'coverband/baseline'
13
17
  require 'coverband/reporters/base'
14
18
  require 'coverband/reporters/simple_cov_report'
@@ -23,27 +27,19 @@ module Coverband
23
27
  attr_accessor :configuration_data
24
28
  end
25
29
 
26
- # this method is left for backwards compatibility with existing configs
27
- def self.parse_baseline(baseline_file = './tmp/coverband_baseline.json')
28
- Coverband::Baseline.parse_baseline(baseline_file)
29
- end
30
-
31
30
  def self.configure(file = nil)
32
31
  configuration
33
32
  if block_given?
34
33
  yield(configuration)
34
+ elsif File.exist?(CONFIG_FILE)
35
+ file ||= CONFIG_FILE
36
+ require file
35
37
  else
36
- if File.exists?(CONFIG_FILE)
37
- file ||= CONFIG_FILE
38
- require file
39
- else
40
- raise ArgumentError, "configure requires a block or the existance of a #{CONFIG_FILE} in your project"
41
- end
38
+ raise ArgumentError, "configure requires a block or the existance of a #{CONFIG_FILE} in your project"
42
39
  end
43
40
  end
44
41
 
45
42
  def self.configuration
46
43
  self.configuration_data ||= Configuration.new
47
44
  end
48
-
49
45
  end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Coverband
2
4
  module Adapters
3
5
  class FileStore
4
6
  attr_accessor :path
5
7
 
6
- def initialize(path, opts = {})
8
+ def initialize(path, _opts = {})
7
9
  @path = path
8
10
 
9
11
  config_dir = File.dirname(@path)
@@ -11,18 +13,16 @@ module Coverband
11
13
  end
12
14
 
13
15
  def clear!
14
- if File.exist?(path)
15
- File.delete(path)
16
- end
16
+ File.delete(path) if File.exist?(path)
17
17
  end
18
18
 
19
19
  def save_report(report)
20
20
  results = existing_data(path)
21
21
  report.each_pair do |file, values|
22
- if results.has_key?(file)
22
+ if results.key?(file)
23
23
  # convert the keys to "3" opposed to 3
24
24
  values = JSON.parse(values.to_json)
25
- results[file].merge!( values ){|k, old_v, new_v| old_v.to_i + new_v.to_i}
25
+ results[file].merge!(values) { |_k, old_v, new_v| old_v.to_i + new_v.to_i }
26
26
  else
27
27
  results[file] = values
28
28
  end
@@ -53,7 +53,6 @@ module Coverband
53
53
  {}
54
54
  end
55
55
  end
56
-
57
56
  end
58
57
  end
59
58
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Coverband
2
4
  module Adapters
3
5
  class MemoryCacheStore
@@ -20,10 +22,11 @@ module Coverband
20
22
  store.save_report(filtered_files) if filtered_files.any?
21
23
  end
22
24
 
25
+ # rubocop:disable Lint/IneffectiveAccessModifier
23
26
  private
24
27
 
25
28
  def self.files_cache
26
- @files_cache ||= Hash.new
29
+ @files_cache ||= {}
27
30
  end
28
31
 
29
32
  def files_cache
@@ -31,16 +34,16 @@ module Coverband
31
34
  end
32
35
 
33
36
  def filter(files)
34
- files.each_with_object(Hash.new) do |(file, lines), filtered_file_hash|
35
- #first time we see a file, we pre-init the in memory cache to whatever is in store(redis)
37
+ files.each_with_object({}) do |(file, lines), filtered_file_hash|
38
+ # first time we see a file, we pre-init the in memory cache to whatever is in store(redis)
36
39
  line_cache = files_cache[file] ||= Set.new(store.covered_lines_for_file(file))
37
40
  lines.reject! do |line|
38
- line_cache.include?(line) ? true : (line_cache << line and false)
41
+ line_cache.include?(line) ? true : (line_cache << line && false)
39
42
  end
40
43
  filtered_file_hash[file] = lines if lines.any?
41
44
  end
42
45
  end
43
-
46
+ # rubocop:enable Lint/IneffectiveAccessModifier
44
47
  end
45
48
  end
46
49
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Coverband
2
4
  module Adapters
3
5
  class RedisStore
@@ -5,10 +7,6 @@ module Coverband
5
7
 
6
8
  def initialize(redis, opts = {})
7
9
  @redis = redis
8
- #remove check for coverband 2.0
9
- @_sadd_supports_array = recent_gem_version? && recent_server_version?
10
- #possibly drop array storage for 2.0
11
- @store_as_array = opts.fetch(:array){ false }
12
10
  end
13
11
 
14
12
  def clear!
@@ -17,20 +15,10 @@ module Coverband
17
15
  end
18
16
 
19
17
  def save_report(report)
20
- if @store_as_array
21
- redis.pipelined do
22
- store_array(BASE_KEY, report.keys)
23
-
24
- report.each do |file, lines|
25
- store_array("#{BASE_KEY}.#{file}", lines.keys)
26
- end
27
- end
28
- else
29
- store_array(BASE_KEY, report.keys)
18
+ store_array(BASE_KEY, report.keys)
30
19
 
31
- report.each do |file, lines|
32
- store_map("#{BASE_KEY}.#{file}", lines)
33
- end
20
+ report.each do |file, lines|
21
+ store_map("#{BASE_KEY}.#{file}", lines)
34
22
  end
35
23
  end
36
24
 
@@ -47,55 +35,27 @@ module Coverband
47
35
  end
48
36
 
49
37
  def covered_lines_for_file(file)
50
- if @store_as_array
51
- @redis.smembers("#{BASE_KEY}.#{file}").map(&:to_i)
52
- else
53
- @redis.hgetall("#{BASE_KEY}.#{file}")
54
- end
38
+ @redis.hgetall("#{BASE_KEY}.#{file}")
55
39
  end
56
40
 
57
41
  private
58
42
 
59
43
  attr_reader :redis
60
44
 
61
- def sadd_supports_array?
62
- @_sadd_supports_array
63
- end
64
-
65
45
  def store_map(key, values)
66
46
  unless values.empty?
67
47
  existing = redis.hgetall(key)
68
- #in redis all keys are strings
69
- values = Hash[values.map{|k,val| [k.to_s,val] } ]
70
- values.merge!( existing ){|k, old_v, new_v| old_v.to_i + new_v.to_i}
48
+ # in redis all keys are strings
49
+ values = Hash[values.map { |k, val| [k.to_s, val] }]
50
+ values.merge!(existing) { |_k, old_v, new_v| old_v.to_i + new_v.to_i }
71
51
  redis.mapped_hmset(key, values)
72
52
  end
73
53
  end
74
54
 
75
55
  def store_array(key, values)
76
- if sadd_supports_array?
77
- redis.sadd(key, values) if (values.length > 0)
78
- else
79
- values.each do |value|
80
- redis.sadd(key, value)
81
- end
82
- end
56
+ redis.sadd(key, values) unless values.empty?
83
57
  values
84
58
  end
85
-
86
- def recent_server_version?
87
- info_data = redis.info
88
- if info_data.is_a?(Hash)
89
- Gem::Version.new(info_data['redis_version']) >= Gem::Version.new('2.4')
90
- else
91
- #guess supported
92
- true
93
- end
94
- end
95
-
96
- def recent_gem_version?
97
- Gem::Version.new(Redis::VERSION) >= Gem::Version.new('3.0')
98
- end
99
59
  end
100
60
  end
101
61
  end
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Coverband
2
4
  class Baseline
3
-
4
5
  def self.record
5
6
  require 'coverage'
6
7
  Coverage.start
@@ -8,12 +9,12 @@ module Coverband
8
9
 
9
10
  project_directory = File.expand_path(Coverband.configuration.root)
10
11
  results = Coverage.result
11
- results = results.reject { |key, val| !key.match(project_directory) || Coverband.configuration.ignore.any? { |pattern| key.match(/#{pattern}/) } }
12
+ results = results.reject { |key, _val| !key.match(project_directory) || Coverband.configuration.ignore.any? { |pattern| key.match(/#{pattern}/) } }
12
13
 
13
14
  Coverband.configuration.store.save_report(convert_coverage_format(results))
14
15
  end
15
16
 
16
- def self.parse_baseline(back_compat = nil)
17
+ def self.parse_baseline(_back_compat = nil)
17
18
  Coverband.configuration.store.coverage
18
19
  end
19
20
 
@@ -33,12 +34,11 @@ module Coverband
33
34
  results.each_pair do |file, data|
34
35
  lines_map = {}
35
36
  data.each_with_index do |hits, index|
36
- lines_map[(index+1)] = hits unless hits.nil?
37
+ lines_map[(index + 1)] = hits unless hits.nil?
37
38
  end
38
39
  file_map[file] = lines_map
39
40
  end
40
41
  file_map
41
42
  end
42
-
43
43
  end
44
44
  end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coverband
4
+ module Collectors
5
+ class Base
6
+ def self.instance
7
+ if Coverband.configuration.collector == 'trace'
8
+ Thread.current[:coverband_instance] ||= Coverband::Collectors::Trace.new
9
+ elsif Coverband.configuration.collector == 'coverage'
10
+ Thread.current[:coverband_instance] ||= Coverband::Collectors::Coverage.new
11
+ else
12
+ raise 'select valid collector [trace, coverage]'
13
+ end
14
+ end
15
+
16
+ def start
17
+ @enabled = true
18
+ record_coverage
19
+ end
20
+
21
+ def stop
22
+ @enabled = false
23
+ unset_tracer
24
+ end
25
+
26
+ def sample
27
+ configure_sampling
28
+ record_coverage
29
+ result = yield
30
+ report_coverage
31
+ result
32
+ end
33
+
34
+ def save
35
+ @enabled = true
36
+ report_coverage
37
+ @enabled = false
38
+ end
39
+
40
+ def reset_instance
41
+ @project_directory = File.expand_path(Coverband.configuration.root)
42
+ @enabled = false
43
+ @disable_on_failure_for = Coverband.configuration.disable_on_failure_for
44
+ @file_line_usage = {}
45
+ @ignored_files = Set.new
46
+ @startup_delay = Coverband.configuration.startup_delay
47
+ @ignore_patterns = Coverband.configuration.ignore + ['internal:prelude', 'schema.rb']
48
+ @ignore_patterns += ['gems'] unless Coverband.configuration.include_gems
49
+ @sample_percentage = Coverband.configuration.percentage
50
+ @store = Coverband.configuration.store
51
+ @store = Coverband::Adapters::MemoryCacheStore.new(@store) if Coverband.configuration.memory_caching
52
+ @stats = Coverband.configuration.stats
53
+ @verbose = Coverband.configuration.verbose
54
+ @logger = Coverband.configuration.logger
55
+ @current_thread = Thread.current
56
+ Thread.current[:coverband_instance] = nil
57
+ self
58
+ end
59
+
60
+ def configure_sampling
61
+ if @startup_delay != 0 || (rand * 100.0) > @sample_percentage
62
+ @startup_delay -= 1 if @startup_delay > 0
63
+ @enabled = false
64
+ else
65
+ @enabled = true
66
+ end
67
+ end
68
+
69
+ def record_coverage
70
+ if @enabled && !failed_recently?
71
+ set_tracer
72
+ else
73
+ unset_tracer
74
+ end
75
+ # support old ruby before &. safe digging
76
+ @stats.increment "coverband.request.recorded.#{@enabled}" if @stats
77
+ rescue RuntimeError => err
78
+ failed!
79
+ if @verbose
80
+ @logger.info 'error stating recording coverage'
81
+ @logger.info "error: #{err.inspect} #{err.message}"
82
+ end
83
+ end
84
+
85
+ def report_coverage
86
+ raise 'abstract'
87
+ end
88
+
89
+ protected
90
+
91
+ def track_file?(file)
92
+ @ignore_patterns.none? { |pattern| file.include?(pattern) } && file.start_with?(@project_directory)
93
+ end
94
+
95
+ def set_tracer
96
+ raise 'abstract'
97
+ end
98
+
99
+ def unset_tracer
100
+ raise 'abstract'
101
+ end
102
+
103
+ def output_file_line_usage
104
+ @logger.info 'coverband debug coverband file:line usage:'
105
+ @file_line_usage.sort_by { |_key, value| value.length }.each do |pair|
106
+ file = pair.first
107
+ lines = pair.last
108
+ @logger.info "file: #{file} => #{lines.sort_by { |_key, value| value }}"
109
+ end
110
+ end
111
+
112
+ private
113
+
114
+ def failed_at_thread_key
115
+ "__#{self.class.name}__failed_at"
116
+ end
117
+
118
+ def failed_at
119
+ Thread.current[failed_at_thread_key]
120
+ end
121
+
122
+ def failed_at=(time)
123
+ Thread.current[failed_at_thread_key] = time
124
+ end
125
+
126
+ def failed!
127
+ self.failed_at = Time.now
128
+ end
129
+
130
+ def failed_recently?
131
+ return false unless @disable_on_failure_for && failed_at
132
+ failed_at + @disable_on_failure_for > Time.now
133
+ end
134
+
135
+ def initialize
136
+ reset_instance
137
+ end
138
+ end
139
+ end
140
+ end