coverband 1.5.4 → 2.0.0.alpha

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