deep-cover 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3a8450fe7655e95139f1443a7e81f3c73b726e03
4
- data.tar.gz: b04f9ef3b69060ff9568cae7224bc89a845137bf
3
+ metadata.gz: 23dab9f10f66926115fc7a84107eaaa364ea232f
4
+ data.tar.gz: a5e5a3e0d3b39829928acd724702f306282d03b4
5
5
  SHA512:
6
- metadata.gz: 822e717af608dd6b6ed0010ce00f75aff18f4f60cfddc866df90ceefaf6131820a19f8a7a0e2d5eb00d961412dbcac23295e4b3aebb8be9cd1ae7ca7ecf13ab5
7
- data.tar.gz: a5849e499b6f4eb7975af2e4529f5564e99a982d0cda525e0d246e6bce698f289e24cadf9a44ba3a85c4e78ae60da41813ef443f3f7114a261a6a856cd6a024e
6
+ metadata.gz: 746c9f0d6ee8bd832e2629d37996050f309bcc7724be618f72da0c3851705b714f6c56b458bc4727bdb5b7fe62f64eda6b99f61e6ca21e306b3ed8f339a526c5
7
+ data.tar.gz: 3aa28c66195e1cd61bfeafe38df9caa82e4e5ffd55bf6fdb3b3c4fcee8aa8ac0efb6a369b74c6656954e761e7e5f659aea36d9d25d582049e0d04b92c20a0f4d
data/.gitignore CHANGED
@@ -1,5 +1,5 @@
1
- /.bundle/
2
- /.yardoc
1
+ .bundle/
2
+ .yardoc
3
3
  /Gemfile.lock
4
4
  /_yardoc/
5
5
  /coverage/
@@ -7,9 +7,10 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
- /.idea
11
- /.sass-cache
10
+ .idea
11
+ .sass-cache
12
12
 
13
13
  # rspec failure tracking
14
14
  .rspec_status
15
15
  /Gemfile.local
16
+ /spec/cli_fixtures/covered_trivial_gem/deep_cover/
@@ -7,6 +7,7 @@ AllCops:
7
7
  - 'spec/full_usage/**/*'
8
8
  - 'spec/specs_tools.rb'
9
9
  - 'bin/**/*'
10
+ - '_*/**/*'
10
11
  TargetRubyVersion: 2.1
11
12
 
12
13
 
@@ -192,6 +193,9 @@ Style/MultilineIfModifier:
192
193
  Style/NilComparison:
193
194
  Enabled: false
194
195
 
196
+ Style/NonNilCheck:
197
+ Enabled: false
198
+
195
199
  Lint/EmptyWhen:
196
200
  Enabled: false
197
201
 
@@ -221,17 +225,14 @@ Style/StructInheritance:
221
225
  Security/YAMLLoad:
222
226
  Enabled: false
223
227
 
224
- Style/ExtendSelf:
225
- Enabled: false
226
-
227
228
  Style/CommentedKeyword:
228
229
  Enabled: false # See https://github.com/bbatsov/rubocop/issues/5259
229
230
 
230
- Gemspec/RequiredRubyVersion:
231
- Enabled: false # See https://github.com/bbatsov/rubocop/issues/5260
232
-
233
231
  Style/MixinUsage:
234
232
  Enabled: false # See https://github.com/bbatsov/rubocop/issues/5261
235
233
 
236
234
  Style/EvalWithLocation:
237
235
  Enabled: false
236
+
237
+ Lint/BooleanSymbol:
238
+ Enabled: false
@@ -1,14 +1,15 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
+ - ruby-head
5
+ - 2.5.0
4
6
  - 2.4.1
5
7
  - 2.3.4
6
8
  - 2.2.7
7
9
  - 2.1.10
8
- - 2.0.0
9
10
  - jruby-9.1.9.0
10
11
  before_install:
11
- - gem install bundler -v 1.15.4
12
+ - gem update --system
12
13
  - npm install -g nyc
13
14
  before_script:
14
15
  - bundle exec rake dev:install
@@ -17,3 +18,4 @@ script:
17
18
  matrix:
18
19
  allow_failures:
19
20
  - rvm: jruby-9.1.9.0
21
+ - rvm: ruby-head
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4
4
+
5
+ * Added `deep-cover exec`
6
+ * Support for # nocov
7
+
8
+ ## 0.3
9
+
10
+ * Text reporter
11
+ * Lazy loading
12
+
3
13
  ## 0.2
4
14
 
5
15
  * HTML reporter
data/README.md CHANGED
@@ -45,9 +45,63 @@ These examples are direct outputs from our HTML reporter:
45
45
 
46
46
  gem install deep-cover
47
47
 
48
+ First we present the official way. There are also quick and dirty ways to try `deep-cover` without changing much your current setup, which we present afterwards.
49
+
50
+ ### Canonical installation
51
+
52
+ *1* Add the `deep-cover` gem as a dependency:
53
+
54
+ For a standalone project (Rails app), add `deep-cover` to your Gemfile:
55
+
56
+ gem 'deep-cover', '~> 0.4', group: :test
57
+
58
+ Then run `bundle`
59
+
60
+ For a gem, you want to add `spec.add_development_dependency 'deep-cover', '~> 0.4'` to your `gemspec` file instead.
61
+
62
+ *2* Require `deep-cover`
63
+
64
+ You must call `require 'deep-cover'` *before* the code you want to cover is loaded.
65
+
66
+ Typically, you want to insert that line in your `test/test_helper.rb` or `spec/spec_helper.rb` file at the right place. For example
67
+
68
+ ```
69
+ ENV['RAILS_ENV'] ||= 'test'
70
+ require 'deep-cover' # Must be before the environment is loaded on the next line
71
+ require File.expand_path('../../config/environment', __FILE__)
72
+ require 'rails/test_help'
73
+ # ...
74
+ ```
75
+
76
+ *3* Create a config file (optional)
77
+
78
+ You may want to create a config file `.deep-cover.rb` at the root of your project, where you can set the config as you wish.
79
+
80
+ ```
81
+ # File .deep-cover.rb
82
+ DeepCover.config do
83
+ ignore :default_arguments
84
+ # ...
85
+ end
86
+ ```
87
+
88
+ *4* Launch it
89
+
90
+ Even after `DeepCover` is `require`d and configured, only a very minimal amount of code is actually loaded and coverage is *not started*.
91
+
92
+ The easiest way to actually start it is to use `deep-cover exec` instead of `bundle exec`.
93
+
94
+ For example:
95
+
96
+ ```
97
+ $ deep-cover exec rspec
98
+ # ...all the output of rspec
99
+ # ...coverage report
100
+ ```
101
+
48
102
  ### Command line interface (for a Rails app or a Gem):
49
103
 
50
- An easy way to check coverage, without any configuration needed:
104
+ An easy way to try `deep-cover`, without any configuration needed:
51
105
 
52
106
  deep-cover /path/to/rails/app/or/gem
53
107
 
@@ -55,7 +109,9 @@ This assumes your project has a `Gemfile`, and that your default `rake` task is
55
109
 
56
110
  It also uses our builtin HTML reporter. Check the produced `coverage/index.html`.
57
111
 
58
- ### Builtin Coverage (including SimpleCov) users
112
+ ### Projects using builtin Coverage (including SimpleCov) users
113
+
114
+ To make it easier to transition for projects already using the builtin `Coverage` library (or indirectly those using `SimpleCov`), there is a way to overwrite the `Coverage` library using `deep-cover`'s extended coverage.
59
115
 
60
116
  Add to your Gemfile `gem 'deep-cover'`, then run `bundle`.
61
117
 
@@ -85,21 +141,9 @@ DeepCover.configure do
85
141
  end
86
142
  ```
87
143
 
88
- ### Low level usage
144
+ The file `.deep-cover.rb` is loaded automatically when requiring `deep-cover` and is the best place to put the configuration.
89
145
 
90
- ```
91
- # Setup
92
- require 'deep-cover'
93
- DeepCover.configure { ignore_uncovered :trivial_if }
94
- # Cover
95
- DeepCover.cover do
96
- require 'my_file_to_cover'
97
- require 'my_other_file_to_cover'
98
- end
99
- require 'this_file_wont_be_covered'
100
- tests.run()
101
- puts DeepCover.line_coverage('foo')
102
- ```
146
+ *Note*: The configuration block is only executed when `deep-cover` is actually started.
103
147
 
104
148
  ## Development
105
149
 
@@ -117,7 +161,7 @@ More details in the [contributing guide](https://github.com/deep-cover/deep-cove
117
161
 
118
162
  ### Status
119
163
 
120
- Currently in heavy development. *Alpha stage, API subject to change every day*. Best time to get involved though ;-)
164
+ Currently in development. *Alpha stage, API still subject to change*. Best time to get involved though ;-)
121
165
 
122
166
  ## Contributing
123
167
 
@@ -12,6 +12,7 @@
12
12
  # Other differences from wwtd:
13
13
  # * automatically installs the bundler gem if it is missing from a ruby version.
14
14
  #
15
+ require 'term/ansicolor'
15
16
  require 'yaml'
16
17
  TRAVIS_CONFIG = '.travis.yml'
17
18
 
@@ -23,13 +23,13 @@ Gem::Specification.new do |spec|
23
23
  spec.require_paths = ['lib']
24
24
 
25
25
  ### Runtime dependencies
26
- spec.required_ruby_version = '>= 2.0.0'
26
+ spec.required_ruby_version = '>= 2.1.0'
27
27
 
28
28
  # Main dependency
29
29
  spec.add_runtime_dependency 'parser'
30
30
 
31
31
  # Support
32
- spec.add_runtime_dependency 'backports', '>= 3.10.1'
32
+ spec.add_runtime_dependency 'backports', '>= 3.11.0'
33
33
  spec.add_runtime_dependency 'binding_of_caller'
34
34
 
35
35
  # CLI
@@ -50,5 +50,5 @@ Gem::Specification.new do |spec|
50
50
  spec.add_development_dependency 'psych', '>= 2.0'
51
51
  spec.add_development_dependency 'rake', '~> 12.0'
52
52
  spec.add_development_dependency 'rspec', '~> 3.0'
53
- spec.add_development_dependency 'rubocop'
53
+ spec.add_development_dependency 'rubocop', '0.52.1' # About every single release breaks something
54
54
  end
@@ -6,6 +6,17 @@ module DeepCover
6
6
  load_absolute_basics
7
7
 
8
8
  extend Base
9
- extend Config::Setter
9
+ extend ConfigSetter
10
10
  end
11
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!('.').and_report!(**DeepCover.config)
22
+ end
@@ -11,7 +11,8 @@ module DeepCover
11
11
  def initialize(source, ignore_uncovered: [], **options)
12
12
  @cache = {}.compare_by_identity
13
13
  super
14
- @allow_filters = Array(ignore_uncovered).map { |kind| method(:"is_#{kind}?") }
14
+ @allow_filters = Array(ignore_uncovered).map { |kind| :"is_#{kind}?" }
15
+ @nocov_ranges = FlagCommentAssociator.new(covered_code)
15
16
  end
16
17
 
17
18
  def node_runs(node)
@@ -22,35 +23,10 @@ module DeepCover
22
23
  end
23
24
  end
24
25
 
25
- def is_raise?(node)
26
- node.is_a?(Node::Send) && (node.message == :raise || node.message == :exit)
27
- end
28
-
29
- def is_default_argument?(node)
30
- node.parent.is_a?(Node::Optarg)
31
- end
32
-
33
- def is_case_implicit_else?(node)
34
- parent = node.parent
35
- node.is_a?(Node::EmptyBody) && parent.is_a?(Node::Case) && !parent.has_else?
36
- end
37
-
38
26
  def in_subset?(node, _parent)
39
27
  node.executable?
40
28
  end
41
29
 
42
- def is_trivial_if?(node)
43
- # Supports only node being a branch or the fork itself
44
- node.parent.is_a?(Node::If) && node.parent.condition.is_a?(Node::SingletonLiteral)
45
- end
46
-
47
- def self.optionally_covered
48
- @optionally_covered ||= instance_methods(false).map do |method|
49
- method =~ /^is_(.*)\?$/
50
- Regexp.last_match(1)
51
- end.compact.map(&:to_sym).freeze
52
- end
53
-
54
30
  protected
55
31
 
56
32
  def convert(node, **)
@@ -60,7 +36,9 @@ module DeepCover
60
36
  private
61
37
 
62
38
  def should_be_ignored?(node)
63
- @allow_filters.any? { |f| f[node] } || is_ignored?(node.parent)
39
+ @nocov_ranges.include?(node) ||
40
+ @allow_filters.any? { |f| node.public_send(f) } ||
41
+ is_ignored?(node.parent)
64
42
  end
65
43
 
66
44
  def is_ignored?(node)
@@ -11,25 +11,36 @@ module DeepCover
11
11
  end
12
12
 
13
13
  def run!
14
- detect
15
- load
14
+ @coverage = load_coverage
16
15
  after_tests { save }
16
+ self
17
17
  end
18
18
 
19
- private
20
-
21
- def detect
22
- Coverage.saved? @covered_path
19
+ def report!(**options)
20
+ after_tests { puts report(**options) }
21
+ self
23
22
  end
24
23
 
25
- def load
26
- @coverage = Coverage.load(@covered_path, with_trackers: false)
24
+ private
25
+
26
+ def load_coverage
27
+ @not_saved = DeepCover.respond_to?(:running?) && DeepCover.running?
28
+ if @not_saved
29
+ DeepCover.coverage
30
+ else
31
+ Coverage.load(@covered_path, with_trackers: false)
32
+ end
27
33
  end
28
34
 
29
35
  def save
36
+ @coverage.save(@covered_path) if @not_saved
30
37
  @coverage.save_trackers(@covered_path)
31
38
  end
32
39
 
40
+ def report(**options)
41
+ @coverage.report(**options)
42
+ end
43
+
33
44
  def after_tests
34
45
  use_at_exit = true
35
46
  if defined?(Minitest)
@@ -49,9 +60,12 @@ module DeepCover
49
60
  end
50
61
 
51
62
  def self.run!(covered_path)
52
- @already_setup ||= false # Avoid ruby warning
53
- Runner.new(covered_path).run! unless @already_setup
54
- @already_setup = true
63
+ @runner ||= Runner.new(covered_path).run!
64
+ self
65
+ end
66
+
67
+ def self.and_report!(**options)
68
+ @runner.report!(**options)
55
69
  end
56
70
  end
57
71
  end
@@ -1,9 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # We use a few features newer than our target of Ruby 2.0+:
4
- class Module
5
- public :prepend # Public in Ruby 2.1+.
6
- end
3
+ # We use a few features newer than our target of Ruby 2.1+:
7
4
  require 'pathname'
8
5
  class Pathname
9
6
  def write(*args)
@@ -13,9 +10,8 @@ class Pathname
13
10
  File.binwrite(to_path, *args)
14
11
  end unless method_defined? :binwrite
15
12
  end
16
- require 'backports/2.1.0/module/include'
17
- require 'backports/2.1.0/enumerable/to_h'
18
13
  require 'backports/2.4.0/false_class/dup'
19
14
  require 'backports/2.4.0/true_class/dup'
20
15
  require 'backports/2.4.0/hash/transform_values'
21
16
  require 'backports/2.4.0/enumerable/sum'
17
+ require 'backports/2.5.0/hash/slice'
@@ -17,6 +17,7 @@ module DeepCover
17
17
  end
18
18
  require_relative 'core_ext/require_overrides'
19
19
  RequireOverride.active = true
20
+ config # actualize configuration
20
21
  @started = true
21
22
  end
22
23
 
@@ -28,7 +29,7 @@ module DeepCover
28
29
  end
29
30
 
30
31
  def line_coverage(filename)
31
- coverage.line_coverage(handle_relative_filename(filename), **config)
32
+ coverage.line_coverage(handle_relative_filename(filename), **config.to_h)
32
33
  end
33
34
 
34
35
  def covered_code(filename)
@@ -48,9 +49,13 @@ module DeepCover
48
49
  end
49
50
 
50
51
  def config_changed(what)
51
- if what == :paths
52
+ case what
53
+ when :paths
52
54
  warn "Changing DeepCover's paths after starting coverage is highly discouraged" if @started
53
55
  @custom_requirer = nil
56
+ when :tracker_global
57
+ raise NotImplementedError, "Changing DeepCover's tracker global after starting coverage is not supported" if @started
58
+ @coverage = nil
54
59
  end
55
60
  end
56
61
 
@@ -62,7 +67,7 @@ module DeepCover
62
67
  end
63
68
 
64
69
  def coverage
65
- @coverage ||= Coverage.new
70
+ @coverage ||= Coverage.new(tracker_global: config.tracker_global)
66
71
  end
67
72
 
68
73
  def custom_requirer
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Basic constants without any dependencies are here
4
+ module DeepCover
5
+ DEFAULTS = {
6
+ ignore_uncovered: [].freeze,
7
+ paths: %w[./app ./lib].freeze,
8
+ allow_partial: false,
9
+ tracker_global: '$_cov',
10
+ reporter: :html,
11
+ output: './coverage',
12
+ }.freeze
13
+
14
+ CLI_DEFAULTS = {
15
+ command: 'bundle exec rake',
16
+ bundle: true,
17
+ process: true,
18
+ open: false,
19
+ }.freeze
20
+
21
+ OPTIONALLY_COVERED = %i[case_implicit_else default_argument raise trivial_if].freeze
22
+ end
@@ -1,6 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeepCover
4
+ require 'deep_cover'
5
+ bootstrap
6
+
4
7
  module CLI
5
8
  class Debugger
6
9
  include Tools
@@ -3,14 +3,16 @@
3
3
  module DeepCover
4
4
  require 'bundler/setup'
5
5
  require 'slop'
6
- require 'deep_cover'
7
- bootstrap
8
- require_relative_dir '.'
6
+ require_relative '../basics'
9
7
 
8
+ module CLI
9
+ end
10
10
  module CLI::DeepCover
11
11
  extend self
12
12
 
13
13
  def show_version
14
+ require_relative '../version'
15
+ require 'parser'
14
16
  puts "deep-cover v#{DeepCover::VERSION}; parser v#{Parser::VERSION}"
15
17
  end
16
18
 
@@ -36,20 +38,26 @@ module DeepCover
36
38
 
37
39
  def menu
38
40
  @menu ||= parse do |o|
39
- o.banner = 'usage: deep-cover [options] [path/to/app/or/gem]'
41
+ o.banner = ['usage: deep-cover [options] exec <command ...>',
42
+ ' or deep-cover [options] [path/to/app/or/gem]',
43
+ ].join("\n")
40
44
  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
45
+ o.string '-o', '--output', 'output folder', default: DEFAULTS[:output]
46
+ o.string '--reporter', 'reporter', default: DEFAULTS[:reporter]
47
+ o.bool '--open', 'open the output coverage', default: CLI_DEFAULTS[:open]
48
+
47
49
  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
50
+ @ignore_uncovered_map = OPTIONALLY_COVERED.map do |option|
51
+ default = DEFAULTS[:ignore_uncovered].include?(option)
52
+ o.bool "--ignore-#{dasherize(option)}", '', default: default
51
53
  [:"ignore_#{option}", option]
52
54
  end.to_h
55
+
56
+ o.separator '\nWhen not using ’exec’:'
57
+ o.string '-c', '--command', 'command to run tests', default: CLI_DEFAULTS[:command]
58
+ o.bool '--bundle', 'run bundle before the tests', default: CLI_DEFAULTS[:bundle]
59
+ o.bool '--process', 'turn off to only redo the reporting', default: CLI_DEFAULTS[:process]
60
+
53
61
  o.separator "\nFor testing purposes:"
54
62
  o.bool '--profile', 'use profiler' unless RUBY_PLATFORM == 'java'
55
63
  o.string '-e', '--expression', 'test ruby expression instead of a covering a path'
@@ -69,19 +77,33 @@ module DeepCover
69
77
  @ignore_uncovered_map.each do |cli_option, option|
70
78
  iu << option if options.delete(cli_option)
71
79
  end
80
+ options[:output] = false if ['false', 'f', ''].include?(options[:output])
72
81
  options
73
82
  end
74
83
 
75
84
  def go
76
85
  options = convert_options(menu.to_h)
86
+ first, *rest = menu.arguments
77
87
  if options[:help]
78
88
  show_help
79
89
  elsif options[:expression]
90
+ require_relative 'debugger'
80
91
  CLI::Debugger.new(options[:expression], **options).show
92
+ elsif first == 'exec'
93
+ require_relative 'exec'
94
+ CLI::Exec.new(rest, **options).run
81
95
  else
82
- path = menu.arguments.first || '.'
96
+ require_relative 'instrumented_clone_reporter'
97
+ path = first || '.'
83
98
  CLI::InstrumentedCloneReporter.new(path, **options).run
84
99
  end
85
100
  end
101
+
102
+ private
103
+
104
+ # Poor man's dasherize. 'an_example' => 'an-example'
105
+ def dasherize(string)
106
+ string.to_s.tr('_', '-')
107
+ end
86
108
  end
87
109
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ module CLI
5
+ class Exec
6
+ class Option
7
+ def keep_file_descriptors?
8
+ end
9
+ end
10
+
11
+ def initialize(argv, **options)
12
+ @argv = argv
13
+ @options = options
14
+ end
15
+
16
+ def run
17
+ require 'bundler'
18
+ require 'bundler/cli'
19
+ require 'bundler/cli/exec'
20
+ require 'yaml'
21
+ require_relative '../backports'
22
+ ENV['DEEP_COVER'] = 't'
23
+ ENV['DEEP_COVER_OPTIONS'] = YAML.dump(@options.slice(*DEFAULTS.keys))
24
+ Bundler::CLI::Exec.new(Option.new, @argv).run
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,18 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'yaml'
4
3
  require 'tmpdir'
5
4
 
6
5
  module DeepCover
6
+ require 'deep_cover'
7
+ bootstrap
8
+
7
9
  module CLI
8
10
  class InstrumentedCloneReporter
9
11
  include Tools
10
12
  # matches regular files, .files, ..files, but not '.' or '..'
11
13
  GLOB_ALL_CONTENT = '{,.[^.],..?}*'
12
14
 
13
- def initialize(source_path, command: 'rake', **options)
14
- @command = command
15
- @options = options
15
+ def initialize(source_path, **options)
16
+ @options = CLI_DEFAULTS.merge(options)
16
17
  @root_path = @source_path = Pathname.new(source_path).expand_path
17
18
  unless @root_path.join('Gemfile').exist?
18
19
  # E.g. rails/activesupport
@@ -149,7 +150,7 @@ module DeepCover
149
150
 
150
151
  def process
151
152
  Bundler.with_clean_env do
152
- system("cd #{@main_path} && #{@command}")
153
+ system("cd #{@main_path} && #{@options[:command]}")
153
154
  end
154
155
  end
155
156
 
@@ -166,12 +167,12 @@ module DeepCover
166
167
  end
167
168
 
168
169
  def run
169
- if @options.fetch(:process, true)
170
+ if @options[:process]
170
171
  clear
171
172
  copy
172
173
  cover
173
174
  patch
174
- bundle if @options.fetch(:bundle, true)
175
+ bundle if @options[:bundle]
175
176
  process
176
177
  end
177
178
  report
@@ -2,14 +2,6 @@
2
2
 
3
3
  module DeepCover
4
4
  class Config
5
- DEFAULTS = {
6
- ignore_uncovered: [].freeze,
7
- paths: %w[./app ./lib].freeze,
8
- allow_partial: false,
9
- }.freeze
10
-
11
- OPTIONALLY_COVERED = %i[raise default_argument case_implicit_else trivial_if]
12
-
13
5
  def initialize(notify = nil)
14
6
  @notify = notify
15
7
  @options = DEFAULTS.dup
@@ -24,7 +16,7 @@ module DeepCover
24
16
  if keywords.empty?
25
17
  @options[:ignore_uncovered]
26
18
  else
27
- check_uncovered(keywords)
19
+ keywords = check_uncovered(keywords)
28
20
  change(:ignore_uncovered, @options[:ignore_uncovered] | keywords)
29
21
  end
30
22
  end
@@ -33,7 +25,7 @@ module DeepCover
33
25
  if keywords.empty?
34
26
  OPTIONALLY_COVERED - @options[:ignore_uncovered]
35
27
  else
36
- check_uncovered(keywords)
28
+ keywords = check_uncovered(keywords)
37
29
  change(:ignore_uncovered, @options[:ignore_uncovered] - keywords)
38
30
  end
39
31
  end
@@ -46,17 +38,53 @@ module DeepCover
46
38
  end
47
39
  end
48
40
 
41
+ def tracker_global(tracker_global = nil)
42
+ if tracker_global
43
+ change(:tracker_global, tracker_global)
44
+ else
45
+ @options[:tracker_global]
46
+ end
47
+ end
48
+
49
+ def reporter(reporter = nil)
50
+ if reporter
51
+ change(:reporter, reporter)
52
+ else
53
+ @options[:reporter]
54
+ end
55
+ end
56
+
57
+ def output(path_or_false = nil)
58
+ if path_or_false != nil
59
+ change(:output, path_or_false)
60
+ else
61
+ @options[:output]
62
+ end
63
+ end
64
+
49
65
  def reset
50
66
  DEFAULTS.each do |key, value|
51
67
  change(key, value)
52
68
  end
69
+ self
70
+ end
71
+
72
+ def set(**options)
73
+ @options[:ignore_uncovered] = [] if options.has_key?(:ignore_uncovered)
74
+ options.each do |key, value|
75
+ next if key == :allow_partial
76
+ public_send key, value
77
+ end
78
+ self
53
79
  end
54
80
 
55
81
  private
56
82
 
57
83
  def check_uncovered(keywords)
84
+ keywords = keywords.first if keywords.size == 1 && keywords.first.is_a?(Array)
58
85
  unknown = keywords - OPTIONALLY_COVERED
59
86
  raise ArgumentError, "unknown options: #{unknown.join(', ')}" unless unknown.empty?
87
+ keywords
60
88
  end
61
89
 
62
90
  def change(option, value)
@@ -66,21 +94,5 @@ module DeepCover
66
94
  end
67
95
  self
68
96
  end
69
-
70
- module Setter
71
- def config(notify = self)
72
- @config ||= Config.new(notify)
73
- end
74
-
75
- def configure(&block)
76
- raise 'Must provide a block' unless block
77
- case block.arity
78
- when 0
79
- config.instance_eval(&block)
80
- when 1
81
- block.call(config)
82
- end
83
- end
84
- end
85
97
  end
86
98
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ module ConfigSetter
5
+ def config_queue
6
+ @config_queue ||= []
7
+ end
8
+
9
+ def config(notify = self)
10
+ @config ||= Config.new(notify)
11
+ config_queue.each { |block| configure(&block) }
12
+ config_queue.clear
13
+ @config
14
+ end
15
+
16
+ def configure(&block)
17
+ raise 'Must provide a block' unless block
18
+ @config ||= nil # avoid warning
19
+ if @config == nil
20
+ config_queue << block
21
+ else
22
+ case block.arity
23
+ when 0
24
+ @config.instance_eval(&block)
25
+ when 1
26
+ block.call(@config)
27
+ end
28
+ end
29
+ self
30
+ end
31
+ end
32
+ end
@@ -34,10 +34,15 @@ module DeepCover
34
34
  end
35
35
 
36
36
  def report(**options)
37
- case (reporter = options.fetch(:reporter, :html).to_sym)
37
+ case (reporter = options.fetch(:reporter, DEFAULTS[:reporter]).to_sym)
38
38
  when :html
39
- Reporter::HTML.report(self, **options)
40
- Reporter::Text.report(self, **options) + "\n\nHTML generated: open #{options[:output]}/index.html"
39
+ msg = if (output = options.fetch(:output, DEFAULTS[:output]))
40
+ Reporter::HTML.report(self, **options)
41
+ "HTML generated: open #{output}/index.html"
42
+ else
43
+ 'No HTML generated'
44
+ end
45
+ Reporter::Text.report(self, **options) + "\n\n" + msg
41
46
  when :istanbul
42
47
  if Reporter::Istanbul.available?
43
48
  report_istanbul(**options)
@@ -70,7 +75,7 @@ module DeepCover
70
75
  end
71
76
 
72
77
  def tracker_global
73
- @options.fetch(:tracker_global, CoveredCode::DEFAULT_TRACKER_GLOBAL)
78
+ @options.fetch(:tracker_global, DEFAULTS[:tracker_global])
74
79
  end
75
80
  end
76
81
  end
@@ -19,7 +19,7 @@ module DeepCover
19
19
 
20
20
  def report_istanbul(output: nil, **options)
21
21
  dir = output_istanbul(**options).dirname
22
- unless [nil, '', 'false'].include? output
22
+ unless [nil, false, '', 'false'].include? output
23
23
  output = File.expand_path(output)
24
24
  html = "--reporter=html --report-dir='#{output}'"
25
25
  if options[:open]
@@ -57,7 +57,7 @@ module DeepCover
57
57
 
58
58
  # rubocop:disable Security/MarshalLoad
59
59
  def load_coverage
60
- Marshal.load(dir_path.join(BASENAME).binread).tap do |version: raise, coverage: raise|
60
+ Marshal.load(dir_path.join(BASENAME).binread).tap do |version:, coverage:|
61
61
  raise "dump version mismatch: #{version}, currently #{DeepCover::VERSION}" unless version == DeepCover::VERSION
62
62
  return coverage
63
63
  end
@@ -65,7 +65,7 @@ module DeepCover
65
65
 
66
66
  def load_trackers
67
67
  tracker_files.each do |full_path|
68
- Marshal.load(full_path.binread).tap do |version: raise, global: raise, trackers: raise|
68
+ Marshal.load(full_path.binread).tap do |version:, global:, trackers:|
69
69
  raise "dump version mismatch: #{version}, currently #{DeepCover::VERSION}" unless version == DeepCover::VERSION
70
70
  merge_trackers(eval("#{global} ||= {}"), trackers) # rubocop:disable Security/Eval
71
71
  end
@@ -5,13 +5,11 @@ module DeepCover
5
5
  load_parser
6
6
 
7
7
  class CoveredCode
8
- DEFAULT_TRACKER_GLOBAL = '$_cov'
9
-
10
8
  attr_accessor :covered_source, :buffer, :tracker_global, :local_var, :name
11
9
  @@counter = 0
12
10
  @@globals = Hash.new { |h, global| h[global] = eval("#{global} ||= {}") } # rubocop:disable Security/Eval
13
11
 
14
- def initialize(path: nil, source: nil, lineno: 1, tracker_global: DEFAULT_TRACKER_GLOBAL, local_var: '_temp', name: nil)
12
+ def initialize(path: nil, source: nil, lineno: 1, tracker_global: DEFAULTS[:tracker_global], local_var: '_temp', name: nil)
15
13
  raise 'Must provide either path or source' unless path || source
16
14
 
17
15
  @buffer = Parser::Source::Buffer.new(path, lineno)
@@ -89,9 +87,14 @@ module DeepCover
89
87
  root.main
90
88
  end
91
89
 
90
+ def comments
91
+ root
92
+ @comments
93
+ end
94
+
92
95
  def root
93
96
  @root ||= begin
94
- ast = parser.parse(@buffer)
97
+ ast, @comments = parser.parse_with_comments(@buffer)
95
98
  Node::Root.new(ast, self)
96
99
  end
97
100
  end
@@ -7,7 +7,7 @@ module DeepCover
7
7
  class LoadPathsSubset
8
8
  attr_reader :last_lookup_path
9
9
 
10
- def initialize(load_paths: raise, lookup_paths: raise)
10
+ def initialize(load_paths:, lookup_paths:)
11
11
  @original_load_paths = load_paths
12
12
  @cached_load_paths_subset = []
13
13
  @cached_load_paths_hash = nil
@@ -69,7 +69,12 @@ module DeepCover
69
69
  else
70
70
  (@load_paths_subset || self).load_paths.each do |load_path|
71
71
  possible_path = File.absolute_path(path, load_path)
72
- return possible_path if (@load_paths_subset || File).exist?(possible_path)
72
+
73
+ next unless (@load_paths_subset || File).exist?(possible_path)
74
+ # Ruby 2.5 changed some behaviors of require related to symlinks in $LOAD_PATH
75
+ # https://bugs.ruby-lang.org/issues/10222
76
+ return File.realpath(possible_path) if RUBY_VERSION >= '2.5'
77
+ return possible_path
73
78
  end
74
79
  nil
75
80
  end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ ##
5
+ # A processor which computes which lines to be considered flagged with the
6
+ # given lookup
7
+ #
8
+ class FlagCommentAssociator
9
+ ##
10
+ # @param [DeepCover::RootNode] ast
11
+ # @param [Array(Parser::Source::Comment)] comments
12
+ def initialize(covered_code, lookup = 'nocov')
13
+ @covered_code = covered_code
14
+ @lookup = /^#[\s#*-]*#{lookup}[\s#*-]*$/
15
+ @ranges = nil
16
+ end
17
+
18
+ def include?(range)
19
+ return false unless (exp = range.expression)
20
+ lineno = exp.line
21
+ ranges.any? { |r| r.cover? lineno }
22
+ end
23
+
24
+ def ranges
25
+ @ranges ||= compute_ranges
26
+ end
27
+
28
+ private
29
+
30
+ def compute_ranges
31
+ @ranges = []
32
+ @flag_start = nil
33
+ index_ast_lines
34
+ @covered_code.comments.each { |comment| process(comment) }
35
+ toggle_flag(@covered_code.buffer.last_line) # handle end of file in case of opened flag
36
+ @ranges
37
+ end
38
+
39
+ def process(comment)
40
+ return unless comment.text =~ @lookup
41
+ ln = comment.location.expression.line
42
+ toggle_flag(ln) unless line_has_only_comments?(ln)
43
+ toggle_flag(ln + 1)
44
+ end
45
+
46
+ def toggle_flag(lineno)
47
+ if @flag_start
48
+ @ranges << (@flag_start..(lineno - 1))
49
+ @flag_start = nil
50
+ else
51
+ @flag_start = lineno
52
+ end
53
+ end
54
+
55
+ def index_ast_lines
56
+ @starts = []
57
+ @covered_code.each_node do |node|
58
+ if (exp = node.expression)
59
+ @starts[exp.line] = true
60
+ end
61
+ end
62
+ end
63
+
64
+ def line_has_only_comments?(line)
65
+ !@starts[line]
66
+ end
67
+ end
68
+ end
@@ -2,19 +2,23 @@
2
2
 
3
3
  module DeepCover
4
4
  module Load
5
- AUTOLOAD = %i[analyser autoload_tracker coverage covered_code custom_requirer memoize
6
- module_override node problem_with_diagnostic reporter
5
+ AUTOLOAD = %i[analyser autoload_tracker config coverage covered_code custom_requirer
6
+ flag_comment_associator memoize module_override node
7
+ problem_with_diagnostic reporter
7
8
  ]
8
9
 
9
10
  def load_absolute_basics
10
11
  require_relative 'base'
11
- require_relative 'config'
12
+ require_relative 'basics'
13
+ require_relative 'config_setter'
12
14
  require_relative 'tools/camelize'
13
15
  AUTOLOAD.each do |module_name|
14
16
  DeepCover.autoload(Tools::Camelize.camelize(module_name), "#{__dir__}/#{module_name}")
15
17
  end
18
+ DeepCover.autoload :VERSION, 'deep_cover/version'
16
19
  Object.autoload :Term, 'term/ansicolor'
17
20
  Object.autoload :Terminal, 'terminal-table'
21
+ Object.autoload :YAML, 'yaml'
18
22
  require 'pry'
19
23
  end
20
24
 
@@ -42,6 +46,7 @@ module DeepCover
42
46
  AUTOLOAD.each do |module_name|
43
47
  DeepCover.const_get(Tools::Camelize.camelize(module_name))
44
48
  end
49
+ DeepCover::VERSION # rubocop:disable Lint/Void
45
50
  @all_loaded = true
46
51
  end
47
52
  end
@@ -14,10 +14,11 @@ module DeepCover
14
14
  include IsStatement
15
15
  include ExecutionLocation
16
16
  include ChildCanBeEmpty
17
+ include Filters
17
18
 
18
19
  attr_reader :index, :parent, :children, :base_node
19
20
 
20
- def initialize(base_node, parent: raise, index: 0, base_children: base_node.children)
21
+ def initialize(base_node, parent:, index: 0, base_children: base_node.children)
21
22
  @base_node = base_node
22
23
  @parent = parent
23
24
  @index = index
@@ -18,7 +18,7 @@ module DeepCover
18
18
  end
19
19
 
20
20
  class TrivialBranch < Node::EmptyBody
21
- def initialize(other_branch: raise, condition: raise, position: true)
21
+ def initialize(other_branch:, condition:, position: true)
22
22
  @condition = condition
23
23
  @other_branch = other_branch
24
24
  super(nil, parent: condition.parent, position: position)
@@ -2,7 +2,7 @@
2
2
 
3
3
  module DeepCover
4
4
  class Node::EmptyBody < Node
5
- def initialize(base_node, parent: raise, index: 0, position: ChildCanBeEmpty.last_empty_position)
5
+ def initialize(base_node, parent:, index: 0, position: ChildCanBeEmpty.last_empty_position)
6
6
  @position = position
7
7
  super(base_node, parent: parent, index: index, base_children: [])
8
8
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ module Node::Mixin
5
+ module Filters
6
+ RAISING_MESSAGES = %i[raise exit].freeze
7
+ def is_raise?
8
+ is_a?(Node::Send) && RAISING_MESSAGES.include?(message) && receiver == nil
9
+ end
10
+
11
+ def is_default_argument?
12
+ parent.is_a?(Node::Optarg)
13
+ end
14
+
15
+ def is_case_implicit_else?
16
+ is_a?(Node::EmptyBody) && parent.is_a?(Node::Case) && !parent.has_else?
17
+ end
18
+
19
+ def is_trivial_if?
20
+ # Supports only node being a branch or the fork itself
21
+ parent.is_a?(Node::If) && parent.condition.is_a?(Node::SingletonLiteral)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -4,7 +4,7 @@ module DeepCover
4
4
  module Reporter::HTML::Base
5
5
  include Tools::ContentTag
6
6
  def setup
7
- DeepCover::Config::DEFAULTS.keys.map do |setting|
7
+ DeepCover::DEFAULTS.keys.map do |setting|
8
8
  value = options[setting]
9
9
  value = value.join(', ') if value.respond_to? :join
10
10
  content_tag :span, value, class: setting
@@ -70,7 +70,7 @@ module DeepCover
70
70
  end
71
71
  end
72
72
 
73
- def self.save(covered_codes, output: raise, **options)
73
+ def self.save(covered_codes, output:, **options)
74
74
  Site.new(covered_codes, output: output, **options).save
75
75
  end
76
76
  end
@@ -11,7 +11,7 @@ module DeepCover
11
11
  coverage.save(dest_path)
12
12
  end
13
13
 
14
- def dump_covered_code(source_path, coverage: raise, dest_path: Dir.mktmpdir, root_path: source_path)
14
+ def dump_covered_code(source_path, coverage:, dest_path: Dir.mktmpdir, root_path: source_path)
15
15
  source_path = File.join(File.expand_path(source_path), '')
16
16
  dest_path = File.join(File.expand_path(dest_path), '')
17
17
  root_path = Pathname.new(root_path)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeepCover
4
- VERSION = '0.3.0'
4
+ VERSION = '0.4.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.3.0
4
+ version: 0.4.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-18 00:00:00.000000000 Z
12
+ date: 2018-02-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: parser
@@ -31,14 +31,14 @@ dependencies:
31
31
  requirements:
32
32
  - - ">="
33
33
  - !ruby/object:Gem::Version
34
- version: 3.10.1
34
+ version: 3.11.0
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - ">="
40
40
  - !ruby/object:Gem::Version
41
- version: 3.10.1
41
+ version: 3.11.0
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: binding_of_caller
44
44
  requirement: !ruby/object:Gem::Requirement
@@ -211,16 +211,16 @@ dependencies:
211
211
  name: rubocop
212
212
  requirement: !ruby/object:Gem::Requirement
213
213
  requirements:
214
- - - ">="
214
+ - - '='
215
215
  - !ruby/object:Gem::Version
216
- version: '0'
216
+ version: 0.52.1
217
217
  type: :development
218
218
  prerelease: false
219
219
  version_requirements: !ruby/object:Gem::Requirement
220
220
  requirements:
221
- - - ">="
221
+ - - '='
222
222
  - !ruby/object:Gem::Version
223
- version: '0'
223
+ version: 0.52.1
224
224
  description: expression and branch coverage for Ruby.
225
225
  email:
226
226
  - github@marc-andre.ca
@@ -269,11 +269,14 @@ files:
269
269
  - lib/deep_cover/autoload_tracker.rb
270
270
  - lib/deep_cover/backports.rb
271
271
  - lib/deep_cover/base.rb
272
+ - lib/deep_cover/basics.rb
272
273
  - lib/deep_cover/builtin_takeover.rb
273
274
  - lib/deep_cover/cli/debugger.rb
274
275
  - lib/deep_cover/cli/deep_cover.rb
276
+ - lib/deep_cover/cli/exec.rb
275
277
  - lib/deep_cover/cli/instrumented_clone_reporter.rb
276
278
  - lib/deep_cover/config.rb
279
+ - lib/deep_cover/config_setter.rb
277
280
  - lib/deep_cover/core_ext/autoload_overrides.rb
278
281
  - lib/deep_cover/core_ext/coverage_replacement.rb
279
282
  - lib/deep_cover/core_ext/load_overrides.rb
@@ -285,6 +288,7 @@ files:
285
288
  - lib/deep_cover/coverage/persistence.rb
286
289
  - lib/deep_cover/covered_code.rb
287
290
  - lib/deep_cover/custom_requirer.rb
291
+ - lib/deep_cover/flag_comment_associator.rb
288
292
  - lib/deep_cover/load.rb
289
293
  - lib/deep_cover/memoize.rb
290
294
  - lib/deep_cover/module_override.rb
@@ -310,6 +314,7 @@ files:
310
314
  - lib/deep_cover/node/mixin/child_can_be_empty.rb
311
315
  - lib/deep_cover/node/mixin/executed_after_children.rb
312
316
  - lib/deep_cover/node/mixin/execution_location.rb
317
+ - lib/deep_cover/node/mixin/filters.rb
313
318
  - lib/deep_cover/node/mixin/flow_accounting.rb
314
319
  - lib/deep_cover/node/mixin/has_child.rb
315
320
  - lib/deep_cover/node/mixin/has_child_handler.rb
@@ -350,7 +355,6 @@ files:
350
355
  - lib/deep_cover/tools/camelize.rb
351
356
  - lib/deep_cover/tools/content_tag.rb
352
357
  - lib/deep_cover/tools/covered.rb
353
- - lib/deep_cover/tools/dasherize.rb
354
358
  - lib/deep_cover/tools/dump_covered_code.rb
355
359
  - lib/deep_cover/tools/execute_sample.rb
356
360
  - lib/deep_cover/tools/format.rb
@@ -379,7 +383,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
379
383
  requirements:
380
384
  - - ">="
381
385
  - !ruby/object:Gem::Version
382
- version: 2.0.0
386
+ version: 2.1.0
383
387
  required_rubygems_version: !ruby/object:Gem::Requirement
384
388
  requirements:
385
389
  - - ">="
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeepCover
4
- module Tools::Dasherize
5
- # Poor man's dasherize. 'an_example' => 'an-example'
6
- def dasherize(string)
7
- string.to_s.tr('_', '-')
8
- end
9
- end
10
- end