yardstick 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.
- data/.document +5 -0
- data/.gitignore +10 -0
- data/LICENSE +20 -0
- data/README.markdown +117 -0
- data/Rakefile +24 -0
- data/VERSION +1 -0
- data/bin/yardstick +7 -0
- data/deps.rip +1 -0
- data/lib/yardstick/autoload.rb +16 -0
- data/lib/yardstick/cli.rb +76 -0
- data/lib/yardstick/core_ext/object.rb +13 -0
- data/lib/yardstick/measurable.rb +78 -0
- data/lib/yardstick/measurement.rb +217 -0
- data/lib/yardstick/measurement_set.rb +130 -0
- data/lib/yardstick/method.rb +111 -0
- data/lib/yardstick/ordered_set.rb +115 -0
- data/lib/yardstick/processor.rb +65 -0
- data/lib/yardstick/rake/measurement.rb +101 -0
- data/lib/yardstick/rake/verify.rb +179 -0
- data/lib/yardstick/rule.rb +61 -0
- data/lib/yardstick/rule_set.rb +18 -0
- data/lib/yardstick/yard_ext.rb +20 -0
- data/lib/yardstick.rb +52 -0
- data/spec/public/yardstick/cli_spec.rb +108 -0
- data/spec/public/yardstick/measurement_set_spec.rb +265 -0
- data/spec/public/yardstick/measurement_spec.rb +256 -0
- data/spec/public/yardstick/method_spec.rb +356 -0
- data/spec/public/yardstick/rake/measurement_spec.rb +173 -0
- data/spec/public/yardstick/rake/verify_spec.rb +229 -0
- data/spec/public/yardstick_spec.rb +70 -0
- data/spec/rcov.opts +6 -0
- data/spec/semipublic/yardstick/rule_spec.rb +28 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +45 -0
- data/tasks/ci.rake +1 -0
- data/tasks/heckle.rake +52 -0
- data/tasks/metrics.rake +5 -0
- data/tasks/rdoc.rake +15 -0
- data/tasks/spec.rake +22 -0
- data/tasks/yardstick.rake +9 -0
- data/yardstick.gemspec +90 -0
- metadata +113 -0
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'rational'
|
2
|
+
|
3
|
+
module Yardstick
|
4
|
+
class MeasurementSet < OrderedSet
|
5
|
+
|
6
|
+
# The total number of measurements
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# measurements.total # => 570
|
10
|
+
#
|
11
|
+
# @return [Integer]
|
12
|
+
# total measurements
|
13
|
+
#
|
14
|
+
# @api public
|
15
|
+
def total
|
16
|
+
length
|
17
|
+
end
|
18
|
+
|
19
|
+
# The number of successful measurements
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# measurements.successful # => 561
|
23
|
+
#
|
24
|
+
# @return [Integer]
|
25
|
+
# successful measurements
|
26
|
+
#
|
27
|
+
# @api public
|
28
|
+
def successful
|
29
|
+
select { |measurement| measurement.ok? }.length
|
30
|
+
end
|
31
|
+
|
32
|
+
# The number of failed measurements
|
33
|
+
#
|
34
|
+
# @example
|
35
|
+
# measurements.failed # => 9
|
36
|
+
#
|
37
|
+
# @return [Integer]
|
38
|
+
# failed measurements
|
39
|
+
#
|
40
|
+
# @api public
|
41
|
+
def failed
|
42
|
+
total - successful
|
43
|
+
end
|
44
|
+
|
45
|
+
# The percentage of successful measurements
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# coverage = measurements.coverage # => Rational(561, 570)
|
49
|
+
# '%.1f%%' % (coverage * 100) # => "98.4%"
|
50
|
+
#
|
51
|
+
# @return [Integer, Rational]
|
52
|
+
# the coverage percentage
|
53
|
+
#
|
54
|
+
# @api public
|
55
|
+
def coverage
|
56
|
+
empty? ? 0 : Rational(successful, total)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Warn the unsuccessful measurements and a summary
|
60
|
+
#
|
61
|
+
# @example
|
62
|
+
# measurements.puts # (outputs measurements results and summary)
|
63
|
+
#
|
64
|
+
# @param [#puts] io
|
65
|
+
# optional object to puts the summary
|
66
|
+
#
|
67
|
+
# @return [undefined]
|
68
|
+
#
|
69
|
+
# @api public
|
70
|
+
def puts(io = $stdout)
|
71
|
+
each { |measurement| measurement.puts(io) }
|
72
|
+
puts_summary(io)
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
# Warn the summary of all measurements
|
78
|
+
#
|
79
|
+
# @param [#puts] io
|
80
|
+
# object to puts the summary
|
81
|
+
#
|
82
|
+
# @return [undefined]
|
83
|
+
#
|
84
|
+
# @api private
|
85
|
+
def puts_summary(io)
|
86
|
+
io.puts("\n#{[ coverage_text, successful_text, failed_text, total_text ].join(' ')}")
|
87
|
+
end
|
88
|
+
|
89
|
+
# The text for the coverage percentage to include in the summary
|
90
|
+
#
|
91
|
+
# @return [String]
|
92
|
+
# the coverage text
|
93
|
+
#
|
94
|
+
# @api private
|
95
|
+
def coverage_text
|
96
|
+
'Coverage: %.1f%%' % (coverage * 100)
|
97
|
+
end
|
98
|
+
|
99
|
+
# The text for the successful measurements to include in the summary
|
100
|
+
#
|
101
|
+
# @return [String]
|
102
|
+
# the successful text
|
103
|
+
#
|
104
|
+
# @api private
|
105
|
+
def successful_text
|
106
|
+
'Success: %d' % successful
|
107
|
+
end
|
108
|
+
|
109
|
+
# The text for the failed measurements to include in the summary
|
110
|
+
#
|
111
|
+
# @return [String]
|
112
|
+
# the failed text
|
113
|
+
#
|
114
|
+
# @api private
|
115
|
+
def failed_text
|
116
|
+
'Failed: %d' % failed
|
117
|
+
end
|
118
|
+
|
119
|
+
# The text for the total measurements to include in the summary
|
120
|
+
#
|
121
|
+
# @return [String]
|
122
|
+
# the total text
|
123
|
+
#
|
124
|
+
# @api private
|
125
|
+
def total_text
|
126
|
+
'Total: %d' % total
|
127
|
+
end
|
128
|
+
|
129
|
+
end # class MeasurementSet
|
130
|
+
end # module Yardstick
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Yardstick
|
2
|
+
module Method
|
3
|
+
include Measurable
|
4
|
+
|
5
|
+
rule 'The method summary should be specified' do
|
6
|
+
skip if has_tag?('see')
|
7
|
+
summary_text != ''
|
8
|
+
end
|
9
|
+
|
10
|
+
rule 'The method summary should be less than 80 characters in length' do
|
11
|
+
summary_text.split(//).length <= 80
|
12
|
+
end
|
13
|
+
|
14
|
+
rule 'The method summary should not end in a period' do
|
15
|
+
summary_text[-1, 1] != '.'
|
16
|
+
end
|
17
|
+
|
18
|
+
rule 'The method summary should be a single line' do
|
19
|
+
!summary_text.include?("\n")
|
20
|
+
end
|
21
|
+
|
22
|
+
rule 'The public/semipublic method should have an example specified' do
|
23
|
+
skip if api?(%w[ private ]) || tag_types('return') == %w[ undefined ]
|
24
|
+
has_tag?('example')
|
25
|
+
end
|
26
|
+
|
27
|
+
rule 'The @api tag should be specified' do
|
28
|
+
has_tag?('api')
|
29
|
+
end
|
30
|
+
|
31
|
+
rule 'The @api tag must be either public, semipublic or private' do
|
32
|
+
%w[ public semipublic private ].include?(tag_text('api'))
|
33
|
+
end
|
34
|
+
|
35
|
+
rule 'A method with protected visibility must have an @api tag of semipublic or private' do
|
36
|
+
skip unless visibility == :protected
|
37
|
+
api?(%w[ semipublic private ])
|
38
|
+
end
|
39
|
+
|
40
|
+
rule 'A method with private visibility must have an @api tag of private' do
|
41
|
+
skip unless visibility == :private
|
42
|
+
api?(%w[ private ])
|
43
|
+
end
|
44
|
+
|
45
|
+
rule 'The @return tag should be specified' do
|
46
|
+
has_tag?('return')
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# The raw text for the summary
|
52
|
+
#
|
53
|
+
# @return [String]
|
54
|
+
# the summary text
|
55
|
+
#
|
56
|
+
# @api private
|
57
|
+
def summary_text
|
58
|
+
split(/\r?\n\r?\n/).first || ''
|
59
|
+
end
|
60
|
+
|
61
|
+
# The text for a specified tag
|
62
|
+
#
|
63
|
+
# @param [String] tag_name
|
64
|
+
# the name of the tag
|
65
|
+
#
|
66
|
+
# @return [String, nil]
|
67
|
+
# the tag text if the tag exists
|
68
|
+
#
|
69
|
+
# @api private
|
70
|
+
def tag_text(tag_name)
|
71
|
+
tag(tag_name).text if has_tag?(tag_name)
|
72
|
+
end
|
73
|
+
|
74
|
+
# The types for a specified tag
|
75
|
+
#
|
76
|
+
# @param [String] tag_name
|
77
|
+
# the name of the tag
|
78
|
+
#
|
79
|
+
# @return [Array<String>, nil]
|
80
|
+
# a collection of tag types if the tag exists
|
81
|
+
#
|
82
|
+
# @api private
|
83
|
+
def tag_types(tag_name)
|
84
|
+
tag(tag_name).types if has_tag?(tag_name)
|
85
|
+
end
|
86
|
+
|
87
|
+
# The method visibility: public, protected or private
|
88
|
+
#
|
89
|
+
# @return [Symbol]
|
90
|
+
# the visibility of the method
|
91
|
+
#
|
92
|
+
# @api private
|
93
|
+
def visibility
|
94
|
+
object.visibility
|
95
|
+
end
|
96
|
+
|
97
|
+
# Check if the method API type matches
|
98
|
+
#
|
99
|
+
# @param [Array<String>] types
|
100
|
+
# a collection of API types
|
101
|
+
#
|
102
|
+
# @return [Boolean]
|
103
|
+
# true if the API type matches
|
104
|
+
#
|
105
|
+
# @api private
|
106
|
+
def api?(types)
|
107
|
+
types.include?(tag_text('api'))
|
108
|
+
end
|
109
|
+
|
110
|
+
end # module Method
|
111
|
+
end # module Yardstick
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Yardstick
|
2
|
+
class OrderedSet
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
# Returns the OrderedSet instance
|
6
|
+
#
|
7
|
+
# @param [Array] entries
|
8
|
+
# optional entries
|
9
|
+
#
|
10
|
+
# @return [OrderedSet]
|
11
|
+
# the ordered set instance
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
def initialize(entries = nil)
|
15
|
+
@entries = []
|
16
|
+
@index = {}
|
17
|
+
merge(entries) if entries
|
18
|
+
end
|
19
|
+
|
20
|
+
# Append to the OrderedSet
|
21
|
+
#
|
22
|
+
# @param [Object] entry
|
23
|
+
# the object to append
|
24
|
+
#
|
25
|
+
# @return [OrderedSet]
|
26
|
+
# returns self
|
27
|
+
#
|
28
|
+
# @api private
|
29
|
+
def <<(entry)
|
30
|
+
unless include?(entry)
|
31
|
+
@index[entry] = @entries.length
|
32
|
+
@entries << entry
|
33
|
+
end
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
# Merge in another OrderedSet
|
38
|
+
#
|
39
|
+
# @param [#each] other
|
40
|
+
# the other ordered set
|
41
|
+
#
|
42
|
+
# @return [OrderedSet]
|
43
|
+
# returns self
|
44
|
+
#
|
45
|
+
# @api private
|
46
|
+
def merge(other)
|
47
|
+
other.each { |entry| self << entry }
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
# Iterate over each entry
|
52
|
+
#
|
53
|
+
# @yield [entry]
|
54
|
+
# yield to the entry
|
55
|
+
#
|
56
|
+
# @yieldparam [Object] entry
|
57
|
+
# an entry in the ordered set
|
58
|
+
#
|
59
|
+
# @return [OrderedSet]
|
60
|
+
# returns self
|
61
|
+
#
|
62
|
+
# @api private
|
63
|
+
def each(&block)
|
64
|
+
@entries.each(&block)
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
# Check if there are any entries
|
69
|
+
#
|
70
|
+
# @return [Boolean]
|
71
|
+
# true if there are no entries, false if there are
|
72
|
+
#
|
73
|
+
# @api private
|
74
|
+
def empty?
|
75
|
+
@entries.empty?
|
76
|
+
end
|
77
|
+
|
78
|
+
# The number of entries
|
79
|
+
#
|
80
|
+
# @return [Integer]
|
81
|
+
# number of entries
|
82
|
+
#
|
83
|
+
# @api private
|
84
|
+
def length
|
85
|
+
@entries.length
|
86
|
+
end
|
87
|
+
|
88
|
+
# Check if the entry exists in the set
|
89
|
+
#
|
90
|
+
# @param [Object] entry
|
91
|
+
# the entry to test for
|
92
|
+
#
|
93
|
+
# @return [Boolean]
|
94
|
+
# true if the entry exists in the set, false if not
|
95
|
+
#
|
96
|
+
# @api private
|
97
|
+
def include?(entry)
|
98
|
+
@index.key?(entry)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Return the index for the entry in the set
|
102
|
+
#
|
103
|
+
# @param [Object] entry
|
104
|
+
# the entry to check the set for
|
105
|
+
#
|
106
|
+
# @return [Integer, nil]
|
107
|
+
# the index for the entry, or nil if it does not exist
|
108
|
+
#
|
109
|
+
# @api private
|
110
|
+
def index(entry)
|
111
|
+
@index[entry]
|
112
|
+
end
|
113
|
+
|
114
|
+
end # class OrderedSet
|
115
|
+
end # module Yardstick
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Yardstick
|
2
|
+
class Processor
|
3
|
+
|
4
|
+
# Measure files provided
|
5
|
+
#
|
6
|
+
# @param [Array<#to_s>, #to_s] path
|
7
|
+
# the files to measure
|
8
|
+
#
|
9
|
+
# @return [MeasurementSet]
|
10
|
+
# a collection of measurements
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
def self.process_path(path)
|
14
|
+
YARD.parse(Array(path).map { |file| file.to_s })
|
15
|
+
measurements
|
16
|
+
end
|
17
|
+
|
18
|
+
# Measure string provided
|
19
|
+
#
|
20
|
+
# @param [#to_str] string
|
21
|
+
# the string to measure
|
22
|
+
#
|
23
|
+
# @return [MeasurementSet]
|
24
|
+
# a collection of measurements
|
25
|
+
#
|
26
|
+
# @api private
|
27
|
+
def self.process_string(string)
|
28
|
+
YARD.parse_string(string.to_str)
|
29
|
+
measurements
|
30
|
+
end
|
31
|
+
|
32
|
+
# Measure method objects in YARD registry
|
33
|
+
#
|
34
|
+
# @return [MeasurementSet]
|
35
|
+
# a collection of measurements
|
36
|
+
#
|
37
|
+
# @api private
|
38
|
+
def self.measurements
|
39
|
+
measurements = MeasurementSet.new
|
40
|
+
method_objects.each do |method_object|
|
41
|
+
measurements.merge(method_object.docstring.measure)
|
42
|
+
end
|
43
|
+
measurements
|
44
|
+
end
|
45
|
+
|
46
|
+
# Return method objects in YARD registry
|
47
|
+
#
|
48
|
+
# @return [Array<YARD::CodeObjects::MethodObject>]
|
49
|
+
# a collection of method objects
|
50
|
+
#
|
51
|
+
# @api private
|
52
|
+
def self.method_objects
|
53
|
+
YARD::Registry.all(:method).sort_by do |method_object|
|
54
|
+
[ method_object.file, method_object.line ]
|
55
|
+
end
|
56
|
+
ensure
|
57
|
+
YARD::Registry.clear
|
58
|
+
end
|
59
|
+
|
60
|
+
class << self
|
61
|
+
private :measurements, :method_objects
|
62
|
+
end
|
63
|
+
|
64
|
+
end # class Processor
|
65
|
+
end # module Yardstick
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/tasklib'
|
3
|
+
|
4
|
+
module Yardstick
|
5
|
+
module Rake
|
6
|
+
class Measurement < ::Rake::TaskLib
|
7
|
+
|
8
|
+
# List of paths to measure
|
9
|
+
#
|
10
|
+
# @param [Array<#to_s>, #to_s] path
|
11
|
+
# optional list of paths to measure
|
12
|
+
#
|
13
|
+
# @return [undefined]
|
14
|
+
#
|
15
|
+
# @api public
|
16
|
+
attr_writer :path
|
17
|
+
|
18
|
+
# The path to the file where the measurements will be written
|
19
|
+
#
|
20
|
+
# @param [String, Pathname] output
|
21
|
+
# optional output path for measurements
|
22
|
+
#
|
23
|
+
# @return [undefined]
|
24
|
+
#
|
25
|
+
# @api public
|
26
|
+
def output=(output)
|
27
|
+
@output = Pathname(output)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Initializes a Measurement task
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# task = Yardstick::Rake::Measurement
|
34
|
+
#
|
35
|
+
# @param [Symbol] name
|
36
|
+
# optional task name
|
37
|
+
#
|
38
|
+
# @yield [task]
|
39
|
+
# yield to self
|
40
|
+
#
|
41
|
+
# @yieldparam [Yardstick::Rake::Measurement] task
|
42
|
+
# the measurement task
|
43
|
+
#
|
44
|
+
# @return [Yardstick::Rake::Measurement]
|
45
|
+
# the measurement task
|
46
|
+
#
|
47
|
+
# @api public
|
48
|
+
def initialize(name = :yardstick_measure)
|
49
|
+
@name = name
|
50
|
+
@path = 'lib/**/*.rb'
|
51
|
+
|
52
|
+
self.output = 'measurements/report.txt'
|
53
|
+
|
54
|
+
yield self if block_given?
|
55
|
+
|
56
|
+
define
|
57
|
+
end
|
58
|
+
|
59
|
+
# Measure the documentation
|
60
|
+
#
|
61
|
+
# @example
|
62
|
+
# task.yardstick_measure # (output measurement report)
|
63
|
+
#
|
64
|
+
# @return [undefined]
|
65
|
+
#
|
66
|
+
# @api public
|
67
|
+
def yardstick_measure
|
68
|
+
write_report { |io| Yardstick.measure(@path).puts(io) }
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
# Define the task
|
74
|
+
#
|
75
|
+
# @return [undefined]
|
76
|
+
#
|
77
|
+
# @api private
|
78
|
+
def define
|
79
|
+
desc "Measure docs in #{@path} with yardstick"
|
80
|
+
task(@name) { yardstick_measure }
|
81
|
+
end
|
82
|
+
|
83
|
+
# Open up a report for writing
|
84
|
+
#
|
85
|
+
# @yield [io]
|
86
|
+
# yield to an object that responds to #puts
|
87
|
+
#
|
88
|
+
# @yieldparam [#puts] io
|
89
|
+
# the object that responds to #puts
|
90
|
+
#
|
91
|
+
# @return [undefined]
|
92
|
+
#
|
93
|
+
# @api private
|
94
|
+
def write_report(&block)
|
95
|
+
@output.dirname.mkpath
|
96
|
+
@output.open('w', &block)
|
97
|
+
end
|
98
|
+
|
99
|
+
end # class Measurement
|
100
|
+
end # module Rake
|
101
|
+
end # module Yardstick
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/tasklib'
|
3
|
+
|
4
|
+
module Yardstick
|
5
|
+
module Rake
|
6
|
+
class Verify < ::Rake::TaskLib
|
7
|
+
|
8
|
+
# Set the threshold
|
9
|
+
#
|
10
|
+
# @param [Number] threshold
|
11
|
+
# the threshold to set
|
12
|
+
#
|
13
|
+
# @return [undefined]
|
14
|
+
#
|
15
|
+
# @api public
|
16
|
+
attr_writer :threshold
|
17
|
+
|
18
|
+
# Specify if the threshold should match the coverage
|
19
|
+
#
|
20
|
+
# @param [Boolean] require_exact_threshold
|
21
|
+
# true if the threshold should match the coverage, false if not
|
22
|
+
#
|
23
|
+
# @return [undefined]
|
24
|
+
#
|
25
|
+
# @api public
|
26
|
+
attr_writer :require_exact_threshold
|
27
|
+
|
28
|
+
# List of paths to measure
|
29
|
+
#
|
30
|
+
# @param [Array<#to_s>, #to_s] path
|
31
|
+
# optional list of paths to measure
|
32
|
+
#
|
33
|
+
# @return [undefined]
|
34
|
+
#
|
35
|
+
# @api public
|
36
|
+
attr_writer :path
|
37
|
+
|
38
|
+
# Specify if the coverage summary should be displayed
|
39
|
+
#
|
40
|
+
# @param [Boolean] verbose
|
41
|
+
# true if the coverage summary should be displayed, false if not
|
42
|
+
#
|
43
|
+
# @return [undefined]
|
44
|
+
#
|
45
|
+
# @api public
|
46
|
+
attr_writer :verbose
|
47
|
+
|
48
|
+
# Initialize a Verify task
|
49
|
+
#
|
50
|
+
# @example
|
51
|
+
# task = Yardstick::Rake::Verify.new do |task|
|
52
|
+
# task.threshold = 100
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# @param [Symbol] name
|
56
|
+
# optional task name
|
57
|
+
#
|
58
|
+
# @yield [task]
|
59
|
+
# yield to self
|
60
|
+
#
|
61
|
+
# @yieldparam [Yardstick::Rake::Verify] task
|
62
|
+
# the verification task
|
63
|
+
#
|
64
|
+
# @return [Yardstick::Rake::Verify] task
|
65
|
+
# the verification task instance
|
66
|
+
#
|
67
|
+
# @api public
|
68
|
+
def initialize(name = :verify_measurements)
|
69
|
+
@name = name
|
70
|
+
@require_exact_threshold = true
|
71
|
+
@path = 'lib/**/*.rb'
|
72
|
+
@verbose = true
|
73
|
+
|
74
|
+
yield self
|
75
|
+
|
76
|
+
assert_threshold
|
77
|
+
define
|
78
|
+
end
|
79
|
+
|
80
|
+
# Verify the YARD coverage measurements
|
81
|
+
#
|
82
|
+
# @example
|
83
|
+
# task.verify_measurements # output coverage and threshold
|
84
|
+
#
|
85
|
+
# @return [undefined]
|
86
|
+
#
|
87
|
+
# @raise [RuntimeError]
|
88
|
+
# raised if threshold is not met
|
89
|
+
# @raise [RuntimeError]
|
90
|
+
# raised if threshold is not equal to the coverage
|
91
|
+
#
|
92
|
+
# @api public
|
93
|
+
def verify_measurements
|
94
|
+
puts "Coverage: #{total_coverage}% (threshold: #{@threshold}%)" if verbose
|
95
|
+
assert_meets_threshold
|
96
|
+
assert_matches_threshold
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
# Define the task
|
102
|
+
#
|
103
|
+
# @return [undefined]
|
104
|
+
#
|
105
|
+
# @api private
|
106
|
+
def define
|
107
|
+
desc "Verify that yardstick coverage is at least #{@threshold}%"
|
108
|
+
task(@name) { verify_measurements }
|
109
|
+
end
|
110
|
+
|
111
|
+
# The total YARD coverage
|
112
|
+
#
|
113
|
+
# @return [Float]
|
114
|
+
# the total coverage
|
115
|
+
#
|
116
|
+
# @api private
|
117
|
+
def total_coverage
|
118
|
+
measurements = Yardstick.measure(@path)
|
119
|
+
round_percentage(measurements.coverage * 100)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Round percentage to 1/10th of a percent
|
123
|
+
#
|
124
|
+
# @param [Float] percentage
|
125
|
+
# the percentage to round
|
126
|
+
#
|
127
|
+
# @return [Float]
|
128
|
+
# the rounded percentage
|
129
|
+
#
|
130
|
+
# @api private
|
131
|
+
def round_percentage(percentage)
|
132
|
+
(percentage * 10).ceil / 10.0
|
133
|
+
end
|
134
|
+
|
135
|
+
# Raise an exception if threshold is not set
|
136
|
+
#
|
137
|
+
# @return [undefined]
|
138
|
+
#
|
139
|
+
# @raise [RuntimeError]
|
140
|
+
# raised if threshold is not set
|
141
|
+
#
|
142
|
+
# @api private
|
143
|
+
def assert_threshold
|
144
|
+
if @threshold.nil?
|
145
|
+
raise 'threshold must be set'
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Raise an exception if the threshold is not met
|
150
|
+
#
|
151
|
+
# @return [undefined]
|
152
|
+
#
|
153
|
+
# @raise [RuntimeError]
|
154
|
+
# raised if threshold is not met
|
155
|
+
#
|
156
|
+
# @api private
|
157
|
+
def assert_meets_threshold
|
158
|
+
if total_coverage < @threshold
|
159
|
+
raise "Coverage must be at least #{@threshold}% but was #{total_coverage}%"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Raise an exception if the threshold is not equal to the coverage
|
164
|
+
#
|
165
|
+
# @return [undefined]
|
166
|
+
#
|
167
|
+
# @raise [RuntimeError]
|
168
|
+
# raised if threshold is not equal to the coverage
|
169
|
+
#
|
170
|
+
# @api private
|
171
|
+
def assert_matches_threshold
|
172
|
+
if @require_exact_threshold && total_coverage > @threshold
|
173
|
+
raise "Coverage has increased above the threshold of #{@threshold}% to #{total_coverage}%. You should update your threshold value."
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
end # class Verify
|
178
|
+
end # module Rake
|
179
|
+
end # module Yardstick
|