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 +4 -4
- data/.travis.yml +3 -0
- data/CONTRIBUTING.md +59 -0
- data/README.md +3 -1
- data/bin/test_gems +52 -0
- data/deep_cover.gemspec +1 -0
- data/lib/deep_cover/autoload_tracker.rb +2 -2
- data/lib/deep_cover/base.rb +5 -1
- data/lib/deep_cover/core_ext/autoload_overrides.rb +22 -20
- data/lib/deep_cover/core_ext/load_overrides.rb +12 -14
- data/lib/deep_cover/core_ext/require_overrides.rb +16 -32
- data/lib/deep_cover/covered_code.rb +1 -5
- data/lib/deep_cover/custom_requirer.rb +17 -3
- data/lib/deep_cover/module_override.rb +30 -0
- data/lib/deep_cover/node/case.rb +7 -1
- data/lib/deep_cover/version.rb +1 -1
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fe152ffb51fdfa686a1448c858bc1ea0d763a7df
|
4
|
+
data.tar.gz: a0e0a3ac5ff89b20747943b8819937593def90d4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c6bfeb927e5f9e667b4623339cf01336c2071e0a38b6222176a87130f429a29e6ece18702a3516021df7ba8c51a454db179e8abea99e5d807627ded57e992183
|
7
|
+
data.tar.gz: 38744bd15d4a21d3d88c436483f7d3a4cb2e66ddac0590439c788853a6c30444483af6f2b6e247c573029e4b57ef88f13a4a29b9f6a730e4ee8629a06bd9eb2f
|
data/.travis.yml
CHANGED
data/CONTRIBUTING.md
ADDED
@@ -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 ;-)
|
data/bin/test_gems
ADDED
@@ -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
|
data/deep_cover.gemspec
CHANGED
@@ -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.
|
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.
|
43
|
+
const.autoload_without_deep_cover(name, absolute_path)
|
44
44
|
end
|
45
45
|
|
46
46
|
raise
|
data/lib/deep_cover/base.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
25
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
21
|
-
|
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
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
86
|
-
|
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
|
-
|
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
|
data/lib/deep_cover/node/case.rb
CHANGED
@@ -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: -> {
|
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
|
data/lib/deep_cover/version.rb
CHANGED
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.
|
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-
|
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
|