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.
- checksums.yaml +4 -4
- data/CONTRIBUTORS +9 -0
- data/Changelog.markdown +42 -0
- data/Gemfile.lock +36 -55
- data/LICENSE +7 -0
- data/README.markdown +124 -73
- data/Rakefile +10 -37
- data/VERSION +1 -1
- data/lib/coco.rb +9 -21
- data/lib/coco/configuration.rb +59 -20
- data/lib/coco/cover.rb +1 -0
- data/lib/coco/cover/coverage_result.rb +72 -16
- data/lib/coco/cover/coverage_stat.rb +20 -8
- data/lib/coco/cover/summary.rb +50 -0
- data/lib/coco/deprecated_message.rb +31 -0
- data/lib/coco/formatter.rb +2 -2
- data/lib/coco/formatter/colored_string.rb +1 -1
- data/lib/coco/formatter/console_formatter.rb +24 -19
- data/lib/coco/formatter/context.rb +10 -39
- data/lib/coco/formatter/html_formatter.rb +7 -13
- data/lib/coco/formatter/html_index_formatter.rb +20 -16
- data/lib/coco/formatter/index_context.rb +37 -0
- data/lib/coco/formatter/index_line.rb +21 -0
- data/lib/coco/formatter/template.rb +2 -3
- data/lib/coco/helpers.rb +88 -68
- data/lib/coco/lister/source_lister.rb +23 -26
- data/lib/coco/lister/uncovered_lister.rb +6 -9
- data/lib/coco/project.rb +65 -0
- data/lib/coco/theme.rb +15 -0
- data/lib/coco/writer/file_writer.rb +5 -5
- data/lib/coco/writer/html_directory.rb +16 -8
- data/lib/coco/writer/html_files_writer.rb +9 -6
- data/lib/coco/writer/html_index_writer.rb +6 -3
- data/template/css/dark.css +178 -0
- data/template/css/{coco.css → light.css} +22 -9
- data/template/file.erb +3 -3
- data/template/index.erb +35 -33
- data/template/js/coco.js +18 -0
- metadata +34 -58
- data/COPYING +0 -674
- data/lib/coco/formatter/formatter.rb +0 -23
- data/template/img/coconut16.png +0 -0
- 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
|
-
|
7
|
-
|
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
|
15
|
-
RSpec::Core::RakeTask.new(:
|
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 '
|
20
|
-
task :
|
21
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
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.
|
1
|
+
0.14.0
|
data/lib/coco.rb
CHANGED
@@ -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
|
14
|
+
# Ruby from 2.0 to 2.3.
|
15
|
+
#
|
12
16
|
module Coco
|
13
|
-
|
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
|
-
|
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
|
data/lib/coco/configuration.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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(
|
37
|
-
|
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
|
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
|
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[:
|
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[:
|
84
|
+
Helpers.rb_files_from(dir).each { |file| self[:exclude] << file }
|
76
85
|
end
|
77
86
|
|
78
87
|
def remove_directories
|
79
|
-
self[:
|
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
|
84
|
-
warn(
|
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
|
-
|
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
|
data/lib/coco/cover.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
@
|
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
|
-
@
|
91
|
+
@coverable_files.delete(File.expand_path(filename))
|
46
92
|
end
|
47
93
|
end
|
48
94
|
|
49
95
|
def exclude_sources_above_threshold
|
50
|
-
@
|
96
|
+
@coverable_files.select do |_, value|
|
51
97
|
CoverageStat.coverage_percent(value) < @threshold
|
52
98
|
end
|
53
99
|
end
|
54
100
|
|
55
|
-
|
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
|
21
|
-
|
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
|
-
|
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
|
-
|
34
|
-
|
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
|