deep-cover 0.1.9 → 0.1.10

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: 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