deep-cover 0.1.9 → 0.1.10

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: 66af2fb754f224d5044bc6455e2b0995c6028e3a
4
- data.tar.gz: eb4865a25287780cbb75730bc093f76cae690a74
3
+ metadata.gz: fe152ffb51fdfa686a1448c858bc1ea0d763a7df
4
+ data.tar.gz: a0e0a3ac5ff89b20747943b8819937593def90d4
5
5
  SHA512:
6
- metadata.gz: 6067f222058c3606c912d24b7eb991c29460a70fe29a6e1dba82277d54cbefb95ef46996074195732aa69e9ae7d1e7a49ebffe22e7038c68882559b34a257103
7
- data.tar.gz: 1de08e4f2dd112e79b6b60196d398b5b79e057b3a54b0c8baca9123bdc50307df65a49eee97235230c96152e78be1dec128c4890b7e4c3444427dc0c7ea480b7
6
+ metadata.gz: c6bfeb927e5f9e667b4623339cf01336c2071e0a38b6222176a87130f429a29e6ece18702a3516021df7ba8c51a454db179e8abea99e5d807627ded57e992183
7
+ data.tar.gz: 38744bd15d4a21d3d88c436483f7d3a4cb2e66ddac0590439c788853a6c30444483af6f2b6e247c573029e4b57ef88f13a4a29b9f6a730e4ee8629a06bd9eb2f
@@ -10,3 +10,6 @@ rvm:
10
10
  before_install:
11
11
  - gem install bundler -v 1.15.4
12
12
  - npm install -g nyc
13
+ matrix:
14
+ allow_failures:
15
+ - rvm: jruby-9.1.9.0
@@ -0,0 +1,59 @@
1
+ # Running the tests
2
+
3
+ ## Dev installation
4
+
5
+ For the moment, you need `nyc`, so you must have `node` (and ideally `yarn`) installed.
6
+
7
+ ```
8
+ $ git clone https://github.com/deep-cover/deep-cover.git
9
+ $ cd deep-cover
10
+ $ bundle
11
+ $ yarn install -g nyc # or use npm
12
+ ```
13
+
14
+ ## rspec
15
+
16
+ To run the full test suite:
17
+ ```
18
+ $ rspec
19
+ ```
20
+
21
+ There's currently a bunch of output we'd like to get rid of, sorry.
22
+
23
+ You should get no failures.
24
+
25
+ ## Char coverage specs
26
+
27
+ Much of the specs is in `spec/char_cover/*.rb`. Each file is a collection of tests, each separated with comments starting with `###` and `####`. Ruby code should be indented with 4 spaces. Each line can be followed by a spec comment, stating for each character if it is executed (space), not executed (x) or not executable (-). Lines that have any non-executed parts must have a spec comment otherwise they won't pass.
28
+
29
+ To run a particular file, say `spec/char_cover/literals.rb`, use `rspec -e literals`.
30
+
31
+ Note that `RuntimeError`s without any message (`raise`) are silently rescued for each test case. Other exceptions must be rescued in the spec.
32
+
33
+ To debug a particular case: `bin/cov literals` (will ask which sub spec to run), or `bin/cov literals 3` (to run the 3rd sub spec).
34
+
35
+ # Implementation notes
36
+
37
+ ## Top level strategy
38
+
39
+ DeepCover is based on the awesome `parser` gem.
40
+
41
+ We use the parsed code to rewrite it such that we can deduce what has been executed or not. We insert trackers looking like `$_some_global[42][555] += 1` where 42 is a number unique to the file and 555 is the id of the tracker.
42
+
43
+ Our rewriting rules:
44
+ * only insert code, never change existing code
45
+ * keep the existing code on their original line
46
+ * are minimal. They don't call any method, rescue exceptions, etc.
47
+ * use the mimimal number of trackers (except for multiple assignments, we use one extra)
48
+
49
+ ### Flow accounting
50
+
51
+ The goal is to instrument any Ruby code such that we can know, for each node, how many time it’s been executed (`Node#execution_count`). More precisely, for any node, we need to know how many times control flow has "entered" the node (`Node#flow_entry_count`), how many times it has exited the node normally (`Node#flow_completion_count`), and by deduction how many times control flow has been interrupted (say by a raise, throw, return, next, etc. `Node#flow_interrupt_count`).
52
+
53
+ We always deduce their execution from normal control flow. E.g. in `var = [1, 2, 3]`, to know if `3` was executed, we check if it's previous sibbling (`2`) was executed. To know that, we check if `1` was executed. `1` has no previous sibbling, so we check the parent `[]`. It itself checks the parent `var =`, which finally asks bring us to our parent (`Node::Root`). Only this parent introduces a small performance hit with a tracker ($_some_global[][] += 1`). So we get the execution count of the literals, the array creation and the variable assignment for free.
54
+
55
+ To summarize our strategy: a Node is not responsible to know how many times it was entered by control flow, that is the responsibility of the parent node. A Node's responsibility is to know how many times control flow exited it normally. For many nodes like literals, `flow_completion_count == flow_entry_count` so there's nothing special to be done. Others must do some accounting, in particular `Node::Send` must add a `$tracker[][]+=1` after the call to know if flow has been interrupted or not. Credit to @MaxLap for pointing us in that direction early on.
56
+
57
+ Default code for nodes in [flow_accounting.rb](https://github.com/deep-cover/deep-cover/blob/master/lib/deep_cover/node/mixin/flow_accounting.rb).
58
+
59
+ *To be continued...*
data/README.md CHANGED
@@ -102,12 +102,14 @@ After checking out the repo, run `bundle` to install dependencies. Then, run `ra
102
102
 
103
103
  For detailed analysis:
104
104
 
105
- `deep-cover -e "if(:try_me); puts 'cool'; end"`
105
+ `deep-cover -d -e "if(:try_me); puts 'cool'; end"`
106
106
 
107
107
  To run one of the specs in `spec`:
108
108
 
109
109
  `bin/cov boolean`
110
110
 
111
+ More details in the [contributing guide](https://github.com/deep-cover/deep-cover/blob/master/CONTRIBUTING.md).
112
+
111
113
  ### Status
112
114
 
113
115
  Currently in heavy development. *Alpha stage, API subject to change every day*. Best time to get involved though ;-)
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
3
+ require "deep_cover"
4
+
5
+ def gem_list
6
+ return %w[erubis thor i18n builder tzinfo diff-lcs rack-test tilt multipart-post diff-lcs
7
+ rubygems-bundler method_source daemons hike multi_json rails-deprecated_sanitizer slop
8
+ rspec-support thread_safe rspec-core rspec-expectations rspec-mocks rspec mini_portile
9
+ multi_json rspec-support rspec-expectations rspec-mocks mail nokogiri rack rspec unf
10
+ json thread_safe rspec-core json rack-protection sdoc docile faraday multi_xml coderay
11
+ mime-types net-scp multi_json turbolinks formatador coffee-script rack-test]
12
+ # How I got those:
13
+ require 'gems'
14
+ Gems.most_downloaded
15
+ .map(&:first) # discard nb downloads
16
+ .map{|h| h['full_name']} # get name-0.1.2
17
+ .map{|n| n.rpartition('-').first } # => name
18
+ end
19
+
20
+ def install_gems
21
+ gem_list.each do |n|
22
+ system "gem install #{n}"
23
+ end
24
+ end
25
+
26
+ # install_gems
27
+
28
+ def test_gems
29
+ gem_list.each do |n|
30
+ puts "Require #{n}"
31
+ begin
32
+ DeepCover.cover { require n }
33
+ rescue LoadError
34
+ puts "Not sure how to load '#{n}'. Skipping"
35
+ rescue Exception => ours
36
+ begin
37
+ require n
38
+ rescue Exception => normal
39
+ puts "Gem #{n} doesn't require normally. Skipping"
40
+ else
41
+ puts "DeepCover creates issue with Gem #{n}:\n#{ours}"
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ test_gems
48
+ # module DeepCover
49
+
50
+ # # puts
51
+ # names = Gems.most_downloaded
52
+ # end
@@ -42,4 +42,5 @@ Gem::Specification.new do |spec|
42
42
  spec.add_development_dependency "rake", "~> 10.0"
43
43
  spec.add_development_dependency "rspec", "~> 3.0"
44
44
  spec.add_development_dependency 'activesupport', "~> 4.0"
45
+ spec.add_development_dependency "psych", ">= 2.0"
45
46
  end
@@ -33,14 +33,14 @@ module DeepCover
33
33
  begin
34
34
  pairs.each do |const, name|
35
35
  # Changing the autoload to an already loaded file (this one)
36
- const.autoload_without_coverage(name, __FILE__)
36
+ const.autoload_without_deep_cover(name, __FILE__)
37
37
  end
38
38
 
39
39
  yield
40
40
  rescue Exception
41
41
  pairs.each do |const, name|
42
42
  # Changing the autoload to an already loaded file (this one)
43
- const.autoload_without_coverage(name, absolute_path)
43
+ const.autoload_without_deep_cover(name, absolute_path)
44
44
  end
45
45
 
46
46
  raise
@@ -6,14 +6,18 @@ module DeepCover
6
6
  # No issues with autoload in jruby, so no need to override it!
7
7
  else
8
8
  require_relative 'core_ext/autoload_overrides'
9
+ AutoloadOverride.active = true
9
10
  autoload_tracker.initialize_autoloaded_paths
10
11
  end
11
12
  require_relative 'core_ext/require_overrides'
13
+ RequireOverride.active = true
12
14
  @started = true
13
15
  end
14
16
 
15
17
  def stop
16
- # TODO
18
+ AutoloadOverride.active = false if defined? AutoloadOverride
19
+ RequireOverride.active = false
20
+ @started = false
17
21
  end
18
22
 
19
23
  def line_coverage(filename)
@@ -12,29 +12,31 @@
12
12
 
13
13
  require 'binding_of_caller'
14
14
 
15
- class << Kernel
16
- alias_method :autoload_without_coverage, :autoload
17
- def autoload(name, path)
18
- mod = binding.of_caller(1).eval('Module.nesting').first || Object
19
- DeepCover.autoload_tracker.add(mod, name, path)
20
- mod.autoload_without_coverage(name, path)
21
- end
22
- end
15
+ module DeepCover
16
+ module KernelAutoloadOverride
17
+ def autoload(name, path)
18
+ mod = binding.of_caller(1).eval('Module.nesting').first || Object
19
+ DeepCover.autoload_tracker.add(mod, name, path)
20
+ mod.autoload_without_deep_cover(name, path)
21
+ end
23
22
 
24
- module Kernel
25
- alias_method :autoload_without_coverage, :autoload
26
- def autoload(name, path)
27
- mod = binding.of_caller(1).eval('Module.nesting').first || Object
28
- DeepCover.autoload_tracker.add(mod, name, path)
29
- mod.autoload_without_coverage(name, path)
23
+ extend ModuleOverride
24
+ override ::Kernel, ::Kernel.singleton_class
30
25
  end
31
- end
32
26
 
27
+ module ModuleAutoloadOverride
28
+ def autoload(name, path)
29
+ DeepCover.autoload_tracker.add(self, name, path)
30
+ autoload_without_deep_cover(name, path)
31
+ end
32
+
33
+ extend ModuleOverride
34
+ override Module
35
+ end
33
36
 
34
- class Module
35
- alias_method :autoload_without_coverage, :autoload
36
- def autoload(name, path)
37
- DeepCover.autoload_tracker.add(self, name, path)
38
- autoload_without_coverage(name, path)
37
+ module AutoloadOverride
38
+ def self.active=(flag)
39
+ KernelAutoloadOverride.active = ModuleAutoloadOverride.active = flag
40
+ end
39
41
  end
40
42
  end
@@ -3,22 +3,20 @@
3
3
  # For now, this is not used, and may never be. The tracking and reporting for things can might be
4
4
  # loaded multiple times can be complex and is beyond the current scope of the project.
5
5
 
6
- class << Kernel
7
- alias_method :load_without_coverage, :load
8
- def load(path, wrap = false)
9
- return load_without_coverage(path, wrap) if wrap
6
+ module DeepCover
7
+ module LoadOverride
8
+ def load(path, wrap = false)
9
+ return load_without_deep_cover(path, wrap) if wrap
10
10
 
11
- result = DeepCover.custom_requirer.load(path)
12
- if [:not_found, :cover_failed, :not_supported].include?(result)
13
- load_without_coverage(path)
14
- else
15
- result
11
+ result = DeepCover.custom_requirer.load(path)
12
+ if [:not_found, :cover_failed, :not_supported].include?(result)
13
+ load_without_deep_cover(path)
14
+ else
15
+ result
16
+ end
16
17
  end
17
18
  end
18
- end
19
19
 
20
- module Kernel
21
- def load(path, wrap = false)
22
- Kernel.require(path, wrap)
23
- end
20
+ extend ModuleOverride
21
+ override ::Kernel, ::Kernel.singleton_class
24
22
  end
@@ -4,42 +4,26 @@
4
4
  # each can have been already overwritten individually. (Rubygems only
5
5
  # overrides Kernel#require)
6
6
 
7
- class << Kernel
8
- alias_method :require_without_coverage, :require
9
- def require(path)
10
- result = DeepCover.custom_requirer.require(path)
11
- if [:not_found, :cover_failed, :not_supported].include?(result)
12
- require_without_coverage(path)
13
- else
14
- result
7
+ module DeepCover
8
+ module RequireOverride
9
+ def require(path)
10
+ result = DeepCover.custom_requirer.require(path)
11
+ if [:not_found, :cover_failed, :not_supported].include?(result)
12
+ require_without_deep_cover(path)
13
+ else
14
+ result
15
+ end
15
16
  end
16
- end
17
-
18
- def require_relative(path)
19
- base = caller(1..1).first[/[^:]+/]
20
- raise LoadError, "cannot infer basepath" unless base
21
- base = File.dirname(base)
22
17
 
23
- require(File.absolute_path(path, base))
24
- end
25
- end
18
+ def require_relative(path)
19
+ base = caller(1..1).first[/[^:]+/]
20
+ raise LoadError, "cannot infer basepath" unless base
21
+ base = File.dirname(base)
26
22
 
27
- module Kernel
28
- alias_method :require_without_coverage, :require
29
- def require(path)
30
- result = DeepCover.custom_requirer.require(path)
31
- if [:not_found, :cover_failed, :not_supported].include?(result)
32
- require_without_coverage(path)
33
- else
34
- result
23
+ require(File.absolute_path(path, base))
35
24
  end
36
- end
37
-
38
- def require_relative(path)
39
- base = caller(1..1).first[/[^:]+/]
40
- raise LoadError, "cannot infer basepath" unless base
41
- base = File.dirname(base)
42
25
 
43
- require(File.absolute_path(path, base))
26
+ extend ModuleOverride
27
+ override ::Kernel, ::Kernel.singleton_class
44
28
  end
45
29
  end
@@ -10,11 +10,7 @@ module DeepCover
10
10
  raise "Must provide either path or source" unless path || source
11
11
 
12
12
  @buffer = ::Parser::Source::Buffer.new(path)
13
- if source
14
- @buffer.source = source
15
- else
16
- @buffer.read
17
- end
13
+ @buffer.source = source ||= File.read(path)
18
14
  @lineno = lineno
19
15
  @tracker_count = 0
20
16
  @tracker_global = tracker_global
@@ -82,10 +82,24 @@ module DeepCover
82
82
 
83
83
  protected
84
84
  def cover_and_execute(path)
85
- covered_code = DeepCover.coverage.covered_code(path)
86
- return :cover_failed unless covered_code
85
+ begin
86
+ covered_code = DeepCover.coverage.covered_code(path)
87
+ rescue Parser::SyntaxError => e
88
+ if e.message =~ /contains escape sequences incompatible with UTF-8/
89
+ warn "Can't cover #{path} because of incompatible encoding (see issue #9)"
90
+ else
91
+ warn "The file #{path} can't be instrumented"
92
+ end
93
+ return :cover_failed
94
+ end
87
95
  DeepCover.autoload_tracker.wrap_require(path) do
88
- covered_code.execute_code
96
+ begin
97
+ covered_code.execute_code
98
+ rescue ::SyntaxError => e
99
+ warn "DeepCover is getting confused with the file #{path} and it won't be instrumented.\n" +
100
+ "Please report this error and provide the source code around the following:\n#{e}"
101
+ return :cover_failed
102
+ end
89
103
  end
90
104
  covered_code
91
105
  end
@@ -0,0 +1,30 @@
1
+ module DeepCover
2
+ # Helps redefine methods in overriden_modules.
3
+ # For each methods in Mod, this defines `<method>_with{out}_deep_cover`.
4
+ # Set `active` to true or false to alias <method> to one or the other.
5
+ module ModuleOverride
6
+ attr_reader :overriden_modules
7
+
8
+ def active=(active)
9
+ each do |mod, method_name|
10
+ mod.send :alias_method, method_name, :"#{method_name}_#{active ? 'with' : 'without'}_deep_cover"
11
+ end
12
+ end
13
+
14
+ def override(*modules)
15
+ @overriden_modules = modules
16
+ each do |mod, method_name|
17
+ mod.send :alias_method, :"#{method_name}_without_deep_cover", method_name
18
+ mod.send :define_method, :"#{method_name}_with_deep_cover", instance_method(method_name)
19
+ end
20
+ end
21
+
22
+ def each(&block)
23
+ overriden_modules.each do |mod|
24
+ instance_methods(false).each do |method_name|
25
+ yield mod, method_name
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -45,7 +45,13 @@ module DeepCover
45
45
  has_tracker :body_entry
46
46
  has_extra_children matches: { splat: WhenSplatCondition, Parser::AST::Node => WhenCondition }
47
47
  has_child body: Node,
48
- can_be_empty: -> { base_node.loc.expression.succ },
48
+ can_be_empty: -> {
49
+ if (after_then = base_node.loc.begin)
50
+ after_then.end
51
+ else
52
+ base_node.loc.expression.succ
53
+ end
54
+ },
49
55
  rewrite: ";%{body_entry_tracker};%{local}=nil;%{node}",
50
56
  is_statement: true,
51
57
  flow_entry_count: :body_entry_tracker_hits
@@ -1,3 +1,3 @@
1
1
  module DeepCover
2
- VERSION = "0.1.9"
2
+ VERSION = "0.1.10"
3
3
  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.1.9
4
+ version: 0.1.10
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-11-03 00:00:00.000000000 Z
12
+ date: 2017-11-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: parser
@@ -179,6 +179,20 @@ dependencies:
179
179
  - - "~>"
180
180
  - !ruby/object:Gem::Version
181
181
  version: '4.0'
182
+ - !ruby/object:Gem::Dependency
183
+ name: psych
184
+ requirement: !ruby/object:Gem::Requirement
185
+ requirements:
186
+ - - ">="
187
+ - !ruby/object:Gem::Version
188
+ version: '2.0'
189
+ type: :development
190
+ prerelease: false
191
+ version_requirements: !ruby/object:Gem::Requirement
192
+ requirements:
193
+ - - ">="
194
+ - !ruby/object:Gem::Version
195
+ version: '2.0'
182
196
  description: Write a longer description or delete this line.
183
197
  email:
184
198
  - github@marc-andre.ca
@@ -192,6 +206,7 @@ files:
192
206
  - ".rspec"
193
207
  - ".travis.yml"
194
208
  - CODE_OF_CONDUCT.md
209
+ - CONTRIBUTING.md
195
210
  - Gemfile
196
211
  - LICENSE.txt
197
212
  - README.md
@@ -201,6 +216,7 @@ files:
201
216
  - bin/gemcov
202
217
  - bin/selfcov
203
218
  - bin/setup
219
+ - bin/test_gems
204
220
  - bin/testall
205
221
  - deep_cover.gemspec
206
222
  - exe/deep-cover
@@ -235,6 +251,7 @@ files:
235
251
  - lib/deep_cover/coverage.rb
236
252
  - lib/deep_cover/covered_code.rb
237
253
  - lib/deep_cover/custom_requirer.rb
254
+ - lib/deep_cover/module_override.rb
238
255
  - lib/deep_cover/node.rb
239
256
  - lib/deep_cover/node/arguments.rb
240
257
  - lib/deep_cover/node/assignments.rb