deep-cover-core 0.6.4 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/deep_cover_core.gemspec +1 -0
  3. data/lib/deep_cover.rb +3 -20
  4. data/lib/deep_cover/auto_run.rb +10 -35
  5. data/lib/deep_cover/autoload_tracker.rb +9 -5
  6. data/lib/deep_cover/base.rb +85 -12
  7. data/lib/deep_cover/basics.rb +15 -3
  8. data/lib/deep_cover/config.rb +36 -2
  9. data/lib/deep_cover/config_setter.rb +1 -1
  10. data/lib/deep_cover/core_ext/exec_callbacks.rb +15 -9
  11. data/lib/deep_cover/core_ext/instruction_sequence_load_iseq.rb +1 -1
  12. data/lib/deep_cover/coverage.rb +51 -41
  13. data/lib/deep_cover/covered_code.rb +54 -17
  14. data/lib/deep_cover/custom_requirer.rb +10 -10
  15. data/lib/deep_cover/global_variables.rb +32 -0
  16. data/lib/deep_cover/load.rb +25 -9
  17. data/lib/deep_cover/node/base.rb +6 -6
  18. data/lib/deep_cover/node/begin.rb +1 -2
  19. data/lib/deep_cover/node/block.rb +5 -1
  20. data/lib/deep_cover/node/case.rb +1 -1
  21. data/lib/deep_cover/node/empty_body.rb +1 -5
  22. data/lib/deep_cover/node/if.rb +3 -4
  23. data/lib/deep_cover/node/loops.rb +1 -1
  24. data/lib/deep_cover/node/mixin/flow_accounting.rb +1 -8
  25. data/lib/deep_cover/node/mixin/has_child.rb +3 -12
  26. data/lib/deep_cover/node/mixin/has_child_handler.rb +2 -5
  27. data/lib/deep_cover/node/mixin/has_tracker.rb +3 -7
  28. data/lib/deep_cover/node/module.rb +20 -23
  29. data/lib/deep_cover/node/root.rb +1 -1
  30. data/lib/deep_cover/node/send.rb +4 -5
  31. data/lib/deep_cover/node/short_circuit.rb +1 -1
  32. data/lib/deep_cover/persistence.rb +100 -0
  33. data/lib/deep_cover/problem_with_diagnostic.rb +2 -2
  34. data/lib/deep_cover/setup/clone_mode_entry_template.rb +66 -0
  35. data/lib/deep_cover/setup/deep_cover_auto_run.rb +21 -0
  36. data/lib/deep_cover/setup/deep_cover_config.rb +19 -0
  37. data/lib/deep_cover/setup/deep_cover_without_config.rb +15 -0
  38. data/lib/deep_cover/tools.rb +0 -2
  39. data/lib/deep_cover/tools/after_tests.rb +33 -0
  40. data/lib/deep_cover/tools/looks_like_rails_project.rb +13 -0
  41. data/lib/deep_cover/tools/our_coverage.rb +1 -1
  42. data/lib/deep_cover/tools/scan_match_datas.rb +1 -1
  43. data/lib/deep_cover/version.rb +4 -2
  44. metadata +24 -7
  45. data/lib/deep_cover/coverage/persistence.rb +0 -84
  46. data/lib/deep_cover/tracker_bucket.rb +0 -50
  47. data/lib/deep_cover/tracker_hits_per_path.rb +0 -35
  48. data/lib/deep_cover/tracker_storage.rb +0 -76
  49. data/lib/deep_cover/tracker_storage_per_path.rb +0 -34
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 786d0c79f687a02eefb1e3c9dc1af8c9b4b25d4c18fa86988d1c1a84d56f80e6
4
- data.tar.gz: 6b3ef23fa825eb7f72673e4a4be0fe3889418c035d0b87c17323c2ceb97f5fba
3
+ metadata.gz: 26469b9b1a015b36a11d432d22d871647ba060fc272c4edd274175d7163dcceb
4
+ data.tar.gz: 51917845e6fdcc9e073e776fc13cade71a82d7f0806a01d36e1b653c52ee8b5f
5
5
  SHA512:
6
- metadata.gz: 42806a5cf72180c3af74f5df4d03c067f52cade9d473adb2794db02416496de9aa6100390a4d3a597710378d8dd9bfbef783a65394cf9920adff115af53108cb
7
- data.tar.gz: 20d7363fe8fa8c1195d0eaf98f21f91bbfeb2371383fde3b1bddc28f986b503da7ef397f4d1ac3f61a49790018fc08ae038bf9314edf4f0e77ffab0eafce8283
6
+ metadata.gz: fd179008eefe481fdcd29f5857228fda6cf1b364b027694309ca40e664a548be13ba404d168d3d1f34e7749b7d037f9d70a4cfaa11450615ccec4d7aa615a6c2
7
+ data.tar.gz: bc452e18b3ec005229d4c889c5396cffa2ddadb1daa9cd4132f9e66b126be6d2a305b76d54718e1fcea5c8f548ef4e6e518620a221c8712298bc318f4068b1ea
@@ -29,6 +29,7 @@ Gem::Specification.new do |spec|
29
29
  # Support
30
30
  spec.add_runtime_dependency 'backports', '>= 3.11.0'
31
31
  spec.add_runtime_dependency 'binding_of_caller'
32
+ spec.add_runtime_dependency 'term-ansicolor'
32
33
 
33
34
  # Reporters
34
35
  spec.add_runtime_dependency 'terminal-table'
data/lib/deep_cover.rb CHANGED
@@ -1,22 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module DeepCover
4
- require_relative 'deep_cover/load'
5
-
6
- load_absolute_basics
7
-
8
- extend Base
9
- extend ConfigSetter
10
- end
11
- DeepCover::GLOBAL_BINDING = binding
12
-
13
- require './.deep_cover.rb' if File.exist?('./.deep_cover.rb')
14
-
15
- if ENV['DEEP_COVER_OPTIONS']
16
- DeepCover.config.set(YAML.load(ENV['DEEP_COVER_OPTIONS']))
17
- end
18
- if %w[1 t true].include?(ENV['DEEP_COVER'])
19
- DeepCover.start
20
- require_relative 'deep_cover/auto_run'
21
- DeepCover::AutoRun.run!('.').report!(**DeepCover.config)
22
- end
3
+ require_relative 'deep_cover/setup/deep_cover_without_config'
4
+ require_relative 'deep_cover/setup/deep_cover_config'
5
+ require_relative 'deep_cover/setup/deep_cover_auto_run'
@@ -1,13 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'core_ext/exec_callbacks'
4
+ require_relative 'tools/after_tests'
4
5
 
5
6
  module DeepCover
6
7
  module AutoRun
7
8
  class Runner
8
- def initialize(covered_path)
9
- @covered_path = covered_path
10
- @saved = !(DeepCover.respond_to?(:running?) && DeepCover.running?)
9
+ include Tools::AfterTests
10
+ def initialize
11
+ @saved = false
11
12
  end
12
13
 
13
14
  def run!
@@ -23,49 +24,23 @@ module DeepCover
23
24
 
24
25
  private
25
26
 
26
- def saved?
27
- @saved
28
- end
29
-
30
- def coverage
31
- @coverage ||= if saved?
32
- Coverage.load(@covered_path, with_trackers: false)
33
- else
34
- DeepCover.coverage
35
- end
36
- end
37
-
38
27
  def save
28
+ return if @saved
39
29
  require_relative '../deep_cover'
40
- coverage.save(@covered_path) unless saved?
41
- coverage.save_trackers(@covered_path)
30
+ DeepCover.persistence.save_trackers(DeepCover::GlobalVariables.tracker_hits_per_path)
31
+ @saved = true
42
32
  end
43
33
 
44
34
  def report(**options)
35
+ save # Some of the hooks seem to do things in reverse order. Not sure if all of them.
36
+ coverage = Coverage.load
45
37
  coverage.report(**options)
46
38
  end
47
-
48
- def after_tests
49
- use_at_exit = true
50
- if defined?(Minitest)
51
- use_at_exit = false
52
- Minitest.after_run { yield }
53
- end
54
- if defined?(Rspec)
55
- use_at_exit = false
56
- RSpec.configure do |config|
57
- config.after(:suite) { yield }
58
- end
59
- end
60
- if use_at_exit
61
- at_exit { yield }
62
- end
63
- end
64
39
  end
65
40
 
66
41
  def self.run!(covered_path)
67
42
  @runners ||= {}
68
- @runners[covered_path] ||= Runner.new(covered_path).run!
43
+ @runners[covered_path] ||= Runner.new.run!
69
44
  end
70
45
  end
71
46
  end
@@ -170,17 +170,21 @@ module DeepCover
170
170
  end
171
171
 
172
172
  def with_rb_extension(path)
173
- path += '.rb' unless has_supported_extension?(path)
173
+ path += '.rb' unless find_requirable_extension(path)
174
174
  path
175
175
  end
176
176
 
177
177
  def without_extension(path)
178
- path = path[0...-3] if has_supported_extension?(path)
179
- path
178
+ if (ext = find_requirable_extension(path))
179
+ path[0...-ext.length]
180
+ else
181
+ path
182
+ end
180
183
  end
181
184
 
182
- def has_supported_extension?(path)
183
- path.end_with?('.rb', '.so')
185
+ private def find_requirable_extension(path)
186
+ ext = File.extname(path)
187
+ REQUIRABLE_EXTENSIONS[ext] && ext
184
188
  end
185
189
 
186
190
  # It is not possible to simply remove an autoload. So, instead, we must change the
@@ -27,7 +27,7 @@ module DeepCover
27
27
  end
28
28
 
29
29
  config # actualize configuration
30
- @lookup_paths = nil
30
+ @lookup_globs = @all_tracked_file_paths = nil
31
31
  @started = true
32
32
  end
33
33
 
@@ -41,6 +41,10 @@ module DeepCover
41
41
  @started = false
42
42
  end
43
43
 
44
+ def delete_trackers
45
+ persistence.delete_trackers
46
+ end
47
+
44
48
  def line_coverage(filename)
45
49
  coverage.line_coverage(handle_relative_filename(filename), **config.to_h)
46
50
  end
@@ -65,7 +69,7 @@ module DeepCover
65
69
  case what
66
70
  when :paths
67
71
  warn "Changing DeepCover's paths after starting coverage is highly discouraged" if running?
68
- @lookup_paths = nil
72
+ @lookup_globs = @all_tracked_file_paths = nil
69
73
  when :tracker_global
70
74
  raise NotImplementedError, "Changing DeepCover's tracker global after starting coverage is not supported" if running?
71
75
  @coverage = nil
@@ -74,25 +78,90 @@ module DeepCover
74
78
 
75
79
  def reset
76
80
  stop if running?
77
- @coverage = @custom_requirer = @autoload_tracker = @lookup_paths = nil
81
+ @coverage = @custom_requirer = @autoload_tracker = @lookup_globs = @all_tracked_file_paths = nil
78
82
  config.reset
79
83
  self
80
84
  end
81
85
 
82
86
  def coverage
83
- @coverage ||= Coverage.new(tracker_global: config.tracker_global)
87
+ @coverage ||= Coverage.new
88
+ end
89
+
90
+ def lookup_globs
91
+ return @lookup_globs if @lookup_globs
92
+ paths = Array(config.paths || :auto_detect).dup
93
+ paths.concat(auto_detected_paths) if paths.delete(:auto_detect)
94
+
95
+ paths = paths.map { |p| File.expand_path(p) }
96
+ paths = ['/'] if paths.include?('/')
97
+ globs = paths.map! do |path|
98
+ if File.directory?(path)
99
+ # File.join is needed to avoid //**/*.rb
100
+ File.join(path, '**/*.rb')
101
+ else
102
+ # Either a single file's path, a glob, or a path that doesn't exists
103
+ path
104
+ end
105
+ end
106
+ @lookup_globs = globs
84
107
  end
85
108
 
86
- def lookup_paths
87
- return @lookup_paths if @lookup_paths
88
- lookup_paths = config.paths || Dir.getwd
89
- lookup_paths = Array(lookup_paths).map { |p| File.expand_path(p) }
90
- lookup_paths = ['/'] if lookup_paths.include?('/')
91
- @lookup_paths = lookup_paths
109
+ # Auto detects path that we want to cover. This is used when :auto_detect is in the config.paths.
110
+ # If the results aren't what you expect, then specify the paths yourself.
111
+ # We want this to work for most project's struture:
112
+ # * Single gems: just a gem directly in the top-level
113
+ # * Multi gems: contains multiple gems, each in a dir of the top-level dir (the rails gem does that)
114
+ # * Hybrid gems: a gem in the top-level dir and one in sub-dirs (the deep-cover gem does that)
115
+ # * Rails application
116
+ #
117
+ # For gems and Rails application, normally, everything to check coverage for is in lib/, and app/.
118
+ # For other projects, we go for every directories except test/ spec/ bin/ exe/.
119
+ #
120
+ # If the current dir has a .gemspec file, we consider it a "root".
121
+ # In addition, if any sub-dir of the current dir (not recursive) has a .gemspec file, we also consider them as "roots"
122
+ # If the current dir looks like a Rails application, add it as a "root"
123
+ # For each "roots", the "tracked dirs" will be "#{root}/app" and "#{root}/lib"
124
+ #
125
+ # If no "tracked dirs" exist, fallback to everything in current directory except each of test/ spec/ bin/ exe/.
126
+ def auto_detected_paths
127
+ require_relative 'tools/looks_like_rails_project'
128
+
129
+ gemspec_paths = Dir['./*.gemspec'] + Dir['./*/*.gemspec']
130
+ root_paths = gemspec_paths.map!(&File.method(:dirname))
131
+ root_paths.uniq!
132
+ root_paths << '.' if !root_paths.include?('.') && Tools::LooksLikeRailsProject.looks_like_rails_project?('.')
133
+
134
+ tracked_paths = root_paths.flat_map { |p| [File.join(p, 'app'), File.join(p, 'lib')] }
135
+ tracked_paths.select! { |p| File.exist?(p) }
136
+
137
+ if tracked_paths.empty?
138
+ # So track every sub-dirs except a couple
139
+ # The final '/' in Dir[] makes it return directories only, but they will also end with a '/'
140
+ # So need to include that last '/' in the substracted paths.
141
+ # TODO: We probably want a cleaner way of filtering out some directories. But for now, that's what we got.
142
+ tracked_paths = Dir['./*/'] - %w(./autotest/ ./features/ ./spec/ ./test/ ./bin/ ./exe/)
143
+ # And track every ruby files in the top-level
144
+ tracked_paths << './*.rb'
145
+ end
146
+ tracked_paths
147
+ # path expansion is done in #lookup_globs
148
+ end
149
+
150
+ def tracked_file_path?(path)
151
+ # The flags are to make fnmatch match the same things as Dir.glob... This doesn't seem to be documented anywhere
152
+ # EXTGLOB: allow matching {lib,app} as either lib or app
153
+ # PATHNAME: Makes wildcard match not match /, and make /**/ (and pattern starting with **/) be any number of nested directory
154
+ lookup_globs.any? { |glob| File.fnmatch?(glob, path, File::FNM_EXTGLOB | File::FNM_PATHNAME) }
92
155
  end
93
156
 
94
- def within_lookup_paths?(path)
95
- lookup_paths.any? { |lookup_path| path.start_with?(lookup_path) }
157
+ def all_tracked_file_paths
158
+ return @all_tracked_file_paths.dup if @all_tracked_file_paths
159
+ paths_found = Dir[*lookup_globs]
160
+ paths_found.select! { |path| path.end_with?('.rb') }
161
+ paths_found.select! { |path| File.file?(path) }
162
+ paths_found.uniq!
163
+ @all_tracked_file_paths = paths_found
164
+ @all_tracked_file_paths.dup
96
165
  end
97
166
 
98
167
  def custom_requirer
@@ -103,6 +172,10 @@ module DeepCover
103
172
  @autoload_tracker ||= AutoloadTracker.new
104
173
  end
105
174
 
175
+ def persistence
176
+ @persistence ||= Persistence.new(config.cache_directory)
177
+ end
178
+
106
179
  private
107
180
 
108
181
  def handle_relative_filename(filename)
@@ -4,19 +4,31 @@
4
4
  module DeepCover
5
5
  DEFAULTS = {
6
6
  ignore_uncovered: [].freeze,
7
- paths: %w[./app ./lib].freeze,
7
+ paths: [:auto_detect].freeze,
8
8
  allow_partial: false,
9
9
  tracker_global: '$_cov',
10
10
  reporter: :html,
11
11
  output: './coverage',
12
+ cache_directory: './deep_cover',
12
13
  }.freeze
13
14
 
14
15
  CLI_DEFAULTS = {
15
- command: 'bundle exec rake',
16
- bundle: true,
16
+ command: %w(bundle exec rake),
17
17
  process: true,
18
18
  open: false,
19
19
  }.freeze
20
20
 
21
21
  OPTIONALLY_COVERED = %i[case_implicit_else default_argument raise trivial_if warn]
22
+
23
+ REQUIRABLE_EXTENSIONS = {
24
+ '.rb' => :ruby,
25
+ ".#{RbConfig::CONFIG['DLEXT']}" => :native_extension,
26
+ }
27
+ unless (RbConfig::CONFIG['DLEXT2'] || '').empty?
28
+ REQUIRABLE_EXTENSIONS[".#{RbConfig::CONFIG['DLEXT2']}"] = :native_extension
29
+ end
30
+ REQUIRABLE_EXTENSIONS.freeze
31
+ REQUIRABLE_EXTENSION_KEYS = REQUIRABLE_EXTENSIONS.keys.freeze
32
+
33
+ CORE_GEM_LIB_DIRECTORY = File.expand_path(__dir__ + '/..')
22
34
  end
@@ -3,8 +3,10 @@
3
3
  module DeepCover
4
4
  class Config
5
5
  def initialize(notify = nil)
6
+ @notify = nil
7
+ @options = {ignore_uncovered: []}
8
+ set(**DEFAULTS)
6
9
  @notify = notify
7
- @options = DEFAULTS.dup
8
10
  end
9
11
 
10
12
  def to_hash
@@ -12,6 +14,23 @@ module DeepCover
12
14
  end
13
15
  alias_method :to_h, :to_hash
14
16
 
17
+ def to_hash_for_serialize
18
+ hash = to_hash
19
+ # TODO: (Max) I don't like mixup of configs being partly on DeepCover and Config like that...
20
+ hash[:paths] = DeepCover.lookup_globs
21
+ hash[:output] = hash[:output] ? File.expand_path(hash[:output]) : hash[:output]
22
+ hash[:cache_directory] = File.expand_path(hash[:cache_directory])
23
+ hash
24
+ end
25
+
26
+ def load_hash_for_serialize(hash)
27
+ @options.merge!(hash)
28
+ hash.each_key { |option| @notify.config_changed(option) } if @notify
29
+ # This was already transformed, it should all be absolute paths / globs, avoid doing it for nothing by setting it right away
30
+ # TODO: (Max) I don't like mixup of configs being partly on DeepCover and Config like that...
31
+ DeepCover.instance_variable_set(:@lookup_globs, hash[:paths])
32
+ end
33
+
15
34
  def ignore_uncovered(*keywords, &block)
16
35
  if block
17
36
  raise ArgumentError, "wrong number of arguments (given #{keywords.size}, expected 0..1)" if keywords.size > 1
@@ -68,6 +87,22 @@ module DeepCover
68
87
  end
69
88
  end
70
89
 
90
+ def cache_directory(cache_directory = nil)
91
+ if cache_directory
92
+ change(:cache_directory, cache_directory)
93
+ else
94
+ File.expand_path(@options[:cache_directory])
95
+ end
96
+ end
97
+
98
+ def allow_partial(allow_partial = nil)
99
+ if allow_partial != nil
100
+ change(:allow_partial, allow_partial)
101
+ else
102
+ @options[:allow_partial]
103
+ end
104
+ end
105
+
71
106
  def reset
72
107
  DEFAULTS.each do |key, value|
73
108
  change(key, value)
@@ -78,7 +113,6 @@ module DeepCover
78
113
  def set(**options)
79
114
  @options[:ignore_uncovered] = [] if options.has_key?(:ignore_uncovered)
80
115
  options.each do |key, value|
81
- next if key == :allow_partial
82
116
  public_send key, value
83
117
  end
84
118
  self
@@ -7,7 +7,7 @@ module DeepCover
7
7
  end
8
8
 
9
9
  def config(notify = self)
10
- raise ArgumentError, 'config does not accept an argument. Did you mean `configure`?' if block_given?
10
+ raise ArgumentError, 'config does not accept a block. Did you mean `configure`?' if block_given?
11
11
  @config ||= Config.new(notify)
12
12
  config_queue.each { |block| configure(&block) }
13
13
  config_queue.clear
@@ -1,27 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../module_override'
3
+ # This file is used by projects cloned with clone mode. As such, special care must be taken to
4
+ # be compatible with any projects.
5
+ # THERE MUST NOT BE ANY USE/REQUIRE OF DEPENDENCIES OF DeepCover HERE
6
+ # See deep-cover/core_gem/lib/deep_cover/setup/clone_mode_entry_template.rb for explanation of
7
+ # clone mode and of this top_level_module stuff.
8
+ top_level_module = Thread.current['_deep_cover_top_level_module'] || Object # rubocop:disable Lint/UselessAssignment
4
9
 
5
10
  # Adds a functionality to add callbacks before an `exec`
6
11
 
7
- module DeepCover
12
+ module top_level_module::DeepCover # rubocop:disable Naming/ClassAndModuleCamelCase
8
13
  module ExecCallbacks
9
14
  class << self
10
15
  attr_reader :callbacks
11
16
 
12
17
  def before_exec(&block)
13
- self.active = true
14
18
  (@callbacks ||= []) << block
15
19
  end
16
20
  end
21
+ end
17
22
 
18
- def exec(*args)
23
+ # We use #object_id of DeepCover to avoid possible overwrite between clone-mode and non-clone-mode
24
+ original_exec_name = :"exec_without_deep_cover_#{self.object_id}"
25
+ [::Kernel, ::Kernel.singleton_class].each do |mod|
26
+ mod.send(:alias_method, original_exec_name, :exec)
27
+ mod.send(:define_method, :exec) do |*args|
19
28
  ExecCallbacks.callbacks.each(&:call)
20
- exec_without_deep_cover(*args)
29
+ send(original_exec_name, *args)
21
30
  end
22
-
23
- extend ModuleOverride
24
- override ::Kernel, ::Kernel.singleton_class
25
- self.active = true
26
31
  end
32
+ ::Kernel.send :private, original_exec_name
27
33
  end
@@ -17,7 +17,7 @@ module DeepCover
17
17
 
18
18
  def self.load_iseq_logic(path)
19
19
  return unless DeepCover.running?
20
- return unless DeepCover.within_lookup_paths?(path)
20
+ return unless DeepCover.tracked_file_path?(path)
21
21
 
22
22
  covered_code = DeepCover.coverage.covered_code_or_warn(path)
23
23
  return unless covered_code