tapout 0.2.3 → 0.3.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/.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
|