deep-cover 0.1.1

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.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +10 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +8 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +127 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +14 -0
  11. data/bin/cov +43 -0
  12. data/bin/gemcov +8 -0
  13. data/bin/selfcov +21 -0
  14. data/bin/setup +8 -0
  15. data/bin/testall +88 -0
  16. data/deep_cover.gemspec +44 -0
  17. data/exe/deep-cover +6 -0
  18. data/future_read_me.md +108 -0
  19. data/lib/deep-cover.rb +1 -0
  20. data/lib/deep_cover.rb +11 -0
  21. data/lib/deep_cover/analyser.rb +24 -0
  22. data/lib/deep_cover/analyser/base.rb +51 -0
  23. data/lib/deep_cover/analyser/branch.rb +20 -0
  24. data/lib/deep_cover/analyser/covered_code_source.rb +31 -0
  25. data/lib/deep_cover/analyser/function.rb +12 -0
  26. data/lib/deep_cover/analyser/ignore_uncovered.rb +19 -0
  27. data/lib/deep_cover/analyser/node.rb +11 -0
  28. data/lib/deep_cover/analyser/per_char.rb +20 -0
  29. data/lib/deep_cover/analyser/per_line.rb +23 -0
  30. data/lib/deep_cover/analyser/statement.rb +31 -0
  31. data/lib/deep_cover/analyser/subset.rb +24 -0
  32. data/lib/deep_cover/auto_run.rb +49 -0
  33. data/lib/deep_cover/autoload_tracker.rb +75 -0
  34. data/lib/deep_cover/backports.rb +9 -0
  35. data/lib/deep_cover/base.rb +55 -0
  36. data/lib/deep_cover/builtin_takeover.rb +2 -0
  37. data/lib/deep_cover/cli/debugger.rb +93 -0
  38. data/lib/deep_cover/cli/deep_cover.rb +49 -0
  39. data/lib/deep_cover/cli/instrumented_clone_reporter.rb +105 -0
  40. data/lib/deep_cover/config.rb +52 -0
  41. data/lib/deep_cover/core_ext/autoload_overrides.rb +40 -0
  42. data/lib/deep_cover/core_ext/coverage_replacement.rb +26 -0
  43. data/lib/deep_cover/core_ext/load_overrides.rb +24 -0
  44. data/lib/deep_cover/core_ext/require_overrides.rb +36 -0
  45. data/lib/deep_cover/coverage.rb +198 -0
  46. data/lib/deep_cover/covered_code.rb +138 -0
  47. data/lib/deep_cover/custom_requirer.rb +93 -0
  48. data/lib/deep_cover/node.rb +8 -0
  49. data/lib/deep_cover/node/arguments.rb +50 -0
  50. data/lib/deep_cover/node/assignments.rb +250 -0
  51. data/lib/deep_cover/node/base.rb +99 -0
  52. data/lib/deep_cover/node/begin.rb +25 -0
  53. data/lib/deep_cover/node/block.rb +53 -0
  54. data/lib/deep_cover/node/boolean.rb +22 -0
  55. data/lib/deep_cover/node/branch.rb +28 -0
  56. data/lib/deep_cover/node/case.rb +94 -0
  57. data/lib/deep_cover/node/collections.rb +21 -0
  58. data/lib/deep_cover/node/const.rb +10 -0
  59. data/lib/deep_cover/node/def.rb +38 -0
  60. data/lib/deep_cover/node/empty_body.rb +21 -0
  61. data/lib/deep_cover/node/exceptions.rb +74 -0
  62. data/lib/deep_cover/node/if.rb +36 -0
  63. data/lib/deep_cover/node/keywords.rb +84 -0
  64. data/lib/deep_cover/node/literals.rb +77 -0
  65. data/lib/deep_cover/node/loops.rb +72 -0
  66. data/lib/deep_cover/node/mixin/can_augment_children.rb +65 -0
  67. data/lib/deep_cover/node/mixin/check_completion.rb +16 -0
  68. data/lib/deep_cover/node/mixin/child_can_be_empty.rb +25 -0
  69. data/lib/deep_cover/node/mixin/executed_after_children.rb +13 -0
  70. data/lib/deep_cover/node/mixin/execution_location.rb +56 -0
  71. data/lib/deep_cover/node/mixin/flow_accounting.rb +63 -0
  72. data/lib/deep_cover/node/mixin/has_child.rb +138 -0
  73. data/lib/deep_cover/node/mixin/has_child_handler.rb +73 -0
  74. data/lib/deep_cover/node/mixin/has_tracker.rb +44 -0
  75. data/lib/deep_cover/node/mixin/is_statement.rb +18 -0
  76. data/lib/deep_cover/node/mixin/rewriting.rb +32 -0
  77. data/lib/deep_cover/node/mixin/wrapper.rb +13 -0
  78. data/lib/deep_cover/node/module.rb +64 -0
  79. data/lib/deep_cover/node/root.rb +18 -0
  80. data/lib/deep_cover/node/send.rb +83 -0
  81. data/lib/deep_cover/node/splat.rb +13 -0
  82. data/lib/deep_cover/node/variables.rb +14 -0
  83. data/lib/deep_cover/parser_ext/range.rb +40 -0
  84. data/lib/deep_cover/reporter.rb +6 -0
  85. data/lib/deep_cover/reporter/istanbul.rb +151 -0
  86. data/lib/deep_cover/tools.rb +18 -0
  87. data/lib/deep_cover/tools/builtin_coverage.rb +50 -0
  88. data/lib/deep_cover/tools/camelize.rb +8 -0
  89. data/lib/deep_cover/tools/dump_covered_code.rb +32 -0
  90. data/lib/deep_cover/tools/execute_sample.rb +23 -0
  91. data/lib/deep_cover/tools/format.rb +16 -0
  92. data/lib/deep_cover/tools/format_char_cover.rb +18 -0
  93. data/lib/deep_cover/tools/format_generated_code.rb +25 -0
  94. data/lib/deep_cover/tools/number_lines.rb +18 -0
  95. data/lib/deep_cover/tools/our_coverage.rb +9 -0
  96. data/lib/deep_cover/tools/require_relative_dir.rb +10 -0
  97. data/lib/deep_cover/tools/silence_warnings.rb +15 -0
  98. data/lib/deep_cover/version.rb +3 -0
  99. metadata +326 -0
@@ -0,0 +1,44 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "deep_cover/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "deep-cover"
8
+ spec.version = DeepCover::VERSION
9
+ spec.authors = ["Marc-André Lafortune", "Maxime Lapointe"]
10
+ spec.email = ["github@marc-andre.ca", "hunter_spawn@hotmail.com"]
11
+
12
+ spec.summary = %q{Write a short summary, because Rubygems requires one.}
13
+ spec.description = %q{Write a longer description or delete this line.}
14
+ spec.homepage = "http://github.com"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ ### Runtime dependencies
25
+ spec.add_runtime_dependency 'parser'
26
+ spec.add_runtime_dependency 'backports', '>= 3.10.1'
27
+ spec.add_runtime_dependency 'binding_of_caller'
28
+
29
+ # CLI
30
+ spec.add_runtime_dependency "term-ansicolor"
31
+ spec.add_runtime_dependency "highline"
32
+ spec.add_runtime_dependency "ruby-progressbar", '<1.9.0'
33
+ spec.add_runtime_dependency 'with_progress'
34
+ spec.add_runtime_dependency 'slop', '~> 4.0'
35
+
36
+ # While in 0.x
37
+ spec.add_runtime_dependency 'pry'
38
+
39
+ ### Dev dependencies
40
+ spec.add_development_dependency "bundler", "~> 1.15"
41
+ spec.add_development_dependency "rake", "~> 10.0"
42
+ spec.add_development_dependency "rspec", "~> 3.0"
43
+ spec.add_development_dependency 'activesupport', "~> 4.0"
44
+ end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
4
+ require 'deep_cover/cli/deep_cover'
5
+
6
+ DeepCover::CLI::DeepCover.go
@@ -0,0 +1,108 @@
1
+ [![Build Status](https://travis-ci.org/deep-cover/deep-cover.svg?branch=master)](https://travis-ci.org/deep-cover/deep-cover)
2
+
3
+ # DeepCover
4
+
5
+ Deep Cover aims to be an improved replacement for the built-in Coverage library.
6
+
7
+ It will report a more accurate picture of your code usage. In particular a line is considered covered if and only if it is entirely executed:
8
+
9
+ foo if false # => This is considered covered by builtin coverage, even though `foo` might not even exist
10
+
11
+ Optionally, branch coverage will detect if some branches are never taken. In the following example, `test_foo` only provides values for `x` that respond to `:to_s`, thus the implicit `else` is never tested (i.e. a value of `x` that does not respond to `:to_s`)
12
+
13
+ def foo(x)
14
+ x = x.to_s if x.respond_to? :to_s
15
+ # ...
16
+ end
17
+
18
+ def test_foo
19
+ assert_equal something, foo(42)
20
+ assert_equal something_else, foo(:hello)
21
+ end
22
+
23
+ ## Installation
24
+
25
+ Add to your Gemfile:
26
+
27
+ gem 'deep-cover'
28
+
29
+ Then run:
30
+
31
+ bundle
32
+
33
+ ### Builtin Coverage (including SimpleCov) users
34
+
35
+ Before you require `coverage` or `simplecov`, do a `require 'deep-cover/takeover'`.
36
+
37
+ For example, the `test/test_helper.rb` file for `simplecov` users will look like
38
+
39
+ ```
40
+ require 'deep-cover/takeover'
41
+ require 'simplecov'
42
+ SimpleCov.start
43
+ # rest of `test_helper.rb`
44
+ ```
45
+
46
+ ### Quick and dirty for Rails app or Gem:
47
+
48
+ Assuming your default `rake` task is set to execute all tests, you can try:
49
+
50
+ `deep-cover /path/to/rails/app/or/gem`
51
+
52
+ ## Usage
53
+
54
+ ### Configuration
55
+
56
+ `configure` is used to specify how specific `DeepCover` should be and which files it should analyse. The following code reflects the default settings:
57
+
58
+ ```
59
+ DeepCover.configure do
60
+ cover_paths %w[app lib]
61
+ require_all_expression_in_line
62
+ require_all_branches(false)
63
+ accept_uncovered_raise(false)
64
+ accept_uncovered_default_arguments(false)
65
+ end
66
+ ```
67
+
68
+ ### Low level usage
69
+
70
+ ```
71
+ # Setup
72
+ require 'deep-cover'
73
+ DeepCover.configure { require_all_branches }
74
+ # Cover
75
+ DeepCover.cover do
76
+ require 'my_file_to_cover'
77
+ require 'my_other_file_to_cover'
78
+ end
79
+ require 'this_file_wont_be_covered'
80
+ tests.run()
81
+ DeepCover.report
82
+ ```
83
+
84
+ ## Development
85
+
86
+ After checking out the repo, run `bundle` to install dependencies. Then, run `rake` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
87
+
88
+ For detailed analysis:
89
+
90
+ `deep-cover -e "some.ruby(:code).here"`
91
+
92
+ To run one of the specs in `spec`:
93
+
94
+ `deep-cover -t boolean`
95
+
96
+ ## Contributing
97
+
98
+ Please ask questions on StackOverflow.com. Maintainers monitor the tag `deep-cover.rb`.
99
+
100
+ Bug reports and pull requests are welcome on GitHub at https://github.com/deep-cover/deep-cover. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
101
+
102
+ ## License
103
+
104
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
105
+
106
+ ## Code of Conduct
107
+
108
+ Everyone interacting in the DeepCover project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/deep-cover/deep-cover/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1 @@
1
+ require_relative 'deep_cover'
@@ -0,0 +1,11 @@
1
+ module DeepCover
2
+ require 'parser'
3
+ require_relative 'deep_cover/backports'
4
+ require_relative 'deep_cover/tools'
5
+ require_relative_dir 'deep_cover/parser_ext'
6
+ require_relative_dir 'deep_cover', except: %w[auto_run builtin_takeover]
7
+
8
+ extend Base
9
+ extend Config::Setter
10
+ end
11
+ DeepCover::GLOBAL_BINDING = binding
@@ -0,0 +1,24 @@
1
+ require_relative 'node'
2
+ require_relative 'covered_code'
3
+
4
+ module DeepCover
5
+ # An analyser works on a subset of the original Node AST.
6
+ # The Root node is always considered part of the subset.
7
+ # One can iterate this subset with `each_node`, or ask
8
+ # the analyser for information about a node's children
9
+ # (i.e. with respect to this subset), or runs for any node
10
+ # in this subset.
11
+
12
+ # An analyser can summarize information with `results`.
13
+ # While CoveredCodeSource is based on a CoveredCode, all
14
+ # other analysers are based on another source analyser.
15
+
16
+ class Analyser
17
+ end
18
+
19
+ require_relative_dir 'analyser'
20
+
21
+ Analyser.include Analyser::IgnoreUncovered, Analyser::Base
22
+ Node.include Analyser::CoveredCodeSource::NodeExtension
23
+ CoveredCode.include Analyser::CoveredCodeSource::CoveredCodeExtension
24
+ end
@@ -0,0 +1,51 @@
1
+ module DeepCover
2
+ module Analyser::Base
3
+ attr_reader :source, :options
4
+
5
+ def initialize(source, **options)
6
+ @source = source.to_analyser
7
+ @options = options
8
+ end
9
+
10
+ # Basic coercion mechanism
11
+ def to_analyser
12
+ self
13
+ end
14
+
15
+ # Looking exclusively at our subset of nodes, returns the node's direct descendants
16
+ def node_children(node)
17
+ @source.node_children(node)
18
+ end
19
+
20
+ # Returns the number of runs of the node (assumed to be in our subset)
21
+ def node_runs(node)
22
+ @source.node_runs(node)
23
+ end
24
+
25
+ def node_runs_map
26
+ each_node.map do |node, sub_statements|
27
+ [node, node_runs(node)]
28
+ end.to_h
29
+ end
30
+
31
+ # Analyser-specific output
32
+ def results
33
+ node_runs_map
34
+ end
35
+
36
+ # Iterates on nodes in the subset.
37
+ # Yields the node and it's children (within the subset)
38
+ def each_node(from = covered_code.root, &block)
39
+ return to_enum(:each_node) unless block_given?
40
+ children = node_children(from)
41
+ yield from, children unless from.is_a?(Node::Root)
42
+ children.each do |child|
43
+ each_node(child, &block)
44
+ end
45
+ end
46
+
47
+ def covered_code
48
+ @source.covered_code
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,20 @@
1
+ require_relative 'subset'
2
+
3
+ module DeepCover
4
+ class Analyser::Branch < Analyser
5
+ include Analyser::Subset
6
+ SUBSET_CLASSES = [Node::Branch]
7
+
8
+ def results
9
+ each_node.map do |node, _children|
10
+ branches_runs = node.branches.map{|b| [b, node_runs(b)]}.to_h
11
+ [node, branches_runs]
12
+ end.to_h
13
+ end
14
+
15
+ def is_trivial_if?(node)
16
+ parent = node.parent
17
+ parent.is_a?(Node::If) && parent.condition.is_a?(Node::SingletonLiteral)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,31 @@
1
+ module DeepCover
2
+ class Analyser::CoveredCodeSource < Analyser
3
+ attr_reader :covered_code
4
+
5
+ def initialize(covered_code)
6
+ @covered_code = covered_code
7
+ end
8
+
9
+ # Looking exclusively at our subset of nodes, returns the node's direct descendants
10
+ def node_children(node)
11
+ node.children_nodes
12
+ end
13
+
14
+ # Returns the number of runs of the node (assumed to be in our subset)
15
+ def node_runs(node)
16
+ node.execution_count if node.executable?
17
+ end
18
+
19
+ module NodeExtension
20
+ def to_analyser
21
+ Analyser::CoveredCodeSource.new(covered_code)
22
+ end
23
+ end
24
+
25
+ module CoveredCodeExtension
26
+ def to_analyser
27
+ Analyser::CoveredCodeSource.new(self)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,12 @@
1
+ require_relative 'subset'
2
+
3
+ module DeepCover
4
+ class Analyser::Function < Analyser
5
+ include Analyser::Subset
6
+ SUBSET_CLASSES = [Node::Block, Node::Defs, Node::Def]
7
+
8
+ def node_runs(node)
9
+ super(node.body)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,19 @@
1
+ module DeepCover
2
+ module Analyser::IgnoreUncovered
3
+ def initialize(source, ignore_uncovered: [], **options)
4
+ super
5
+ @allow_filters = Array(ignore_uncovered)
6
+ .map{|kind| :"is_#{kind}?"}
7
+ .select{|name| respond_to?(name) }
8
+ .map{|name| method(name)} # So was tempted to write `.map(&method(:method))`!
9
+ end
10
+
11
+ def node_runs(node)
12
+ runs = super
13
+ if runs == 0 && @allow_filters.any?{ |f| f[node] }
14
+ runs = nil
15
+ end
16
+ runs
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,11 @@
1
+ module DeepCover
2
+ class Analyser::Node < Analyser
3
+ def is_raise?(node)
4
+ node.is_a?(Node::Send) && (node.method_name == :raise || node.method_name == :exit)
5
+ end
6
+
7
+ def is_default_argument?(node)
8
+ node.parent.is_a?(Node::Optarg)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,20 @@
1
+ module DeepCover
2
+ class Analyser::PerChar < Analyser
3
+ # Returns an array of characters for each line of code.
4
+ # Each character is either ' ' (executed), '-' (not executable) or 'x' (not covered)
5
+ def results
6
+ buffer = covered_code.buffer
7
+ bc = buffer.source_lines.map{|line| '-' * line.size}
8
+ each_node do |node, _children|
9
+ runs = node_runs(node)
10
+ if runs != nil
11
+ node.proper_range.each do |pos|
12
+ bc[buffer.line_for_position(pos)-1][buffer.column_for_position(pos)] = runs > 0 ? ' ' : 'x'
13
+ end
14
+ end
15
+ end
16
+ bc.zip(buffer.source_lines){|cov, line| cov[line.size..-1] = ''} # remove extraneous character for end lines, in any
17
+ bc
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,23 @@
1
+ module DeepCover
2
+ class Analyser::PerLine < Analyser
3
+ # Returns an array of runs, one per line.
4
+ def results
5
+ disallow_partial = !options.fetch(:allow_partial, true)
6
+ line_hits = Array.new(covered_code.nb_lines)
7
+ each_node do |node, _children|
8
+ if (runs = node_runs(node))
9
+ node.executed_locs.each do |loc|
10
+ lineno = loc.line - 1
11
+ if disallow_partial
12
+ line_hits[lineno] = 0 if runs == 0
13
+ next if line_hits[lineno] == 0
14
+ end
15
+ line_hits[lineno] = [line_hits[lineno] || 0, runs].max
16
+ end
17
+ end
18
+ end
19
+
20
+ line_hits
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,31 @@
1
+ require_relative 'subset'
2
+
3
+ module DeepCover
4
+ class Analyser::Statement < Analyser
5
+ include Analyser::Subset
6
+ # Returns a map of Range => runs
7
+ def results
8
+ each_node.map do |node, _sub_statements|
9
+ [node.expression, node_runs(node)]
10
+ end.to_h
11
+ end
12
+
13
+ private
14
+
15
+ def in_subset?(node, parent)
16
+ is_statement = node.is_statement
17
+ if node.expression.nil?
18
+ false
19
+ elsif is_statement != :if_incompatible
20
+ is_statement
21
+ else
22
+ !compatible_runs?(node_runs(parent), node_runs(node))
23
+ end
24
+ end
25
+
26
+ def compatible_runs?(expression_runs, sub_expression_runs)
27
+ sub_expression_runs.nil? ||
28
+ (sub_expression_runs == 0) == (expression_runs == 0)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,24 @@
1
+ module DeepCover
2
+ # A module to create a subset from a criteria called `in_subset?`
3
+ # Including classes can refine it, or specify SUBSET_CLASSES
4
+ module Analyser::Subset
5
+ def node_children(node)
6
+ find_children(node)
7
+ end
8
+
9
+ private
10
+ def find_children(from, parent = from)
11
+ @source.node_children(from).flat_map do |node|
12
+ if in_subset?(node, parent)
13
+ [node]
14
+ else
15
+ find_children(node, parent)
16
+ end
17
+ end
18
+ end
19
+
20
+ def in_subset?(node, _parent)
21
+ self.class::SUBSET_CLASSES.any?{|klass| node.is_a?(klass)}
22
+ end
23
+ end
24
+ end