deep-cover 0.1.16 → 0.2.0

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