coverband 1.3.1 → 1.5.0

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 (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
@@ -0,0 +1,35 @@
1
+ module Coverband
2
+ class Baseline
3
+
4
+ def self.record
5
+ require 'coverage'
6
+ Coverage.start
7
+ yield
8
+
9
+ project_directory = File.expand_path(Coverband.configuration.root)
10
+ results = Coverage.result
11
+ results = results.reject { |key, val| !key.match(project_directory) || Coverband.configuration.ignore.any? { |pattern| key.match(/#{pattern}/) } }
12
+
13
+ Coverband.configuration.store.save_report(convert_coverage_format(results))
14
+ end
15
+
16
+ def self.parse_baseline(back_compat = nil)
17
+ Coverband.configuration.store.coverage
18
+ end
19
+
20
+ private
21
+
22
+ def self.convert_coverage_format(results)
23
+ file_map = {}
24
+ results.each_pair do |file, data|
25
+ lines_map = {}
26
+ data.each_with_index do |hits, index|
27
+ lines_map[(index+1)] = hits unless hits.nil?
28
+ end
29
+ file_map[file] = lines_map
30
+ end
31
+ file_map
32
+ end
33
+
34
+ end
35
+ end
@@ -1,17 +1,19 @@
1
1
  module Coverband
2
2
  class Configuration
3
3
 
4
- attr_accessor :redis, :coverage_baseline, :root_paths, :root,
4
+ attr_accessor :redis, :root_paths, :root,
5
5
  :ignore, :percentage, :verbose, :reporter, :stats,
6
- :logger, :startup_delay, :baseline_file, :trace_point_events,
7
- :include_gems, :memory_caching, :s3_bucket
6
+ :logger, :startup_delay, :trace_point_events,
7
+ :include_gems, :memory_caching, :s3_bucket, :coverage_file, :store
8
+
9
+ # deprecated, but leaving to allow old configs to 'just work'
10
+ # remove for 2.0
11
+ attr_accessor :coverage_baseline
8
12
 
9
13
  def initialize
10
14
  @root = Dir.pwd
11
15
  @redis = nil
12
16
  @stats = nil
13
- @coverage_baseline = {}
14
- @baseline_file = './tmp/coverband_baseline.json'
15
17
  @root_paths = []
16
18
  @ignore = []
17
19
  @include_gems = false
@@ -22,11 +24,24 @@ module Coverband
22
24
  @startup_delay = 0
23
25
  @trace_point_events = [:line]
24
26
  @memory_caching = false
27
+ @coverage_file = nil
28
+ @store = nil
25
29
  end
26
30
 
27
31
  def logger
28
32
  @logger ||= Logger.new(STDOUT)
29
33
  end
30
34
 
35
+ #TODO considering removing @redis / @coveragefile and have user set store directly
36
+ def store
37
+ return @store if @store
38
+ if redis
39
+ @store = Coverband::Adapters::RedisStore.new(redis)
40
+ elsif Coverband.configuration.coverage_file
41
+ @store = Coverband::Adapters::FileStore.new(coverage_file)
42
+ end
43
+ @store
44
+ end
45
+
31
46
  end
32
47
  end
@@ -0,0 +1,150 @@
1
+ module Coverband
2
+ module Reporters
3
+ class Base
4
+
5
+ def self.report(store, options = {})
6
+ roots = get_roots
7
+ additional_coverage_data = options.fetch(:additional_scov_data) { [] }
8
+
9
+ if Coverband.configuration.verbose
10
+ Coverband.configuration.logger.info "fixing root: #{roots.join(', ')}"
11
+ Coverband.configuration.logger.info "additional data:\n #{additional_coverage_data}"
12
+ end
13
+
14
+ scov_style_report = report_scov_with_additional_data(store, additional_coverage_data, roots)
15
+
16
+ if Coverband.configuration.verbose
17
+ Coverband.configuration.logger.info "report:\n #{scov_style_report.inspect}"
18
+ end
19
+ scov_style_report
20
+ end
21
+
22
+ def self.get_roots
23
+ roots = Coverband.configuration.root_paths
24
+ roots << "#{current_root}/"
25
+ roots
26
+ end
27
+
28
+ def self.current_root
29
+ File.expand_path(Coverband.configuration.root)
30
+ end
31
+
32
+ protected
33
+
34
+ def self.fix_file_names(report_hash, roots)
35
+ fixed_report = {} #normalize names across servers
36
+ report_hash.each_pair do |key, values|
37
+ filename = filename_from_key(key, roots)
38
+ fixed_report[filename] = values
39
+ end
40
+ fixed_report
41
+ end
42
+
43
+ # > merge_arrays([0,0,1,0,1],[nil,0,1,0,0])
44
+ # [0,0,1,0,1]
45
+ def self.merge_arrays(first, second)
46
+ merged = []
47
+ longest = first.length > second.length ? first : second
48
+
49
+ longest.each_with_index do |line, index|
50
+ if first[index] || second[index]
51
+ merged[index] = (first[index].to_i + second[index].to_i)
52
+ else
53
+ merged[index] = nil
54
+ end
55
+ end
56
+
57
+ merged
58
+ end
59
+
60
+ # > merge_existing_coverage({"file.rb" => [0,1,2,nil,nil,nil]}, {"file.rb" => [0,1,2,nil,0,1,2]})
61
+ # expects = {"file.rb" => [0,2,4,nil,0,1,2]}
62
+ def self.merge_existing_coverage(scov_style_report, existing_coverage)
63
+ existing_coverage.each_pair do |file_key, existing_lines|
64
+ next if Coverband.configuration.ignore.any?{ |i| file_key.match(i) }
65
+ if current_line_hits = scov_style_report[file_key]
66
+ scov_style_report[file_key] = merge_arrays(current_line_hits, existing_lines)
67
+ else
68
+ scov_style_report[file_key] = existing_lines
69
+ end
70
+ end
71
+ scov_style_report
72
+ end
73
+
74
+ def self.filename_from_key(key, roots)
75
+ filename = key
76
+ roots.each do |root|
77
+ filename = filename.gsub(/^#{root}/, './')
78
+ end
79
+ # the filename for SimpleCov is expected to be a full path.
80
+ # roots.last should be roots << current_root}/
81
+ # a fully expanded path of config.root
82
+ filename = filename.gsub('./', roots.last)
83
+ filename
84
+ end
85
+
86
+ # > line_hash(store, 'hearno/script/tester.rb', ['/app/', '/Users/danmayer/projects/hearno/'])
87
+ # {"/Users/danmayer/projects/hearno/script/tester.rb"=>[1, nil, 1, 2, nil, nil, nil]}
88
+ def self.line_hash(store, key, roots)
89
+ filename = filename_from_key(key, roots)
90
+ if File.exists?(filename)
91
+
92
+ count = File.foreach(filename).inject(0) { |c, line| c + 1 }
93
+ line_array = Array.new(count, nil)
94
+
95
+ lines_hit = store.covered_lines_for_file(key)
96
+ if lines_hit.is_a?(Array)
97
+ line_array.each_with_index{|_,index| line_array[index] = 1 if lines_hit.include?((index + 1)) }
98
+ else
99
+ line_array.each_with_index{|_,index| line_array[index] = (line_array[index].to_i + lines_hit[(index + 1).to_s].to_i) if lines_hit.keys.include?((index + 1).to_s) }
100
+ end
101
+ {filename => line_array}
102
+ else
103
+ Coverband.configuration.logger.info "file #{filename} not found in project"
104
+ nil
105
+ end
106
+ end
107
+
108
+ def self.get_current_scov_data_imp(store, roots)
109
+ scov_style_report = {}
110
+
111
+ ###
112
+ # why do we need to merge covered files data?
113
+ # basically because paths on machines or deployed hosts could be different, so
114
+ # two different keys could point to the same filename or `line_key`
115
+ # this logic should be pushed to base report
116
+ ###
117
+ store.covered_files.each do |key|
118
+ next if Coverband.configuration.ignore.any?{ |i| key.match(i) }
119
+ line_data = line_hash(store, key, roots)
120
+
121
+ if line_data
122
+ line_key = line_data.keys.first
123
+ previous_line_hash = scov_style_report[line_key]
124
+
125
+ if previous_line_hash
126
+ line_data[line_key] = merge_arrays(line_data[line_key], previous_line_hash)
127
+ end
128
+
129
+ scov_style_report.merge!(line_data)
130
+ end
131
+ end
132
+
133
+ scov_style_report = fix_file_names(scov_style_report, roots)
134
+ scov_style_report
135
+ end
136
+
137
+ def self.report_scov_with_additional_data(store, additional_scov_data, roots)
138
+ scov_style_report = get_current_scov_data_imp(store, roots)
139
+
140
+ additional_scov_data.each do |data|
141
+ scov_style_report = merge_existing_coverage(scov_style_report, data)
142
+ end
143
+
144
+ scov_style_report
145
+ end
146
+
147
+ end
148
+ end
149
+ end
150
+
@@ -0,0 +1,17 @@
1
+ module Coverband
2
+ module Reporters
3
+ class ConsoleReport < Base
4
+
5
+ def self.report(store, options = {})
6
+ scov_style_report = super(store, options)
7
+
8
+ scov_style_report.each_pair do |file, usage|
9
+ Coverband.configuration.logger.info "#{file}: #{usage}"
10
+ end
11
+ scov_style_report
12
+ end
13
+
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,46 @@
1
+ module Coverband
2
+ module Reporters
3
+ class SimpleCovReport < Base
4
+
5
+ def self.report(store, options = {})
6
+ begin
7
+ require 'simplecov'
8
+ rescue
9
+ Coverband.configuration.logger.error "coverband requires simplecov in order to generate a report, when configured for the scov report style."
10
+ return
11
+ end
12
+
13
+ scov_style_report = super(store, options)
14
+
15
+ open_report = options.fetch(:open_report) { true }
16
+
17
+ # set root to show files if user has simplecov profiles
18
+ # https://github.com/danmayer/coverband/issues/59
19
+ SimpleCov.root(current_root)
20
+
21
+ # add in files never hit in coverband
22
+ SimpleCov.track_files "#{current_root}/{app,lib,config}/**/*.{rb,haml,erb,slim}"
23
+
24
+ # still apply coverband filters
25
+ report_files = SimpleCov.add_not_loaded_files(scov_style_report)
26
+ filtered_report_files = {}
27
+ report_files.each_pair do |file, data|
28
+ next if Coverband.configuration.ignore.any?{ |i| file.match(i) }
29
+ filtered_report_files[file] = data
30
+ end
31
+
32
+ SimpleCov::Result.new(filtered_report_files).format!
33
+
34
+ if open_report
35
+ `open #{SimpleCov.coverage_dir}/index.html`
36
+ else
37
+ Coverband.configuration.logger.info "report is ready and viewable: open #{SimpleCov.coverage_dir}/index.html"
38
+ end
39
+
40
+ S3ReportWriter.new(Coverband.configuration.s3_bucket).persist! if Coverband.configuration.s3_bucket
41
+ end
42
+
43
+ end
44
+ end
45
+ end
46
+
@@ -2,6 +2,12 @@ class S3ReportWriter
2
2
 
3
3
  def initialize(bucket_name)
4
4
  @bucket_name = bucket_name
5
+ begin
6
+ require 'aws-sdk'
7
+ rescue
8
+ Coverband.configuration.logger.error "coverband requires 'aws-sdk' in order use S3ReportWriter."
9
+ return
10
+ end
5
11
  end
6
12
 
7
13
  def persist!
@@ -26,5 +32,4 @@ class S3ReportWriter
26
32
  s3.bucket(@bucket_name)
27
33
  end
28
34
 
29
-
30
35
  end
@@ -2,21 +2,23 @@ namespace :coverband do
2
2
 
3
3
  desc "record coverband coverage baseline"
4
4
  task :baseline do
5
- Coverband::Reporter.baseline {
5
+ Coverband::Baseline.record {
6
6
  if Rake::Task.tasks.any?{ |key| key.to_s.match(/environment$/) }
7
+ Coverband.configuration.logger.info "invoking rake environment"
7
8
  Rake::Task['environment'].invoke
8
9
  elsif Rake::Task.tasks.any?{ |key| key.to_s.match(/env$/) }
10
+ Coverband.configuration.logger.info "invoking rake env"
9
11
  Rake::Task["env"].invoke
10
- else
11
- baseline_files = [File.expand_path('./config/boot.rb', Dir.pwd),
12
- File.expand_path('./config/application.rb', Dir.pwd),
13
- File.expand_path('./config/environment.rb', Dir.pwd)]
14
-
15
- baseline_files.each do |baseline_file|
16
- if File.exists?(baseline_file)
17
- require baseline_file
18
- end
19
- end
12
+ end
13
+
14
+ baseline_files = [File.expand_path('./config/boot.rb', Dir.pwd),
15
+ File.expand_path('./config/application.rb', Dir.pwd),
16
+ File.expand_path('./config/environment.rb', Dir.pwd)]
17
+
18
+ baseline_files.each do |baseline_file|
19
+ if File.exists?(baseline_file)
20
+ require baseline_file
21
+ end
20
22
  end
21
23
  if defined? Rails
22
24
  Dir.glob("#{Rails.root}/app/**/*.rb").sort.each { |file|
@@ -44,7 +46,11 @@ namespace :coverband do
44
46
  ###
45
47
  desc "report runtime coverband code coverage"
46
48
  task :coverage => :environment do
47
- Coverband::Reporter.report
49
+ if Coverband.configuration.reporter=='scov'
50
+ Coverband::Reporters::SimpleCovReport.report(Coverband.configuration.store)
51
+ else
52
+ Coverband::Reporters::ConsoleReport.report(Coverband.configuration.store)
53
+ end
48
54
  end
49
55
 
50
56
  def clear_simplecov_filters
@@ -55,18 +61,22 @@ namespace :coverband do
55
61
 
56
62
  desc "report runtime coverband code coverage after disabling simplecov filters"
57
63
  task :coverage_no_filters => :environment do
58
- clear_simplecov_filters
59
- Coverband::Reporter.report
64
+ if Coverband.configuration.reporter=='scov'
65
+ clear_simplecov_filters
66
+ Coverband::Reporters::SimpleCovReport.report(Coverband.configuration.store)
67
+ else
68
+ puts "coverage without filters only makes sense for SimpleCov reports"
69
+ end
60
70
  end
61
71
 
62
72
  ###
63
73
  # You likely want to clear coverage after significant code changes.
64
74
  # You may want to have a hook that saves current coverband data on deploy
65
- # and then resets the redis data.
75
+ # and then resets the coverband store data.
66
76
  ###
67
77
  desc "reset coverband coverage data"
68
78
  task :clear => :environment do
69
- Coverband::Reporter.clear_coverage
79
+ Coverband.configuration.store.clear!
70
80
  end
71
81
 
72
82
  end
@@ -1,3 +1,3 @@
1
1
  module Coverband
2
- VERSION = "1.3.1"
2
+ VERSION = "1.5.0"
3
3
  end
@@ -14,7 +14,7 @@ namespace :benchmarks do
14
14
  end
15
15
  end
16
16
 
17
- desc 'set up coverband'
17
+ desc 'set up coverband default redis'
18
18
  task :setup do
19
19
  clone_classifier
20
20
  $LOAD_PATH.unshift(File.join(classifier_dir, 'lib'))
@@ -34,6 +34,42 @@ namespace :benchmarks do
34
34
 
35
35
  end
36
36
 
37
+ desc 'set up coverband redis array'
38
+ task :setup_array do
39
+ clone_classifier
40
+ $LOAD_PATH.unshift(File.join(classifier_dir, 'lib'))
41
+ require 'benchmark'
42
+ require 'classifier-reborn'
43
+
44
+ Coverband.configure do |config|
45
+ config.redis = Redis.new
46
+ config.root = Dir.pwd
47
+ config.startup_delay = 0
48
+ config.percentage = 100.0
49
+ config.logger = $stdout
50
+ config.verbose = false
51
+ config.store = Coverband::Adapters::RedisStore.new(Redis.new, array: true)
52
+ end
53
+ end
54
+
55
+ desc 'set up coverband filestore'
56
+ task :setup_file do
57
+ clone_classifier
58
+ $LOAD_PATH.unshift(File.join(classifier_dir, 'lib'))
59
+ require 'benchmark'
60
+ require 'classifier-reborn'
61
+
62
+ Coverband.configure do |config|
63
+ config.redis = nil
64
+ config.store = nil
65
+ config.root = Dir.pwd
66
+ config.startup_delay = 0
67
+ config.percentage = 100.0
68
+ config.logger = $stdout
69
+ config.verbose = false
70
+ config.coverage_file = '/tmp/benchmark_store.json'
71
+ end
72
+ end
37
73
 
38
74
  def bayes_classification
39
75
  b = ClassifierReborn::Bayes.new 'Interesting', 'Uninteresting'
@@ -65,11 +101,9 @@ namespace :benchmarks do
65
101
  10_000.times { Dog.new.bark }
66
102
  end
67
103
 
68
-
69
-
70
- desc 'runs benchmarks'
71
- task :run => :setup do
72
- SAMPLINGS = 3
104
+ def run_work
105
+ puts "benchmark for: #{Coverband.configuration.inspect}"
106
+ puts "store: #{Coverband.configuration.store.inspect}"
73
107
  bm = Benchmark.bm(15) do |x|
74
108
 
75
109
  x.report 'coverband' do
@@ -85,10 +119,26 @@ namespace :benchmarks do
85
119
  work
86
120
  end
87
121
  end
88
-
89
122
  end
90
123
  end
91
124
 
125
+ desc 'runs benchmarks on default redis setup'
126
+ task :run => :setup do
127
+ SAMPLINGS = 5
128
+ run_work
129
+ end
130
+
131
+ desc 'runs benchmarks redis array'
132
+ task :run_array => :setup_array do
133
+ SAMPLINGS = 5
134
+ run_work
135
+ end
136
+
137
+ desc 'runs benchmarks file store'
138
+ task :run_file => :setup_file do
139
+ SAMPLINGS = 5
140
+ run_work
141
+ end
92
142
  end
93
143
 
94
144
  desc "runs benchmarks"