coverfield 0.0.4 → 0.1.0

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