coco 0.13.0 → 0.14.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTORS +9 -0
  3. data/Changelog.markdown +42 -0
  4. data/Gemfile.lock +36 -55
  5. data/LICENSE +7 -0
  6. data/README.markdown +124 -73
  7. data/Rakefile +10 -37
  8. data/VERSION +1 -1
  9. data/lib/coco.rb +9 -21
  10. data/lib/coco/configuration.rb +59 -20
  11. data/lib/coco/cover.rb +1 -0
  12. data/lib/coco/cover/coverage_result.rb +72 -16
  13. data/lib/coco/cover/coverage_stat.rb +20 -8
  14. data/lib/coco/cover/summary.rb +50 -0
  15. data/lib/coco/deprecated_message.rb +31 -0
  16. data/lib/coco/formatter.rb +2 -2
  17. data/lib/coco/formatter/colored_string.rb +1 -1
  18. data/lib/coco/formatter/console_formatter.rb +24 -19
  19. data/lib/coco/formatter/context.rb +10 -39
  20. data/lib/coco/formatter/html_formatter.rb +7 -13
  21. data/lib/coco/formatter/html_index_formatter.rb +20 -16
  22. data/lib/coco/formatter/index_context.rb +37 -0
  23. data/lib/coco/formatter/index_line.rb +21 -0
  24. data/lib/coco/formatter/template.rb +2 -3
  25. data/lib/coco/helpers.rb +88 -68
  26. data/lib/coco/lister/source_lister.rb +23 -26
  27. data/lib/coco/lister/uncovered_lister.rb +6 -9
  28. data/lib/coco/project.rb +65 -0
  29. data/lib/coco/theme.rb +15 -0
  30. data/lib/coco/writer/file_writer.rb +5 -5
  31. data/lib/coco/writer/html_directory.rb +16 -8
  32. data/lib/coco/writer/html_files_writer.rb +9 -6
  33. data/lib/coco/writer/html_index_writer.rb +6 -3
  34. data/template/css/dark.css +178 -0
  35. data/template/css/{coco.css → light.css} +22 -9
  36. data/template/file.erb +3 -3
  37. data/template/index.erb +35 -33
  38. data/template/js/coco.js +18 -0
  39. metadata +34 -58
  40. data/COPYING +0 -674
  41. data/lib/coco/formatter/formatter.rb +0 -23
  42. data/template/img/coconut16.png +0 -0
  43. data/template/img/licenses +0 -19
data/Rakefile CHANGED
@@ -3,49 +3,22 @@ require 'rake/dsl_definition'
3
3
  require 'rake'
4
4
  require 'rspec/core/rake_task'
5
5
 
6
- def ruby_files_for_shell
7
- files = Dir.glob 'lib/**/*.rb'
8
- files.join(' ')
9
- end
10
-
11
- desc 'Test coco'
12
- task :default => :spec
6
+ desc 'Test with all tools'
7
+ task :default => :all_tests
13
8
 
14
- desc 'Test coco'
15
- RSpec::Core::RakeTask.new(:spec) do |t|
9
+ desc 'Test with RSpec'
10
+ RSpec::Core::RakeTask.new(:rspec) do |t|
16
11
  t.rspec_opts = ['--color --order=random']
17
12
  end
18
13
 
19
- desc 'Check for code smells with reek'
20
- task :reek do
21
- puts 'Checking for code smells.'
22
- puts '-------------------------'
23
- system "reek #{ruby_files_for_shell}"
14
+ desc 'Test with Cucumber'
15
+ task :cucumber do
16
+ exec 'cucumber'
24
17
  end
25
18
 
26
- desc 'Check for duplicate code with flay'
27
- task :flay do
28
- puts 'Checking for duplicate code.'
29
- puts '----------------------------'
30
- exec "flay lib"
19
+ task :all_tests do
20
+ Rake::Task['rspec'].execute
21
+ Rake::Task['cucumber'].execute
31
22
  end
32
23
 
33
- desc 'Check various code metrics'
34
- task :metrics do
35
- puts 'Checking various metrics.'
36
- puts '========================='
37
- Rake::Task['reek'].execute
38
- Rake::Task['flay'].execute
39
- end
40
24
 
41
- namespace :doc do
42
- desc 'Generate documentation for developpers'
43
- task :create do
44
- exec 'yardoc'
45
- end
46
-
47
- desc 'Delete documentation'
48
- task :clean do
49
- rm_rf 'doc'
50
- end
51
- end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.13.0
1
+ 0.14.0
@@ -1,38 +1,26 @@
1
+ require 'coco/project'
2
+ require 'coco/theme'
1
3
  require 'coco/formatter'
2
4
  require 'coco/cover'
3
5
  require 'coco/writer'
4
6
  require 'coco/helpers'
5
7
  require 'coco/configuration'
6
8
  require 'coco/lister'
9
+ require 'coco/deprecated_message'
7
10
 
8
11
  require 'coverage'
9
12
 
10
13
  # Public: Main namespace of Coco, a code coverage utilily for
11
- # Ruby from 1.9.3 to 2.1.
14
+ # Ruby from 2.0 to 2.3.
15
+ #
12
16
  module Coco
13
- ROOT = File.expand_path(File.dirname(__FILE__) + '/..').freeze
17
+ ROOT = File.expand_path(File.dirname(__FILE__) + '/..').freeze
14
18
  end
15
19
 
16
20
  Coverage.start
17
21
 
22
+ # The coverage's analysis happens at the very end of the test suite.
23
+ #
18
24
  at_exit do
19
- config = Coco::Configuration.new
20
- if config.user_wants_to_run?
21
- result = Coco::CoverageResult.new(config, Coverage.result)
22
- covered = result.covered_from_domain
23
-
24
- sources = Coco::SourceLister.new(config).list
25
- uncovered = Coco::UncoveredLister.new(sources, result.all_from_domain).list
26
-
27
- console_formatter = Coco::ConsoleFormatter.new(covered, uncovered,
28
- config[:threshold])
29
- puts console_formatter.format(config[:single_line_report])
30
- puts console_formatter.link if config[:show_link_in_terminal]
31
-
32
- html_files = Coco::HtmlFormatter.new(covered).format
33
- Coco::HtmlFilesWriter.new(html_files).write
34
-
35
- index = Coco::HtmlIndexFormatter.new(covered, uncovered).format
36
- Coco::HtmlIndexWriter.new(index).write
37
- end
25
+ Coco::Project.run(Coverage.result)
38
26
  end
@@ -18,27 +18,36 @@ module Coco
18
18
  #
19
19
  # Note you can set the threshold above 100% (to be sure to see all
20
20
  # files) but you cannot set it under 0.
21
+ #
21
22
  class Configuration < Hash
22
23
 
24
+ DEFAULT_OPTIONS = {
25
+ threshold: 100,
26
+ include: ['lib'],
27
+ exclude: %w( spec test ),
28
+ single_line_report: true,
29
+ always_run: true,
30
+ show_link_in_terminal: false,
31
+ exclude_above_threshold: true,
32
+ theme: 'light',
33
+ }.freeze
34
+
23
35
  # Public: Initialize a Configuration.
36
+ #
24
37
  def initialize
25
- self[:threshold] = 100
26
- self[:directories] = ['lib']
27
- self[:excludes] = ['spec', 'test']
28
- self[:single_line_report] = false
29
- self[:always_run] = true
30
- self[:show_link_in_terminal] = false
31
- self[:exclude_above_threshold] = true
38
+ merge!(DEFAULT_OPTIONS)
32
39
  if File.exist?('.coco.yml')
33
- self.merge!(YAML.load_file('.coco.yml'))
40
+ merge!(YAML.load_file('.coco.yml'))
34
41
  # Deprecated: Support of '.coco' file will be removed in v1.0.
35
42
  elsif File.exist?('.coco')
36
- warn('Please use `.coco.yml` instead of `.coco`.')
37
- warn('Support for `.coco` will be removed in future versions.')
38
- self.merge!(YAML.load_file('.coco'))
43
+ warn(DeprecatedMessage.for_legacy_config_file)
44
+ merge!(YAML.load_file('.coco'))
39
45
  end
40
46
 
47
+ ensure_known_theme
41
48
  ensure_threeshold_compatibility
49
+ ensure_directories_compatibility
50
+ ensure_excludes_compatibility
42
51
  expand_directories
43
52
  remove_directories
44
53
  end
@@ -49,13 +58,13 @@ module Coco
49
58
  # If the configuration key :always_run is set to true, we always
50
59
  # run the coverage.
51
60
  # In case the configuration key :always_run is set to false, we have
52
- # to check for an environement variable named 'COCO' to decide if
61
+ # to check for an environment variable named 'COCO' to decide if
53
62
  # we launch the coverage or not. When 'COCO' doesn't exist, or is
54
63
  # the empty string, or '0', or 'false', we don't run coverage.
55
64
  # When 'COCO' is set to any other value, we start coverage.
56
65
  #
57
66
  # Returns true if coverage should start.
58
- def user_wants_to_run?
67
+ def run_this_time?
59
68
  if self[:always_run]
60
69
  true
61
70
  else
@@ -66,27 +75,57 @@ module Coco
66
75
  private
67
76
 
68
77
  def expand_directories
69
- self[:excludes].each do |file_or_dir|
78
+ self[:exclude].each do |file_or_dir|
70
79
  add_files file_or_dir if File.directory?(file_or_dir)
71
80
  end
72
81
  end
73
82
 
74
83
  def add_files(dir)
75
- Helpers.rb_files_from(dir).each {|file| self[:excludes] << file }
84
+ Helpers.rb_files_from(dir).each { |file| self[:exclude] << file }
76
85
  end
77
86
 
78
87
  def remove_directories
79
- self[:excludes].delete_if {|file_or_dir| File.directory?(file_or_dir) }
88
+ self[:exclude].delete_if { |file_or_dir| File.directory?(file_or_dir) }
80
89
  end
81
90
 
82
91
  def ensure_threeshold_compatibility
83
- if !self[:threeshold].nil?
84
- warn('Please change `threeshold` to `threshold`.')
85
- warn('Support for the misspelt `threeshold` configuration key will be removed in future COCO versions.')
92
+ if threeshold_present?
93
+ warn(DeprecatedMessage.for_threeshold)
86
94
  self[:threshold] = self[:threeshold]
87
95
  end
88
96
  end
89
97
 
90
- end
98
+ def threeshold_present?
99
+ self[:threeshold]
100
+ end
101
+
102
+ def ensure_directories_compatibility
103
+ if directories_present?
104
+ warn(DeprecatedMessage.for_directories)
105
+ self[:include] = self[:directories]
106
+ end
107
+ end
108
+
109
+ def directories_present?
110
+ self[:directories]
111
+ end
91
112
 
113
+ def ensure_excludes_compatibility
114
+ if excludes_present?
115
+ warn(DeprecatedMessage.for_excludes)
116
+ self[:exclude] = self[:excludes]
117
+ end
118
+ end
119
+
120
+ def excludes_present?
121
+ self[:excludes]
122
+ end
123
+
124
+ def ensure_known_theme
125
+ unless %w( light dark ).include?(self[:theme])
126
+ warn("\n\nThe theme '#{self[:theme]}' didn't exist.\n\n")
127
+ self[:theme] = 'light'
128
+ end
129
+ end
130
+ end
92
131
  end
@@ -1,2 +1,3 @@
1
1
  require 'coco/cover/coverage_stat'
2
2
  require 'coco/cover/coverage_result'
3
+ require 'coco/cover/summary'
@@ -2,56 +2,112 @@ module Coco
2
2
 
3
3
  # Compute results of interest from the big results information (from
4
4
  # Coverage.result)
5
+ #
5
6
  class CoverageResult
6
7
 
7
8
  # Returns a Hash coverage for all the sources that live in the root
8
9
  # project folder.
9
- attr_reader :all_from_domain
10
+ #
11
+ attr_reader :coverable_files
10
12
 
11
13
  # Returns a Hash coverage for sources that are not sufficiently
12
14
  # covered. More technically, the sources that live in the root
13
15
  # project folder and for which the coverage percentage is under the
14
16
  # threshold.
15
- attr_reader :covered_from_domain
17
+ #
18
+ attr_reader :not_covered_enough
16
19
 
17
20
  # Public: Initialize a CoverageResult.
18
21
  #
19
22
  # config - Hash
20
- # raw_results - Hash results obtained from Coverage.result.
23
+ # raw_results - The Hash from Coverage.result. Keys are filenames
24
+ # and values are an Array representing each lines of
25
+ # the file :
26
+ # + nil : Unreacheable (comments, etc).
27
+ # + 0 : Not hit.
28
+ # + 1 or more : Number of hits.
29
+ #
21
30
  def initialize(config, raw_results)
22
- @exclude_files = config[:excludes]
31
+ raise ArgumentError if config[:threshold] < 0
32
+
33
+ @exclude_files = config[:exclude]
23
34
  @threshold = config[:threshold]
24
- raise ArgumentError if @threshold < 0
25
35
  @result = raw_results
36
+
26
37
  exclude_external_sources
27
- exclude_files_user_dont_want
28
- if config[:exclude_above_threshold]
29
- @covered_from_domain = exclude_sources_above_threshold
30
- else
31
- @covered_from_domain = @all_from_domain
32
- end
38
+ exclude_files_user_dont_want if @exclude_files
39
+
40
+ @not_covered_enough = if config[:exclude_above_threshold]
41
+ exclude_sources_above_threshold
42
+ else
43
+ @coverable_files
44
+ end
45
+ end
46
+
47
+ # Public: Count the number of "coverable" files.
48
+ #
49
+ # Returns the Fixnum number of files.
50
+ #
51
+ def count
52
+ coverable_files.size
53
+ end
54
+
55
+ # Public: Count the number of uncovered files, that is, files with a
56
+ # coverage rate of 0%.
57
+ #
58
+ # Returns the Fixnum number of uncovered files.
59
+ #
60
+ def uncovered_count
61
+ not_covered_enough.select do |_, hits|
62
+ CoverageStat.coverage_percent(hits).zero?
63
+ end.size
64
+ end
65
+
66
+ # Public: Computes the average coverage rate.
67
+ # The formula is simple:
68
+ #
69
+ # N = number of files
70
+ # f = a file
71
+ # average = sum(f_i%) / N
72
+ #
73
+ # In words: Take the sum of the coverage's percentage of all files
74
+ # and divide this sum by the number of files.
75
+ #
76
+ # Returns the Fixnum rounded average rate of coverage.
77
+ #
78
+ def average
79
+ files_present? ? (sum / count).round : 0
33
80
  end
34
81
 
35
82
  private
36
83
 
37
84
  def exclude_external_sources
38
85
  here = Dir.pwd
39
- @all_from_domain = @result.select {|key, value| key.start_with?(here) }
86
+ @coverable_files = @result.select { |key, _| key.start_with?(here) }
40
87
  end
41
88
 
42
89
  def exclude_files_user_dont_want
43
- return if @exclude_files.nil?
44
90
  @exclude_files.each do |filename|
45
- @all_from_domain.delete(File.expand_path(filename))
91
+ @coverable_files.delete(File.expand_path(filename))
46
92
  end
47
93
  end
48
94
 
49
95
  def exclude_sources_above_threshold
50
- @all_from_domain.select do |key, value|
96
+ @coverable_files.select do |_, value|
51
97
  CoverageStat.coverage_percent(value) < @threshold
52
98
  end
53
99
  end
54
100
 
55
- end
101
+ # Returns the Float sum of all files' percentage.
102
+ #
103
+ def sum
104
+ @coverable_files.values.map do |hits|
105
+ CoverageStat.real_percent(hits)
106
+ end.reduce(&:+)
107
+ end
56
108
 
109
+ def files_present?
110
+ count > 0
111
+ end
112
+ end
57
113
  end
@@ -9,20 +9,32 @@ module Coco
9
9
  # * nil: source line will never be reached (like comments).
10
10
  # * 0: source line could be reached, but was not.
11
11
  # * 1 and above: number of time the source line has been reached.
12
+ #
12
13
  module CoverageStat
13
- extend self
14
14
 
15
- # Public: Compute the percentage of code coverage for a file.
15
+ # Public: Compute the decimal percentage of code coverage for a file.
16
16
  # The file is represented by an array of hits.
17
17
  #
18
18
  # hits - Array of Integer.
19
19
  #
20
- # Returns a Integer (rounded) percentage.
21
- def coverage_percent(hits)
20
+ # Returns a Float percentage of coverage.
21
+ #
22
+ def self.real_percent(hits)
22
23
  hits = hits.compact
23
24
  return 0 if hits.empty?
24
25
  one_percent = 100.0 / hits.size
25
- (number_of_covered_lines(hits) * one_percent).to_i
26
+ number_of_covered_lines(hits) * one_percent
27
+ end
28
+
29
+ # Public: Compute the integer percentage of code coverage for a file.
30
+ # The file is represented by an array of hits.
31
+ #
32
+ # hits - Array of Integer.
33
+ #
34
+ # Returns a Integer rounded percentage of coverage.
35
+ #
36
+ def self.coverage_percent(hits)
37
+ real_percent(hits).round
26
38
  end
27
39
 
28
40
  # Compute the total of covered lines in a hits array.
@@ -30,9 +42,9 @@ module Coco
30
42
  # hits - Array of Integer.
31
43
  #
32
44
  # Returns Integer.
33
- def number_of_covered_lines(hits)
34
- hits.select {|hit| hit > 0 }.size
45
+ #
46
+ def self.number_of_covered_lines(hits)
47
+ hits.select { |hit| hit > 0 }.size
35
48
  end
36
-
37
49
  end
38
50
  end
@@ -0,0 +1,50 @@
1
+ module Coco
2
+
3
+ # A very brief summary of the coverage result.
4
+ #
5
+ class Summary
6
+ attr_reader :count, :uncovered_count
7
+
8
+ def initialize(result, uncovered)
9
+ @uncovered_count = uncovered.size
10
+ @coverable_files = result.coverable_files
11
+ @count = @coverable_files.size + @uncovered_count
12
+ end
13
+
14
+ def to_s
15
+ "Cover #{average}% | #{uncovered_count} uncovered | #{count} files"
16
+ end
17
+
18
+ # Public: Computes the average coverage rate.
19
+ # The formula is simple:
20
+ #
21
+ # N = number of files
22
+ # f = a file
23
+ # average = sum(f_i%) / N
24
+ #
25
+ # In words: Take the sum of the coverage's percentage of all files
26
+ # and divide this sum by the number of files.
27
+ #
28
+ # Returns the Fixnum rounded average rate of coverage.
29
+ #
30
+ def average
31
+ files_present? ? (sum / count).round : 0
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :coverable_files
37
+
38
+ # Returns the Float sum of all files' percentage.
39
+ #
40
+ def sum
41
+ coverable_files.values.map do |hits|
42
+ CoverageStat.real_percent(hits)
43
+ end.reduce(&:+)
44
+ end
45
+
46
+ def files_present?
47
+ count > 0
48
+ end
49
+ end
50
+ end