deep-cover 0.2.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
  SHA1:
3
- metadata.gz: de76a03a06ec4a16d33c5a24a9c510678f83b33a
4
- data.tar.gz: db775778d2b7d2c65ce0c299f3ff4f3900796e02
3
+ metadata.gz: 3a8450fe7655e95139f1443a7e81f3c73b726e03
4
+ data.tar.gz: b04f9ef3b69060ff9568cae7224bc89a845137bf
5
5
  SHA512:
6
- metadata.gz: 246c598d3da7d0fca5d5c4e453dc134f2e36cc59e7529f4148fa159c8e31c3288316e731faec5d08be37167fdd605454f0155b12cea3fdcbd22235f3e9ad9df3
7
- data.tar.gz: 96fea731d9cd93158718857999a9da092b9ec619126dc0814b339b8e3c460a224dca594194b35d686b8dbf5c5c4cde03de69bd17a1bcd482e92ab1851bbb4ee7
6
+ metadata.gz: 822e717af608dd6b6ed0010ce00f75aff18f4f60cfddc866df90ceefaf6131820a19f8a7a0e2d5eb00d961412dbcac23295e4b3aebb8be9cd1ae7ca7ecf13ab5
7
+ data.tar.gz: a5849e499b6f4eb7975af2e4529f5564e99a982d0cda525e0d246e6bce698f289e24cadf9a44ba3a85c4e78ae60da41813ef443f3f7114a261a6a856cd6a024e
@@ -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
@@ -13,7 +13,7 @@ before_install:
13
13
  before_script:
14
14
  - bundle exec rake dev:install
15
15
  script:
16
- - rake test:all
16
+ - bundle exec rake test:all
17
17
  matrix:
18
18
  allow_failures:
19
19
  - rvm: jruby-9.1.9.0
@@ -38,6 +38,9 @@ Gem::Specification.new do |spec|
38
38
  spec.add_runtime_dependency 'term-ansicolor'
39
39
  spec.add_runtime_dependency 'with_progress'
40
40
 
41
+ # Reporters
42
+ spec.add_runtime_dependency 'terminal-table'
43
+
41
44
  # While in 0.x
42
45
  spec.add_runtime_dependency 'pry'
43
46
 
@@ -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
- # External dependencies (ex parser)
6
- require 'parser'
7
- require 'term/ansicolor'
8
- require 'pry'
4
+ require_relative 'deep_cover/load'
9
5
 
10
- # Bootstrapping
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
@@ -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
@@ -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 '../deep_cover/core_ext/coverage_replacement'
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
- module DeepCover
11
- extend self
10
+ module CLI::DeepCover
11
+ extend self
12
12
 
13
- def show_version
14
- puts "deep-cover v#{::DeepCover::VERSION}; parser v#{::Parser::VERSION}"
15
- end
13
+ def show_version
14
+ puts "deep-cover v#{DeepCover::VERSION}; parser v#{Parser::VERSION}"
15
+ end
16
16
 
17
- def show_help
18
- puts menu
19
- end
17
+ def show_help
18
+ puts menu
19
+ end
20
20
 
21
- class Parser < 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]}]"
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
- def parse
32
- Slop.parse do |o|
33
- yield Parser.new(o)
34
- end
31
+ def parse
32
+ Slop.parse do |o|
33
+ yield OptionParser.new(o)
35
34
  end
35
+ end
36
36
 
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'
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
- o.separator "\nOther available commands:"
59
- o.on('--version', 'print the version') do
60
- show_version
61
- exit
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
- 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)
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
- def go
76
- options = convert_options(menu.to_h)
77
- if options[:help]
78
- show_help
79
- elsif options[:expression]
80
- Debugger.new(options[:expression], **options).show
81
- else
82
- path = menu.arguments.first || '.'
83
- InstrumentedCloneReporter.new(path, **options).run
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
@@ -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
- def initialize(notify = nil, **options)
11
+ OPTIONALLY_COVERED = %i[raise default_argument case_implicit_else trivial_if]
12
+
13
+ def initialize(notify = nil)
12
14
  @notify = notify
13
- @options = copy(DEFAULTS.merge(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
- Analyser::Node.optionally_covered - @options[:ignore_uncovered]
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 - Analyser::Node.optionally_covered
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)
@@ -7,6 +7,8 @@
7
7
  # overrides Kernel#require)
8
8
 
9
9
  module DeepCover
10
+ load_all
11
+
10
12
  module RequireOverride
11
13
  def require(path)
12
14
  result = catch(:use_fallback) { DeepCover.custom_requirer.require(path) }
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeepCover
4
- require_relative 'covered_code'
5
- Coverage = Class.new
4
+ bootstrap
5
+
6
+ class Coverage
7
+ end
6
8
  require_relative_dir 'coverage'
7
9
  Coverage.include Coverage::Istanbul
8
10
  end
@@ -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
- basic_report
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 = ::Parser::Source::Buffer.new(path, lineno)
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 = DeepCover.parser.parse(@buffer)
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 = ::Parser::Source::TreeRewriter.new(@buffer)
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
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeepCover
4
+ bootstrap
5
+
4
6
  # Memoize is a quick way to prepend a module that defines
5
7
  # the memoized methods as `@_cache ||= super.freeze`
6
8
  # It also refines `freeze` to precache memoized methods
@@ -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
@@ -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: -> { ::Parser::Source::Range.new(covered_code.buffer, 0, 0) },
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.is_a?(Parser::Source::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
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeepCover
4
+ bootstrap
5
+
4
6
  module Reporter
5
7
  end
6
8
  require_relative 'node'
9
+ require_relative_dir 'reporter/util'
7
10
  require_relative_dir 'reporter'
8
11
  end
@@ -1,23 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeepCover
4
- module Reporter
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 HTML::Tree
13
+ include Util::Tree
15
14
  include HTML::Base
16
15
 
17
16
  def stats_to_data
18
- @map = Tools.transform_keys(analysis.stat_map, &:name)
19
- tree = paths_to_tree(@map.keys)
20
- transform_data(populate(tree))
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(tree)
67
- return unless tree
68
- tree.each do |node|
69
- node[:data] = Tools.merge(
70
- node[:data].transform_values(&:to_h),
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 = ::Parser::Source::TreeRewriter.new(covered_code.buffer)
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
  {'<' => '&lt;', '>' => '&gt;', '&' => '&amp;'}.each do |char, escaped|
127
127
  source.scan(char) do
128
128
  m = Regexp.last_match
129
- range = ::Parser::Source::Range.new(buffer, m.begin(0), m.end(0))
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
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeepCover
4
- Tools = Module.new
4
+ module Tools
5
+ end
5
6
 
6
7
  require_relative 'tools/require_relative_dir'
7
8
  extend Tools::RequireRelativeDir
@@ -2,9 +2,8 @@
2
2
 
3
3
  module DeepCover
4
4
  module Tools::BuiltinCoverage
5
- require 'coverage'
6
-
7
5
  def builtin_coverage(source, fn, lineno)
6
+ require 'coverage'
8
7
  fn = File.absolute_path(File.expand_path(fn))
9
8
  ::Coverage.start
10
9
  Tools.silence_warnings do
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeepCover
4
- module Tools::Camelize
5
- # Poor man's camelize. 'an_example' => 'AnExample'
6
- def camelize(string)
7
- string.to_s.gsub(/([a-z\d]*)[_?!]?/) { Regexp.last_match(1).capitalize }
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeepCover
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
5
5
  end
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.2.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-08 00:00:00.000000000 Z
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