coverfield 0.0.4 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c9769d1a0bdcb6d2f686f03b0fe60207779fc2e3
4
- data.tar.gz: 41b064b0ea8701426554cd0326228cc4f8a9edc0
3
+ metadata.gz: c06502048cd2cfbb46d8383572891c9a6a4ebff8
4
+ data.tar.gz: 1156c6922c0181fd7267ed4bd3fe9a370127aad5
5
5
  SHA512:
6
- metadata.gz: 9e27ac0765a91a7c474a1022107d951bc6f7362423fb9d80ab460ae78e6d6503ba249ec09f3309620a7f3b1b6d6d56f497004110b3624967efce9582885e7234
7
- data.tar.gz: 0c09947fd5dfe20d34bcba1b9ee09500a58153c4ae1e43e6fe1cbda8ae99f14b3231cbaa48bb16f651c894d217aedc476bda6e593b4382f43ac2e172462f4efb
6
+ metadata.gz: 1be8dfd4b92c3e2b0d6188c3fee72e8f28868994d284e600bad14eaba2a317932e0f186337f44c8290fe8934e3406308a2004d3d489f0a98a897af582dba5fcb
7
+ data.tar.gz: b33d06bb0e8d0bd9155294110fc245ec8f5ed3c351e438000c2add9d6ac6f4d1c4a84360c5e682cbfa51c38083695032de21206c59ac38508116221079506b30
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- coverfield (0.0.2)
4
+ coverfield (0.1.0)
5
5
  colorize (~> 0.7)
6
6
  rubocop (~> 0.40)
7
7
 
@@ -31,4 +31,4 @@ DEPENDENCIES
31
31
  coverfield!
32
32
 
33
33
  BUNDLED WITH
34
- 1.12.4
34
+ 1.12.5
data/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Coverfield
2
2
 
3
+ [![Gem Version](http://img.shields.io/gem/v/coverfield.svg)](http://badge.fury.io/rb/coverfield)
4
+
5
+ **Warning:** Alpha Release, do not rely on or use in production yet!
6
+
3
7
  One day I found a class in my ruby app with > 95% coverage in
4
8
  [SimpleCov](https://github.com/colszowka/simplecov) but without any dedicated
5
9
  spec. SimpleCov is an awesome tool if you want to get an idea of your test
@@ -30,11 +34,11 @@ test suite.
30
34
 
31
35
  ## Future
32
36
 
33
- This project has still prototype character and there's plenty to do like
34
- respecting `:nocov:` tags and so on.
37
+ This project has still prototype character (this is an alpha release) and
38
+ there's plenty to do (specs for example).
35
39
 
36
- And I'm gonna contact [Christoph Olszowka](https://github.com/colszowka) to ask
37
- him if there is a chance that this code will be included to SimpleCov in some
40
+ And I wrote [Christoph Olszowka](https://github.com/colszowka) to ask
41
+ him if there is a chance that this will be included to SimpleCov in some
38
42
  way. I would really appreciate that!
39
43
 
40
44
 
@@ -66,8 +70,9 @@ Coverfield requires you to have a specific architecture of your RSpec Suite.
66
70
  2. Within `spec` all specs are placed in the same path as the file which is
67
71
  tested by the spec. For example the spec for the file
68
72
  `/lib/some/nice_class.rb` have to be placed in
69
- `/spec/lib/some/nice_class_spec.rb`. And the spec for the file
70
- `/app/models/post.rb` goes to `/spec/models/post.rb`
73
+ `/spec/lib/some/nice_class_spec.rb` or `/spec/some/nice_class_spec.rb`.
74
+ And the spec for the file `/app/models/post.rb` goes to
75
+ `/spec/app/models/post.rb` or `/spec/models/post.rb`
71
76
  [Why?](http://stackoverflow.com/questions/14180003/rspec-naming-conventions-for-files-and-directory-structure)
72
77
  3. The first `describe` call have to be built like that:
73
78
  `describe Some::NiceClass do` assuming, that `/lib/some/nice_code.rb` defines
@@ -76,3 +81,4 @@ Coverfield requires you to have a specific architecture of your RSpec Suite.
76
81
  4. All inner `describe` calls for the methods have to be built like that:
77
82
  `describe '#method_name' do`. The `#` is optional and may also be a `.`.
78
83
  [Why?](http://betterspecs.org/#describe)
84
+ 5. All dependencies of your app have to be installed (`bundle install`).
data/bin/coverfield CHANGED
@@ -9,9 +9,9 @@ require 'colorize'
9
9
  APP_ROOT = Bundler.root.to_s
10
10
 
11
11
  require 'coverfield'
12
- require 'coverfield/source_class'
13
- require 'coverfield/source_file'
14
- require 'coverfield/test_file'
12
+ require 'coverfield/source/class'
13
+ require 'coverfield/source/file'
14
+ require 'coverfield/source/test_file'
15
15
 
16
16
 
17
17
  # Ensure there are paths to search for
@@ -25,31 +25,41 @@ target_files = target_finder.find(paths)
25
25
 
26
26
 
27
27
  # Map all found files to SourceFiles
28
- source_files = target_files.map { |file| Coverfield::SourceFile.new(file) }
28
+ source_files = target_files.map { |file| Coverfield::Source::File.new(file) }
29
29
 
30
- overall_covered = 0
31
- overall_methods = 0
32
30
 
33
- # Iterate over all found files and their classes
31
+ # Initialize counter variables
32
+ total_covered = 0
33
+ total_methods = 0
34
+ total_relevant_methods = 0
35
+
36
+ # Iterate over all found files and their classes for fancy output
34
37
  source_files.each do |file|
35
38
  file.classes.each do |cls|
36
- class_name = (cls.module_name.to_s + (cls.module_name.empty? ? '' : '::') + cls.name.to_s).light_blue
37
- coverage = "#{file.coverage}/#{cls.methods.size}"
38
- covered = file.coverage == cls.methods.size
39
+ class_name = cls.full_qualified_name.to_s.light_blue
40
+ coverage = "#{file.coverage}/#{cls.relevant_method_count}/#{cls.method_count}"
41
+ covered = file.coverage == cls.relevant_method_count
39
42
  puts "#{covered ? '[X]'.green : '[ ]'.red} Found class: #{class_name} with #{covered ? coverage.green : coverage.red} covered methods in #{file.relative_file_name.light_blue}".bold
40
43
 
41
44
  file.hints.each do |hint|
42
45
  puts " - #{hint}"
43
46
  end
44
47
 
45
- overall_methods += cls.methods.size
46
- overall_covered += file.coverage
48
+ total_methods += cls.method_count
49
+ total_relevant_methods += cls.relevant_method_count
50
+ total_covered += file.coverage
47
51
 
48
52
  puts
49
53
  end
50
54
  end
51
55
 
56
+ relevant_percent = (total_relevant_methods * 100 / total_methods).round.to_s + '%'
57
+ covered_percent = (total_covered * 100 / total_methods).round.to_s + '%'
58
+ uncovered_percent = ((total_relevant_methods - total_covered) * 100 / total_methods).round.to_s + '%'
52
59
 
53
60
  puts
54
- puts "Have found #{overall_methods.to_s.yellow} methods and #{overall_covered.to_s.yellow} are covered by tests. Thus there are #{(overall_methods - overall_covered).to_s.yellow} uncovered methods."
61
+ puts "There are #{total_methods.to_s.yellow} methods in total."
62
+ puts "#{total_relevant_methods.to_s.yellow} (#{relevant_percent.yellow}) of them are relevant for coverage."
63
+ puts "And #{total_covered.to_s.yellow} (#{covered_percent.yellow}) methods are covered by tests."
64
+ puts "Thus there are #{(total_relevant_methods - total_covered).to_s.yellow} (#{uncovered_percent.yellow}) uncovered methods."
55
65
  puts
@@ -0,0 +1,37 @@
1
+ require 'coverfield/source/method'
2
+
3
+ class Coverfield::Source::Class
4
+ attr_reader :name, :module_name, :node, :methods, :source_file
5
+
6
+ # Constructor
7
+ public def initialize(class_name, module_name, node, source_file)
8
+ @name = class_name
9
+ @module_name = module_name
10
+ @node = node
11
+ @methods = []
12
+ @source_file = source_file
13
+ find_methods
14
+ end
15
+
16
+ public def full_qualified_name
17
+ name = @name
18
+ name = "#{@module_name}::#{name}" unless @module_name.empty?
19
+ name
20
+ end
21
+
22
+ public def relevant_method_count
23
+ relevant_methods = @methods.select { |m| !m.nocov?}
24
+ relevant_methods.size
25
+ end
26
+
27
+ public def method_count
28
+ @methods.size
29
+ end
30
+
31
+ # Finds all methods
32
+ private def find_methods
33
+ node.each_node(:def) do |node|
34
+ @methods << Coverfield::Source::Method.new(*node, self)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,109 @@
1
+ require 'coverfield/source/file_methods'
2
+ require 'coverfield/source/test_file'
3
+ require 'coverfield/source/class'
4
+ require 'coverfield/source/nocov_range'
5
+
6
+ class Coverfield::Source::File
7
+ include Coverfield::Source::FileMethods
8
+
9
+ attr_reader :classes, :test_file, :coverage, :hints
10
+
11
+ # Constructor
12
+ public def initialize(file_name)
13
+ @file_name = file_name
14
+ @classes = []
15
+ @coverage = 0
16
+ @hints = []
17
+ @nocov_ranges = []
18
+
19
+ unless File.zero?(file_name)
20
+ parse_code
21
+ find_nocov_ranges
22
+ find_classes
23
+ find_test_file
24
+ calculate_coverage
25
+ end
26
+ rescue Exception => e
27
+ raise RuntimeError, "Error while processing file #{file_name}: #{e.message}", e.backtrace
28
+ end
29
+
30
+
31
+ # Calculates the number of covered methods of this file and sets @coverage and @hints
32
+ private def calculate_coverage
33
+ @coverage = 0
34
+
35
+ classes.each do |cls|
36
+ cls.methods.each do |method|
37
+ if test_file.cover?(cls.full_qualified_name, method.name)
38
+ @coverage += 1
39
+ else
40
+ method_name = "#{cls.name}.#{method.name}".red
41
+
42
+ if method.nocov?
43
+ @coverage += 1
44
+ else
45
+ @hints << "Missing test for #{method_name} in #{test_file.relative_file_name.yellow}"
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+
53
+ public def nocov?(method_body_node)
54
+ @nocov_ranges.each do |nocov_range|
55
+ return true if nocov_range.includes?(method_body_node)
56
+ end
57
+
58
+ false
59
+ end
60
+
61
+
62
+ # Find class definitions
63
+ private def find_classes
64
+ @processed_source.ast.each_node(:class) do |node|
65
+ name, superclass, body = *node
66
+ _scope, const_name, value = *name
67
+ module_name = node.parent_module_name
68
+
69
+ if module_name == 'Object'
70
+ nothing, scope_name, nothing = *_scope
71
+ module_name = scope_name.to_s
72
+ end
73
+
74
+ @classes << Coverfield::Source::Class.new(const_name, module_name, node, self)
75
+ end
76
+ end
77
+
78
+
79
+ # Find the spec file for that class
80
+ private def find_test_file
81
+ spec_path = APP_ROOT + '/spec'
82
+ relative_file_name = @file_name.to_s.gsub(APP_ROOT, '')
83
+ @test_file = Coverfield::Source::TestFile.new(spec_path + relative_file_name.gsub('.rb', '_spec.rb'))
84
+
85
+ # When no file was found also try without '/lib' or '/app'
86
+ unless @test_file.file_exists?
87
+ relative_file_name.gsub!(/^\/(lib|app)/, '')
88
+ @test_file = Coverfield::Source::TestFile.new(spec_path + relative_file_name)
89
+ end
90
+ end
91
+
92
+
93
+ private def find_nocov_ranges
94
+ first = true
95
+ line = 0
96
+
97
+ @processed_source.comments.each do |comment|
98
+ if comment.type == :inline && comment.text.strip =~ /^#\s*\:nocov\:/
99
+ if first
100
+ line = comment.loc.expression.first_line
101
+ else
102
+ @nocov_ranges << Coverfield::Source::NocovRange.new(line, comment.loc.expression.first_line)
103
+ end
104
+
105
+ first = !first
106
+ end
107
+ end
108
+ end
109
+ end
@@ -1,4 +1,4 @@
1
- module Coverfield::FileMethods
1
+ module Coverfield::Source::FileMethods
2
2
  attr_reader :file_name
3
3
 
4
4
  # Parse the source code
@@ -11,4 +11,4 @@ module Coverfield::FileMethods
11
11
  public def relative_file_name
12
12
  @file_name.gsub(APP_ROOT + '/', '')
13
13
  end
14
- end
14
+ end
@@ -0,0 +1,14 @@
1
+ class Coverfield::Source::Method
2
+ attr_reader :name, :args, :body, :source_class
3
+
4
+ public def initialize(method_name, args, body, source_class)
5
+ @name = method_name
6
+ @args = args
7
+ @body = body
8
+ @source_class = source_class
9
+ end
10
+
11
+ public def nocov?
12
+ @source_class.source_file.nocov? @body
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ class Coverfield::Source::NocovRange
2
+ public def initialize(first_line, last_line)
3
+ @first_line = first_line
4
+ @last_line = last_line
5
+ end
6
+
7
+ public def includes?(node)
8
+ source_range = node.source_range
9
+ source_range.first_line > @first_line && source_range.last_line < @last_line
10
+ end
11
+ end
@@ -1,5 +1,7 @@
1
- class Coverfield::TestFile
2
- include Coverfield::FileMethods
1
+ require 'coverfield/source/file_methods'
2
+
3
+ class Coverfield::Source::TestFile
4
+ include Coverfield::Source::FileMethods
3
5
 
4
6
  # Constructor
5
7
  public def initialize(file_name)
@@ -37,6 +39,7 @@ class Coverfield::TestFile
37
39
 
38
40
  # Small helper method which builts the full qualified class name out of a describe arguments node
39
41
  private def get_spec_class_name(describe_args_node)
42
+ return describe_args_node if describe_args_node.is_a?(String)
40
43
  subject_ary = []
41
44
 
42
45
  describe_args_node.each_node(:const) do |const_part|
@@ -52,6 +55,7 @@ class Coverfield::TestFile
52
55
  private def find_describes
53
56
  # Contains the current test subject where alls test methods should be associated with
54
57
  current_subject = nil
58
+ first_describe = true
55
59
 
56
60
  # Iterate over all send nodes (method calls)
57
61
  @processed_source.ast.each_node(:send) do |node|
@@ -60,15 +64,25 @@ class Coverfield::TestFile
60
64
 
61
65
  # We only care if it's a describe() call
62
66
  if method_name == :describe
63
- if args.const_type?
67
+ if first_describe || args.const_type?
64
68
  # If it's a const, it's the first describe, which describes the class/module to test
65
69
  current_subject = get_spec_class_name(args)
66
70
  @describes[current_subject] = []
67
71
  else
68
72
  # Otherwise, get the String argument, it will contain something like '#method_name'
69
73
  value, nothing = *args.each_node(:str).first
74
+
75
+ if value == nil
76
+ # That happens if the argument is a symbol
77
+ value, nothing = *args.each_node(:sym).first
78
+ value = value.to_s
79
+ end
80
+
70
81
  @describes[current_subject] << value.strip.gsub(/^(?:\.|#)(.+)$/i, '\1')
82
+
71
83
  end
84
+
85
+ first_describe = false
72
86
  end
73
87
  end
74
88
  end
@@ -1,5 +1,5 @@
1
1
  module Coverfield
2
- version = '0.0.4'
2
+ version = '0.1.0'
3
3
 
4
4
  def version.to_a
5
5
  split('.').map(&:to_i)
@@ -22,4 +22,4 @@ module Coverfield
22
22
  end
23
23
 
24
24
  VERSION = version
25
- end
25
+ end
data/lib/coverfield.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  module Coverfield
2
-
3
- end
2
+ module Source
3
+ end
4
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: coverfield
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin Klein
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-06-20 00:00:00.000000000 Z
11
+ date: 2016-06-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -69,10 +69,12 @@ files:
69
69
  - bin/coverfield
70
70
  - coverfield.gemspec
71
71
  - lib/coverfield.rb
72
- - lib/coverfield/file_methods.rb
73
- - lib/coverfield/source_class.rb
74
- - lib/coverfield/source_file.rb
75
- - lib/coverfield/test_file.rb
72
+ - lib/coverfield/source/class.rb
73
+ - lib/coverfield/source/file.rb
74
+ - lib/coverfield/source/file_methods.rb
75
+ - lib/coverfield/source/method.rb
76
+ - lib/coverfield/source/nocov_range.rb
77
+ - lib/coverfield/source/test_file.rb
76
78
  - lib/coverfield/version.rb
77
79
  homepage: http://github.com/phortx/coverfield
78
80
  licenses:
@@ -1,26 +0,0 @@
1
- class Coverfield::SourceClass
2
- attr_reader :name, :module_name, :node, :methods
3
-
4
- # Constructor
5
- public def initialize(class_name, module_name, node)
6
- @name = class_name
7
- @module_name = module_name
8
- @node = node
9
- @methods = []
10
- find_methods
11
- end
12
-
13
- public def full_qualified_name
14
- name = @name
15
- name = "#{@module_name}::#{name}" unless @module_name.empty?
16
- name
17
- end
18
-
19
- # Finds all methods
20
- private def find_methods
21
- node.each_node(:def) do |node|
22
- method_name, args, body = *node
23
- @methods << method_name
24
- end
25
- end
26
- end
@@ -1,66 +0,0 @@
1
- require 'coverfield/file_methods'
2
-
3
- class Coverfield::SourceFile
4
- include Coverfield::FileMethods
5
-
6
- attr_reader :classes, :test_file, :coverage, :hints
7
-
8
- # Constructor
9
- public def initialize(file_name)
10
- @file_name = file_name
11
- @classes = []
12
- @coverage = 0
13
- @hints = []
14
-
15
- unless File.zero?(file_name)
16
- parse_code
17
- find_classes
18
- find_test_file
19
- calculate_coverage
20
- end
21
- rescue Exception => e
22
- raise RuntimeError, "Error while processing file #{file_name}: #{e.message}", e.backtrace
23
- end
24
-
25
-
26
- # Calculates the number of covered methods of this file and sets @coverage and @hints
27
- public def calculate_coverage
28
- @coverage = 0
29
-
30
- classes.each do |cls|
31
- cls.methods.each do |method_name|
32
- if test_file.cover?(cls.full_qualified_name, method_name)
33
- @coverage += 1
34
- else
35
- method_name = "#{cls.name}.#{method_name}".red
36
- @hints << "Missing test for #{method_name} in #{test_file.relative_file_name.yellow}"
37
- end
38
- end
39
- end
40
- end
41
-
42
-
43
- # Find class definitions
44
- private def find_classes
45
- @processed_source.ast.each_node(:class) do |node|
46
- name, superclass, body = *node
47
- _scope, const_name, value = *name
48
- module_name = node.parent_module_name
49
-
50
- if module_name == 'Object'
51
- nothing, scope_name, nothing = *_scope
52
- module_name = scope_name.to_s
53
- end
54
-
55
- @classes << Coverfield::SourceClass.new(const_name, module_name, node)
56
- end
57
- end
58
-
59
-
60
- # Find the spec file for that class
61
- private def find_test_file
62
- relative_file_name = @file_name.to_s.gsub(APP_ROOT, '')
63
- relative_file_name.gsub!('/app', '')
64
- @test_file = Coverfield::TestFile.new(APP_ROOT + '/spec' + relative_file_name.gsub('.rb', '_spec.rb'))
65
- end
66
- end