deep-cover 0.3.0 → 0.4.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: 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