deep-cover 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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