deep-cover 0.1.14 → 0.1.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +227 -0
  3. data/Gemfile +5 -2
  4. data/Rakefile +9 -6
  5. data/bin/console +3 -3
  6. data/bin/cov +8 -8
  7. data/bin/gemcov +2 -2
  8. data/bin/selfcov +5 -5
  9. data/bin/test_gems +11 -10
  10. data/bin/testall +6 -6
  11. data/deep_cover.gemspec +26 -21
  12. data/exe/deep-cover +1 -0
  13. data/lib/deep-cover.rb +2 -0
  14. data/lib/deep_cover.rb +3 -0
  15. data/lib/deep_cover/analyser.rb +2 -0
  16. data/lib/deep_cover/analyser/base.rb +2 -0
  17. data/lib/deep_cover/analyser/branch.rb +4 -2
  18. data/lib/deep_cover/analyser/covered_code_source.rb +3 -1
  19. data/lib/deep_cover/analyser/function.rb +3 -1
  20. data/lib/deep_cover/analyser/ignore_uncovered.rb +6 -4
  21. data/lib/deep_cover/analyser/node.rb +3 -0
  22. data/lib/deep_cover/analyser/optionally_covered.rb +12 -7
  23. data/lib/deep_cover/analyser/per_char.rb +7 -6
  24. data/lib/deep_cover/analyser/per_line.rb +9 -8
  25. data/lib/deep_cover/analyser/statement.rb +2 -0
  26. data/lib/deep_cover/analyser/subset.rb +4 -1
  27. data/lib/deep_cover/auto_run.rb +3 -0
  28. data/lib/deep_cover/autoload_tracker.rb +6 -3
  29. data/lib/deep_cover/backports.rb +2 -0
  30. data/lib/deep_cover/base.rb +11 -2
  31. data/lib/deep_cover/builtin_takeover.rb +2 -0
  32. data/lib/deep_cover/cli/debugger.rb +55 -30
  33. data/lib/deep_cover/cli/deep_cover.rb +17 -11
  34. data/lib/deep_cover/cli/instrumented_clone_reporter.rb +16 -14
  35. data/lib/deep_cover/config.rb +29 -16
  36. data/lib/deep_cover/core_ext/autoload_overrides.rb +2 -0
  37. data/lib/deep_cover/core_ext/coverage_replacement.rb +2 -0
  38. data/lib/deep_cover/core_ext/load_overrides.rb +5 -6
  39. data/lib/deep_cover/core_ext/require_overrides.rb +6 -7
  40. data/lib/deep_cover/coverage.rb +21 -18
  41. data/lib/deep_cover/covered_code.rb +22 -12
  42. data/lib/deep_cover/custom_requirer.rb +82 -35
  43. data/lib/deep_cover/memoize.rb +48 -0
  44. data/lib/deep_cover/module_override.rb +2 -0
  45. data/lib/deep_cover/node.rb +14 -1
  46. data/lib/deep_cover/node/arguments.rb +2 -0
  47. data/lib/deep_cover/node/assignments.rb +32 -30
  48. data/lib/deep_cover/node/base.rb +30 -29
  49. data/lib/deep_cover/node/begin.rb +3 -1
  50. data/lib/deep_cover/node/block.rb +5 -2
  51. data/lib/deep_cover/node/branch.rb +2 -1
  52. data/lib/deep_cover/node/case.rb +15 -13
  53. data/lib/deep_cover/node/collections.rb +2 -0
  54. data/lib/deep_cover/node/const.rb +2 -0
  55. data/lib/deep_cover/node/def.rb +10 -8
  56. data/lib/deep_cover/node/empty_body.rb +2 -0
  57. data/lib/deep_cover/node/exceptions.rb +3 -1
  58. data/lib/deep_cover/node/if.rb +3 -1
  59. data/lib/deep_cover/node/keywords.rb +4 -2
  60. data/lib/deep_cover/node/literals.rb +2 -0
  61. data/lib/deep_cover/node/loops.rb +5 -3
  62. data/lib/deep_cover/node/mixin/can_augment_children.rb +8 -7
  63. data/lib/deep_cover/node/mixin/check_completion.rb +3 -1
  64. data/lib/deep_cover/node/mixin/child_can_be_empty.rb +4 -2
  65. data/lib/deep_cover/node/mixin/executed_after_children.rb +2 -0
  66. data/lib/deep_cover/node/mixin/execution_location.rb +4 -2
  67. data/lib/deep_cover/node/mixin/flow_accounting.rb +2 -0
  68. data/lib/deep_cover/node/mixin/has_child.rb +22 -18
  69. data/lib/deep_cover/node/mixin/has_child_handler.rb +10 -8
  70. data/lib/deep_cover/node/mixin/has_tracker.rb +4 -2
  71. data/lib/deep_cover/node/mixin/is_statement.rb +3 -1
  72. data/lib/deep_cover/node/mixin/rewriting.rb +5 -3
  73. data/lib/deep_cover/node/mixin/wrapper.rb +2 -0
  74. data/lib/deep_cover/node/module.rb +11 -9
  75. data/lib/deep_cover/node/root.rb +2 -0
  76. data/lib/deep_cover/node/send.rb +4 -2
  77. data/lib/deep_cover/node/short_circuit.rb +4 -2
  78. data/lib/deep_cover/node/splat.rb +2 -0
  79. data/lib/deep_cover/node/variables.rb +2 -0
  80. data/lib/deep_cover/parser_ext/range.rb +3 -1
  81. data/lib/deep_cover/problem_with_diagnostic.rb +11 -9
  82. data/lib/deep_cover/reporter.rb +2 -0
  83. data/lib/deep_cover/reporter/istanbul.rb +28 -24
  84. data/lib/deep_cover/tools.rb +2 -0
  85. data/lib/deep_cover/tools/builtin_coverage.rb +6 -4
  86. data/lib/deep_cover/tools/camelize.rb +3 -1
  87. data/lib/deep_cover/tools/dasherize.rb +3 -1
  88. data/lib/deep_cover/tools/dump_covered_code.rb +7 -6
  89. data/lib/deep_cover/tools/execute_sample.rb +13 -13
  90. data/lib/deep_cover/tools/format.rb +3 -1
  91. data/lib/deep_cover/tools/format_char_cover.rb +4 -2
  92. data/lib/deep_cover/tools/format_generated_code.rb +3 -1
  93. data/lib/deep_cover/tools/number_lines.rb +2 -0
  94. data/lib/deep_cover/tools/our_coverage.rb +5 -3
  95. data/lib/deep_cover/tools/profiling.rb +66 -0
  96. data/lib/deep_cover/tools/require_relative_dir.rb +3 -1
  97. data/lib/deep_cover/tools/silence_warnings.rb +4 -1
  98. data/lib/deep_cover/tools/slice.rb +3 -1
  99. data/lib/deep_cover/tools/truncate_backtrace.rb +2 -0
  100. data/lib/deep_cover/version.rb +3 -1
  101. metadata +47 -30
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeepCover
2
4
  require 'bundler/setup'
3
5
  require 'slop'
@@ -9,7 +11,7 @@ module DeepCover
9
11
  extend self
10
12
 
11
13
  def show_version
12
- puts "deep-cover v#{DeepCover::VERSION}; parser v#{Parser::Version}"
14
+ puts "deep-cover v#{::DeepCover::VERSION}; parser v#{::Parser::VERSION}"
13
15
  end
14
16
 
15
17
  def show_help
@@ -17,7 +19,7 @@ module DeepCover
17
19
  end
18
20
 
19
21
  class Parser < Struct.new(:delegate)
20
- def method_missing(method, *args, &block)
22
+ def method_missing(method, *args, &block) # rubocop:disable Style/MethodMissing
21
23
  options = args.last
22
24
  if options.is_a?(Hash) && options.has_key?(:default)
23
25
  args[-2] += " [#{options[:default]}]"
@@ -34,7 +36,7 @@ module DeepCover
34
36
 
35
37
  def menu
36
38
  @menu ||= parse do |o|
37
- o.banner = "usage: deep-cover [options] [path/to/app/or/gem]"
39
+ o.banner = 'usage: deep-cover [options] [path/to/app/or/gem]'
38
40
  o.separator ''
39
41
  o.string '-o', '--output', 'output folder', default: './coverage'
40
42
  o.string '-c', '--command', 'command to run tests', default: 'bundle exec rake'
@@ -43,18 +45,20 @@ module DeepCover
43
45
  o.separator 'Coverage options'
44
46
  @ignore_uncovered_map = Analyser.optionally_covered.map do |option|
45
47
  default = Config::DEFAULTS[:ignore_uncovered].include?(option)
46
- o.bool "--ignore-#{Tools.dasherize(option)}", "", default: default
48
+ o.bool "--ignore-#{Tools.dasherize(option)}", '', default: default
47
49
  [:"ignore_#{option}", option]
48
50
  end.to_h
49
- o.separator ''
50
- o.separator 'For testing purposes:'
51
+ o.separator "\nFor testing purposes:"
52
+ o.bool '--profile', 'use profiler' unless RUBY_PLATFORM == 'java'
51
53
  o.string '-e', '--expression', 'test ruby expression instead of a covering a path'
52
54
  o.bool '-d', '--debug', 'enter debugging after cover'
53
55
 
54
- o.separator ''
55
- o.separator 'Other available commands:'
56
- o.on('--version', 'print the version') { version; exit }
57
- o.on('-h', '--help') { help; exit }
56
+ o.separator "\nOther available commands:"
57
+ o.on('--version', 'print the version') do
58
+ show_version
59
+ exit
60
+ end
61
+ o.boolean('-h', '--help')
58
62
  end
59
63
  end
60
64
 
@@ -68,7 +72,9 @@ module DeepCover
68
72
 
69
73
  def go
70
74
  options = convert_options(menu.to_h)
71
- if options[:expression]
75
+ if options[:help]
76
+ show_help
77
+ elsif options[:expression]
72
78
  Debugger.new(options[:expression], **options).show
73
79
  elsif (path = menu.arguments.first)
74
80
  InstrumentedCloneReporter.new(path, **options).run
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yaml'
2
4
  require 'tmpdir'
3
5
 
@@ -18,7 +20,7 @@ module DeepCover
18
20
  raise "Can't find Gemfile" unless @root_path.join('Gemfile').exist?
19
21
  end
20
22
  @dest_root = Pathname('~/test_deep_cover').expand_path
21
- @dest_root = Pathname.new(Dir.mktmpdir("deep_cover_test")) unless @dest_root.exist?
23
+ @dest_root = Pathname.new(Dir.mktmpdir('deep_cover_test')) unless @dest_root.exist?
22
24
 
23
25
  gem_relative_path = @source_path.relative_path_from(@root_path)
24
26
  @main_path = @dest_root.join(gem_relative_path)
@@ -31,7 +33,7 @@ module DeepCover
31
33
 
32
34
  def copy
33
35
  return true if @copied
34
- puts "Cloning..."
36
+ puts 'Cloning...'
35
37
  FileUtils.cp_r(Dir.glob("#{@root_path}/#{GLOB_ALL_CONTENT}"), @dest_root)
36
38
  @copied = true
37
39
  end
@@ -39,7 +41,7 @@ module DeepCover
39
41
  def patch_ruby_file(ruby_file)
40
42
  content = ruby_file.read
41
43
  # Insert our code after leading comments:
42
- content.sub!(/^((#.*\n+)*)/, "#{$1}require 'deep_cover/auto_run';DeepCover::AutoRun.run! '#{@dest_root}';")
44
+ content.sub!(/^(#.*\n+)*/) { |header| "#{header}require 'deep_cover/auto_run';DeepCover::AutoRun.run! '#{@dest_root}';" }
43
45
  ruby_file.write(content)
44
46
  end
45
47
 
@@ -79,7 +81,7 @@ module DeepCover
79
81
  module GemCollection
80
82
  include Gem
81
83
  def each_gem_path
82
- Pathname.glob(@main_path.join('*/lib')).each{|p| yield p.dirname}
84
+ Pathname.glob(@main_path.join('*/lib')).each { |p| yield p.dirname }
83
85
  end
84
86
  end
85
87
 
@@ -108,19 +110,19 @@ module DeepCover
108
110
  unless content =~ /gem 'deep-cover'/
109
111
  puts "Patching Gemfile #{gemfile}"
110
112
  File.write(gemfile, [
111
- "# This file was modified by DeepCover",
112
- content,
113
- "gem 'deep-cover', path: '#{File.expand_path(__dir__ + '/../../../')}'",
114
- '',
115
- ].join("\n"))
113
+ '# This file was modified by DeepCover',
114
+ content,
115
+ "gem 'deep-cover', path: '#{File.expand_path(__dir__ + '/../../../')}'",
116
+ '',
117
+ ].join("\n"))
116
118
  end
117
119
  end
118
120
 
119
121
  def patch_rubocop
120
122
  path = @dest_root.join('.rubocop.yml')
121
123
  return unless path.exist?
122
- puts "Patching .rubocop.yml"
123
- config = YAML.load(path.read.gsub(/(?<!\w)lib(?!\w)/, 'lib_original'))
124
+ puts 'Patching .rubocop.yml'
125
+ config = YAML.safe_load(path.read.gsub(/(?<!\w)lib(?!\w)/, 'lib_original'))
124
126
  ((config['AllCops'] ||= {})['Exclude'] ||= []) << 'lib/**/*' << 'app/**/*'
125
127
  path.write("# This file was modified by DeepCover\n" + YAML.dump(config))
126
128
  end
@@ -137,8 +139,8 @@ module DeepCover
137
139
  original = to_cover.sub_ext('_original')
138
140
  FileUtils.cp_r(to_cover, original)
139
141
  Tools.dump_covered_code(original,
140
- coverage: coverage, root_path: @dest_root.to_s,
141
- dest_path: to_cover)
142
+ coverage: coverage, root_path: @dest_root.to_s,
143
+ dest_path: to_cover)
142
144
  end
143
145
  coverage.save(@dest_root.to_s)
144
146
  end
@@ -155,7 +157,7 @@ module DeepCover
155
157
  end
156
158
 
157
159
  def bundle
158
- puts "Running `bundle install`"
160
+ puts 'Running `bundle install`'
159
161
  Bundler.with_clean_env do
160
162
  `cd #{@dest_root} && bundle`
161
163
  end
@@ -1,12 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeepCover
2
4
  class Config
3
5
  DEFAULTS = {
4
- ignore_uncovered: [],
5
- paths: %w[./app ./lib],
6
- allow_partial: false,
7
- }
6
+ ignore_uncovered: [],
7
+ paths: %w[./app ./lib],
8
+ allow_partial: false,
9
+ }.freeze
8
10
 
9
- def initialize(**options)
11
+ def initialize(notify = nil, **options)
12
+ @notify = notify
10
13
  @options = copy(DEFAULTS.merge(options))
11
14
  end
12
15
 
@@ -17,38 +20,48 @@ module DeepCover
17
20
 
18
21
  def ignore_uncovered(*keywords)
19
22
  check_uncovered(keywords)
20
- @options[:ignore_uncovered] += keywords
21
- self
23
+ change(:ignore_uncovered, @options[:ignore_uncovered] | keywords)
22
24
  end
23
25
 
24
26
  def detect_uncovered(*keywords)
25
27
  check_uncovered(keywords)
26
- @options[:ignore_uncovered] -= keywords
27
- self
28
+ change(:ignore_uncovered, @options[:ignore_uncovered] - keywords)
28
29
  end
29
30
 
30
- def paths(paths)
31
- @options[:paths] = paths
32
- self
31
+ def paths(paths = nil)
32
+ if paths
33
+ change(:paths, Array(paths).dup.freeze)
34
+ else
35
+ @options[:paths]
36
+ end
33
37
  end
34
38
 
35
39
  private
40
+
36
41
  def check_uncovered(keywords)
37
42
  unknown = keywords - Analyser.optionally_covered
38
43
  raise ArgumentError, "unknown options: #{unknown.join(', ')}" unless unknown.empty?
39
44
  end
40
45
 
46
+ def change(option, value)
47
+ if @options[option] != value
48
+ @options[option] = value
49
+ @notify.config_changed(option) if @notify.respond_to? :config_changed
50
+ end
51
+ self
52
+ end
53
+
41
54
  def copy(h)
42
- h.dup.transform_values(&:dup)
55
+ h.dup.transform_values(&:dup).transform_values(&:freeze)
43
56
  end
44
57
 
45
58
  module Setter
46
- def config
47
- @config ||= Config.new
59
+ def config(notify = self)
60
+ @config ||= Config.new(notify)
48
61
  end
49
62
 
50
63
  def configure(&block)
51
- raise "Must provide a block" unless block
64
+ raise 'Must provide a block' unless block
52
65
  case block.arity
53
66
  when 0
54
67
  config.instance_eval(&block)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # We need to override autoload, because MRI has special behaviors associated with it
2
4
  # that we can't reuse, hence we need to do workarounds.
3
5
  #
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This is a complete replacement for the builtin Coverage module of Ruby
2
4
 
3
5
  require 'coverage'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # These are the monkeypatches to replace the default #load in order
2
4
  # to instrument the code before it gets run.
3
5
  # For now, this is not used, and may never be. The tracking and reporting for things can might be
@@ -8,12 +10,9 @@ module DeepCover
8
10
  def load(path, wrap = false)
9
11
  return load_without_deep_cover(path, wrap) if wrap
10
12
 
11
- result = DeepCover.custom_requirer.load(path)
12
- if [:not_found, :cover_failed, :not_supported].include?(result)
13
- load_without_deep_cover(path)
14
- else
15
- result
16
- end
13
+ result = catch(:use_fallback) { DeepCover.custom_requirer.load(path) }
14
+ result = load_without_deep_cover(path) if result.is_a? Symbol
15
+ result
17
16
  end
18
17
  end
19
18
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # These are the monkeypatches to replace the default #require and
2
4
  # #require_relative in order to instrument the code before it gets run.
3
5
  # Kernel.require and Kernel#require must both have their version because
@@ -7,17 +9,14 @@
7
9
  module DeepCover
8
10
  module RequireOverride
9
11
  def require(path)
10
- result = DeepCover.custom_requirer.require(path)
11
- if [:not_found, :cover_failed, :not_supported].include?(result)
12
- require_without_deep_cover(path)
13
- else
14
- result
15
- end
12
+ result = catch(:use_fallback) { DeepCover.custom_requirer.require(path) }
13
+ result = require_without_deep_cover(path) if result.is_a? Symbol
14
+ result
16
15
  end
17
16
 
18
17
  def require_relative(path)
19
18
  base = caller(1..1).first[/[^:]+/]
20
- raise LoadError, "cannot infer basepath" unless base
19
+ raise LoadError, 'cannot infer basepath' unless base
21
20
  base = File.dirname(base)
22
21
 
23
22
  require(File.absolute_path(path, base))
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeepCover
2
4
  require 'parser'
3
5
  silence_warnings do
@@ -34,7 +36,7 @@ module DeepCover
34
36
 
35
37
  def each
36
38
  return to_enum unless block_given?
37
- @covered_codes.each{|_path, covered_code| yield covered_code}
39
+ @covered_codes.each_value { |covered_code| yield covered_code }
38
40
  self
39
41
  end
40
42
 
@@ -45,7 +47,7 @@ module DeepCover
45
47
  end.inject(:merge)
46
48
  end
47
49
 
48
- def output_istanbul(dir: '.', name: ".nyc_output", **options)
50
+ def output_istanbul(dir: '.', name: '.nyc_output', **options)
49
51
  path = Pathname.new(dir).expand_path.join(name)
50
52
  path.mkpath
51
53
  path.each_child(&:delete)
@@ -82,7 +84,7 @@ module DeepCover
82
84
  if Reporter::Istanbul.available?
83
85
  report_istanbul(**options)
84
86
  else
85
- warn "nyc not available. Please install `nyc` using `yarn global add nyc` or `npm i nyc -g`"
87
+ warn 'nyc not available. Please install `nyc` using `yarn global add nyc` or `npm i nyc -g`'
86
88
  basic_report
87
89
  end
88
90
  end
@@ -110,6 +112,7 @@ module DeepCover
110
112
  end
111
113
 
112
114
  class Persistence
115
+ # rubocop:disable Security/MarshalLoad
113
116
  BASENAME = 'coverage.dc'
114
117
  TRACKER_TEMPLATE = 'trackers%{unique}.dct'
115
118
 
@@ -132,16 +135,16 @@ module DeepCover
132
135
 
133
136
  def save_trackers(global)
134
137
  saved?
135
- trackers = eval(global)
138
+ trackers = eval(global) # rubocop:disable Security/Eval
136
139
  # Some testing involves more than one process, some of which don't run any of our covered code.
137
140
  # Don't save anything if that's the case
138
141
  return if trackers.nil?
139
- basename = TRACKER_TEMPLATE % {unique: SecureRandom.urlsafe_base64}
140
- dir_path.join(basename).binwrite(Marshal.dump({
141
- version: DeepCover::VERSION,
142
- global: global,
143
- trackers: trackers,
144
- }))
142
+ basename = format(TRACKER_TEMPLATE, unique: SecureRandom.urlsafe_base64)
143
+ dir_path.join(basename).binwrite(Marshal.dump(
144
+ version: DeepCover::VERSION,
145
+ global: global,
146
+ trackers: trackers,
147
+ ))
145
148
  end
146
149
 
147
150
  def saved?
@@ -156,10 +159,10 @@ module DeepCover
156
159
  end
157
160
 
158
161
  def save_coverage(coverage)
159
- dir_path.join(BASENAME).binwrite(Marshal.dump({
160
- version: DeepCover::VERSION,
161
- coverage: coverage,
162
- }))
162
+ dir_path.join(BASENAME).binwrite(Marshal.dump(
163
+ version: DeepCover::VERSION,
164
+ coverage: coverage,
165
+ ))
163
166
  end
164
167
 
165
168
  def load_coverage
@@ -173,22 +176,22 @@ module DeepCover
173
176
  tracker_files.each do |full_path|
174
177
  Marshal.load(full_path.binread).tap do |version: raise, global: raise, trackers: raise|
175
178
  raise "dump version mismatch: #{version}, currently #{DeepCover::VERSION}" unless version == DeepCover::VERSION
176
- merge_trackers(eval("#{global} ||= {}"), trackers)
179
+ merge_trackers(eval("#{global} ||= {}"), trackers) # rubocop:disable Security/Eval
177
180
  end
178
181
  end
179
182
  end
180
183
 
181
184
  def merge_trackers(hash, to_merge)
182
185
  hash.merge!(to_merge) do |_key, current, to_add|
183
- unless current.size == 0 || current.size == to_add.size
186
+ unless current.empty? || current.size == to_add.size
184
187
  warn "Merging trackers of different sizes: #{current.size} vs #{to_add.size}"
185
188
  end
186
- to_add.zip(current).map{|a, b| a+b}
189
+ to_add.zip(current).map { |a, b| a + b }
187
190
  end
188
191
  end
189
192
 
190
193
  def tracker_files
191
- basename = TRACKER_TEMPLATE % { unique: '*' }
194
+ basename = format(TRACKER_TEMPLATE, unique: '*')
192
195
  Pathname.glob(dir_path.join(basename))
193
196
  end
194
197
 
@@ -1,13 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeepCover
2
4
  class CoveredCode
3
5
  DEFAULT_TRACKER_GLOBAL = '$_cov'
4
6
 
5
7
  attr_accessor :covered_source, :buffer, :tracker_global, :local_var, :name
6
8
  @@counter = 0
7
- @@globals = Hash.new{|h, global| h[global] = eval("#{global} ||= {}") }
9
+ @@globals = Hash.new { |h, global| h[global] = eval("#{global} ||= {}") } # rubocop:disable Security/Eval
8
10
 
9
11
  def initialize(path: nil, source: nil, lineno: 1, tracker_global: DEFAULT_TRACKER_GLOBAL, local_var: '_temp', name: nil)
10
- raise "Must provide either path or source" unless path || source
12
+ raise 'Must provide either path or source' unless path || source
11
13
 
12
14
  @buffer = ::Parser::Source::Buffer.new(path, lineno)
13
15
  @buffer.source = source ||= File.read(path)
@@ -27,20 +29,18 @@ module DeepCover
27
29
  end
28
30
 
29
31
  def nb_lines
30
- @nb_lines ||= begin
31
- lines = buffer.source_lines
32
- if lines.size == 0
33
- 0
34
- else
35
- lines.size - (lines.last.empty? ? 1 : 0)
36
- end
32
+ lines = buffer.source_lines
33
+ if lines.empty?
34
+ 0
35
+ else
36
+ lines.size - (lines.last.empty? ? 1 : 0)
37
37
  end
38
38
  end
39
39
 
40
40
  def execute_code(binding: DeepCover::GLOBAL_BINDING.dup)
41
41
  return if has_executed?
42
42
  global[nb] = Array.new(@tracker_count, 0)
43
- eval(@covered_source, binding, @buffer.name || '<raw_code>', lineno)
43
+ eval(@covered_source, binding, @buffer.name || '<raw_code>', lineno) # rubocop:disable Security/Eval
44
44
  self
45
45
  end
46
46
 
@@ -71,7 +71,7 @@ module DeepCover
71
71
  # Returns a range of tracker ids
72
72
  def allocate_trackers(nb_needed)
73
73
  prev = @tracker_count
74
- @tracker_count += nb_needed
74
+ @tracker_count += nb_needed if nb_needed > 0 # Avoid error if frozen and called with 0.
75
75
  prev...@tracker_count
76
76
  end
77
77
 
@@ -109,7 +109,7 @@ module DeepCover
109
109
  prefix, _node, suffix = rule.partition('%{node}')
110
110
  unless prefix.empty?
111
111
  prefix = yield prefix, node, range.begin, :prefix if block_given?
112
- rewriter.insert_before_multi range, prefix rescue binding.pry
112
+ rewriter.insert_before_multi range, prefix
113
113
  end
114
114
  unless suffix.empty?
115
115
  suffix = yield suffix, node, range.end, :suffix if block_given?
@@ -124,7 +124,17 @@ module DeepCover
124
124
  global[nb] != nil
125
125
  end
126
126
 
127
+ def freeze
128
+ unless frozen? # Guard against reentrance
129
+ must_have_executed
130
+ super
131
+ root.each_node(&:freeze)
132
+ end
133
+ self
134
+ end
135
+
127
136
  protected
137
+
128
138
  def global
129
139
  @@globals[tracker_global]
130
140
  end