minitest-heat 0.0.5 → 0.0.6
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/.gitignore +1 -0
- data/.rubocop.yml +23 -0
- data/Gemfile +5 -3
- data/Gemfile.lock +30 -1
- data/README.md +12 -3
- data/Rakefile +8 -6
- data/lib/minitest/heat/backtrace.rb +19 -74
- data/lib/minitest/heat/hit.rb +83 -0
- data/lib/minitest/heat/issue.rb +46 -31
- data/lib/minitest/heat/line.rb +74 -0
- data/lib/minitest/heat/location.rb +20 -14
- data/lib/minitest/heat/map.rb +10 -21
- data/lib/minitest/heat/output/backtrace.rb +19 -29
- data/lib/minitest/heat/output/issue.rb +110 -0
- data/lib/minitest/heat/output/map.rb +47 -0
- data/lib/minitest/heat/output/marker.rb +50 -0
- data/lib/minitest/heat/output/results.rb +17 -5
- data/lib/minitest/heat/output/source_code.rb +2 -2
- data/lib/minitest/heat/output/token.rb +15 -13
- data/lib/minitest/heat/output.rb +12 -121
- data/lib/minitest/heat/results.rb +16 -3
- data/lib/minitest/heat/version.rb +3 -1
- data/lib/minitest/heat.rb +2 -0
- data/lib/minitest/heat_plugin.rb +5 -5
- data/lib/minitest/heat_reporter.rb +25 -24
- data/minitest-heat.gemspec +4 -2
- metadata +63 -4
- data/lib/minitest/heat/output/location.rb +0 -20
data/lib/minitest/heat/output.rb
CHANGED
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
require_relative 'output/backtrace'
|
4
4
|
require_relative 'output/issue'
|
5
|
-
require_relative 'output/location'
|
6
5
|
require_relative 'output/map'
|
6
|
+
require_relative 'output/marker'
|
7
7
|
require_relative 'output/results'
|
8
8
|
require_relative 'output/source_code'
|
9
9
|
require_relative 'output/token'
|
@@ -12,35 +12,6 @@ module Minitest
|
|
12
12
|
module Heat
|
13
13
|
# Friendly API for printing nicely-formatted output to the console
|
14
14
|
class Output
|
15
|
-
FORMATTERS = {
|
16
|
-
error: [
|
17
|
-
[ %i[error label], %i[muted spacer], %i[default test_name] ],
|
18
|
-
[ %i[italicized summary], ],
|
19
|
-
[ %i[default backtrace_summary] ],
|
20
|
-
],
|
21
|
-
broken: [
|
22
|
-
[ %i[broken label], %i[muted spacer], %i[default test_name], %i[muted spacer], %i[muted test_class] ],
|
23
|
-
[ %i[italicized summary], ],
|
24
|
-
[ %i[default backtrace_summary] ],
|
25
|
-
],
|
26
|
-
failure: [
|
27
|
-
[ %i[failure label], %i[muted spacer], %i[default test_name], %i[muted spacer], %i[muted test_class] ],
|
28
|
-
[ %i[italicized summary] ],
|
29
|
-
[ %i[muted short_location], ],
|
30
|
-
[ %i[default source_summary], ],
|
31
|
-
],
|
32
|
-
skipped: [
|
33
|
-
[ %i[skipped label], %i[muted spacer], %i[default test_name], %i[muted spacer], %i[muted test_class] ],
|
34
|
-
[ %i[italicized summary] ],
|
35
|
-
[], # New Line
|
36
|
-
],
|
37
|
-
slow: [
|
38
|
-
[ %i[slow label], %i[muted spacer], %i[default test_name], %i[muted spacer], %i[default test_class] ],
|
39
|
-
[ %i[bold slowness], %i[muted spacer], %i[default location], ],
|
40
|
-
[], # New Line
|
41
|
-
]
|
42
|
-
}
|
43
|
-
|
44
15
|
attr_reader :stream
|
45
16
|
|
46
17
|
def initialize(stream = $stdout)
|
@@ -59,106 +30,22 @@ module Minitest
|
|
59
30
|
end
|
60
31
|
alias newline puts
|
61
32
|
|
62
|
-
# TOOD: Convert to output class
|
63
|
-
# - This should likely live in the output/issue class
|
64
|
-
# - Add a 'fail_fast' option that shows the issue as soon as the failure occurs
|
65
|
-
def marker(value)
|
66
|
-
case value
|
67
|
-
when 'E' then text(:error, value)
|
68
|
-
when 'B' then text(:failure, value)
|
69
|
-
when 'F' then text(:failure, value)
|
70
|
-
when 'S' then text(:skipped, value)
|
71
|
-
else text(:success, value)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
# TOOD: Convert to output class
|
76
|
-
# - This should likely live in the output/issue class
|
77
|
-
# - There may be justification for creating different "strategies" for the various types
|
78
33
|
def issue_details(issue)
|
79
|
-
|
80
|
-
|
81
|
-
formatter.each do |lines|
|
82
|
-
lines.each do |tokens|
|
83
|
-
style, content_method = *tokens
|
84
|
-
|
85
|
-
if issue.respond_to?(content_method)
|
86
|
-
# If it's an available method on issue, use that to get the content
|
87
|
-
content = issue.send(content_method)
|
88
|
-
text(style, content)
|
89
|
-
else
|
90
|
-
# Otherwise, fall back to output and pass issue to *it*
|
91
|
-
send(content_method, issue)
|
92
|
-
end
|
93
|
-
end
|
94
|
-
newline
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
# TOOD: Convert to output class
|
99
|
-
def heat_map(map)
|
100
|
-
map.files.each do |file|
|
101
|
-
pathname = Pathname(file[0])
|
102
|
-
|
103
|
-
path = pathname.dirname.to_s
|
104
|
-
filename = pathname.basename.to_s
|
105
|
-
|
106
|
-
values = map.hits[pathname.to_s]
|
107
|
-
|
108
|
-
|
109
|
-
text(:error, 'E' * values[:error].size) if values[:error]&.any?
|
110
|
-
text(:broken, 'B' * values[:broken].size) if values[:broken]&.any?
|
111
|
-
text(:failure, 'F' * values[:failure].size) if values[:failure]&.any?
|
112
|
-
|
113
|
-
unless values[:error]&.any? || values[:broken]&.any? || values[:failure]&.any?
|
114
|
-
text(:skipped, 'S' * values[:skipped].size) if values[:skipped]&.any?
|
115
|
-
text(:painful, '—' * values[:painful].size) if values[:painful]&.any?
|
116
|
-
text(:slow, '–' * values[:slow].size) if values[:slow]&.any?
|
117
|
-
end
|
118
|
-
|
119
|
-
text(:muted, ' ') if map.hits.any?
|
120
|
-
|
121
|
-
text(:muted, "#{path.delete_prefix(Dir.pwd)}/")
|
122
|
-
text(:default, filename)
|
123
|
-
|
124
|
-
text(:muted, ':')
|
125
|
-
|
126
|
-
all_line_numbers = values.fetch(:error, []) + values.fetch(:failure, [])
|
127
|
-
all_line_numbers += values.fetch(:skipped, [])
|
128
|
-
|
129
|
-
line_numbers = all_line_numbers.compact.uniq.sort
|
130
|
-
line_numbers.each { |line_number| text(:muted, "#{line_number} ") }
|
131
|
-
newline
|
132
|
-
end
|
133
|
-
newline
|
34
|
+
print_tokens Minitest::Heat::Output::Issue.new(issue).tokens
|
134
35
|
end
|
135
36
|
|
136
|
-
|
137
|
-
|
138
|
-
text(:default, "#{issue.test_class} > #{issue.test_name}")
|
37
|
+
def marker(issue_type)
|
38
|
+
print_token Minitest::Heat::Output::Marker.new(issue_type).token
|
139
39
|
end
|
140
40
|
|
141
41
|
def compact_summary(results)
|
142
|
-
results_tokens = ::Minitest::Heat::Output::Results.new(results).tokens
|
143
|
-
|
144
|
-
newline
|
145
|
-
print_tokens(results_tokens)
|
146
42
|
newline
|
43
|
+
print_tokens ::Minitest::Heat::Output::Results.new(results).tokens
|
147
44
|
end
|
148
45
|
|
149
|
-
def
|
150
|
-
|
151
|
-
|
152
|
-
backtrace_tokens = ::Minitest::Heat::Output::Backtrace.new(location).tokens
|
153
|
-
print_tokens(backtrace_tokens)
|
154
|
-
end
|
155
|
-
|
156
|
-
def source_summary(issue)
|
157
|
-
filename = issue.location.project_file
|
158
|
-
line_number = issue.location.project_failure_line
|
159
|
-
|
160
|
-
source_code_tokens = ::Minitest::Heat::Output::SourceCode.new(filename, line_number).tokens
|
161
|
-
print_tokens(source_code_tokens)
|
46
|
+
def heat_map(map)
|
47
|
+
newline
|
48
|
+
print_tokens ::Minitest::Heat::Output::Map.new(map).tokens
|
162
49
|
end
|
163
50
|
|
164
51
|
private
|
@@ -176,6 +63,10 @@ module Minitest
|
|
176
63
|
style_enabled? ? :styled : :unstyled
|
177
64
|
end
|
178
65
|
|
66
|
+
def print_token(token)
|
67
|
+
print Token.new(*token).to_s(token_format)
|
68
|
+
end
|
69
|
+
|
179
70
|
def print_tokens(lines_of_tokens)
|
180
71
|
lines_of_tokens.each do |tokens|
|
181
72
|
tokens.each do |token|
|
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
module Minitest
|
4
4
|
module Heat
|
5
|
+
# A collection of test failures
|
5
6
|
class Results
|
6
|
-
|
7
7
|
attr_reader :test_count,
|
8
8
|
:assertion_count,
|
9
9
|
:success_count,
|
@@ -20,6 +20,7 @@ module Minitest
|
|
20
20
|
broken: [],
|
21
21
|
failure: [],
|
22
22
|
skipped: [],
|
23
|
+
painful: [],
|
23
24
|
slow: []
|
24
25
|
}
|
25
26
|
@start_time = nil
|
@@ -69,12 +70,20 @@ module Minitest
|
|
69
70
|
issues.fetch(:skipped) { [] }
|
70
71
|
end
|
71
72
|
|
73
|
+
def painfuls
|
74
|
+
issues
|
75
|
+
.fetch(:painful) { [] }
|
76
|
+
.sort_by(&:time)
|
77
|
+
.reverse
|
78
|
+
.take(5)
|
79
|
+
end
|
80
|
+
|
72
81
|
def slows
|
73
82
|
issues
|
74
83
|
.fetch(:slow) { [] }
|
75
|
-
.
|
84
|
+
.sort_by(&:time)
|
76
85
|
.reverse
|
77
|
-
.take(
|
86
|
+
.take(5)
|
78
87
|
end
|
79
88
|
|
80
89
|
def errors?
|
@@ -93,6 +102,10 @@ module Minitest
|
|
93
102
|
skips.any?
|
94
103
|
end
|
95
104
|
|
105
|
+
def painfuls?
|
106
|
+
painfuls.any?
|
107
|
+
end
|
108
|
+
|
96
109
|
def slows?
|
97
110
|
slows.any?
|
98
111
|
end
|
data/lib/minitest/heat.rb
CHANGED
data/lib/minitest/heat_plugin.rb
CHANGED
@@ -3,12 +3,12 @@
|
|
3
3
|
require_relative 'heat_reporter'
|
4
4
|
|
5
5
|
module Minitest
|
6
|
-
def self.plugin_heat_options(opts,
|
7
|
-
opts.on '--show-fast',
|
6
|
+
def self.plugin_heat_options(opts, _options)
|
7
|
+
opts.on '--show-fast', 'Show failures as they happen instead of waiting for the entire suite.' do
|
8
8
|
# Heat.show_fast!
|
9
9
|
end
|
10
10
|
|
11
|
-
# TODO options.
|
11
|
+
# TODO: options.
|
12
12
|
# 1. Fail Fast
|
13
13
|
# 2. Don't worry about skips.
|
14
14
|
# 3. Skip coverage.
|
@@ -18,9 +18,9 @@ module Minitest
|
|
18
18
|
io = options[:io]
|
19
19
|
|
20
20
|
# Clean out the existing reporters.
|
21
|
-
|
21
|
+
reporter.reporters = []
|
22
22
|
|
23
23
|
# Use Reviewer as the sole reporter.
|
24
|
-
|
24
|
+
reporter << HeatReporter.new(io, options)
|
25
25
|
end
|
26
26
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative 'heat'
|
4
4
|
|
5
5
|
module Minitest
|
6
6
|
# Custom minitest reporter to proactively identify likely culprits in test failures by focusing on
|
@@ -29,31 +29,31 @@ module Minitest
|
|
29
29
|
# Pulls from minitest-color as well:
|
30
30
|
# https://github.com/teoljungberg/minitest-color/blob/master/lib/minitest/color_plugin.rb
|
31
31
|
class HeatReporter < AbstractReporter
|
32
|
-
|
33
32
|
attr_reader :output,
|
34
33
|
:options,
|
35
34
|
:results,
|
36
35
|
:map
|
37
36
|
|
38
37
|
def initialize(io = $stdout, options = {})
|
39
|
-
@output = Heat::Output.new(io)
|
40
38
|
@options = options
|
41
39
|
|
42
|
-
@results =
|
43
|
-
@map =
|
40
|
+
@results = Heat::Results.new
|
41
|
+
@map = Heat::Map.new
|
42
|
+
@output = Heat::Output.new(io)
|
44
43
|
end
|
45
44
|
|
46
45
|
# Starts reporting on the run.
|
47
46
|
def start
|
48
|
-
|
49
|
-
|
50
|
-
|
47
|
+
results.start_timer!
|
48
|
+
|
49
|
+
# A couple of blank lines to create some breathing room
|
50
|
+
output.newline
|
51
|
+
output.newline
|
51
52
|
end
|
52
53
|
|
53
54
|
# About to start running a test. This allows a reporter to show that it is starting or that we
|
54
55
|
# are in the middle of a test run.
|
55
|
-
def prerecord(klass, name)
|
56
|
-
end
|
56
|
+
def prerecord(klass, name); end
|
57
57
|
|
58
58
|
# Records the data from a result.
|
59
59
|
# Minitest::Result source:
|
@@ -61,16 +61,17 @@ module Minitest
|
|
61
61
|
def record(result)
|
62
62
|
issue = Heat::Issue.new(result)
|
63
63
|
|
64
|
-
|
65
|
-
|
64
|
+
results.record(issue)
|
65
|
+
map.add(*issue.to_hit) if issue.hit?
|
66
66
|
|
67
|
-
output.marker(issue.
|
67
|
+
output.marker(issue.type)
|
68
68
|
end
|
69
69
|
|
70
70
|
# Outputs the summary of the run.
|
71
71
|
def report
|
72
|
-
|
72
|
+
results.stop_timer!
|
73
73
|
|
74
|
+
# A couple of blank lines to create some breathing room
|
74
75
|
output.newline
|
75
76
|
output.newline
|
76
77
|
|
@@ -78,20 +79,20 @@ module Minitest
|
|
78
79
|
# pressing issues are displayed at the bottom of the report in order to reduce scrolling.
|
79
80
|
# This way, as you fix issues, the list gets shorter, and eventually the least critical
|
80
81
|
# issues will be displayed without scrolling once more problematic issues are resolved.
|
81
|
-
|
82
|
-
results.
|
82
|
+
%i[slows painfuls skips failures brokens errors].each do |issue_category|
|
83
|
+
results.send(issue_category).each { |issue| output.issue_details(issue) }
|
83
84
|
end
|
84
85
|
|
85
|
-
|
86
|
-
|
87
|
-
end
|
88
|
-
|
89
|
-
results.failures.each { |issue| output.issue_details(issue) }
|
90
|
-
results.brokens.each { |issue| output.issue_details(issue) }
|
91
|
-
results.errors.each { |issue| output.issue_details(issue) }
|
92
|
-
|
86
|
+
# Display a short summary of the total issue counts fore ach category as well as performance
|
87
|
+
# details for the test suite as a whole
|
93
88
|
output.compact_summary(results)
|
89
|
+
|
90
|
+
# If there were issues, shows a short heat map summary of which files and lines were the most
|
91
|
+
# common sources of issues
|
94
92
|
output.heat_map(map)
|
93
|
+
|
94
|
+
# A blank line to create some breathing room
|
95
|
+
output.newline
|
95
96
|
end
|
96
97
|
|
97
98
|
# Did this run pass?
|
data/minitest-heat.gemspec
CHANGED
@@ -21,8 +21,6 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.metadata['source_code_uri'] = 'https://github.com/garrettdimon/minitest-heat'
|
22
22
|
spec.metadata['wiki_uri'] = 'https://github.com/garrettdimon/minitest-heat/wiki'
|
23
23
|
|
24
|
-
|
25
|
-
|
26
24
|
# Specify which files should be added to the gem when it is released.
|
27
25
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
28
26
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
@@ -34,7 +32,11 @@ Gem::Specification.new do |spec|
|
|
34
32
|
|
35
33
|
spec.add_runtime_dependency 'minitest'
|
36
34
|
|
35
|
+
spec.add_development_dependency 'awesome_print'
|
37
36
|
spec.add_development_dependency 'dead_end'
|
38
37
|
spec.add_development_dependency 'pry'
|
38
|
+
spec.add_development_dependency 'rubocop'
|
39
|
+
spec.add_development_dependency 'rubocop-minitest'
|
40
|
+
spec.add_development_dependency 'rubocop-rake'
|
39
41
|
spec.add_development_dependency 'simplecov'
|
40
42
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: minitest-heat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Garrett Dimon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-10-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: awesome_print
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: dead_end
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,6 +66,48 @@ dependencies:
|
|
52
66
|
- - ">="
|
53
67
|
- !ruby/object:Gem::Version
|
54
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop-minitest
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop-rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
55
111
|
- !ruby/object:Gem::Dependency
|
56
112
|
name: simplecov
|
57
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -75,6 +131,7 @@ extensions: []
|
|
75
131
|
extra_rdoc_files: []
|
76
132
|
files:
|
77
133
|
- ".gitignore"
|
134
|
+
- ".rubocop.yml"
|
78
135
|
- ".travis.yml"
|
79
136
|
- CODE_OF_CONDUCT.md
|
80
137
|
- Gemfile
|
@@ -86,14 +143,16 @@ files:
|
|
86
143
|
- bin/setup
|
87
144
|
- lib/minitest/heat.rb
|
88
145
|
- lib/minitest/heat/backtrace.rb
|
146
|
+
- lib/minitest/heat/hit.rb
|
89
147
|
- lib/minitest/heat/issue.rb
|
148
|
+
- lib/minitest/heat/line.rb
|
90
149
|
- lib/minitest/heat/location.rb
|
91
150
|
- lib/minitest/heat/map.rb
|
92
151
|
- lib/minitest/heat/output.rb
|
93
152
|
- lib/minitest/heat/output/backtrace.rb
|
94
153
|
- lib/minitest/heat/output/issue.rb
|
95
|
-
- lib/minitest/heat/output/location.rb
|
96
154
|
- lib/minitest/heat/output/map.rb
|
155
|
+
- lib/minitest/heat/output/marker.rb
|
97
156
|
- lib/minitest/heat/output/results.rb
|
98
157
|
- lib/minitest/heat/output/source_code.rb
|
99
158
|
- lib/minitest/heat/output/token.rb
|
@@ -128,7 +187,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
128
187
|
- !ruby/object:Gem::Version
|
129
188
|
version: '0'
|
130
189
|
requirements: []
|
131
|
-
rubygems_version: 3.1.
|
190
|
+
rubygems_version: 3.1.6
|
132
191
|
signing_key:
|
133
192
|
specification_version: 4
|
134
193
|
summary: Presents test results in a visual manner to guide you to where to look first.
|
@@ -1,20 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Minitest
|
4
|
-
module Heat
|
5
|
-
class Output
|
6
|
-
class Location
|
7
|
-
attr_accessor :location
|
8
|
-
|
9
|
-
def initialize(location)
|
10
|
-
@location = location
|
11
|
-
end
|
12
|
-
|
13
|
-
def tokens
|
14
|
-
end
|
15
|
-
|
16
|
-
private
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|