coverband 1.3.1 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +39 -6
  4. data/changes.md +23 -0
  5. data/coverband.gemspec +2 -1
  6. data/lib/coverband.rb +12 -11
  7. data/lib/coverband/adapters/file_store.rb +59 -0
  8. data/lib/coverband/adapters/memory_cache_store.rb +46 -0
  9. data/lib/coverband/adapters/redis_store.rb +101 -0
  10. data/lib/coverband/base.rb +4 -7
  11. data/lib/coverband/baseline.rb +35 -0
  12. data/lib/coverband/configuration.rb +20 -5
  13. data/lib/coverband/reporters/base.rb +150 -0
  14. data/lib/coverband/reporters/console_report.rb +17 -0
  15. data/lib/coverband/reporters/simple_cov_report.rb +46 -0
  16. data/lib/coverband/s3_report_writer.rb +6 -1
  17. data/lib/coverband/tasks.rb +26 -16
  18. data/lib/coverband/version.rb +1 -1
  19. data/test/benchmarks/benchmark.rake +57 -7
  20. data/test/test_helper.rb +20 -0
  21. data/test/unit/adapters_file_store_test.rb +41 -0
  22. data/test/unit/{memory_cache_store_test.rb → adapters_memory_cache_store_test.rb} +12 -12
  23. data/test/unit/adapters_redis_store_test.rb +164 -0
  24. data/test/unit/base_test.rb +8 -7
  25. data/test/unit/baseline_test.rb +50 -0
  26. data/test/unit/middleware_test.rb +8 -8
  27. data/test/unit/reports_base_test.rb +140 -0
  28. data/test/unit/reports_console_test.rb +37 -0
  29. data/test/unit/reports_simple_cov_test.rb +68 -0
  30. data/test/unit/s3_report_writer_test.rb +1 -0
  31. metadata +37 -24
  32. data/lib/coverband/memory_cache_store.rb +0 -42
  33. data/lib/coverband/redis_store.rb +0 -57
  34. data/lib/coverband/reporter.rb +0 -223
  35. data/test/unit/redis_store_test.rb +0 -107
  36. data/test/unit/reporter_test.rb +0 -207
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e98fd5cb692315012eaf942729c5dda8733dc1f2
4
- data.tar.gz: f5aa2c9ed5c11d804a5849f17044c557545adb02
3
+ metadata.gz: 4e4f7c810900bee83c3516d394e950fde6cb5186
4
+ data.tar.gz: ba60c4f523b5d50e145cf179113dd6431fd6be4c
5
5
  SHA512:
6
- metadata.gz: 520f5f7d174cbb4839b8d2aa8a763c5810efcc5102d32e0891ca1923a7cdf2a87183d6267cd24576159001f1829b7ac8eed4eddbc1d3f2eb196285d78adc80ea
7
- data.tar.gz: 1398b2283ba65698d8ce5040b69742e365c3cf9c26a52300322baff70fad708e65152531487dec576ec4a8030fc6b1e1542645f21bb3c32f1d868d3eb16431d2
6
+ metadata.gz: ca67d3de1ce546e6d5d3050bde171f70ef258b0792a2e871d8d4c4b93c5609f71e85afcb7d39b9a08d582381e6476fe13c791c45d764726eccaada3170dfd25a
7
+ data.tar.gz: a1543e402bcc3ef6a0c68c478235d34d1794f695ea9c3099eb31f7c4b54b56015622f1fb920804e0c20614118516fabcf12364e5c97a220108e820e53c29683b
data/.gitignore CHANGED
@@ -3,6 +3,7 @@
3
3
  .bundle
4
4
  .config
5
5
  .yardoc
6
+ .idea/
6
7
  Gemfile.lock
7
8
  InstalledFiles
8
9
  _yardoc
data/README.md CHANGED
@@ -13,12 +13,31 @@ A gem to measure production code usage, showing each line of code that is execut
13
13
  __Notes:__ Latest versions of Coverband drop support for anything less than Ruby 2.0, and Ruby 2.1+ is recommended.
14
14
 
15
15
  ###### Success:
16
+
16
17
  After running in production for 30 minutes, we were able very easily delete 2000 LOC after looking through the data. We expect to be able to clean up much more after it has collected more data.
17
18
 
18
19
  ###### Performance Impact
19
20
 
20
21
  The performance impact on Ruby 2.1+ is fairly small and no longer requires a C-extension. Look at the benchmark rake task for specific details.
21
22
 
23
+ ## How I use Coverband
24
+
25
+ * install coverband.
26
+ * take baseline: `rake coverband:baseline`
27
+ * validate baseline with `rake coverband:coverage`
28
+ * test setup in development (hit endpoints and generate new report)
29
+ * deploy to staging and verify functionality
30
+ * deploy to production and verify functionality
31
+ * every 2 weeks or with major releases
32
+ * clear old coverage: `rake coverband:clear`
33
+ * take new baseline: `rake coverband:baseline`
34
+ * deploy and verify coverage is matching expectations
35
+ * __COVERAGE DRIFT__
36
+ * if you never clear you have lines of code drift from when they were recorded
37
+ * if you clear on every deploy you don't capture as useful of data
38
+ * there is a tradeoff on accuracy and data value
39
+ * I recommend clearing after major code changes, significant releases, or some regular schedule.
40
+
22
41
  ## Installation
23
42
 
24
43
  Add this line to your application's Gemfile:
@@ -82,10 +101,16 @@ You need to configure cover band you can either do that passing in all configura
82
101
  #config/coverband.rb
83
102
  Coverband.configure do |config|
84
103
  config.root = Dir.pwd
104
+
85
105
  if defined? Redis
86
106
  config.redis = Redis.new(:host => 'redis.host.com', :port => 49182, :db => 1)
87
107
  end
88
- config.coverage_baseline = Coverband.parse_baseline
108
+ # don't want to use redis, store to file system ;)
109
+ # config.coverage_file = './tmp/coverband_coverage.json'
110
+
111
+ # DEPRECATED now will use redis or file store
112
+ # config.coverage_baseline = Coverband.parse_baseline
113
+
89
114
  config.root_paths = ['/app/'] # /app/ is needed for heroku deployments
90
115
  # regex paths can help if you are seeing files duplicated for each capistrano deployment release
91
116
  #config.root_paths = ['/server/apps/my_app/releases/\d+/']
@@ -103,6 +128,7 @@ Coverband.configure do |config|
103
128
  if defined? Statsd
104
129
  config.stats = Statsd.new('statsd.host.com', 8125)
105
130
  end
131
+
106
132
  # config options false, true, or 'debug'. Always use false in production
107
133
  # true and debug can give helpful and interesting code usage information
108
134
  # they both increase the performance overhead of the gem a little.
@@ -260,7 +286,8 @@ coverband.sample {
260
286
 
261
287
  ### Clearing Line Coverage Data
262
288
 
263
- After a deploy where code has changed.
289
+ After a deploy where code has changed significantly.
290
+
264
291
  The line numbers previously recorded in Redis may no longer match the current state of the files.
265
292
  If being slightly out of sync isn't as important as gathering data over a long period,
266
293
  you can live with minor inconsistency for some files.
@@ -300,6 +327,8 @@ If you are trying to debug locally wondering what code is being run during a req
300
327
 
301
328
  If you are clearing data on every deploy. You might want to write the data out to a file first. Then you could merge the data into the final results later.
302
329
 
330
+ __note:__ I don't actually recommend clearing on every deploy, but only following significant releases where many line numbers would be off. If you follow that approach you don't need to merge data over time as this example shows how.
331
+
303
332
  ```ruby
304
333
  data = JSON.generate Coverband::Reporter.get_current_scov_data
305
334
  File.write("blah.json", data)
@@ -321,20 +350,24 @@ If you are working on adding features, PRs, or bugfixes to Coverband this sectio
321
350
 
322
351
  ### Known issues
323
352
 
353
+ * __total fail__ on front end code, because of the precompiled template step basically coverage doesn't work well for `erb`, `slim`, and the like.
324
354
  * If you don't have a baseline recorded your coverage can look odd like you are missing a bunch of data. It would be good if Coverband gave a more actionable warning in this situation.
325
355
  * If you have SimpleCov filters, you need to clear them prior to generating your coverage report. As the filters will be applied to Coverband as well and can often filter out everything we are recording.
326
356
  * the line numbers reported for `ERB` files are often off and aren't considered useful. I recommend filtering out .erb using the `config.ignore` option.
357
+ * coverage doesn't show for Rails `config/application.rb` or `config/boot.rb` as they get loaded when loading the Rake environment prior to starting to record the baseline..
327
358
 
328
359
  ## TODO
329
360
 
361
+ * graphite adapters (it would allow passing in date ranges on usage)
362
+ * perf test for array vs hash
363
+ * redis pipeline around hash (or batch get then push)
364
+ * pass in namespace to redis (coverage vs baseline)
365
+ * what about having baseline a onetime recording into redis no merge later
366
+ * move to SimpleCov console out, or make similar console tabular output
330
367
  * Fix network performance by logging to files that purge later (like NR) (far more time lost in TracePoint than sending files, hence not a high priority, but would be cool)
331
368
  * Add support for [zadd](http://redis.io/topics/data-types-intro) so one could determine single call versus multiple calls on a line, letting us determine the most executed code in production.
332
369
  * Possibly add ability to record code run for a given route
333
370
  * Improve client code api, around manual usage of sampling (like event usage)
334
- * Provide a better lighter example app, to show how to use Coverband.
335
- * blank rails app
336
- * blank Sinatra app
337
- * report on Coverband files that haven't recorded any coverage (find things like events and crons that aren't recording, or dead files)
338
371
  * ability to change the Coverband config at runtime by changing the config pushed to the Redis hash. In memory cache around the changes to only make that call periodically.
339
372
  * Opposed to just showing code usage on a route allow 'tagging' events which would record line coverage for that tag (this would allow tagging all code that modified an ActiveRecord model for example
340
373
  * mountable rack app to view coverage similar to flipper-ui
@@ -0,0 +1,23 @@
1
+ ### 1.5.0
2
+
3
+ This is a major release with significant refactoring a stepping stone for a 2.0 release.
4
+
5
+ * staging a changes.md document!
6
+ * refactored out full abstraction for stores
7
+ * supports hit counts vs binary covered / not covered for lines
8
+ * this will let you find density of code usage just not if it was used
9
+ * this is a slight performance hit, so you can fall back to the old system if you want `redisstore.new(@redis, array: true)`
10
+ * this is the primary new feature in 1.5.0
11
+ * Redis has configurable base name, so I can safely change storage formats between releases
12
+ * improved documentation
13
+ * supports `SimpleCov.root`
14
+ * show files that were never touched
15
+ * apply coverband filters to ignore files in report not just collection
16
+ * improved test coverage
17
+ * improved benchmarks including support for multiple stores ;)
18
+
19
+ ### 1.3.1
20
+
21
+ * This was a small fix release addressing some issues
22
+ * mostly readme updates
23
+ * last release prior to having a changes document!
@@ -26,8 +26,9 @@ Gem::Specification.new do |spec|
26
26
  spec.add_development_dependency "test-unit"
27
27
  spec.add_development_dependency 'sinatra'
28
28
  spec.add_development_dependency 'classifier-reborn'
29
+ spec.add_development_dependency 'aws-sdk', '~> 2'
29
30
  spec.add_runtime_dependency "simplecov"
30
31
  spec.add_runtime_dependency "json"
32
+ # TODO make redis optional dependancy as we add additional adapters
31
33
  spec.add_runtime_dependency "redis"
32
- spec.add_runtime_dependency 'aws-sdk', '~> 2'
33
34
  end
@@ -1,30 +1,31 @@
1
- require 'redis'
2
1
  require 'logger'
3
- require 'aws-sdk'
2
+ require 'json'
3
+ # todo move to only be request if using redis store
4
+ require 'redis'
4
5
 
5
6
  require 'coverband/version'
6
7
  require 'coverband/configuration'
7
- require 'coverband/redis_store'
8
- require 'coverband/memory_cache_store'
8
+ require 'coverband/adapters/redis_store'
9
+ require 'coverband/adapters/memory_cache_store'
10
+ require 'coverband/adapters/file_store'
9
11
  require 'coverband/base'
10
- require 'coverband/reporter'
12
+ require 'coverband/baseline'
13
+ require 'coverband/reporters/base'
14
+ require 'coverband/reporters/simple_cov_report'
15
+ require 'coverband/reporters/console_report'
11
16
  require 'coverband/middleware'
12
17
  require 'coverband/s3_report_writer'
13
18
 
14
19
  module Coverband
15
-
16
20
  CONFIG_FILE = './config/coverband.rb'
17
21
 
18
22
  class << self
19
23
  attr_accessor :configuration_data
20
24
  end
21
25
 
26
+ # this method is left for backwards compatibility with existing configs
22
27
  def self.parse_baseline(baseline_file = './tmp/coverband_baseline.json')
23
- baseline = if File.exist?(baseline_file)
24
- JSON.parse(File.read(baseline_file))
25
- else
26
- {}
27
- end
28
+ Coverband::Baseline.parse_baseline(baseline_file)
28
29
  end
29
30
 
30
31
  def self.configure(file = nil)
@@ -0,0 +1,59 @@
1
+ module Coverband
2
+ module Adapters
3
+ class FileStore
4
+ attr_accessor :path
5
+
6
+ def initialize(path, opts = {})
7
+ @path = path
8
+
9
+ config_dir = File.dirname(@path)
10
+ Dir.mkdir config_dir unless File.exist?(config_dir)
11
+ end
12
+
13
+ def clear!
14
+ if File.exist?(path)
15
+ File.delete(path)
16
+ end
17
+ end
18
+
19
+ def save_report(report)
20
+ results = existing_data(path)
21
+ report.each_pair do |file, values|
22
+ if results.has_key?(file)
23
+ # convert the keys to "3" opposed to 3
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}
26
+ else
27
+ results[file] = values
28
+ end
29
+ end
30
+ File.open(path, 'w') { |f| f.write(results.to_json) }
31
+ end
32
+
33
+ def coverage
34
+ existing_data(path)
35
+ end
36
+
37
+ def covered_files
38
+ report = existing_data(path)
39
+ existing_data(path).merge(report).keys || []
40
+ end
41
+
42
+ def covered_lines_for_file(file)
43
+ report = existing_data(path)
44
+ report[file] || []
45
+ end
46
+
47
+ private
48
+
49
+ def existing_data(path)
50
+ if File.exist?(path)
51
+ JSON.parse(File.read(path))
52
+ else
53
+ {}
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,46 @@
1
+ module Coverband
2
+ module Adapters
3
+ class MemoryCacheStore
4
+ attr_accessor :store
5
+
6
+ def initialize(store)
7
+ @store = store
8
+ end
9
+
10
+ def self.reset!
11
+ files_cache.clear
12
+ end
13
+
14
+ def clear!
15
+ self.class.reset!
16
+ end
17
+
18
+ def save_report(files)
19
+ filtered_files = filter(files)
20
+ store.save_report(filtered_files) if filtered_files.any?
21
+ end
22
+
23
+ private
24
+
25
+ def self.files_cache
26
+ @files_cache ||= Hash.new
27
+ end
28
+
29
+ def files_cache
30
+ self.class.files_cache
31
+ end
32
+
33
+ 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)
36
+ line_cache = files_cache[file] ||= Set.new(store.covered_lines_for_file(file))
37
+ lines.reject! do |line|
38
+ line_cache.include?(line) ? true : (line_cache << line and false)
39
+ end
40
+ filtered_file_hash[file] = lines if lines.any?
41
+ end
42
+ end
43
+
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,101 @@
1
+ module Coverband
2
+ module Adapters
3
+ class RedisStore
4
+ BASE_KEY = 'coverband1'
5
+
6
+ def initialize(redis, opts = {})
7
+ @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
+ end
13
+
14
+ def clear!
15
+ @redis.smembers(BASE_KEY).each { |key| @redis.del("#{BASE_KEY}.#{key}") }
16
+ @redis.del(BASE_KEY)
17
+ end
18
+
19
+ 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)
30
+
31
+ report.each do |file, lines|
32
+ store_map("#{BASE_KEY}.#{file}", lines)
33
+ end
34
+ end
35
+ end
36
+
37
+ def coverage
38
+ data = {}
39
+ redis.smembers(BASE_KEY).each do |key|
40
+ data[key] = covered_lines_for_file(key)
41
+ end
42
+ data
43
+ end
44
+
45
+ def covered_files
46
+ redis.smembers(BASE_KEY)
47
+ end
48
+
49
+ 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
55
+ end
56
+
57
+ private
58
+
59
+ attr_reader :redis
60
+
61
+ def sadd_supports_array?
62
+ @_sadd_supports_array
63
+ end
64
+
65
+ def store_map(key, values)
66
+ unless values.empty?
67
+ 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}
71
+ redis.mapped_hmset(key, values)
72
+ end
73
+ end
74
+
75
+ 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
83
+ values
84
+ 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
+ end
100
+ end
101
+ end
@@ -45,10 +45,8 @@ module Coverband
45
45
  @ignore_patterns = Coverband.configuration.ignore + ["internal:prelude"]
46
46
  @ignore_patterns += ['gems'] unless Coverband.configuration.include_gems
47
47
  @sample_percentage = Coverband.configuration.percentage
48
- if Coverband.configuration.redis
49
- @reporter = Coverband::RedisStore.new(Coverband.configuration.redis)
50
- @reporter = Coverband::MemoryCacheStore.new(@reporter) if Coverband.configuration.memory_caching
51
- end
48
+ @store = Coverband.configuration.store
49
+ @store = Coverband::Adapters::MemoryCacheStore.new(@store) if Coverband.configuration.memory_caching
52
50
  @stats = Coverband.configuration.stats
53
51
  @verbose = Coverband.configuration.verbose
54
52
  @logger = Coverband.configuration.logger
@@ -95,12 +93,12 @@ module Coverband
95
93
  end
96
94
  end
97
95
 
98
- if @reporter
96
+ if @store
99
97
  if @stats
100
98
  @before_time = Time.now
101
99
  @stats.count "coverband.files.recorded_files", @file_line_usage.length
102
100
  end
103
- @reporter.store_report(@file_line_usage)
101
+ @store.save_report(@file_line_usage)
104
102
  if @stats
105
103
  @time_spent = Time.now - @before_time
106
104
  @stats.timing "coverband.files.recorded_time", @time_spent
@@ -123,7 +121,6 @@ module Coverband
123
121
  !@ignore_patterns.any?{ |pattern| file.include?(pattern) } && file.start_with?(@project_directory)
124
122
  end
125
123
 
126
-
127
124
  def set_tracer
128
125
  unless @tracer_set
129
126
  @trace.enable