deep-cover 0.1.16 → 0.2.0

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +3 -8
  4. data/.travis.yml +4 -4
  5. data/CHANGELOG.md +10 -0
  6. data/Gemfile +3 -1
  7. data/README.md +9 -4
  8. data/Rakefile +6 -3
  9. data/deep_cover.gemspec +2 -2
  10. data/lib/deep_cover.rb +10 -0
  11. data/lib/deep_cover/analyser.rb +1 -2
  12. data/lib/deep_cover/analyser/base.rb +32 -0
  13. data/lib/deep_cover/analyser/branch.rb +19 -4
  14. data/lib/deep_cover/analyser/node.rb +52 -0
  15. data/lib/deep_cover/analyser/per_char.rb +18 -1
  16. data/lib/deep_cover/analyser/stats.rb +54 -0
  17. data/lib/deep_cover/backports.rb +1 -0
  18. data/lib/deep_cover/base.rb +17 -1
  19. data/lib/deep_cover/builtin_takeover.rb +5 -0
  20. data/lib/deep_cover/cli/deep_cover.rb +2 -1
  21. data/lib/deep_cover/cli/instrumented_clone_reporter.rb +12 -10
  22. data/lib/deep_cover/config.rb +22 -8
  23. data/lib/deep_cover/core_ext/coverage_replacement.rb +22 -18
  24. data/lib/deep_cover/coverage.rb +3 -203
  25. data/lib/deep_cover/coverage/analysis.rb +36 -0
  26. data/lib/deep_cover/coverage/base.rb +91 -0
  27. data/lib/deep_cover/coverage/istanbul.rb +34 -0
  28. data/lib/deep_cover/coverage/persistence.rb +93 -0
  29. data/lib/deep_cover/covered_code.rb +12 -22
  30. data/lib/deep_cover/custom_requirer.rb +6 -2
  31. data/lib/deep_cover/node/base.rb +1 -1
  32. data/lib/deep_cover/node/case.rb +13 -2
  33. data/lib/deep_cover/node/exceptions.rb +2 -2
  34. data/lib/deep_cover/node/if.rb +21 -2
  35. data/lib/deep_cover/node/mixin/flow_accounting.rb +1 -0
  36. data/lib/deep_cover/node/send.rb +9 -2
  37. data/lib/deep_cover/node/short_circuit.rb +10 -0
  38. data/lib/deep_cover/parser_ext/range.rb +4 -4
  39. data/lib/deep_cover/reporter/html.rb +15 -0
  40. data/lib/deep_cover/reporter/html/base.rb +14 -0
  41. data/lib/deep_cover/reporter/html/index.rb +78 -0
  42. data/lib/deep_cover/reporter/html/site.rb +78 -0
  43. data/lib/deep_cover/reporter/html/source.rb +136 -0
  44. data/lib/deep_cover/reporter/html/template/assets/32px.png +0 -0
  45. data/lib/deep_cover/reporter/html/template/assets/40px.png +0 -0
  46. data/lib/deep_cover/reporter/html/template/assets/deep_cover.css.sass +338 -0
  47. data/lib/deep_cover/reporter/html/template/assets/jquery-3.2.1.min.js +4 -0
  48. data/lib/deep_cover/reporter/html/template/assets/jquery-3.2.1.min.map +1 -0
  49. data/lib/deep_cover/reporter/html/template/assets/jstree.css +1108 -0
  50. data/lib/deep_cover/reporter/html/template/assets/jstree.js +8424 -0
  51. data/lib/deep_cover/reporter/html/template/assets/jstreetable.js +1069 -0
  52. data/lib/deep_cover/reporter/html/template/assets/throbber.gif +0 -0
  53. data/lib/deep_cover/reporter/html/template/index.html.erb +75 -0
  54. data/lib/deep_cover/reporter/html/template/source.html.erb +35 -0
  55. data/lib/deep_cover/reporter/html/tree.rb +55 -0
  56. data/lib/deep_cover/tools/content_tag.rb +11 -0
  57. data/lib/deep_cover/tools/covered.rb +9 -0
  58. data/lib/deep_cover/tools/merge.rb +16 -0
  59. data/lib/deep_cover/tools/render_template.rb +13 -0
  60. data/lib/deep_cover/tools/transform_keys.rb +9 -0
  61. data/lib/deep_cover/version.rb +1 -1
  62. metadata +33 -7
  63. data/lib/deep_cover/analyser/ignore_uncovered.rb +0 -21
  64. data/lib/deep_cover/analyser/optionally_covered.rb +0 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e164c802351be75ed8094fb9fe6d2a93d2928ccc
4
- data.tar.gz: 5ec49dbb3275309c8585dc2ae919c75ac36b4cfb
3
+ metadata.gz: de76a03a06ec4a16d33c5a24a9c510678f83b33a
4
+ data.tar.gz: db775778d2b7d2c65ce0c299f3ff4f3900796e02
5
5
  SHA512:
6
- metadata.gz: f38996b4c6804ea7f56e7ae31254ef741607684b48bcecb22bfc37d42a68a8ebc7f2e635e14f2d7ab77b42c6eed959f4eac6419d0e37841bd1d66fc3f9442a88
7
- data.tar.gz: 06fa0ffb32772c5c6303acee40999336920f743ee9eef3202bd66006de5e1dc46077666f41dce453e97b563ec9fa46a47e27d36595c1df5028df643a01bb5b08
6
+ metadata.gz: 246c598d3da7d0fca5d5c4e453dc134f2e36cc59e7529f4148fa159c8e31c3288316e731faec5d08be37167fdd605454f0155b12cea3fdcbd22235f3e9ad9df3
7
+ data.tar.gz: 96fea731d9cd93158718857999a9da092b9ec619126dc0814b339b8e3c460a224dca594194b35d686b8dbf5c5c4cde03de69bd17a1bcd482e92ab1851bbb4ee7
data/.gitignore CHANGED
@@ -8,6 +8,8 @@
8
8
  /spec/reports/
9
9
  /tmp/
10
10
  /.idea
11
+ /.sass-cache
11
12
 
12
13
  # rspec failure tracking
13
14
  .rspec_status
15
+ /Gemfile.local
@@ -103,14 +103,6 @@ Naming/FileName:
103
103
  Style/FrozenStringLiteralComment:
104
104
  EnforcedStyle: always
105
105
 
106
- Style/InverseMethods:
107
- InverseMethods:
108
- :present?: :blank?,
109
- :include?: :exclude?
110
- Exclude:
111
- - bin/*
112
- - gemfiles/*
113
-
114
106
  Style/NegatedIf:
115
107
  Enabled: false
116
108
 
@@ -225,3 +217,6 @@ Style/Lambda:
225
217
 
226
218
  Style/StructInheritance:
227
219
  Enabled: false
220
+
221
+ Security/YAMLLoad:
222
+ Enabled: false
@@ -11,9 +11,9 @@ before_install:
11
11
  - gem install bundler -v 1.15.4
12
12
  - npm install -g nyc
13
13
  before_script:
14
- - rake dev:install
14
+ - bundle exec rake dev:install
15
15
  script:
16
16
  - rake test:all
17
- # matrix:
18
- # allow_failures:
19
- # - rvm: jruby-9.1.9.0
17
+ matrix:
18
+ allow_failures:
19
+ - rvm: jruby-9.1.9.0
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## 0.2
4
+
5
+ * HTML reporter
6
+ * Improved analyser
7
+
8
+ ## 0.1
9
+
10
+ * Initial "proof of concept" release
data/Gemfile CHANGED
@@ -7,5 +7,7 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
7
7
  # Specify your gem's dependencies in deep_cover.gemspec
8
8
  gemspec
9
9
 
10
- gem 'pry-byebug', platforms: :mri
10
+ eval_gemfile 'Gemfile.local' if File.exist?('Gemfile.local')
11
+
12
+ gem 'parser', git: 'https://github.com/marcandre/parser.git', branch: 'tree_rewriter_release'
11
13
  gem 'ruby-prof', platforms: :mri
data/README.md CHANGED
@@ -34,13 +34,16 @@ def test_foo
34
34
  end
35
35
  ```
36
36
 
37
- ## Installation
37
+ ## Examples
38
38
 
39
- gem install deep-cover
39
+ These examples are direct outputs from our HTML reporter:
40
+
41
+ * [Rails' `activesupport`](https://deep-cover.github.io/rails-cover/activesupport/)
42
+ * [Rails' `activerecord`](https://deep-cover.github.io/rails-cover/activerecord/)
40
43
 
41
- DeepCover currently uses [`Istanbul`](https://istanbul.js.org/)'s reporter, so you'll need `node` and Istanbul's command line `nyc`:
44
+ ## Installation
42
45
 
43
- yarn global add nyc # or npm install nyc -g
46
+ gem install deep-cover
44
47
 
45
48
  ### Command line interface (for a Rails app or a Gem):
46
49
 
@@ -50,6 +53,8 @@ An easy way to check coverage, without any configuration needed:
50
53
 
51
54
  This assumes your project has a `Gemfile`, and that your default `rake` task is set to execute all tests (otherwise set the `--command` option)
52
55
 
56
+ It also uses our builtin HTML reporter. Check the produced `coverage/index.html`.
57
+
53
58
  ### Builtin Coverage (including SimpleCov) users
54
59
 
55
60
  Add to your Gemfile `gem 'deep-cover'`, then run `bundle`.
data/Rakefile CHANGED
@@ -26,12 +26,15 @@ namespace :dev do
26
26
  commands << 'bundle install --gemfile=spec/full_usage/rails51_project/Gemfile'
27
27
  end
28
28
  commands << 'bundle install --gemfile=spec/cli_fixtures/simple_rails42_app/Gemfile'
29
+ commands << 'bundle install --gemfile=spec/cli_fixtures/rails_like_gem/Gemfile'
29
30
 
30
31
  commands.each do |command|
31
32
  puts "Running: #{command}"
32
- unless system(command)
33
- puts "Failed to run `#{command}`, see above for details. When it is fixed, try running this rake task again."
34
- exit(1)
33
+ Bundler.with_clean_env do
34
+ unless system(command)
35
+ puts "Failed to run `#{command}`, see above for details. When it is fixed, try running this rake task again."
36
+ exit(1)
37
+ end
35
38
  end
36
39
  puts "Command succeeded: #{command}"
37
40
  end
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
 
13
13
  spec.summary = 'In depth coverage of your Ruby code.'
14
14
  spec.description = 'expression and branch coverage for Ruby.'
15
- spec.homepage = 'http://github.com'
15
+ spec.homepage = 'https://github.com/deep-cover/deep-cover'
16
16
  spec.license = 'MIT'
17
17
 
18
18
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
@@ -45,7 +45,7 @@ Gem::Specification.new do |spec|
45
45
  spec.add_development_dependency 'activesupport', '~> 4.0'
46
46
  spec.add_development_dependency 'bundler', '~> 1.15'
47
47
  spec.add_development_dependency 'psych', '>= 2.0'
48
- spec.add_development_dependency 'rake', '~> 10.0'
48
+ spec.add_development_dependency 'rake', '~> 12.0'
49
49
  spec.add_development_dependency 'rspec', '~> 3.0'
50
50
  spec.add_development_dependency 'rubocop'
51
51
  end
@@ -2,12 +2,22 @@
2
2
 
3
3
  # rubocop:disable Style/MixinUsage (See https://github.com/bbatsov/rubocop/issues/5055)
4
4
  module DeepCover
5
+ # External dependencies (ex parser)
5
6
  require 'parser'
6
7
  require 'term/ansicolor'
7
8
  require 'pry'
9
+
10
+ # Bootstrapping
8
11
  require_relative 'deep_cover/backports'
9
12
  require_relative 'deep_cover/tools'
13
+
14
+ # Parser
15
+ silence_warnings do
16
+ require 'parser/current'
17
+ end
10
18
  require_relative_dir 'deep_cover/parser_ext'
19
+
20
+ # Main
11
21
  require_relative_dir 'deep_cover', except: %w[auto_run builtin_takeover]
12
22
 
13
23
  extend Base
@@ -20,6 +20,5 @@ module DeepCover
20
20
 
21
21
  require_relative_dir 'analyser'
22
22
 
23
- Analyser.include Analyser::IgnoreUncovered, Analyser::Base
24
- Analyser.extend Analyser::OptionallyCovered
23
+ Analyser.include Analyser::Base
25
24
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module DeepCover
4
4
  module Analyser::Base
5
+ include Tools::Covered
6
+
5
7
  attr_reader :source, :options
6
8
 
7
9
  def initialize(source, **options)
@@ -19,6 +21,10 @@ module DeepCover
19
21
  @source.node_runs(node)
20
22
  end
21
23
 
24
+ def node_covered?(node)
25
+ covered?(node_runs(node))
26
+ end
27
+
22
28
  def node_runs_map
23
29
  each_node.map do |node|
24
30
  [node, node_runs(node)]
@@ -30,6 +36,32 @@ module DeepCover
30
36
  node_runs_map
31
37
  end
32
38
 
39
+ def node_stat_type(node)
40
+ return :not_executable unless node.executable?
41
+ case node_runs(node)
42
+ when nil
43
+ :ignored
44
+ when 0
45
+ :not_executed
46
+ else
47
+ :executed
48
+ end
49
+ end
50
+
51
+ def node_stat_contributions(nodes)
52
+ if respond_to? :node_stat_contribution
53
+ nodes.sum { |n| node_stat_contribution(n) }
54
+ else
55
+ nodes.size
56
+ end
57
+ end
58
+
59
+ def stats
60
+ st = each_node.group_by { |n| node_stat_type(n) }
61
+ .transform_values { |nodes| node_stat_contributions(nodes) }
62
+ Analyser::Stats.new(**st)
63
+ end
64
+
33
65
  # Iterates on nodes in the subset.
34
66
  # Yields the node and it's children (within the subset)
35
67
  def each_node(from = covered_code.root, &block)
@@ -4,19 +4,34 @@ require_relative 'subset'
4
4
 
5
5
  module DeepCover
6
6
  class Analyser::Branch < Analyser
7
+ def self.human_name
8
+ 'Branches'
9
+ end
7
10
  include Analyser::Subset
8
11
  SUBSET_CLASSES = [Node::Branch].freeze
9
12
 
13
+ def node_runs(node)
14
+ runs = super
15
+ if node.is_a?(Node::Branch) && covered?(runs)
16
+ worst = worst_branch_runs(node)
17
+ runs = worst unless covered?(worst)
18
+ end
19
+ runs
20
+ end
21
+
10
22
  def results
11
23
  each_node.map do |node|
12
- branches_runs = node.branches.map { |b| [b, node_runs(b)] }.to_h
24
+ branches_runs = node.branches.map { |jump| [jump, source.node_runs(jump)] }.to_h
13
25
  [node, branches_runs]
14
26
  end.to_h
15
27
  end
16
28
 
17
- def is_trivial_if?(node)
18
- parent = node.parent
19
- parent.is_a?(Node::If) && parent.condition.is_a?(Node::SingletonLiteral)
29
+ private
30
+
31
+ def worst_branch_runs(fork)
32
+ fork.branches.map { |jump| source.node_runs(jump) }
33
+ .sort_by { |runs| runs == 0 ? -2 : runs || -1 }
34
+ .first
20
35
  end
21
36
  end
22
37
  end
@@ -2,6 +2,26 @@
2
2
 
3
3
  module DeepCover
4
4
  class Analyser::Node < Analyser
5
+ include Analyser::Subset
6
+
7
+ def self.human_name
8
+ 'Nodes'
9
+ end
10
+
11
+ def initialize(source, ignore_uncovered: [], **options)
12
+ @cache = {}.compare_by_identity
13
+ super
14
+ @allow_filters = Array(ignore_uncovered).map { |kind| method(:"is_#{kind}?") }
15
+ end
16
+
17
+ def node_runs(node)
18
+ @cache.fetch(node) do
19
+ runs = super
20
+ runs = nil if runs == 0 && should_be_ignored?(node)
21
+ @cache[node] = runs
22
+ end
23
+ end
24
+
5
25
  def is_raise?(node)
6
26
  node.is_a?(Node::Send) && (node.message == :raise || node.message == :exit)
7
27
  end
@@ -15,10 +35,42 @@ module DeepCover
15
35
  node.is_a?(Node::EmptyBody) && parent.is_a?(Node::Case) && !parent.has_else?
16
36
  end
17
37
 
38
+ def in_subset?(node, _parent)
39
+ node.executable?
40
+ end
41
+
42
+ def is_trivial_if?(node)
43
+ # Supports only node being a branch or the fork itself
44
+ node.parent.is_a?(Node::If) && node.parent.condition.is_a?(Node::SingletonLiteral)
45
+ end
46
+
47
+ def self.optionally_covered
48
+ @optionally_covered ||= instance_methods(false).map do |method|
49
+ method =~ /^is_(.*)\?$/
50
+ Regexp.last_match(1)
51
+ end.compact.map(&:to_sym).freeze
52
+ end
53
+
18
54
  protected
19
55
 
20
56
  def convert(node, **)
21
57
  Analyser::CoveredCodeSource.new(node)
22
58
  end
59
+
60
+ private
61
+
62
+ def should_be_ignored?(node)
63
+ @allow_filters.any? { |f| f[node] } || is_ignored?(node.parent)
64
+ end
65
+
66
+ def is_ignored?(node)
67
+ if node == nil
68
+ false
69
+ elsif node.executable?
70
+ node_runs(node).nil?
71
+ else
72
+ is_ignored?(node.parent)
73
+ end
74
+ end
23
75
  end
24
76
  end
@@ -2,10 +2,13 @@
2
2
 
3
3
  module DeepCover
4
4
  class Analyser::PerChar < Analyser
5
+ def self.human_name
6
+ 'Chars'
7
+ end
8
+
5
9
  # Returns an array of characters for each line of code.
6
10
  # Each character is either ' ' (executed), '-' (not executable) or 'x' (not covered)
7
11
  def results
8
- buffer = covered_code.buffer
9
12
  bc = buffer.source_lines.map { |line| '-' * line.size }
10
13
  each_node do |node|
11
14
  runs = node_runs(node)
@@ -17,5 +20,19 @@ module DeepCover
17
20
  bc.zip(buffer.source_lines) { |cov, line| cov[line.size..-1] = '' } # remove extraneous character for end lines, in any
18
21
  bc
19
22
  end
23
+
24
+ def node_stat_contribution(node)
25
+ node.executed_locs.sum(&:size)
26
+ end
27
+
28
+ def stats
29
+ s = super
30
+ actual_total = buffer.source.size
31
+ s.with not_executable: actual_total - s.total
32
+ end
33
+
34
+ def buffer
35
+ covered_code.buffer
36
+ end
20
37
  end
21
38
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ class Analyser::StatsBase
5
+ DECIMALS = 2
6
+ include Memoize
7
+ memoize :to_h, :total
8
+
9
+ VALUES = %i[executed not_executed not_executable ignored].freeze # All are exclusive
10
+
11
+ attr_reader(*VALUES)
12
+
13
+ def to_h
14
+ VALUES.map { |val| [val, public_send(val)] }.to_h
15
+ end
16
+
17
+ def initialize(executed: 0, not_executed: 0, not_executable: 0, ignored: 0)
18
+ @executed = executed
19
+ @not_executed = not_executed
20
+ @not_executable = not_executable
21
+ @ignored = ignored
22
+ freeze
23
+ end
24
+
25
+ def +(other)
26
+ self.class.new(to_h.merge(other.to_h) { |k, a, b| a + b })
27
+ end
28
+
29
+ def total
30
+ to_h.values.inject(:+)
31
+ end
32
+
33
+ def with(**values)
34
+ self.class.new(to_h.merge(values))
35
+ end
36
+
37
+ def potentially_executable
38
+ total - not_executable
39
+ end
40
+
41
+ def percent_covered
42
+ return 100 if potentially_executable == 0
43
+ (100 * (1 - not_executed.fdiv(potentially_executable))).round(DECIMALS)
44
+ end
45
+ end
46
+
47
+ class Analyser::Stats < Analyser::StatsBase
48
+ memoize :percent
49
+
50
+ def percent
51
+ Analyser::StatsBase.new(to_h.transform_values { |v| (100 * v).fdiv(total).round(DECIMALS) })
52
+ end
53
+ end
54
+ end
@@ -18,3 +18,4 @@ require 'backports/2.1.0/enumerable/to_h'
18
18
  require 'backports/2.4.0/false_class/dup'
19
19
  require 'backports/2.4.0/true_class/dup'
20
20
  require 'backports/2.4.0/hash/transform_values'
21
+ require 'backports/2.4.0/enumerable/sum'
@@ -2,6 +2,10 @@
2
2
 
3
3
  module DeepCover
4
4
  module Base
5
+ def running?
6
+ @started
7
+ end
8
+
5
9
  def start
6
10
  return if @started
7
11
  if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
@@ -30,11 +34,16 @@ module DeepCover
30
34
  coverage.covered_code(handle_relative_filename(filename))
31
35
  end
32
36
 
33
- def cover
37
+ def cover(paths: nil)
38
+ if paths
39
+ prev = config.paths
40
+ config.paths(paths)
41
+ end
34
42
  start
35
43
  yield
36
44
  ensure
37
45
  stop
46
+ config.paths(prev) if paths
38
47
  end
39
48
 
40
49
  def config_changed(what)
@@ -44,6 +53,13 @@ module DeepCover
44
53
  end
45
54
  end
46
55
 
56
+ def reset
57
+ stop if @started
58
+ @coverage = @custom_requirer = @autoload_tracker = nil
59
+ config.reset
60
+ self
61
+ end
62
+
47
63
  def coverage
48
64
  @coverage ||= Coverage.new
49
65
  end