covered 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 201e73d2bc612e802b0e41f05944bf7ba7b293d9d4e2672140eea2bdbee1f639
4
- data.tar.gz: d9f3b4eaf2d53d6fe394753c3a5c0945854239cf0f19c8148464aa5b76e52478
3
+ metadata.gz: d4f302d30603afbab7530590d0977348f223cf38968ef55e718f0a247ed1d943
4
+ data.tar.gz: 8ccad0a37ce4b92d624c91b31d5d20b4dd4de741eb22870ca7b5950efb472378
5
5
  SHA512:
6
- metadata.gz: d9760ff45293a83ed132c467919871926a01f477e84d2627ab656bb752aeb57ec707e992941f66cc37a119378da51c1cca2b671d5783b2a8e77d9b335e0dea12
7
- data.tar.gz: e1774282c354fad33bbe7e5317eedb18ff3d5f7d8ce58eafe3d1d20ac69d6002127adcf72414b953376e4fc32de7301d6a214c79d25bf98d31b14d236780098a
6
+ metadata.gz: 1cd42253ea62751005d0a5971300cc04a7795744cd47b648112569257d0e81ebec0c25da3766ace5a439a919f39a2746a265b4aa8bcbd9d4058a07738a9ebaa5
7
+ data.tar.gz: ab4ee6d0b138747335951c5bc45e9da5f326f2118845c3523b8b45f1b37bd615cb0bd0c83d0957d705ffc8b5b9ad8412632977a090a771d1e75d08518d95d65b
data/README.md CHANGED
@@ -1,9 +1,15 @@
1
1
  # Covered [![Build Status](https://travis-ci.com/ioquatix/covered.svg)](hhttps://travis-ci.com/ioquatix/covered)
2
2
 
3
- Covered uses modern Ruby features to generate comprehensive coverage, including support for templates which are compiled into Ruby.
3
+ Covered uses modern Ruby features to generate comprehensive coverage, including support for templates which are compiled into Ruby. **This only works with Ruby 2.6 and it's experimental support for RubyVM::AST**
4
4
 
5
5
  ![Screenshot](media/example.png)
6
6
 
7
+ ## Motivation
8
+
9
+ Existing Ruby coverage tools are unable to handle `eval`ed code. This is because the `coverage` module built into Ruby doesn't expose the necessary hooks to capture it. With Ruby 2.6, `RubyVM::AST.parse(source)` came in to existance, which gives us a fine grained tool for computing initial source coverage (i.e. what lines are executable), and thus making it possible to compute coverage for "templates".
10
+
11
+ It's still tricky to do it correctly, but it is feasible now to compute coverage of web application "views" by using this technique. This gem is an exploration to see what is possible.
12
+
7
13
  ## Installation
8
14
 
9
15
  Add this line to your application's Gemfile:
@@ -0,0 +1,94 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Covered
22
+ module Ratio
23
+ def ratio
24
+ return 1 if executable_count.zero?
25
+
26
+ Rational(executed_count, executable_count)
27
+ end
28
+
29
+ def complete?
30
+ executed_count == executable_count
31
+ end
32
+
33
+ def percentage
34
+ ratio * 100
35
+ end
36
+ end
37
+
38
+ class Coverage
39
+ def initialize(path, counts = [])
40
+ @path = path
41
+ @counts = counts
42
+
43
+ @executable_lines = nil
44
+ @executed_lines = nil
45
+ end
46
+
47
+ attr :path
48
+ attr :counts
49
+
50
+ def freeze
51
+ return if frozen?
52
+
53
+ @counts.freeze
54
+ executable_lines
55
+ executed_lines
56
+
57
+ super
58
+ end
59
+
60
+ def [] lineno
61
+ @counts[lineno]
62
+ end
63
+
64
+ def mark(lineno, value = 1)
65
+ if @counts[lineno]
66
+ @counts[lineno] += value
67
+ else
68
+ @counts[lineno] = value
69
+ end
70
+ end
71
+
72
+ def executable_lines
73
+ @executable_lines ||= @counts.compact
74
+ end
75
+
76
+ def executable_count
77
+ executable_lines.count
78
+ end
79
+
80
+ def executed_lines
81
+ @executed_lines ||= executable_lines.reject(&:zero?)
82
+ end
83
+
84
+ def executed_count
85
+ executed_lines.count
86
+ end
87
+
88
+ include Ratio
89
+
90
+ def print_summary(output)
91
+ output.puts "** #{executed_count}/#{executable_count} lines executed; #{percentage.to_f.round(2)}% covered."
92
+ end
93
+ end
94
+ end
data/lib/covered/files.rb CHANGED
@@ -18,6 +18,8 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
+ require_relative 'coverage'
22
+
21
23
  require 'set'
22
24
 
23
25
  module Covered
@@ -34,18 +36,20 @@ module Covered
34
36
  @paths.empty?
35
37
  end
36
38
 
37
- def mark(path, lineno)
38
- file = @paths[path] ||= []
39
+ def mark(path, *args)
40
+ coverage = (@paths[path] ||= Coverage.new(path))
39
41
 
40
- if file[lineno]
41
- file[lineno] += 1
42
- else
43
- file[lineno] = 1
44
- end
42
+ coverage.mark(*args)
43
+
44
+ return coverage
45
45
  end
46
46
 
47
- def each(&block)
48
- @paths.each(&block)
47
+ def each
48
+ return to_enum unless block_given?
49
+
50
+ @paths.each do |path, coverage|
51
+ yield coverage
52
+ end
49
53
  end
50
54
  end
51
55
 
@@ -73,14 +77,14 @@ module Covered
73
77
  def each(&block)
74
78
  paths = glob
75
79
 
76
- super do |path, lines|
77
- paths.delete(path)
80
+ super do |coverage|
81
+ paths.delete(coverage.path)
78
82
 
79
- yield path, lines
83
+ yield coverage
80
84
  end
81
85
 
82
86
  paths.each do |path|
83
- yield path, []
87
+ yield Coverage.new(path)
84
88
  end
85
89
  end
86
90
  end
@@ -90,13 +94,13 @@ module Covered
90
94
  true
91
95
  end
92
96
 
93
- def mark(path, lineno)
97
+ def mark(path, *args)
94
98
  super if accept?(path)
95
99
  end
96
100
 
97
101
  def each(&block)
98
- super do |path, counts|
99
- yield path, counts if accept?(path)
102
+ super do |coverage|
103
+ yield coverage if accept?(coverage.path)
100
104
  end
101
105
  end
102
106
  end
@@ -18,7 +18,7 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require_relative "report"
21
+ require_relative "summary"
22
22
  require_relative "files"
23
23
  require_relative "source"
24
24
  require_relative "capture"
@@ -43,7 +43,7 @@ module Covered
43
43
  return if frozen?
44
44
 
45
45
  capture
46
- report
46
+ summary
47
47
 
48
48
  super
49
49
  end
@@ -80,12 +80,12 @@ module Covered
80
80
  capture.disable
81
81
  end
82
82
 
83
- def report
84
- @report ||= Report.new(@output)
83
+ def summary(*args)
84
+ @summary ||= Summary.new(@output, *args)
85
85
  end
86
86
 
87
87
  def print_summary(*args)
88
- report.print_partial_summary(*args)
88
+ summary.print_partial_summary(*args)
89
89
  end
90
90
  end
91
91
  end
data/lib/covered/rspec.rb CHANGED
@@ -18,8 +18,8 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require 'pry'
22
21
  require_relative '../covered'
22
+ require 'rspec/core/formatters'
23
23
 
24
24
  $covered = Covered.policy do
25
25
  root Dir.pwd
@@ -29,15 +29,30 @@ $covered = Covered.policy do
29
29
  source
30
30
  end
31
31
 
32
+ module Covered
33
+ module RSpec
34
+ class Formatter
35
+ ::RSpec::Core::Formatters.register self, :dump_summary
36
+
37
+ def initialize(output)
38
+ @output = output
39
+ end
40
+
41
+ def dump_summary notification
42
+ $covered.print_summary(@output)
43
+ end
44
+ end
45
+ end
46
+ end
47
+
32
48
  RSpec.configure do |config|
49
+ config.add_formatter(Covered::RSpec::Formatter)
50
+
33
51
  config.before(:suite) do
34
52
  $covered.enable
35
53
  end
36
54
 
37
- config.after(:suite) do |context|
55
+ config.after(:suite) do
38
56
  $covered.disable
39
-
40
- statistics = $covered.print_summary
41
- expect(statistics).to be_complete
42
57
  end
43
58
  end
@@ -77,15 +77,15 @@ module Covered
77
77
  node.type =~ @ignore
78
78
  end
79
79
 
80
- def expand(node, lines)
80
+ def expand(node, counts)
81
81
  # puts "#{node.first_lineno}: #{node.inspect}"
82
82
 
83
- lines[node.first_lineno] ||= 0 if executable?(node)
83
+ counts[node.first_lineno] ||= 0 if executable?(node)
84
84
 
85
85
  node.children.each do |child|
86
86
  next if child.nil? or ignore?(child)
87
87
 
88
- expand(child, lines)
88
+ expand(child, counts)
89
89
  end
90
90
  end
91
91
 
@@ -102,14 +102,13 @@ module Covered
102
102
  end
103
103
 
104
104
  def each(&block)
105
- @output.each do |path, lines|
106
- lines = lines.dup
107
-
108
- if top = parse(path)
109
- expand(top, lines)
105
+ @output.each do |coverage|
106
+ # This is a little bit inefficient, perhaps add a cache layer?
107
+ if top = parse(coverage.path)
108
+ expand(top, coverage.counts)
110
109
  end
111
110
 
112
- yield path, lines
111
+ yield coverage.freeze
113
112
  end
114
113
  end
115
114
  end
@@ -18,8 +18,11 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
+ require_relative 'wrapper'
22
+ require_relative 'coverage'
23
+
21
24
  module Covered
22
- class Statistics
25
+ class Statistics < Wrapper
23
26
  def initialize
24
27
  @count = 0
25
28
  @executable_count = 0
@@ -41,60 +44,10 @@ module Covered
41
44
  @executed_count += coverage.executed_count
42
45
  end
43
46
 
44
- def percentage
45
- return nil if executable_count.zero?
46
-
47
- Rational(executed_count, executable_count) * 100
48
- end
49
-
50
- def complete?
51
- executed_count == executable_count
52
- end
47
+ include Ratio
53
48
 
54
49
  def print_summary(output)
55
50
  output.puts "* #{count} files checked; #{executed_count}/#{executable_count} lines executed; #{percentage.to_f.round(2)}% covered."
56
51
  end
57
52
  end
58
-
59
- class Coverage
60
- def initialize(path, lines)
61
- @path = path
62
- @lines = lines
63
-
64
- @executable_lines = nil
65
- @executed_lines = nil
66
- end
67
-
68
- attr :lines
69
-
70
- def executable_lines
71
- @executable_lines ||= @lines.compact
72
- end
73
-
74
- def executable_count
75
- executable_lines.count
76
- end
77
-
78
- def executed_lines
79
- @executed_lines ||= executable_lines.reject(&:zero?)
80
- end
81
-
82
- def executed_count
83
- executed_lines.count
84
- end
85
-
86
- def complete?
87
- executed_count == executable_count
88
- end
89
-
90
- def percentage
91
- return nil if executable_count.zero?
92
-
93
- Rational(executed_count, executable_count) * 100
94
- end
95
-
96
- def print_summary(output)
97
- output.puts "** #{executed_count}/#{executable_count} lines executed; #{percentage.to_f.round(2)}% covered."
98
- end
99
- end
100
53
  end
@@ -24,21 +24,36 @@ require_relative 'wrapper'
24
24
  require 'rainbow'
25
25
 
26
26
  module Covered
27
- class Report < Wrapper
28
- # A coverage array gives, for each line, the number of line execution by the interpreter. A nil value means coverage is disabled for this line (lines like else and end).
29
- def print_summary(output = $stdout)
30
- statistics = Statistics.new
27
+ class Summary < Wrapper
28
+ def initialize(output, threshold: 1.0)
29
+ super(output)
31
30
 
32
- @output.each do |path, counts|
33
- coverage = Coverage.new(path, counts)
34
- statistics << coverage
35
-
36
- next if coverage.complete?
31
+ @statistics = nil
32
+
33
+ @threshold = threshold
34
+ end
35
+
36
+ def each
37
+ @statistics = Statistics.new
38
+
39
+ super do |coverage|
40
+ @statistics << coverage
37
41
 
42
+ if coverage.ratio < @threshold
43
+ yield coverage
44
+ end
45
+ end
46
+ end
47
+
48
+ # A coverage array gives, for each line, the number of line execution by the interpreter. A nil value means coverage is disabled for this line (lines like else and end).
49
+ def print_summary(output = $stdout)
50
+ self.each do |coverage|
38
51
  line_offset = 1
39
- output.puts Rainbow(path).bold.underline
52
+ output.puts "", Rainbow(coverage.path).bold.underline
53
+
54
+ counts = coverage.counts
40
55
 
41
- File.open(path, "r") do |file|
56
+ File.open(coverage.path, "r") do |file|
42
57
  file.each_line do |line|
43
58
  count = counts[line_offset]
44
59
 
@@ -65,40 +80,35 @@ module Covered
65
80
  coverage.print_summary(output)
66
81
  end
67
82
 
68
- statistics.print_summary(output)
69
-
70
- return statistics
83
+ @statistics.print_summary(output)
71
84
  end
72
85
 
73
86
  def print_partial_summary(output = $stdout, before: 4, after: 4)
74
87
  statistics = Statistics.new
75
88
 
76
- @output.each do |path, counts|
77
- coverage = Coverage.new(path, counts)
78
- statistics << coverage
79
-
80
- next if coverage.complete?
81
-
89
+ self.each do |coverage|
82
90
  line_offset = 1
83
- output.puts Rainbow(path).bold.underline
91
+ output.puts "", Rainbow(coverage.path).bold.underline
84
92
 
85
- printing = false
93
+ counts = coverage.counts
86
94
 
87
- File.open(path, "r") do |file|
95
+ File.open(coverage.path, "r") do |file|
88
96
  file.each_line do |line|
89
97
  range = Range.new([line_offset - before, 0].max, line_offset+after)
90
98
 
91
99
  if counts[range]&.include?(0)
92
100
  count = counts[line_offset]
93
101
 
94
- output.write("#{line_offset}|".rjust(8))
95
- output.write("#{count}:".rjust(8))
102
+ prefix = "#{line_offset} ".rjust(8) + "#{count} ".rjust(8)
96
103
 
97
104
  if count == nil
105
+ output.write prefix
98
106
  output.write Rainbow(line).faint
99
107
  elsif count == 0
108
+ output.write Rainbow(prefix).background(:darkred)
100
109
  output.write Rainbow(line).red
101
110
  else
111
+ output.write Rainbow(prefix).background(:darkgreen)
102
112
  output.write Rainbow(line).green
103
113
  end
104
114
 
@@ -115,9 +125,8 @@ module Covered
115
125
  coverage.print_summary(output)
116
126
  end
117
127
 
118
- statistics.print_summary(output)
119
-
120
- return statistics
128
+ output.puts
129
+ @statistics.print_summary(output)
121
130
  end
122
131
  end
123
132
  end
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Covered
22
- VERSION = "0.3.0"
22
+ VERSION = "0.4.0"
23
23
  end
@@ -20,14 +20,16 @@
20
20
 
21
21
  module Covered
22
22
  class Wrapper
23
+ include Enumerable
24
+
23
25
  def initialize(output = nil)
24
26
  @output = output
25
27
  end
26
28
 
27
29
  attr :output
28
30
 
29
- def mark(path, lineno)
30
- @output.mark(path, lineno) if @output
31
+ def mark(*args)
32
+ @output.mark(*args) if @output
31
33
  end
32
34
 
33
35
  def enable
@@ -38,9 +40,13 @@ module Covered
38
40
  @output.disable if @output
39
41
  end
40
42
 
41
- # @yield [path, counts] the path to the file, and the execution counts.
43
+ # @yield [Coverage] the path to the file, and the execution counts.
42
44
  def each(&block)
43
45
  @output.each(&block)
44
46
  end
47
+
48
+ def to_h
49
+ collect{|coverage| [coverage.path, coverage]}.to_h
50
+ end
45
51
  end
46
52
  end
data/media/example.png CHANGED
Binary file
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: covered
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-02 00:00:00.000000000 Z
11
+ date: 2018-07-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rainbow
@@ -96,13 +96,14 @@ files:
96
96
  - covered.gemspec
97
97
  - lib/covered.rb
98
98
  - lib/covered/capture.rb
99
+ - lib/covered/coverage.rb
99
100
  - lib/covered/eval.rb
100
101
  - lib/covered/files.rb
101
102
  - lib/covered/policy.rb
102
- - lib/covered/report.rb
103
103
  - lib/covered/rspec.rb
104
104
  - lib/covered/source.rb
105
105
  - lib/covered/statistics.rb
106
+ - lib/covered/summary.rb
106
107
  - lib/covered/version.rb
107
108
  - lib/covered/wrapper.rb
108
109
  - media/example.png