covered 0.1.0 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 025c9e0620ca9bbb2115fc1dcaca9f36c4d827fb047ee303645bf80d4fc3db0e
4
- data.tar.gz: 4fd5fd8b45eaf3a90e43b9813c2e86d24a9e3081233e5f1bd24a6bda8b005ce2
3
+ metadata.gz: 201e73d2bc612e802b0e41f05944bf7ba7b293d9d4e2672140eea2bdbee1f639
4
+ data.tar.gz: d9f3b4eaf2d53d6fe394753c3a5c0945854239cf0f19c8148464aa5b76e52478
5
5
  SHA512:
6
- metadata.gz: 8c3f947a44f456b9278ff554487a0262d29e02d8aa0719d6ac57810b4d780c82cf3d0bffd70ddc47daffeccf64cec3a736c244449791501b93a33c14b9439305
7
- data.tar.gz: 1b7a1c9456e8529d3b248dd1d50f0bf6a9448522951acbcc021a0fa55de17dc14ed3aa214411c9a1a9144f27c30954a131f0b3b92d82fa34e76db4af2a474e08
6
+ metadata.gz: d9760ff45293a83ed132c467919871926a01f477e84d2627ab656bb752aeb57ec707e992941f66cc37a119378da51c1cca2b671d5783b2a8e77d9b335e0dea12
7
+ data.tar.gz: e1774282c354fad33bbe7e5317eedb18ff3d5f7d8ce58eafe3d1d20ac69d6002127adcf72414b953376e4fc32de7301d6a214c79d25bf98d31b14d236780098a
data/Gemfile CHANGED
@@ -1,6 +1,8 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
-
5
3
  # Specify your gem's dependencies in covered.gemspec
6
4
  gemspec
5
+
6
+ group :development do
7
+ gem "pry"
8
+ end
data/README.md CHANGED
@@ -1,22 +1,22 @@
1
- # Covered
1
+ # Covered [![Build Status](https://travis-ci.com/ioquatix/covered.svg)](hhttps://travis-ci.com/ioquatix/covered)
2
2
 
3
3
  Covered uses modern Ruby features to generate comprehensive coverage, including support for templates which are compiled into Ruby.
4
4
 
5
- [![Build Status](https://travis-ci.com/ioquatix/covered.svg)](hhttps://travis-ci.com/ioquatix/covered)
5
+ ![Screenshot](media/example.png)
6
6
 
7
7
  ## Installation
8
8
 
9
9
  Add this line to your application's Gemfile:
10
10
 
11
- gem 'covered'
11
+ gem 'covered'
12
12
 
13
13
  And then execute:
14
14
 
15
- $ bundle
15
+ $ bundle
16
16
 
17
17
  Or install it yourself as:
18
18
 
19
- $ gem install covered
19
+ $ gem install covered
20
20
 
21
21
  ## Usage
22
22
 
@@ -38,7 +38,7 @@ require 'covered/rspec'
38
38
 
39
39
  Released under the MIT license.
40
40
 
41
- Copyright, 2015, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
41
+ Copyright, 2018, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
42
42
 
43
43
  Permission is hereby granted, free of charge, to any person obtaining a copy
44
44
  of this software and associated documentation files (the "Software"), to deal
@@ -18,39 +18,32 @@
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 'rainbow'
21
+ require_relative 'wrapper'
22
22
 
23
23
  module Covered
24
- class Capture
24
+ class Capture < Wrapper
25
25
  def initialize(output)
26
- @paths = {}
26
+ super(output)
27
27
 
28
- @line_trace = TracePoint.new(:line) do |trace_point|
28
+ @trace = TracePoint.new(:line, :call) do |trace_point|
29
29
  if path = trace_point.path
30
- # if path =~ /\.xnode$/
31
- # binding.pry
32
- # end
33
- #
34
- output.mark(path, trace_point.lineno)
30
+ @output.mark(path, trace_point.lineno)
35
31
  end
36
32
  end
37
- #
38
- # @call_trace = TracePoint.new(:c_call) do |trace_point|
39
- # if trace_point.method_id == :eval
40
- # end
41
- # end
42
33
  end
43
34
 
44
35
  attr :paths
45
36
 
46
37
  def enable
47
- # @call_trace.enable
48
- @line_trace.enable
38
+ super
39
+
40
+ @trace.enable
49
41
  end
50
42
 
51
43
  def disable
52
- @line_trace.disable
53
- # @call_trace.disable
44
+ @trace.disable
45
+
46
+ super
54
47
  end
55
48
  end
56
49
  end
data/lib/covered/eval.rb CHANGED
@@ -18,61 +18,61 @@
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 'thread'
22
+
23
+ module Covered
24
+ module Eval
25
+ @mutex = Mutex.new
26
+ @handler = nil
27
+
28
+ def self.enable(handler)
29
+ @mutex.synchronize do
30
+ @handler = handler
31
+ end
32
+ end
33
+
34
+ def self.disable(handler)
35
+ @mutex.synchronize do
36
+ @handler = nil
37
+ end
38
+ end
39
+
40
+ def self.intercept_eval(*args)
41
+ @mutex.synchronize do
42
+ @handler.intercept_eval(*args) if @handler
43
+ end
44
+ end
45
+ end
46
+ end
47
+
21
48
  module Kernel
22
49
  class << self
23
- alias_method :original_eval, :eval
50
+ alias_method :__eval__, :eval
24
51
 
25
52
  def eval(*args)
26
- if source = $source
27
- source.map(*args)
28
- end
53
+ Covered::Eval.intercept_eval(*args)
29
54
 
30
- original_eval(*args)
55
+ __eval__(*args)
31
56
  end
32
57
  end
33
58
 
34
59
  private
35
60
 
36
- alias_method :original_eval, :eval
61
+ alias_method :__eval__, :eval
37
62
 
38
63
  def eval(*args)
39
- if source = $source
40
- source.map(*args)
41
- end
64
+ Covered::Eval.intercept_eval(*args)
42
65
 
43
- original_eval(*args)
66
+ __eval__(*args)
44
67
  end
45
68
  end
46
69
 
47
70
  class Binding
48
- alias_method :original_eval, :eval
71
+ alias_method :__eval__, :eval
49
72
 
50
73
  def eval(*args)
51
- if source = $source
52
- source.map(*args)
53
- end
74
+ Covered::Eval.intercept_eval(*args)
54
75
 
55
- original_eval(*args)
76
+ __eval__(*args)
56
77
  end
57
78
  end
58
-
59
- # class BasicObject
60
- # alias_method :original_instance_eval, :instance_eval
61
- #
62
- # def instance_eval(*args, &block)
63
- # puts args.inspect
64
- # original_instance_eval(*args, &block)
65
- # end
66
- # end
67
- #
68
- # class Module
69
- # alias_method :original_module_eval, :module_eval
70
- #
71
- # def module_eval(*args, &block)
72
- # puts args.inspect
73
- # original_module_eval(*args, &block)
74
- # end
75
- #
76
- # remove_method :class_eval
77
- # alias_method :class_eval, :module_eval
78
- # end
data/lib/covered/files.rb CHANGED
@@ -18,15 +18,22 @@
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 'set'
22
+
21
23
  module Covered
22
- class Files
23
- def initialize
24
+ class Files < Wrapper
25
+ def initialize(output = nil)
26
+ super(output)
27
+
24
28
  @paths = {}
25
- @map = {}
26
29
  end
27
30
 
28
31
  attr :paths
29
32
 
33
+ def empty?
34
+ @paths.empty?
35
+ end
36
+
30
37
  def mark(path, lineno)
31
38
  file = @paths[path] ||= []
32
39
 
@@ -42,42 +49,97 @@ module Covered
42
49
  end
43
50
  end
44
51
 
45
- class Ignore
46
- def initialize(pattern, output)
52
+ class Include < Wrapper
53
+ def initialize(output, pattern)
54
+ super(output)
55
+
47
56
  @pattern = pattern
48
- @output = output
49
57
  end
50
58
 
51
- def mark(path, lineno)
52
- @output.mark(path, lineno) unless @pattern.match? path
59
+ attr :pattern
60
+
61
+ def glob
62
+ paths = Set.new
63
+
64
+ Dir.glob(@pattern) do |path|
65
+ unless File.directory?(path)
66
+ paths << File.realpath(path)
67
+ end
68
+ end
69
+
70
+ return paths
71
+ end
72
+
73
+ def each(&block)
74
+ paths = glob
75
+
76
+ super do |path, lines|
77
+ paths.delete(path)
78
+
79
+ yield path, lines
80
+ end
81
+
82
+ paths.each do |path|
83
+ yield path, []
84
+ end
53
85
  end
54
86
  end
55
87
 
56
- class Group
57
- def initialize(patterns, output)
58
- @patterns = patterns
59
- @output = output
88
+ class Filter < Wrapper
89
+ def accept?(path)
90
+ true
60
91
  end
61
92
 
62
93
  def mark(path, lineno)
63
- @patterns.each do |pattern, output|
64
- return output.mark(path, lineno) if pattern.match? path
94
+ super if accept?(path)
95
+ end
96
+
97
+ def each(&block)
98
+ super do |path, counts|
99
+ yield path, counts if accept?(path)
65
100
  end
101
+ end
102
+ end
103
+
104
+ class Skip < Filter
105
+ def initialize(output, pattern)
106
+ super(output)
66
107
 
67
- @output.mark(path, lineno) if @output
108
+ @pattern = pattern
109
+ end
110
+
111
+ attr :pattern
112
+
113
+ def accept? path
114
+ !(@pattern === path)
68
115
  end
69
116
  end
70
117
 
71
- class Relative
72
- def initialize(root, output)
73
- @root = root
74
- @output = output
118
+ class Only < Filter
119
+ def initialize(output, pattern)
120
+ super(output)
121
+
122
+ @pattern = pattern
75
123
  end
76
124
 
77
- def mark(path, lineno)
78
- if path.start_with? @root
79
- @output.mark(path, lineno)
80
- end
125
+ attr :pattern
126
+
127
+ def accept?(path)
128
+ @pattern === path
129
+ end
130
+ end
131
+
132
+ class Root < Filter
133
+ def initialize(output, path)
134
+ super(output)
135
+
136
+ @path = path
137
+ end
138
+
139
+ attr :path
140
+
141
+ def accept?(path)
142
+ path.start_with?(@path)
81
143
  end
82
144
  end
83
145
  end
@@ -0,0 +1,91 @@
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
+ require_relative "report"
22
+ require_relative "files"
23
+ require_relative "source"
24
+ require_relative "capture"
25
+
26
+ module Covered
27
+ def self.policy(&block)
28
+ policy = Policy.new
29
+
30
+ policy.instance_eval(&block)
31
+
32
+ policy.freeze
33
+
34
+ return policy
35
+ end
36
+
37
+ class Policy < Wrapper
38
+ def initialize
39
+ super(Files.new)
40
+ end
41
+
42
+ def freeze
43
+ return if frozen?
44
+
45
+ capture
46
+ report
47
+
48
+ super
49
+ end
50
+
51
+ def source(*args)
52
+ @output = Source.new(@output, *args)
53
+ end
54
+
55
+ def include(*args)
56
+ @output = Include.new(@output, *args)
57
+ end
58
+
59
+ def skip(*args)
60
+ @output = Skip.new(@output, *args)
61
+ end
62
+
63
+ def only(*args)
64
+ @output = Only.new(@output, *args)
65
+ end
66
+
67
+ def root(*args)
68
+ @output = Root.new(@output, *args)
69
+ end
70
+
71
+ def capture
72
+ @capture ||= Capture.new(@output)
73
+ end
74
+
75
+ def enable
76
+ capture.enable
77
+ end
78
+
79
+ def disable
80
+ capture.disable
81
+ end
82
+
83
+ def report
84
+ @report ||= Report.new(@output)
85
+ end
86
+
87
+ def print_summary(*args)
88
+ report.print_partial_summary(*args)
89
+ end
90
+ end
91
+ end
@@ -18,32 +18,22 @@
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 'rainbow'
21
+ require_relative 'statistics'
22
+ require_relative 'wrapper'
22
23
 
23
- require_relative 'ast'
24
+ require 'rainbow'
24
25
 
25
26
  module Covered
26
- class Report
27
- def initialize(source)
28
- @source = source
29
- end
30
-
31
- def expand(node, lines)
32
- # puts "#{node.first_lineno}: #{node.inspect}"
33
-
34
- lines[node.first_lineno] ||= 0 if node.executable?
35
-
36
- node.children.each do |child|
37
- next if child.nil?
38
-
39
- expand(child, lines)
40
- end
41
- end
42
-
27
+ class Report < Wrapper
43
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).
44
29
  def print_summary(output = $stdout)
45
- @source.each do |path, ast, counts|
46
- expand(ast, counts)
30
+ statistics = Statistics.new
31
+
32
+ @output.each do |path, counts|
33
+ coverage = Coverage.new(path, counts)
34
+ statistics << coverage
35
+
36
+ next if coverage.complete?
47
37
 
48
38
  line_offset = 1
49
39
  output.puts Rainbow(path).bold.underline
@@ -52,8 +42,8 @@ module Covered
52
42
  file.each_line do |line|
53
43
  count = counts[line_offset]
54
44
 
55
- output.write("#{line_offset}".rjust(4))
56
- output.write(" #{count}|".rjust(4))
45
+ output.write("#{line_offset}|".rjust(8))
46
+ output.write("#{count}:".rjust(8))
57
47
 
58
48
  if count == nil
59
49
  output.write Rainbow(line).faint
@@ -72,11 +62,62 @@ module Covered
72
62
  end
73
63
  end
74
64
 
75
- covered = counts.compact
76
- hits = covered.reject(&:zero?)
77
- percentage = Rational(hits.count, covered.count) * 100
78
- output.puts "** #{hits.count}/#{covered.count} lines executed; #{percentage.to_f.round(2)}% covered."
65
+ coverage.print_summary(output)
79
66
  end
67
+
68
+ statistics.print_summary(output)
69
+
70
+ return statistics
71
+ end
72
+
73
+ def print_partial_summary(output = $stdout, before: 4, after: 4)
74
+ statistics = Statistics.new
75
+
76
+ @output.each do |path, counts|
77
+ coverage = Coverage.new(path, counts)
78
+ statistics << coverage
79
+
80
+ next if coverage.complete?
81
+
82
+ line_offset = 1
83
+ output.puts Rainbow(path).bold.underline
84
+
85
+ printing = false
86
+
87
+ File.open(path, "r") do |file|
88
+ file.each_line do |line|
89
+ range = Range.new([line_offset - before, 0].max, line_offset+after)
90
+
91
+ if counts[range]&.include?(0)
92
+ count = counts[line_offset]
93
+
94
+ output.write("#{line_offset}|".rjust(8))
95
+ output.write("#{count}:".rjust(8))
96
+
97
+ if count == nil
98
+ output.write Rainbow(line).faint
99
+ elsif count == 0
100
+ output.write Rainbow(line).red
101
+ else
102
+ output.write Rainbow(line).green
103
+ end
104
+
105
+ # If there was no newline at end of file, we add one:
106
+ unless line.end_with? $/
107
+ output.puts
108
+ end
109
+ end
110
+
111
+ line_offset += 1
112
+ end
113
+ end
114
+
115
+ coverage.print_summary(output)
116
+ end
117
+
118
+ statistics.print_summary(output)
119
+
120
+ return statistics
80
121
  end
81
122
  end
82
123
  end
data/lib/covered/rspec.rb CHANGED
@@ -18,20 +18,26 @@
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'
21
22
  require_relative '../covered'
22
23
 
23
- files = Covered::Files.new
24
- root = Dir.pwd
25
- output = Covered::Relative.new(root, files)
26
- $source = output = Covered::Source.new(output)
27
- output = Covered::Ignore.new(/spec/, output)
24
+ $covered = Covered.policy do
25
+ root Dir.pwd
26
+
27
+ include "lib/**/*.rb"
28
+
29
+ source
30
+ end
28
31
 
29
- report = Covered::Report.new(files)
30
- capture = Covered::Capture.new(output)
31
-
32
- capture.enable
33
- #
34
- # at_exit do
35
- # capture.disable
36
- # report.print_summary
37
- # end
32
+ RSpec.configure do |config|
33
+ config.before(:suite) do
34
+ $covered.enable
35
+ end
36
+
37
+ config.after(:suite) do |context|
38
+ $covered.disable
39
+
40
+ statistics = $covered.print_summary
41
+ expect(statistics).to be_complete
42
+ end
43
+ end
@@ -18,19 +18,48 @@
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 'eval'
22
+ require_relative 'wrapper'
23
+
24
+ require 'thread'
25
+
21
26
  module Covered
22
- class Source
23
- def initialize(files)
24
- @files = files
25
- @paths = {}
27
+ # The source map, loads the source file, parses the AST to generate which lines contain executable code.
28
+ class Source < Wrapper
29
+ EXECUTABLE = /NODE_(.?CALL|.VAR|.ASGN|DEFN)/.freeze
30
+
31
+ # Deviate from the standard policy above, because all the files are already loaded, so we skip NODE_FCALL.
32
+ DOGFOOD = /NODE_([V]?CALL|.VAR|.ASGN|DEFN)/.freeze
33
+
34
+ # Ruby trace points don't trigger for argument execution.
35
+ # Constants are loaded when the file loads, so they are less interesting.
36
+ IGNORE = /NODE_(ARGS|CDECL)/.freeze
37
+
38
+ def initialize(output, executable: EXECUTABLE, ignore: IGNORE)
39
+ super(output)
26
40
 
41
+ @paths = {}
27
42
  @mutex = Mutex.new
43
+
44
+ @executable = executable
45
+ @ignore = ignore
46
+ end
47
+
48
+ def enable
49
+ super
50
+
51
+ Eval::enable(self)
52
+ end
53
+
54
+ def disable
55
+ Eval::disable(self)
56
+
57
+ super
28
58
  end
29
59
 
30
- # [String -> Source]
31
60
  attr :paths
32
61
 
33
- def map(string, binding = nil, filename = nil, lineno = 1)
62
+ def intercept_eval(string, binding = nil, filename = nil, lineno = 1)
34
63
  return unless filename
35
64
 
36
65
  # TODO replace with Concurrent::Map
@@ -39,17 +68,48 @@ module Covered
39
68
  end
40
69
  end
41
70
 
42
- def mark(path, lineno)
43
- @files.mark(path, lineno)
71
+ def executable?(node)
72
+ node.type =~ @executable
73
+ end
74
+
75
+ def ignore?(node)
76
+ # NODE_ARGS Ruby doesn't report execution of arguments in :line tracepoint.
77
+ node.type =~ @ignore
78
+ end
79
+
80
+ def expand(node, lines)
81
+ # puts "#{node.first_lineno}: #{node.inspect}"
82
+
83
+ lines[node.first_lineno] ||= 0 if executable?(node)
84
+
85
+ node.children.each do |child|
86
+ next if child.nil? or ignore?(child)
87
+
88
+ expand(child, lines)
89
+ end
90
+ end
91
+
92
+ def parse(path)
93
+ # puts "Parse #{path}"
94
+
95
+ if source = @paths[path]
96
+ RubyVM::AST.parse(source)
97
+ elsif File.exist?(path)
98
+ RubyVM::AST.parse_file(path)
99
+ else
100
+ warn "Couldn't parse #{path}, file doesn't exist?"
101
+ end
44
102
  end
45
103
 
46
104
  def each(&block)
47
- @files.each do |path, lines|
48
- if source = @paths[path]
49
- yield path, RubyVM::AST.parse(source), lines
50
- else
51
- yield path, RubyVM::AST.parse_file(path), lines
105
+ @output.each do |path, lines|
106
+ lines = lines.dup
107
+
108
+ if top = parse(path)
109
+ expand(top, lines)
52
110
  end
111
+
112
+ yield path, lines
53
113
  end
54
114
  end
55
115
  end
@@ -0,0 +1,100 @@
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
+ class Statistics
23
+ def initialize
24
+ @count = 0
25
+ @executable_count = 0
26
+ @executed_count = 0
27
+ end
28
+
29
+ # Total number of files added.
30
+ attr :count
31
+
32
+ # The number of lines which could have been executed.
33
+ attr :executable_count
34
+
35
+ # The number of lines that were executed.
36
+ attr :executed_count
37
+
38
+ def << coverage
39
+ @count += 1
40
+ @executable_count += coverage.executable_count
41
+ @executed_count += coverage.executed_count
42
+ end
43
+
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
53
+
54
+ def print_summary(output)
55
+ output.puts "* #{count} files checked; #{executed_count}/#{executable_count} lines executed; #{percentage.to_f.round(2)}% covered."
56
+ end
57
+ 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
+ end
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Covered
22
- VERSION = "0.1.0"
22
+ VERSION = "0.3.0"
23
23
  end
@@ -18,8 +18,29 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- class RubyVM::AST::Node
22
- def executable?
23
- self.type =~ /NODE_(CALL|IVAR|LVAR|IASGN)/
21
+ module Covered
22
+ class Wrapper
23
+ def initialize(output = nil)
24
+ @output = output
25
+ end
26
+
27
+ attr :output
28
+
29
+ def mark(path, lineno)
30
+ @output.mark(path, lineno) if @output
31
+ end
32
+
33
+ def enable
34
+ @output.enable if @output
35
+ end
36
+
37
+ def disable
38
+ @output.disable if @output
39
+ end
40
+
41
+ # @yield [path, counts] the path to the file, and the execution counts.
42
+ def each(&block)
43
+ @output.each(&block)
44
+ end
24
45
  end
25
46
  end
data/lib/covered.rb CHANGED
@@ -20,7 +20,4 @@
20
20
 
21
21
  require_relative "covered/version"
22
22
 
23
- require_relative "covered/report"
24
- require_relative "covered/files"
25
- require_relative "covered/source"
26
- require_relative "covered/capture"
23
+ require_relative "covered/policy"
data/media/example.png ADDED
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.1.0
4
+ version: 0.3.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-01 00:00:00.000000000 Z
11
+ date: 2018-07-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rainbow
@@ -95,14 +95,17 @@ files:
95
95
  - Rakefile
96
96
  - covered.gemspec
97
97
  - lib/covered.rb
98
- - lib/covered/ast.rb
99
98
  - lib/covered/capture.rb
100
99
  - lib/covered/eval.rb
101
100
  - lib/covered/files.rb
101
+ - lib/covered/policy.rb
102
102
  - lib/covered/report.rb
103
103
  - lib/covered/rspec.rb
104
104
  - lib/covered/source.rb
105
+ - lib/covered/statistics.rb
105
106
  - lib/covered/version.rb
107
+ - lib/covered/wrapper.rb
108
+ - media/example.png
106
109
  homepage: https://github.com/ioquatix/covered
107
110
  licenses: []
108
111
  metadata: {}