covered 0.1.0 → 0.3.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: 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: {}