deep-cover 0.2.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 +4 -4
- data/.rubocop.yml +15 -0
- data/.travis.yml +1 -1
- data/deep_cover.gemspec +3 -0
- data/lib/deep_cover.rb +2 -17
- data/lib/deep_cover/analyser.rb +2 -3
- data/lib/deep_cover/base.rb +3 -7
- data/lib/deep_cover/builtin_takeover.rb +2 -1
- data/lib/deep_cover/cli/deep_cover.rb +61 -62
- data/lib/deep_cover/config.rb +8 -10
- data/lib/deep_cover/core_ext/require_overrides.rb +2 -0
- data/lib/deep_cover/coverage.rb +4 -2
- data/lib/deep_cover/coverage/analysis.rb +4 -0
- data/lib/deep_cover/coverage/base.rb +2 -17
- data/lib/deep_cover/coverage/persistence.rb +2 -1
- data/lib/deep_cover/covered_code.rb +15 -3
- data/lib/deep_cover/load.rb +50 -0
- data/lib/deep_cover/memoize.rb +2 -0
- data/lib/deep_cover/node.rb +3 -1
- data/lib/deep_cover/node/root.rb +1 -1
- data/lib/deep_cover/problem_with_diagnostic.rb +1 -1
- data/lib/deep_cover/reporter.rb +3 -0
- data/lib/deep_cover/reporter/html/index.rb +24 -40
- data/lib/deep_cover/reporter/html/source.rb +2 -2
- data/lib/deep_cover/reporter/text.rb +66 -0
- data/lib/deep_cover/reporter/util/tree.rb +88 -0
- data/lib/deep_cover/tools.rb +2 -1
- data/lib/deep_cover/tools/builtin_coverage.rb +1 -2
- data/lib/deep_cover/tools/camelize.rb +7 -4
- data/lib/deep_cover/version.rb +1 -1
- metadata +19 -3
- data/lib/deep_cover/reporter/html/tree.rb +0 -55
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3a8450fe7655e95139f1443a7e81f3c73b726e03
|
4
|
+
data.tar.gz: b04f9ef3b69060ff9568cae7224bc89a845137bf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 822e717af608dd6b6ed0010ce00f75aff18f4f60cfddc866df90ceefaf6131820a19f8a7a0e2d5eb00d961412dbcac23295e4b3aebb8be9cd1ae7ca7ecf13ab5
|
7
|
+
data.tar.gz: a5849e499b6f4eb7975af2e4529f5564e99a982d0cda525e0d246e6bce698f289e24cadf9a44ba3a85c4e78ae60da41813ef443f3f7114a261a6a856cd6a024e
|
data/.rubocop.yml
CHANGED
@@ -220,3 +220,18 @@ Style/StructInheritance:
|
|
220
220
|
|
221
221
|
Security/YAMLLoad:
|
222
222
|
Enabled: false
|
223
|
+
|
224
|
+
Style/ExtendSelf:
|
225
|
+
Enabled: false
|
226
|
+
|
227
|
+
Style/CommentedKeyword:
|
228
|
+
Enabled: false # See https://github.com/bbatsov/rubocop/issues/5259
|
229
|
+
|
230
|
+
Gemspec/RequiredRubyVersion:
|
231
|
+
Enabled: false # See https://github.com/bbatsov/rubocop/issues/5260
|
232
|
+
|
233
|
+
Style/MixinUsage:
|
234
|
+
Enabled: false # See https://github.com/bbatsov/rubocop/issues/5261
|
235
|
+
|
236
|
+
Style/EvalWithLocation:
|
237
|
+
Enabled: false
|
data/.travis.yml
CHANGED
data/deep_cover.gemspec
CHANGED
data/lib/deep_cover.rb
CHANGED
@@ -1,24 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# rubocop:disable Style/MixinUsage (See https://github.com/bbatsov/rubocop/issues/5055)
|
4
3
|
module DeepCover
|
5
|
-
|
6
|
-
require 'parser'
|
7
|
-
require 'term/ansicolor'
|
8
|
-
require 'pry'
|
4
|
+
require_relative 'deep_cover/load'
|
9
5
|
|
10
|
-
|
11
|
-
require_relative 'deep_cover/backports'
|
12
|
-
require_relative 'deep_cover/tools'
|
13
|
-
|
14
|
-
# Parser
|
15
|
-
silence_warnings do
|
16
|
-
require 'parser/current'
|
17
|
-
end
|
18
|
-
require_relative_dir 'deep_cover/parser_ext'
|
19
|
-
|
20
|
-
# Main
|
21
|
-
require_relative_dir 'deep_cover', except: %w[auto_run builtin_takeover]
|
6
|
+
load_absolute_basics
|
22
7
|
|
23
8
|
extend Base
|
24
9
|
extend Config::Setter
|
data/lib/deep_cover/analyser.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'node'
|
4
|
-
require_relative 'covered_code'
|
5
|
-
|
6
3
|
module DeepCover
|
4
|
+
bootstrap
|
5
|
+
|
7
6
|
# An analyser works on a subset of the original Node AST.
|
8
7
|
# The Root node is always considered part of the subset.
|
9
8
|
# One can iterate this subset with `each_node`, or ask
|
data/lib/deep_cover/base.rb
CHANGED
@@ -21,6 +21,7 @@ module DeepCover
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def stop
|
24
|
+
require_relative 'core_ext/require_overrides'
|
24
25
|
AutoloadOverride.active = false if defined? AutoloadOverride
|
25
26
|
RequireOverride.active = false
|
26
27
|
@started = false
|
@@ -72,6 +73,8 @@ module DeepCover
|
|
72
73
|
@autoload_tracker ||= AutoloadTracker.new
|
73
74
|
end
|
74
75
|
|
76
|
+
private
|
77
|
+
|
75
78
|
def handle_relative_filename(filename)
|
76
79
|
unless Pathname.new(filename).absolute?
|
77
80
|
relative_to = File.dirname(caller(2..2).first.partition(/\.rb:\d/).first)
|
@@ -80,12 +83,5 @@ module DeepCover
|
|
80
83
|
filename += '.rb' unless filename =~ /\.rb$/
|
81
84
|
filename
|
82
85
|
end
|
83
|
-
|
84
|
-
def parser
|
85
|
-
Parser::CurrentRuby.new.tap do |parser|
|
86
|
-
parser.diagnostics.all_errors_are_fatal = true
|
87
|
-
parser.diagnostics.ignore_warnings = true
|
88
|
-
end
|
89
|
-
end
|
90
86
|
end
|
91
87
|
end
|
@@ -1,7 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../deep_cover'
|
4
|
-
require_relative '
|
4
|
+
require_relative 'coverage'
|
5
|
+
require_relative 'core_ext/coverage_replacement'
|
5
6
|
|
6
7
|
require 'coverage'
|
7
8
|
BuiltinCoverage = Coverage
|
@@ -4,84 +4,83 @@ module DeepCover
|
|
4
4
|
require 'bundler/setup'
|
5
5
|
require 'slop'
|
6
6
|
require 'deep_cover'
|
7
|
+
bootstrap
|
7
8
|
require_relative_dir '.'
|
8
9
|
|
9
|
-
module CLI
|
10
|
-
|
11
|
-
extend self
|
10
|
+
module CLI::DeepCover
|
11
|
+
extend self
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
def show_version
|
14
|
+
puts "deep-cover v#{DeepCover::VERSION}; parser v#{Parser::VERSION}"
|
15
|
+
end
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
def show_help
|
18
|
+
puts menu
|
19
|
+
end
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
27
|
-
delegate.public_send(method, *args, &block)
|
21
|
+
class OptionParser < Struct.new(:delegate)
|
22
|
+
def method_missing(method, *args, &block) # rubocop:disable Style/MethodMissing
|
23
|
+
options = args.last
|
24
|
+
if options.is_a?(Hash) && options.has_key?(:default)
|
25
|
+
args[-2] += " [#{options[:default]}]"
|
28
26
|
end
|
27
|
+
delegate.public_send(method, *args, &block)
|
29
28
|
end
|
29
|
+
end
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
31
|
+
def parse
|
32
|
+
Slop.parse do |o|
|
33
|
+
yield OptionParser.new(o)
|
35
34
|
end
|
35
|
+
end
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
37
|
+
def menu
|
38
|
+
@menu ||= parse do |o|
|
39
|
+
o.banner = 'usage: deep-cover [options] [path/to/app/or/gem]'
|
40
|
+
o.separator ''
|
41
|
+
o.string '-o', '--output', 'output folder', default: './coverage'
|
42
|
+
o.string '-c', '--command', 'command to run tests', default: 'bundle exec rake'
|
43
|
+
o.string '--reporter', 'reporter', default: 'html'
|
44
|
+
o.bool '--bundle', 'run bundle before the tests', default: true
|
45
|
+
o.bool '--process', 'turn off to only redo the reporting', default: true
|
46
|
+
o.bool '--open', 'open the output coverage', default: false
|
47
|
+
o.separator 'Coverage options'
|
48
|
+
@ignore_uncovered_map = Analyser::Node.optionally_covered.map do |option|
|
49
|
+
default = Config::DEFAULTS[:ignore_uncovered].include?(option)
|
50
|
+
o.bool "--ignore-#{Tools.dasherize(option)}", '', default: default
|
51
|
+
[:"ignore_#{option}", option]
|
52
|
+
end.to_h
|
53
|
+
o.separator "\nFor testing purposes:"
|
54
|
+
o.bool '--profile', 'use profiler' unless RUBY_PLATFORM == 'java'
|
55
|
+
o.string '-e', '--expression', 'test ruby expression instead of a covering a path'
|
56
|
+
o.bool '-d', '--debug', 'enter debugging after cover'
|
57
57
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
end
|
63
|
-
o.boolean('-h', '--help')
|
58
|
+
o.separator "\nOther available commands:"
|
59
|
+
o.on('--version', 'print the version') do
|
60
|
+
show_version
|
61
|
+
exit
|
64
62
|
end
|
63
|
+
o.boolean('-h', '--help')
|
65
64
|
end
|
65
|
+
end
|
66
66
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
end
|
72
|
-
options
|
67
|
+
def convert_options(options)
|
68
|
+
iu = options[:ignore_uncovered] = []
|
69
|
+
@ignore_uncovered_map.each do |cli_option, option|
|
70
|
+
iu << option if options.delete(cli_option)
|
73
71
|
end
|
72
|
+
options
|
73
|
+
end
|
74
74
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
end
|
75
|
+
def go
|
76
|
+
options = convert_options(menu.to_h)
|
77
|
+
if options[:help]
|
78
|
+
show_help
|
79
|
+
elsif options[:expression]
|
80
|
+
CLI::Debugger.new(options[:expression], **options).show
|
81
|
+
else
|
82
|
+
path = menu.arguments.first || '.'
|
83
|
+
CLI::InstrumentedCloneReporter.new(path, **options).run
|
85
84
|
end
|
86
85
|
end
|
87
86
|
end
|
data/lib/deep_cover/config.rb
CHANGED
@@ -3,14 +3,16 @@
|
|
3
3
|
module DeepCover
|
4
4
|
class Config
|
5
5
|
DEFAULTS = {
|
6
|
-
ignore_uncovered: [],
|
7
|
-
paths: %w[./app ./lib],
|
6
|
+
ignore_uncovered: [].freeze,
|
7
|
+
paths: %w[./app ./lib].freeze,
|
8
8
|
allow_partial: false,
|
9
9
|
}.freeze
|
10
10
|
|
11
|
-
|
11
|
+
OPTIONALLY_COVERED = %i[raise default_argument case_implicit_else trivial_if]
|
12
|
+
|
13
|
+
def initialize(notify = nil)
|
12
14
|
@notify = notify
|
13
|
-
@options =
|
15
|
+
@options = DEFAULTS.dup
|
14
16
|
end
|
15
17
|
|
16
18
|
def to_hash
|
@@ -29,7 +31,7 @@ module DeepCover
|
|
29
31
|
|
30
32
|
def detect_uncovered(*keywords)
|
31
33
|
if keywords.empty?
|
32
|
-
|
34
|
+
OPTIONALLY_COVERED - @options[:ignore_uncovered]
|
33
35
|
else
|
34
36
|
check_uncovered(keywords)
|
35
37
|
change(:ignore_uncovered, @options[:ignore_uncovered] - keywords)
|
@@ -53,7 +55,7 @@ module DeepCover
|
|
53
55
|
private
|
54
56
|
|
55
57
|
def check_uncovered(keywords)
|
56
|
-
unknown = keywords -
|
58
|
+
unknown = keywords - OPTIONALLY_COVERED
|
57
59
|
raise ArgumentError, "unknown options: #{unknown.join(', ')}" unless unknown.empty?
|
58
60
|
end
|
59
61
|
|
@@ -65,10 +67,6 @@ module DeepCover
|
|
65
67
|
self
|
66
68
|
end
|
67
69
|
|
68
|
-
def copy(h)
|
69
|
-
h.dup.transform_values(&:dup).transform_values(&:freeze)
|
70
|
-
end
|
71
|
-
|
72
70
|
module Setter
|
73
71
|
def config(notify = self)
|
74
72
|
@config ||= Config.new(notify)
|
data/lib/deep_cover/coverage.rb
CHANGED
@@ -15,6 +15,10 @@ module DeepCover
|
|
15
15
|
analyser_map.transform_values { |a| a.transform_values(&:stats) }
|
16
16
|
end
|
17
17
|
|
18
|
+
def self.template
|
19
|
+
{node: Analyser::Node, per_char: Analyser::PerChar, branch: Analyser::Branch}
|
20
|
+
end
|
21
|
+
|
18
22
|
private
|
19
23
|
|
20
24
|
def compute_analysers(covered_code)
|
@@ -37,6 +37,7 @@ module DeepCover
|
|
37
37
|
case (reporter = options.fetch(:reporter, :html).to_sym)
|
38
38
|
when :html
|
39
39
|
Reporter::HTML.report(self, **options)
|
40
|
+
Reporter::Text.report(self, **options) + "\n\nHTML generated: open #{options[:output]}/index.html"
|
40
41
|
when :istanbul
|
41
42
|
if Reporter::Istanbul.available?
|
42
43
|
report_istanbul(**options)
|
@@ -44,28 +45,12 @@ module DeepCover
|
|
44
45
|
warn 'nyc not available. Please install `nyc` using `yarn global add nyc` or `npm i nyc -g`'
|
45
46
|
end
|
46
47
|
when :text
|
47
|
-
|
48
|
+
Reporter::Text.report(self, **options)
|
48
49
|
else
|
49
50
|
raise ArgumentError, "Unknown reporter: #{reporter}"
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
53
|
-
def basic_report
|
54
|
-
missing = map do |covered_code|
|
55
|
-
if covered_code.has_executed?
|
56
|
-
missed = covered_code.line_coverage.each_with_index.map do |line_cov, line_index|
|
57
|
-
line_index + 1 if line_cov == 0
|
58
|
-
end.compact
|
59
|
-
else
|
60
|
-
missed = ['all']
|
61
|
-
end
|
62
|
-
[covered_code.buffer.name, missed] unless missed.empty?
|
63
|
-
end.compact.to_h
|
64
|
-
missing.map do |path, lines|
|
65
|
-
"#{File.basename(path)}: #{lines.join(', ')}"
|
66
|
-
end.join("\n")
|
67
|
-
end
|
68
|
-
|
69
54
|
def self.load(dest_path, dirname = 'deep_cover', with_trackers: true)
|
70
55
|
Persistence.new(dest_path, dirname).load(with_trackers: with_trackers)
|
71
56
|
end
|
@@ -3,7 +3,6 @@
|
|
3
3
|
module DeepCover
|
4
4
|
require 'securerandom'
|
5
5
|
class Coverage::Persistence
|
6
|
-
# rubocop:disable Security/MarshalLoad
|
7
6
|
BASENAME = 'coverage.dc'
|
8
7
|
TRACKER_TEMPLATE = 'trackers%{unique}.dct'
|
9
8
|
|
@@ -56,6 +55,7 @@ module DeepCover
|
|
56
55
|
))
|
57
56
|
end
|
58
57
|
|
58
|
+
# rubocop:disable Security/MarshalLoad
|
59
59
|
def load_coverage
|
60
60
|
Marshal.load(dir_path.join(BASENAME).binread).tap do |version: raise, coverage: raise|
|
61
61
|
raise "dump version mismatch: #{version}, currently #{DeepCover::VERSION}" unless version == DeepCover::VERSION
|
@@ -71,6 +71,7 @@ module DeepCover
|
|
71
71
|
end
|
72
72
|
end
|
73
73
|
end
|
74
|
+
# rubocop:enable Security/MarshalLoad
|
74
75
|
|
75
76
|
def merge_trackers(hash, to_merge)
|
76
77
|
hash.merge!(to_merge) do |_key, current, to_add|
|
@@ -1,6 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module DeepCover
|
4
|
+
bootstrap
|
5
|
+
load_parser
|
6
|
+
|
4
7
|
class CoveredCode
|
5
8
|
DEFAULT_TRACKER_GLOBAL = '$_cov'
|
6
9
|
|
@@ -11,7 +14,7 @@ module DeepCover
|
|
11
14
|
def initialize(path: nil, source: nil, lineno: 1, tracker_global: DEFAULT_TRACKER_GLOBAL, local_var: '_temp', name: nil)
|
12
15
|
raise 'Must provide either path or source' unless path || source
|
13
16
|
|
14
|
-
@buffer =
|
17
|
+
@buffer = Parser::Source::Buffer.new(path, lineno)
|
15
18
|
@buffer.source = source || File.read(path)
|
16
19
|
@tracker_count = 0
|
17
20
|
@tracker_global = tracker_global
|
@@ -88,7 +91,7 @@ module DeepCover
|
|
88
91
|
|
89
92
|
def root
|
90
93
|
@root ||= begin
|
91
|
-
ast =
|
94
|
+
ast = parser.parse(@buffer)
|
92
95
|
Node::Root.new(ast, self)
|
93
96
|
end
|
94
97
|
end
|
@@ -98,7 +101,7 @@ module DeepCover
|
|
98
101
|
end
|
99
102
|
|
100
103
|
def instrument_source
|
101
|
-
rewriter =
|
104
|
+
rewriter = Parser::Source::TreeRewriter.new(@buffer)
|
102
105
|
covered_ast.each_node(:postorder) do |node|
|
103
106
|
node.rewriting_rules.each do |range, rule|
|
104
107
|
prefix, _node, suffix = rule.partition('%{node}')
|
@@ -132,5 +135,14 @@ module DeepCover
|
|
132
135
|
def global
|
133
136
|
@@globals[tracker_global]
|
134
137
|
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
def parser
|
142
|
+
Parser::CurrentRuby.new.tap do |parser|
|
143
|
+
parser.diagnostics.all_errors_are_fatal = true
|
144
|
+
parser.diagnostics.ignore_warnings = true
|
145
|
+
end
|
146
|
+
end
|
135
147
|
end
|
136
148
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
module Load
|
5
|
+
AUTOLOAD = %i[analyser autoload_tracker coverage covered_code custom_requirer memoize
|
6
|
+
module_override node problem_with_diagnostic reporter
|
7
|
+
]
|
8
|
+
|
9
|
+
def load_absolute_basics
|
10
|
+
require_relative 'base'
|
11
|
+
require_relative 'config'
|
12
|
+
require_relative 'tools/camelize'
|
13
|
+
AUTOLOAD.each do |module_name|
|
14
|
+
DeepCover.autoload(Tools::Camelize.camelize(module_name), "#{__dir__}/#{module_name}")
|
15
|
+
end
|
16
|
+
Object.autoload :Term, 'term/ansicolor'
|
17
|
+
Object.autoload :Terminal, 'terminal-table'
|
18
|
+
require 'pry'
|
19
|
+
end
|
20
|
+
|
21
|
+
def bootstrap
|
22
|
+
return if @bootstrapped
|
23
|
+
require_relative 'backports'
|
24
|
+
require_relative 'tools'
|
25
|
+
@bootstrapped = true
|
26
|
+
end
|
27
|
+
|
28
|
+
def load_parser
|
29
|
+
return if @parser_loaded
|
30
|
+
require 'parser'
|
31
|
+
silence_warnings do
|
32
|
+
require 'parser/current'
|
33
|
+
end
|
34
|
+
require_relative_dir 'parser_ext'
|
35
|
+
@parser_loaded
|
36
|
+
end
|
37
|
+
|
38
|
+
def load_all
|
39
|
+
return if @all_loaded
|
40
|
+
bootstrap
|
41
|
+
load_parser
|
42
|
+
AUTOLOAD.each do |module_name|
|
43
|
+
DeepCover.const_get(Tools::Camelize.camelize(module_name))
|
44
|
+
end
|
45
|
+
@all_loaded = true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
extend Load
|
50
|
+
end
|
data/lib/deep_cover/memoize.rb
CHANGED
data/lib/deep_cover/node.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module DeepCover
|
4
|
+
bootstrap
|
5
|
+
load_parser
|
6
|
+
|
4
7
|
class Node
|
5
8
|
# Reopened in base
|
6
9
|
CLASSES = []
|
@@ -13,7 +16,6 @@ module DeepCover
|
|
13
16
|
require_relative 'node/base'
|
14
17
|
require_relative_dir 'node'
|
15
18
|
|
16
|
-
require_relative 'memoize'
|
17
19
|
Node.include Memoize
|
18
20
|
Node::CLASSES.freeze.each do |klass|
|
19
21
|
klass.memoize :flow_entry_count, :flow_completion_count, :execution_count, :loc_hash
|
data/lib/deep_cover/node/root.rb
CHANGED
@@ -4,7 +4,7 @@ module DeepCover
|
|
4
4
|
class Node::Root < Node
|
5
5
|
has_tracker :root
|
6
6
|
has_child main: Node,
|
7
|
-
can_be_empty: -> {
|
7
|
+
can_be_empty: -> { Parser::Source::Range.new(covered_code.buffer, 0, 0) },
|
8
8
|
is_statement: true,
|
9
9
|
rewrite: -> {
|
10
10
|
"#{covered_code.trackers_setup_source};%{root_tracker};%{local}=nil;%{node}"
|
@@ -6,7 +6,7 @@ module DeepCover
|
|
6
6
|
|
7
7
|
def initialize(covered_code, line_range, original_exception = nil)
|
8
8
|
@covered_code = covered_code
|
9
|
-
if line_range.
|
9
|
+
if line_range.respond_to? :last_line
|
10
10
|
@line_range = line_range.line..line_range.last_line
|
11
11
|
else
|
12
12
|
@line_range = line_range
|
data/lib/deep_cover/reporter.rb
CHANGED
@@ -1,23 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module DeepCover
|
4
|
-
|
5
|
-
require_relative 'tree'
|
6
|
-
require_relative 'base'
|
4
|
+
require_relative 'base'
|
7
5
|
|
6
|
+
module Reporter
|
8
7
|
class HTML::Index < Struct.new(:analysis, :options)
|
9
8
|
def initialize(analysis, **options)
|
10
9
|
raise ArgumentError unless analysis.is_a? Coverage::Analysis
|
11
10
|
super
|
12
11
|
end
|
13
12
|
|
14
|
-
include
|
13
|
+
include Util::Tree
|
15
14
|
include HTML::Base
|
16
15
|
|
17
16
|
def stats_to_data
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
populate_stats(analysis) do |full_path, partial_path, data, children|
|
18
|
+
data = transform_data(data)
|
19
|
+
if children.empty?
|
20
|
+
{
|
21
|
+
text: %{<a href="#{full_path}.html">#{partial_path}</a>},
|
22
|
+
data: data,
|
23
|
+
}
|
24
|
+
else
|
25
|
+
{
|
26
|
+
text: partial_path,
|
27
|
+
data: data,
|
28
|
+
children: children,
|
29
|
+
state: {opened: true},
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
21
33
|
end
|
22
34
|
|
23
35
|
def columns
|
@@ -38,40 +50,12 @@ module DeepCover
|
|
38
50
|
|
39
51
|
private
|
40
52
|
|
41
|
-
# {a: {}} => [{text: a, data: stat_map[a]}]
|
42
|
-
# {b: {...}} => [{text: b, data: sum(stats), children: [...]}]
|
43
|
-
def populate(tree, dir = '')
|
44
|
-
tree.map do |path, children_hash|
|
45
|
-
full_path = [dir, path].join
|
46
|
-
if children_hash.empty?
|
47
|
-
{
|
48
|
-
text: %{<a href="#{full_path}.html">#{path}</a>},
|
49
|
-
data: @map[full_path],
|
50
|
-
}
|
51
|
-
else
|
52
|
-
children = populate(children_hash, "#{full_path}/")
|
53
|
-
data = Tools.merge(*children.map { |c| c[:data] }, :+)
|
54
|
-
{
|
55
|
-
text: path,
|
56
|
-
data: data,
|
57
|
-
children: children,
|
58
|
-
state: {opened: true},
|
59
|
-
}
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
# Modifies in place the tree:
|
65
53
|
# {per_char: Stat, ...} => {per_char: {ignored: ...}, per_char_percent: 55.55, ...}
|
66
|
-
def transform_data(
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
*node[:data].map { |type, stat| {:"#{type}_percent" => stat.percent_covered} }
|
72
|
-
)
|
73
|
-
transform_data(node[:children])
|
74
|
-
end
|
54
|
+
def transform_data(data)
|
55
|
+
Tools.merge(
|
56
|
+
data.transform_values(&:to_h),
|
57
|
+
*data.map { |type, stat| {:"#{type}_percent" => stat.percent_covered} }
|
58
|
+
)
|
75
59
|
end
|
76
60
|
end
|
77
61
|
end
|
@@ -25,7 +25,7 @@ module DeepCover
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def convert_source
|
28
|
-
@rewriter =
|
28
|
+
@rewriter = Parser::Source::TreeRewriter.new(covered_code.buffer)
|
29
29
|
insert_node_tags
|
30
30
|
insert_branch_tags
|
31
31
|
html_escape
|
@@ -126,7 +126,7 @@ module DeepCover
|
|
126
126
|
{'<' => '<', '>' => '>', '&' => '&'}.each do |char, escaped|
|
127
127
|
source.scan(char) do
|
128
128
|
m = Regexp.last_match
|
129
|
-
range =
|
129
|
+
range = Parser::Source::Range.new(buffer, m.begin(0), m.end(0))
|
130
130
|
@rewriter.replace(range, escaped)
|
131
131
|
end
|
132
132
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
module Reporter
|
5
|
+
class Text
|
6
|
+
include Util::Tree
|
7
|
+
def initialize(coverage, **options)
|
8
|
+
@coverage = coverage
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def analysis
|
13
|
+
Coverage::Analysis.new(@coverage.covered_codes, **@options)
|
14
|
+
end
|
15
|
+
|
16
|
+
INDENT = ' '
|
17
|
+
def report
|
18
|
+
formatted_headings = headings.map.with_index { |h, i| {value: h, alignment: :center} }
|
19
|
+
columns = rows.transpose
|
20
|
+
(1...columns.size).step(2) { |i| columns[i] = formatted_stats(columns[i]) }
|
21
|
+
table = Terminal::Table.new(
|
22
|
+
headings: formatted_headings,
|
23
|
+
rows: columns.transpose,
|
24
|
+
style: {border_bottom: false, border_top: false, alignment: :right},
|
25
|
+
)
|
26
|
+
table.align_column 0, :left
|
27
|
+
table.render
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.report(coverage, **options)
|
31
|
+
Text.new(coverage, **options).report
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def formatted_stats(data)
|
37
|
+
columns = data.transpose
|
38
|
+
columns[1..1] = [] if columns[1].none?
|
39
|
+
Terminal::Table.new(
|
40
|
+
rows: columns.transpose,
|
41
|
+
style: {border_x: '', border_bottom: false, border_top: false, alignment: :right}
|
42
|
+
).render.gsub(' | ', ' ').gsub(/ ?\| ?/, '').split("\n")
|
43
|
+
end
|
44
|
+
|
45
|
+
def rows
|
46
|
+
populate_stats(analysis).map do |full_path, partial_path, data, children|
|
47
|
+
[partial_path, *transform_data(data)]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def headings
|
52
|
+
Coverage::Analysis.template.values.flat_map do |analyser|
|
53
|
+
[analyser.human_name, '%']
|
54
|
+
end.unshift('Path')
|
55
|
+
end
|
56
|
+
|
57
|
+
# {per_char: Stat, ...} => ['1 [+2] / 3', '100 %', ...]
|
58
|
+
def transform_data(data)
|
59
|
+
data.flat_map do |type, stat|
|
60
|
+
ignored = "[+#{stat.ignored}]" if stat.ignored > 0
|
61
|
+
[[stat.executed, ignored, '/', stat.potentially_executable], format('%.2f', stat.percent_covered)]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
module Reporter
|
5
|
+
module Util
|
6
|
+
# Utility functions to deal with trees
|
7
|
+
module Tree
|
8
|
+
def paths_to_tree(paths)
|
9
|
+
twigs = paths.map do |path|
|
10
|
+
partials = path_to_partial_paths(path)
|
11
|
+
list_to_twig(partials)
|
12
|
+
end
|
13
|
+
tree = deep_merge(twigs)
|
14
|
+
simplify(tree)
|
15
|
+
end
|
16
|
+
|
17
|
+
# 'some/example/path' => %w[some example path]
|
18
|
+
def path_to_partial_paths(path)
|
19
|
+
path.to_s.split('/')
|
20
|
+
end
|
21
|
+
|
22
|
+
# A twig is a tree with only single branches
|
23
|
+
# [a, b, c] =>
|
24
|
+
# {a: {b: {c: {} } } }
|
25
|
+
def list_to_twig(items)
|
26
|
+
result = {}
|
27
|
+
items.inject(result) do |parent, value|
|
28
|
+
parent[value] = {}
|
29
|
+
end
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
33
|
+
# [{a: {b: {c: {} } } }
|
34
|
+
# {a: {b: {d: {} } } }]
|
35
|
+
# => {a: {b: {c: {}, d: {} }}}
|
36
|
+
def deep_merge(trees)
|
37
|
+
trees.inject do |result, h|
|
38
|
+
result.merge(h) { |k, val, val_b| deep_merge([val, val_b]) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# {a: {b: {c: {}, d: {} }}}
|
43
|
+
# => {a/b: {c: {}, d: {} }}
|
44
|
+
def simplify(tree)
|
45
|
+
tree.map do |key, sub_tree|
|
46
|
+
sub_tree = simplify(sub_tree)
|
47
|
+
if sub_tree.size == 1
|
48
|
+
key2, sub_tree = sub_tree.first
|
49
|
+
key = "#{key}/#{key2}"
|
50
|
+
end
|
51
|
+
[key, sub_tree]
|
52
|
+
end.to_h
|
53
|
+
end
|
54
|
+
|
55
|
+
# {a: {b: {}}} => [ra, rb]
|
56
|
+
# where rb = yield('a/b', 'b', [])
|
57
|
+
# and ra = yield('a', 'a', [rb])
|
58
|
+
def populate(tree, dir = '', &block)
|
59
|
+
return to_enum(__method__, tree, dir) unless block_given?
|
60
|
+
tree.map do |path, children_hash|
|
61
|
+
full_path = [dir, path].join
|
62
|
+
children = populate(children_hash, "#{full_path}/", &block)
|
63
|
+
yield full_path, path, children
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Same as populate, but also yields data, which is either the analysis data (for leaves)
|
68
|
+
# of the sum of the children (for subtrees)
|
69
|
+
def populate_stats(analysis)
|
70
|
+
return to_enum(__method__, analysis) unless block_given?
|
71
|
+
map = Tools.transform_keys(analysis.stat_map, &:name)
|
72
|
+
tree = paths_to_tree(map.keys)
|
73
|
+
final_results, _final_data = populate(tree) do |full_path, partial_path, children|
|
74
|
+
if children.empty?
|
75
|
+
data = map[full_path]
|
76
|
+
else
|
77
|
+
child_results, child_data = children.transpose
|
78
|
+
data = Tools.merge(*child_data, :+)
|
79
|
+
end
|
80
|
+
result = yield full_path, partial_path, data, child_results || []
|
81
|
+
[result, data]
|
82
|
+
end.transpose
|
83
|
+
final_results
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/deep_cover/tools.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module DeepCover
|
4
|
-
module Tools
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
module Tools
|
5
|
+
module Camelize
|
6
|
+
extend self # Loaded before bootstrap
|
7
|
+
# Poor man's camelize. 'an_example' => 'AnExample'
|
8
|
+
def camelize(string)
|
9
|
+
string.to_s.gsub(/([a-z\d]*)[_?!]?/) { Regexp.last_match(1).capitalize }
|
10
|
+
end
|
8
11
|
end
|
9
12
|
end
|
10
13
|
end
|
data/lib/deep_cover/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: deep-cover
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marc-André Lafortune
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2017-12-
|
12
|
+
date: 2017-12-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: parser
|
@@ -109,6 +109,20 @@ dependencies:
|
|
109
109
|
- - ">="
|
110
110
|
- !ruby/object:Gem::Version
|
111
111
|
version: '0'
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: terminal-table
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
type: :runtime
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
112
126
|
- !ruby/object:Gem::Dependency
|
113
127
|
name: pry
|
114
128
|
requirement: !ruby/object:Gem::Requirement
|
@@ -271,6 +285,7 @@ files:
|
|
271
285
|
- lib/deep_cover/coverage/persistence.rb
|
272
286
|
- lib/deep_cover/covered_code.rb
|
273
287
|
- lib/deep_cover/custom_requirer.rb
|
288
|
+
- lib/deep_cover/load.rb
|
274
289
|
- lib/deep_cover/memoize.rb
|
275
290
|
- lib/deep_cover/module_override.rb
|
276
291
|
- lib/deep_cover/node.rb
|
@@ -327,8 +342,9 @@ files:
|
|
327
342
|
- lib/deep_cover/reporter/html/template/assets/throbber.gif
|
328
343
|
- lib/deep_cover/reporter/html/template/index.html.erb
|
329
344
|
- lib/deep_cover/reporter/html/template/source.html.erb
|
330
|
-
- lib/deep_cover/reporter/html/tree.rb
|
331
345
|
- lib/deep_cover/reporter/istanbul.rb
|
346
|
+
- lib/deep_cover/reporter/text.rb
|
347
|
+
- lib/deep_cover/reporter/util/tree.rb
|
332
348
|
- lib/deep_cover/tools.rb
|
333
349
|
- lib/deep_cover/tools/builtin_coverage.rb
|
334
350
|
- lib/deep_cover/tools/camelize.rb
|
@@ -1,55 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module DeepCover
|
4
|
-
module Reporter
|
5
|
-
# Utility functions to deal with trees
|
6
|
-
module HTML::Tree
|
7
|
-
def paths_to_tree(paths)
|
8
|
-
twigs = paths.map do |path|
|
9
|
-
partials = path_to_partial_paths(path)
|
10
|
-
list_to_twig(partials)
|
11
|
-
end
|
12
|
-
tree = deep_merge(twigs)
|
13
|
-
simplify(tree)
|
14
|
-
end
|
15
|
-
|
16
|
-
# 'some/example/path' => %w[some example path]
|
17
|
-
def path_to_partial_paths(path)
|
18
|
-
path.to_s.split('/')
|
19
|
-
end
|
20
|
-
|
21
|
-
# A twig is a tree with only single branches
|
22
|
-
# [a, b, c] =>
|
23
|
-
# {a: {b: {c: {} } } }
|
24
|
-
def list_to_twig(items)
|
25
|
-
result = {}
|
26
|
-
items.inject(result) do |parent, value|
|
27
|
-
parent[value] = {}
|
28
|
-
end
|
29
|
-
result
|
30
|
-
end
|
31
|
-
|
32
|
-
# [{a: {b: {c: {} } } }
|
33
|
-
# {a: {b: {d: {} } } }]
|
34
|
-
# => {a: {b: {c: {}, d: {} }}}
|
35
|
-
def deep_merge(trees)
|
36
|
-
trees.inject do |result, h|
|
37
|
-
result.merge(h) { |k, val, val_b| deep_merge([val, val_b]) }
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
# {a: {b: {c: {}, d: {} }}}
|
42
|
-
# => {a/b: {c: {}, d: {} }}
|
43
|
-
def simplify(tree)
|
44
|
-
tree.map do |key, sub_tree|
|
45
|
-
sub_tree = simplify(sub_tree)
|
46
|
-
if sub_tree.size == 1
|
47
|
-
key2, sub_tree = sub_tree.first
|
48
|
-
key = "#{key}/#{key2}"
|
49
|
-
end
|
50
|
-
[key, sub_tree]
|
51
|
-
end.to_h
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|