tapout 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.ruby +6 -5
- data/HISTORY.rdoc +11 -0
- data/PROFILE +1 -1
- data/TAP-YJ.md +32 -12
- data/lib/tapout.rb +16 -8
- data/lib/tapout.yml +46 -0
- data/lib/tapout/adapters/perl.rb +1 -1
- data/lib/tapout/core_ext.rb +31 -0
- data/lib/tapout/parsers/json.rb +1 -1
- data/lib/tapout/reporters.rb +10 -6
- data/lib/tapout/reporters/abstract.rb +90 -53
- data/lib/tapout/reporters/{breakdown.rb → breakdown_reporter.rb} +5 -4
- data/lib/tapout/reporters/{dotprogress.rb → dot_reporter.rb} +4 -3
- data/lib/tapout/reporters/{html.rb → html_reporter.rb} +2 -2
- data/lib/tapout/reporters/{outline.rb → outline_reporter.rb} +2 -2
- data/lib/tapout/reporters/pretty_reporter.rb +205 -0
- data/lib/tapout/reporters/progress_reporter.rb +205 -0
- data/lib/tapout/reporters/{tap.rb → tap_reporter.rb} +2 -2
- data/lib/tapout/reporters/turn_reporter.rb +178 -0
- data/lib/tapout/version.rb +17 -4
- data/spec/applique/env.rb +5 -0
- data/spec/perl_adapter.rdoc +68 -0
- data/spec/reporters/applique/cli.rb +13 -0
- data/spec/reporters/fixtures/tapy.yml +78 -0
- data/spec/reporters/reporters.rdoc +86 -0
- data/test/unit/test-progressbar.rb +5 -0
- metadata +26 -16
- data/lib/tapout/reporters/progressbar.rb +0 -93
data/.ruby
CHANGED
@@ -29,17 +29,18 @@ resources:
|
|
29
29
|
code: http://github.com/rubyworks/tapout
|
30
30
|
load_path:
|
31
31
|
- lib
|
32
|
-
extra:
|
33
|
-
|
34
|
-
version: 0.2.3
|
35
|
-
alternatives: []
|
36
|
-
revision: 0
|
32
|
+
extra:
|
33
|
+
manifest: MANIFEST
|
37
34
|
source:
|
38
35
|
- PROFILE
|
36
|
+
alternatives: []
|
37
|
+
revision: 0
|
39
38
|
name: tapout
|
40
39
|
title: TapOut
|
40
|
+
version: 0.3.0
|
41
41
|
summary: Progressive TAP Harness
|
42
42
|
description: Tapout is a TAP consumer that can take any TAP, TAP-Y or TAP-J stream
|
43
43
|
and output it in a variety of useful formats.
|
44
44
|
organization: RubyWorks
|
45
45
|
created: '2010-12-23'
|
46
|
+
date: '2011-10-08'
|
data/HISTORY.rdoc
CHANGED
@@ -1,6 +1,17 @@
|
|
1
1
|
= RELEASE HISTORY
|
2
2
|
|
3
3
|
|
4
|
+
== 0.3.0 / 2011-10-09
|
5
|
+
|
6
|
+
This release supports revision 3 of the TAP-Y/J specification, which adds `final`
|
7
|
+
to the document types. The `final` document is the last document of a test suite.
|
8
|
+
It takes the place of `tally`, which is now simply an optional running subtotal
|
9
|
+
document.
|
10
|
+
|
11
|
+
This release also makes some intreface imporvements to the `Reporters::Abstract`
|
12
|
+
base class and ports over all applicable reporters from othe Turn project.
|
13
|
+
|
14
|
+
|
4
15
|
== 0.2.3 / 2011-10-08
|
5
16
|
|
6
17
|
Exit code are important for a test output format. This release addressed that
|
data/PROFILE
CHANGED
data/TAP-YJ.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# TAP-Y/J
|
1
|
+
# TAP-Y/J Specification
|
2
2
|
|
3
3
|
TAP-Y and TAP-J are test streams. They are essentially the same except
|
4
4
|
for the underlying format used, which are YAML and JSON repsectively.
|
@@ -26,12 +26,15 @@ A `suite` document marks the beginning of a forthcoming stream of tests,
|
|
26
26
|
i.e. a <i>test suite</i>. All TAP-Y streams MUST begin with a suite
|
27
27
|
document.
|
28
28
|
|
29
|
+
|
29
30
|
---
|
30
31
|
type: suite
|
31
32
|
start: 2011-10-10 12:12:32
|
32
33
|
count: 2
|
34
|
+
seed: 32154
|
33
35
|
rev: 2
|
34
36
|
|
37
|
+
|
35
38
|
The `start` field marks the date and time testing began. It MUST be
|
36
39
|
an ISO-8601 formated timestamp.
|
37
40
|
|
@@ -40,6 +43,9 @@ If the number of test units is unknown, the total can be omitted or
|
|
40
43
|
marked as `~` (nil). The total should only indicate the number of
|
41
44
|
<i>test units</i>, not any enumeration of <i>test cases</i>.
|
42
45
|
|
46
|
+
The `seed` is provided if the test runner has randomized the order of
|
47
|
+
execution unit tests. The seed can be used to reproduce the same order.
|
48
|
+
|
43
49
|
The `rev` field provides the version of the TAP-Y/J format that is
|
44
50
|
being used. The specification will change little, if at all, as it
|
45
51
|
become more mainstream. But just in case, having a revision field
|
@@ -50,12 +56,14 @@ apps to adjust to any future variation.
|
|
50
56
|
|
51
57
|
The `case` type indicates the start of a test case.
|
52
58
|
|
59
|
+
|
53
60
|
---
|
54
61
|
type: case
|
55
62
|
subtype: feature
|
56
63
|
label: Multiplication
|
57
64
|
level: 0
|
58
65
|
|
66
|
+
|
59
67
|
The case document MAY provide a `class` which is a label for the
|
60
68
|
typ of test case. For example, a test framwework that uses Gherkin
|
61
69
|
nomenclature would classify a test case as a "feature".
|
@@ -79,6 +87,7 @@ common fields.
|
|
79
87
|
|
80
88
|
Here is an example of a passing unit document.
|
81
89
|
|
90
|
+
|
82
91
|
---
|
83
92
|
type: test
|
84
93
|
subtype: step
|
@@ -100,6 +109,7 @@ Here is an example of a passing unit document.
|
|
100
109
|
code: Foo#*
|
101
110
|
time: 0.01
|
102
111
|
|
112
|
+
|
103
113
|
Besides the `status`, all test documents MUST have a `label`.
|
104
114
|
|
105
115
|
A test document MAY provide a `setup` field, which is used to describe
|
@@ -140,6 +150,7 @@ If a test has a status other than `pass` it MUST also provide a `exception`
|
|
140
150
|
subsection which is used to describe the nature of the failure, error or
|
141
151
|
omission.
|
142
152
|
|
153
|
+
|
143
154
|
---
|
144
155
|
type: test
|
145
156
|
subtype: step
|
@@ -176,6 +187,7 @@ omission.
|
|
176
187
|
- test/test_foo.rb:45
|
177
188
|
time: 0.02
|
178
189
|
|
190
|
+
|
179
191
|
The `exception` section MUST give the `message`, describing the nature
|
180
192
|
of the failure or exception. In this subsection, `file` and `line` indicate
|
181
193
|
the location in code that triggered the exception or failed assertion.
|
@@ -200,25 +212,28 @@ code with having to do additional processing.
|
|
200
212
|
The `note` type is used to interject a message between tests that
|
201
213
|
is not tied to a specific unit or case. It has only a few fields.
|
202
214
|
|
215
|
+
|
203
216
|
---
|
204
217
|
type: note
|
205
218
|
text:
|
206
219
|
This is an example note.
|
207
220
|
|
221
|
+
|
208
222
|
The note document is simply used to interject any information the
|
209
223
|
tester might want to know, but doesn't properly fit elsewhere in the
|
210
224
|
stream. A note cna appear any where in the document stream prior
|
211
225
|
to the tally.
|
212
226
|
|
213
|
-
### Tally
|
227
|
+
### Final & Tally
|
228
|
+
|
229
|
+
The `final` and `tally` types are the same. The difference is only that a `tally`
|
230
|
+
entry is a running tally, and can technically occur anywhere in the document
|
231
|
+
stream. The `final` entry on the other hand incidates the end of a test suite,
|
232
|
+
which will be followed by an end-document-marker (`...`).
|
214
233
|
|
215
|
-
While a running tally can technically occur anywhere in the document
|
216
|
-
stream without consequence, it generally incidates the end of a test
|
217
|
-
suite, which is strictly complete with the end-document-marker (`...`)
|
218
|
-
appears.
|
219
234
|
|
220
235
|
---
|
221
|
-
type :
|
236
|
+
type : final
|
222
237
|
time : 0.03
|
223
238
|
counts:
|
224
239
|
total: 2
|
@@ -229,11 +244,17 @@ appears.
|
|
229
244
|
todo : 0
|
230
245
|
...
|
231
246
|
|
232
|
-
|
247
|
+
|
248
|
+
A tally/final document MUST provide a counts mapping with the `total` number of
|
233
249
|
tests (this MUST be same as `count` in the suite document if it was given)
|
234
250
|
and the totals for each test status. It SHOULD also give the time elapsed
|
235
251
|
since the suite time.
|
236
252
|
|
253
|
+
Tally documents are very rare, if used at all. They only make sense for very
|
254
|
+
large test suites as a progress report mechanism. As a rule of thumb, TAP-Y/J
|
255
|
+
consumer apps will ignore them unless a configuration option (e.g. `--verbose`)
|
256
|
+
is used.
|
257
|
+
|
237
258
|
As mentioned, the test stream ends when a full ellipsis (<code>...</code>)
|
238
259
|
appears.
|
239
260
|
|
@@ -241,7 +262,6 @@ As you can see TAP-Y streams provides a great deal of detail. They are not
|
|
241
262
|
intended for the end-user, but rather to pipe to a consuming app to process
|
242
263
|
into a human readable form.
|
243
264
|
|
244
|
-
|
245
265
|
## Glossery of Fields
|
246
266
|
|
247
267
|
### count
|
@@ -321,10 +341,10 @@ On the other hand, `todo` means the test will be used in the future
|
|
321
341
|
but implementation has not been completed. It serves as reminder to developers
|
322
342
|
to write a missing test.
|
323
343
|
|
324
|
-
###
|
344
|
+
### counts
|
325
345
|
|
326
|
-
The footer MUST provide
|
327
|
-
but broken down into status groups.
|
346
|
+
The footer MUST provide counts for all status categories and the total.
|
347
|
+
This is like `count` in the suite entry but broken down into status groups.
|
328
348
|
|
329
349
|
### time
|
330
350
|
|
data/lib/tapout.rb
CHANGED
@@ -3,27 +3,35 @@ require 'tapout/parsers'
|
|
3
3
|
|
4
4
|
module TapOut
|
5
5
|
|
6
|
-
#
|
7
|
-
|
6
|
+
#
|
7
|
+
def self.trace
|
8
|
+
@trace
|
9
|
+
end
|
10
|
+
|
11
|
+
#
|
12
|
+
def self.trace=(depth)
|
13
|
+
@trace = depth.to_i
|
14
|
+
end
|
8
15
|
|
16
|
+
# Command line interface.
|
9
17
|
#
|
10
18
|
def self.cli(*argv)
|
11
19
|
options = {}
|
12
20
|
type = :modern
|
13
21
|
|
14
22
|
parser = OptionParser.new do |opt|
|
15
|
-
opt.banner = "tapout [options] [
|
23
|
+
opt.banner = "tapout [options] [reporter]"
|
16
24
|
|
17
25
|
opt.separator("\nOPTIONS:")
|
18
26
|
|
19
|
-
#opt.on('--format', '-f FORMAT', 'Report format') do |fmt|
|
20
|
-
# options[:format] = fmt
|
21
|
-
#end
|
22
|
-
|
23
27
|
#opt.on('-t', '--tap', 'Consume legacy TAP input') do |fmt|
|
24
28
|
# type = :legacy
|
25
29
|
#end
|
26
30
|
|
31
|
+
opt.on('--trace', '-t DEPTH', 'set backtrace depth') do |depth|
|
32
|
+
self.trace = depth
|
33
|
+
end
|
34
|
+
|
27
35
|
opt.on('--no-color', 'Supress ANSI color codes') do
|
28
36
|
$ansi = false # TODO: Is this correct?
|
29
37
|
end
|
@@ -32,7 +40,7 @@ module TapOut
|
|
32
40
|
$DEBUG = true
|
33
41
|
end
|
34
42
|
|
35
|
-
opt.separator("\
|
43
|
+
opt.separator("\nREPORTERS:\n " + Reporters.index.keys.join("\n "))
|
36
44
|
end
|
37
45
|
|
38
46
|
parser.parse!(argv)
|
data/lib/tapout.yml
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
---
|
2
|
+
authors:
|
3
|
+
- name: Thomas Sawyer
|
4
|
+
email: transfire@gmail.com
|
5
|
+
copyrights:
|
6
|
+
- holder: Thomas Sawyer
|
7
|
+
year: '2010'
|
8
|
+
license: BSD-2-Clause
|
9
|
+
replacements: []
|
10
|
+
conflicts: []
|
11
|
+
requirements:
|
12
|
+
- name: ansi
|
13
|
+
- name: json
|
14
|
+
- name: detroit
|
15
|
+
groups:
|
16
|
+
- build
|
17
|
+
development: true
|
18
|
+
- name: qed
|
19
|
+
groups:
|
20
|
+
- test
|
21
|
+
development: true
|
22
|
+
dependencies: []
|
23
|
+
repositories:
|
24
|
+
- uri: git://github.com/rubyworks/tapout.git
|
25
|
+
scm: git
|
26
|
+
name: upstream
|
27
|
+
resources:
|
28
|
+
home: http://rubyworks.github.com/tapout
|
29
|
+
code: http://github.com/rubyworks/tapout
|
30
|
+
load_path:
|
31
|
+
- lib
|
32
|
+
extra:
|
33
|
+
manifest: MANIFEST
|
34
|
+
source:
|
35
|
+
- PROFILE
|
36
|
+
alternatives: []
|
37
|
+
revision: 0
|
38
|
+
name: tapout
|
39
|
+
title: TapOut
|
40
|
+
version: 0.3.0
|
41
|
+
summary: Progressive TAP Harness
|
42
|
+
description: Tapout is a TAP consumer that can take any TAP, TAP-Y or TAP-J stream
|
43
|
+
and output it in a variety of useful formats.
|
44
|
+
organization: RubyWorks
|
45
|
+
created: '2010-12-23'
|
46
|
+
date: '2011-10-08'
|
data/lib/tapout/adapters/perl.rb
CHANGED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Borrowed methods from Ruby Facets.
|
2
|
+
|
3
|
+
class String
|
4
|
+
|
5
|
+
# Aligns each line n spaces.
|
6
|
+
def tab(n)
|
7
|
+
gsub(/^ */, ' ' * n)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Preserves relative tabbing.
|
11
|
+
# The first non-empty line ends up with n spaces before nonspace.
|
12
|
+
def tabto(n)
|
13
|
+
if self =~ /^( *)\S/
|
14
|
+
indent(n - $1.length)
|
15
|
+
else
|
16
|
+
self
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Indent left or right by n spaces.
|
21
|
+
# (This used to be called #tab and aliased as #indent.)
|
22
|
+
def indent(n, c=' ')
|
23
|
+
if n >= 0
|
24
|
+
gsub(/^/, c * n)
|
25
|
+
else
|
26
|
+
gsub(/^#{Regexp.escape(c)}{0,#{-n}}/, "")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
data/lib/tapout/parsers/json.rb
CHANGED
data/lib/tapout/reporters.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
require 'tapout/reporters/abstract'
|
2
|
-
|
3
|
-
require 'tapout/reporters/
|
4
|
-
require 'tapout/reporters/
|
5
|
-
require 'tapout/reporters/
|
6
|
-
require 'tapout/reporters/
|
7
|
-
require 'tapout/reporters/
|
2
|
+
|
3
|
+
require 'tapout/reporters/dot_reporter'
|
4
|
+
require 'tapout/reporters/pretty_reporter'
|
5
|
+
require 'tapout/reporters/turn_reporter'
|
6
|
+
require 'tapout/reporters/html_reporter'
|
7
|
+
require 'tapout/reporters/outline_reporter'
|
8
|
+
require 'tapout/reporters/progress_reporter'
|
9
|
+
require 'tapout/reporters/breakdown_reporter'
|
10
|
+
require 'tapout/reporters/tap_reporter'
|
11
|
+
|
@@ -22,9 +22,11 @@ module TapOut
|
|
22
22
|
#
|
23
23
|
# TODO: Simplify this class and have the sublcasses handle more of the load.
|
24
24
|
class Abstract
|
25
|
+
|
25
26
|
# When Abstract is inherited it saves a reference to it in `Reporters.index`.
|
26
27
|
def self.inherited(subclass)
|
27
28
|
name = subclass.name.split('::').last.downcase
|
29
|
+
name = name.chomp('reporter')
|
28
30
|
Reporters.index[name] = subclass
|
29
31
|
end
|
30
32
|
|
@@ -36,55 +38,16 @@ module TapOut
|
|
36
38
|
@skipped = []
|
37
39
|
@omitted = []
|
38
40
|
|
39
|
-
@
|
40
|
-
@
|
41
|
-
@exit_code
|
41
|
+
@case_stack = []
|
42
|
+
@source = {}
|
43
|
+
@exit_code = 0 # assume passing
|
42
44
|
end
|
43
45
|
|
44
|
-
#
|
45
|
-
def
|
46
|
+
# When all is said and done.
|
47
|
+
def finalize
|
46
48
|
@exit_code
|
47
49
|
end
|
48
50
|
|
49
|
-
#
|
50
|
-
def <<(entry)
|
51
|
-
handle(entry)
|
52
|
-
end
|
53
|
-
|
54
|
-
# Handler method. This dispatches a given entry to the appropriate
|
55
|
-
# report methods.
|
56
|
-
def handle(entry)
|
57
|
-
case entry['type']
|
58
|
-
when 'suite'
|
59
|
-
start_suite(entry)
|
60
|
-
when 'case'
|
61
|
-
finish_case(@previous_case) if @previous_case
|
62
|
-
@previous_case = entry
|
63
|
-
start_case(entry)
|
64
|
-
when 'note'
|
65
|
-
note(entry)
|
66
|
-
when 'test'
|
67
|
-
test(entry)
|
68
|
-
case entry['status']
|
69
|
-
when 'pass'
|
70
|
-
pass(entry)
|
71
|
-
when 'fail'
|
72
|
-
@exit_code = -1
|
73
|
-
fail(entry)
|
74
|
-
when 'error'
|
75
|
-
@exit_code = -1
|
76
|
-
err(entry)
|
77
|
-
when 'omit'
|
78
|
-
omit(entry)
|
79
|
-
when 'todo', 'skip', 'pending'
|
80
|
-
skip(entry)
|
81
|
-
end
|
82
|
-
when 'tally'
|
83
|
-
finish_case(@previous_case) if @previous_case
|
84
|
-
finish_suite(entry)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
51
|
# Handle header.
|
89
52
|
def start_suite(entry)
|
90
53
|
end
|
@@ -93,12 +56,8 @@ module TapOut
|
|
93
56
|
def start_case(entry)
|
94
57
|
end
|
95
58
|
|
96
|
-
# Handle an arbitray note.
|
97
|
-
def note(entry)
|
98
|
-
end
|
99
|
-
|
100
59
|
# Handle test. This is run before the status handlers.
|
101
|
-
def
|
60
|
+
def start_test(entry)
|
102
61
|
end
|
103
62
|
|
104
63
|
# Handle test with pass status.
|
@@ -112,7 +71,7 @@ module TapOut
|
|
112
71
|
end
|
113
72
|
|
114
73
|
# Handle test with error status.
|
115
|
-
def
|
74
|
+
def error(entry)
|
116
75
|
@raised << entry
|
117
76
|
end
|
118
77
|
|
@@ -126,16 +85,79 @@ module TapOut
|
|
126
85
|
@skipped << entry
|
127
86
|
end
|
128
87
|
|
88
|
+
# Handle an arbitray note.
|
89
|
+
def note(entry)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Handle running tally.
|
93
|
+
def tally(entry)
|
94
|
+
end
|
95
|
+
|
96
|
+
# When a test unit is complete.
|
97
|
+
def finish_test(entry)
|
98
|
+
end
|
99
|
+
|
129
100
|
# When a test case is complete.
|
130
101
|
def finish_case(entry)
|
131
102
|
end
|
132
103
|
|
133
|
-
# Handle
|
104
|
+
# Handle final entry.
|
134
105
|
def finish_suite(entry)
|
135
106
|
end
|
136
107
|
|
137
|
-
#
|
138
|
-
|
108
|
+
# -- H A N D L E R --
|
109
|
+
|
110
|
+
#
|
111
|
+
def <<(entry)
|
112
|
+
handle(entry)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Handler method. This dispatches a given entry to the appropriate
|
116
|
+
# report methods.
|
117
|
+
def handle(entry)
|
118
|
+
case entry['type']
|
119
|
+
when 'suite'
|
120
|
+
start_suite(entry)
|
121
|
+
when 'case'
|
122
|
+
complete_cases(entry)
|
123
|
+
@case_stack << entry
|
124
|
+
start_case(entry)
|
125
|
+
when 'note'
|
126
|
+
note(entry)
|
127
|
+
when 'test'
|
128
|
+
start_test(entry)
|
129
|
+
case entry['status']
|
130
|
+
when 'pass'
|
131
|
+
pass(entry)
|
132
|
+
when 'fail'
|
133
|
+
@exit_code = -1
|
134
|
+
fail(entry)
|
135
|
+
when 'error'
|
136
|
+
@exit_code = -1
|
137
|
+
error(entry)
|
138
|
+
when 'omit'
|
139
|
+
omit(entry)
|
140
|
+
when 'todo', 'skip', 'pending'
|
141
|
+
skip(entry)
|
142
|
+
end
|
143
|
+
finish_test(entry)
|
144
|
+
when 'tally'
|
145
|
+
tally(entry)
|
146
|
+
when 'final'
|
147
|
+
complete_cases
|
148
|
+
finish_suite(entry)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Get the exit code.
|
153
|
+
def exit_code
|
154
|
+
@exit_code
|
155
|
+
end
|
156
|
+
|
157
|
+
# Generate a tally message given a tally or final entry.
|
158
|
+
#
|
159
|
+
# @return [String] tally message
|
160
|
+
def tally_message(entry)
|
139
161
|
total = @passed.size + @failed.size + @raised.size #+ @skipped.size + @omitted.size
|
140
162
|
|
141
163
|
if entry['counts']
|
@@ -174,7 +196,9 @@ module TapOut
|
|
174
196
|
end
|
175
197
|
end
|
176
198
|
|
199
|
+
# Used to clean-up backtrace.
|
177
200
|
#
|
201
|
+
# TODO: Use Rubinius global system instead.
|
178
202
|
INTERNALS = /(lib|bin)#{Regexp.escape(File::SEPARATOR)}tapout/
|
179
203
|
|
180
204
|
# Clean the backtrace of any reference to ko/ paths and code.
|
@@ -270,6 +294,19 @@ module TapOut
|
|
270
294
|
returnf source_file, source_line
|
271
295
|
end
|
272
296
|
|
297
|
+
#
|
298
|
+
def complete_cases(case_entry=nil)
|
299
|
+
if case_entry
|
300
|
+
while @case_stack.last and @case_stack.last['level'].to_i >= case_entry['level'].to_i
|
301
|
+
finish_case(@case_stack.pop)
|
302
|
+
end
|
303
|
+
else
|
304
|
+
while @case_stack.last
|
305
|
+
finish_case(@case_stack.pop)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
273
310
|
end#class Abstract
|
274
311
|
|
275
312
|
end#module Reporters
|