rspec-ontap 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/.ruby +38 -0
- data/.yardopts +7 -0
- data/COPYING.rdoc +32 -0
- data/HISTORY.md +13 -0
- data/README.md +49 -0
- data/lib/rspec/ontap.rb +336 -0
- data/lib/rspec/tap.rb +336 -0
- data/lib/rspec/tap_j.rb +5 -0
- data/lib/rspec/tap_y.rb +5 -0
- data/spec/demonstrate.rb +16 -0
- metadata +92 -0
data/.ruby
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
---
|
2
|
+
source:
|
3
|
+
- var
|
4
|
+
authors:
|
5
|
+
- name: Trans
|
6
|
+
email: transfire@gmail.com
|
7
|
+
copyrights:
|
8
|
+
- holder: Rubyworks
|
9
|
+
year: '2011'
|
10
|
+
license: BSD-2-Clause
|
11
|
+
replacements: []
|
12
|
+
alternatives: []
|
13
|
+
requirements:
|
14
|
+
- name: rspec
|
15
|
+
- name: tapout
|
16
|
+
- name: detroit
|
17
|
+
groups:
|
18
|
+
- build
|
19
|
+
development: true
|
20
|
+
dependencies: []
|
21
|
+
conflicts: []
|
22
|
+
repositories: []
|
23
|
+
resources:
|
24
|
+
home: http://rubyworks.github.com/rspec-ontap
|
25
|
+
code: http://github.com/rubyworks/rspec-ontap
|
26
|
+
mail: http://google.groups.com/groups/rubyworks-mailinglist
|
27
|
+
extra: {}
|
28
|
+
load_path:
|
29
|
+
- lib
|
30
|
+
revision: 0
|
31
|
+
created: '2011-12-13'
|
32
|
+
summary: RSpec On Tap-Y/J
|
33
|
+
title: RSpecOnTap
|
34
|
+
version: 0.1.0
|
35
|
+
name: rspec-ontap
|
36
|
+
description: RSpecOnTap is a TAP-Y/J formatter for RSpec.
|
37
|
+
organization: Rubyworks
|
38
|
+
date: '2011-12-12'
|
data/.yardopts
ADDED
data/COPYING.rdoc
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
= COPYRIGHT NOTICES
|
2
|
+
|
3
|
+
== RSpecOnTap
|
4
|
+
|
5
|
+
Copyright:: (c) 2008 Rubyworks
|
6
|
+
License:: BSD-2-Clause
|
7
|
+
Website:: http://rubyworks.github.com/rspec-ontap
|
8
|
+
|
9
|
+
Copyright 2008 Rubyworks. All rights reserved.
|
10
|
+
|
11
|
+
Redistribution and use in source and binary forms, with or without
|
12
|
+
modification, are permitted provided that the following conditions are met:
|
13
|
+
|
14
|
+
1. Redistributions of source code must retain the above copyright notice,
|
15
|
+
this list of conditions and the following disclaimer.
|
16
|
+
|
17
|
+
2. Redistributions in binary form must reproduce the above copyright
|
18
|
+
notice, this list of conditions and the following disclaimer in the
|
19
|
+
documentation and/or other materials provided with the distribution.
|
20
|
+
|
21
|
+
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
22
|
+
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
23
|
+
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
24
|
+
COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
25
|
+
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
26
|
+
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
27
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
28
|
+
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
29
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
30
|
+
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
31
|
+
|
32
|
+
|
data/HISTORY.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Release History
|
2
|
+
|
3
|
+
|
4
|
+
## 0.1.0 / 2011-12-13
|
5
|
+
|
6
|
+
This is the first release of `RSpec On Tap`, a formatter for RSpec
|
7
|
+
providing TAP-Y and TAP-J output, which can then be used with
|
8
|
+
TapOut to produce a variety of formats.
|
9
|
+
|
10
|
+
Changes:
|
11
|
+
|
12
|
+
* The code was written.
|
13
|
+
|
data/README.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# RSpec On Tap
|
2
|
+
|
3
|
+
[Homepage](http://rubyworks.github.com/rspec-ontap) |
|
4
|
+
[Source Code](http://github.com/rubyworks/rspec-ontap) |
|
5
|
+
[Report Issue](http://github.com/rubyworks/rspec-ontap/issues)
|
6
|
+
|
7
|
+
|
8
|
+
## Description
|
9
|
+
|
10
|
+
**RSpec On Tap** is a TAP-Y/J formatter for RSpec.
|
11
|
+
|
12
|
+
You can learn more about TAP-Y/J [here](https://github.com/rubyworks/tapout).
|
13
|
+
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
Usage is simply a matter of passing the name of the format class to
|
18
|
+
the `rspec` command via the `-f` option.
|
19
|
+
|
20
|
+
$ rspec -r rspec/ontap -f TapY spec/*.rb
|
21
|
+
|
22
|
+
or
|
23
|
+
|
24
|
+
$ rspec -f RSpec::TapY spec/*.rb
|
25
|
+
|
26
|
+
With TAP-Y in hand, the `tapout` tool can then be used to produce a
|
27
|
+
variety of other output formats. For example:
|
28
|
+
|
29
|
+
$ rspec -f RSpec::TapY spec/*.rb | tapout progress
|
30
|
+
|
31
|
+
See the [TapOut project](http://rubyworks.github.com/tapout) for more
|
32
|
+
information.
|
33
|
+
|
34
|
+
|
35
|
+
## Installation
|
36
|
+
|
37
|
+
Installation follows the usual pattern:
|
38
|
+
|
39
|
+
$ gem install rspec-ontap
|
40
|
+
|
41
|
+
|
42
|
+
## Copyrights
|
43
|
+
|
44
|
+
Copyright (c) 2011 Rubyworks. All Rights Reserved.
|
45
|
+
|
46
|
+
RSpecOnTap is distributable in accordance with the *FreeBSD* license.
|
47
|
+
|
48
|
+
See COPYING.md for details.
|
49
|
+
|
data/lib/rspec/ontap.rb
ADDED
@@ -0,0 +1,336 @@
|
|
1
|
+
require 'rspec/core/formatters/base_formatter'
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
#module Core
|
5
|
+
#module Formatters
|
6
|
+
|
7
|
+
#
|
8
|
+
class TapBaseFormatter < Core::Formatters::BaseFormatter
|
9
|
+
|
10
|
+
# TAP-Y/J Revision
|
11
|
+
REVISION = 3
|
12
|
+
|
13
|
+
attr_accessor :example_group_stack
|
14
|
+
|
15
|
+
def initialize(output)
|
16
|
+
super(output)
|
17
|
+
@example_group_stack = []
|
18
|
+
end
|
19
|
+
|
20
|
+
# This method is invoked before any examples are run, right after
|
21
|
+
# they have all been collected. This can be useful for special
|
22
|
+
# formatters that need to provide progress on feedback (graphical ones)
|
23
|
+
#
|
24
|
+
# This will only be invoked once, and the next one to be invoked
|
25
|
+
# is #example_group_started
|
26
|
+
def start(example_count)
|
27
|
+
super(example_count)
|
28
|
+
|
29
|
+
@start_time = Time.now
|
30
|
+
|
31
|
+
doc = {
|
32
|
+
'type' => 'suite',
|
33
|
+
'start' => @start_time.strftime('%Y-%m-%d %H:%M:%S'),
|
34
|
+
'count' => example_count,
|
35
|
+
'seed' => @seed,
|
36
|
+
'rev' => REVISION
|
37
|
+
}
|
38
|
+
return doc
|
39
|
+
end
|
40
|
+
|
41
|
+
# This method is invoked at the beginning of the execution of each example group.
|
42
|
+
# +example_group+ is the example_group.
|
43
|
+
#
|
44
|
+
# The next method to be invoked after this is +example_passed+,
|
45
|
+
# +example_pending+, or +example_finished+
|
46
|
+
def example_group_started(example_group)
|
47
|
+
super(example_group) #@example_group = example_group
|
48
|
+
doc = {
|
49
|
+
'type' => 'case',
|
50
|
+
'subtype' => 'describe',
|
51
|
+
'label' => "#{example_group.description}",
|
52
|
+
'level' => @example_group_stack.size
|
53
|
+
}
|
54
|
+
@example_group_stack << example_group
|
55
|
+
return doc
|
56
|
+
end
|
57
|
+
|
58
|
+
# This method is invoked at the end of the execution of each example group.
|
59
|
+
# +example_group+ is the example_group.
|
60
|
+
def example_group_finished(example_group)
|
61
|
+
@example_group_stack.pop
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
def example_started(example)
|
66
|
+
examples << example
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
def example_passed(example)
|
71
|
+
super(example)
|
72
|
+
|
73
|
+
file, line = example.location.split(':')
|
74
|
+
file = self.class.relative_path(file)
|
75
|
+
line = line.to_i
|
76
|
+
|
77
|
+
doc = {
|
78
|
+
'type' => 'test',
|
79
|
+
'subtype' => 'it',
|
80
|
+
'status' => 'pass',
|
81
|
+
#'setup': foo instance
|
82
|
+
'label' => "#{example.description}",
|
83
|
+
#'expected' => 2
|
84
|
+
#'returned' => 2
|
85
|
+
'file' => file,
|
86
|
+
'line' => line,
|
87
|
+
'source' => source(file)[line-1].strip,
|
88
|
+
'snippet' => code_snippet(file, line),
|
89
|
+
#'coverage' => {
|
90
|
+
# file: lib/foo.rb
|
91
|
+
# line: 11..13
|
92
|
+
# code: Foo#*
|
93
|
+
#}
|
94
|
+
'time' => Time.now - @start_time
|
95
|
+
}
|
96
|
+
return doc
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
def example_pending(example)
|
101
|
+
super(example)
|
102
|
+
|
103
|
+
file, line = example.location.split(':')
|
104
|
+
file = self.class.relative_path(file)
|
105
|
+
line = line.to_i
|
106
|
+
|
107
|
+
doc = {
|
108
|
+
'type' => 'test',
|
109
|
+
'subtype' => 'it',
|
110
|
+
'status' => 'todo',
|
111
|
+
#'setup': foo instance
|
112
|
+
'label' => "#{example.description}",
|
113
|
+
'file' => file,
|
114
|
+
'line' => line,
|
115
|
+
'source' => source(file)[line-1].strip,
|
116
|
+
'snippet' => code_snippet(file, line),
|
117
|
+
#'coverage' => {
|
118
|
+
# file: lib/foo.rb
|
119
|
+
# line: 11..13
|
120
|
+
# code: Foo#*
|
121
|
+
#}
|
122
|
+
'time' => Time.now - @start_time
|
123
|
+
}
|
124
|
+
return doc
|
125
|
+
end
|
126
|
+
|
127
|
+
def example_failed(example)
|
128
|
+
super(example)
|
129
|
+
|
130
|
+
file, line = example.location.split(':')
|
131
|
+
|
132
|
+
file = self.class.relative_path(file)
|
133
|
+
line = line.to_i
|
134
|
+
|
135
|
+
if RSpec::Expectations::ExpectationNotMetError === example.exception
|
136
|
+
status = 'fail'
|
137
|
+
if md = /expected:\s*(.*?)\n\s*got:\s*(.*?)\s+/.match(example.exception.to_s)
|
138
|
+
expected, returned = md[1], md[2]
|
139
|
+
else
|
140
|
+
expected, returned = nil, nil
|
141
|
+
end
|
142
|
+
else
|
143
|
+
status = 'error'
|
144
|
+
end
|
145
|
+
|
146
|
+
backtrace = format_backtrace(example.exception.backtrace, example)
|
147
|
+
|
148
|
+
efile, eline = parse_source_location(backtrace)
|
149
|
+
|
150
|
+
doc = {
|
151
|
+
'type' => 'test',
|
152
|
+
'subtype' => 'it',
|
153
|
+
'status' => status,
|
154
|
+
'label' => "#{example.description}",
|
155
|
+
#'setup' => "foo instance",
|
156
|
+
'file' => file,
|
157
|
+
'line' => line,
|
158
|
+
'source' => source(file)[line-1].strip,
|
159
|
+
'snippet' => code_snippet(file, line),
|
160
|
+
#'coverage' =>
|
161
|
+
#{
|
162
|
+
# 'file' => lib/foo.rb
|
163
|
+
# 'line' => 11..13
|
164
|
+
# 'code' => Foo#*
|
165
|
+
#}
|
166
|
+
}
|
167
|
+
|
168
|
+
if expected or returned
|
169
|
+
doc.update(
|
170
|
+
'expected' => expected,
|
171
|
+
'returned' => returned,
|
172
|
+
)
|
173
|
+
end
|
174
|
+
|
175
|
+
doc.update(
|
176
|
+
'exception' => {
|
177
|
+
'message' => example.exception.to_s.strip,
|
178
|
+
'class' => example.exception.class.name,
|
179
|
+
'file' => efile,
|
180
|
+
'line' => eline,
|
181
|
+
'source' => source(efile)[eline-1].strip,
|
182
|
+
'snippet' => code_snippet(efile, eline),
|
183
|
+
'backtrace' => backtrace
|
184
|
+
},
|
185
|
+
'time' => Time.now - @start_time
|
186
|
+
)
|
187
|
+
|
188
|
+
return doc
|
189
|
+
end
|
190
|
+
|
191
|
+
# @todo Is this a note?
|
192
|
+
def message(message)
|
193
|
+
end
|
194
|
+
|
195
|
+
#def stop
|
196
|
+
#end
|
197
|
+
|
198
|
+
# This method is invoked after the dumping of examples and failures.
|
199
|
+
def dump_summary(duration, example_count, failure_count, pending_count)
|
200
|
+
super(duration, example_count, failure_count, pending_count)
|
201
|
+
|
202
|
+
error_count = 0
|
203
|
+
|
204
|
+
@failed_examples.each do |e|
|
205
|
+
if RSpec::Expectations::ExpectationNotMetError === e.exception
|
206
|
+
#failure_count += 1
|
207
|
+
else
|
208
|
+
failure_count -= 1
|
209
|
+
error_count += 1
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
passing_count = example_count - failure_count - error_count - pending_count
|
214
|
+
|
215
|
+
doc = {
|
216
|
+
'type' => 'final',
|
217
|
+
'time' => duration,
|
218
|
+
'counts' => {
|
219
|
+
'total' => example_count,
|
220
|
+
'pass' => passing_count,
|
221
|
+
'fail' => failure_count,
|
222
|
+
'error' => error_count,
|
223
|
+
'omit' => 0,
|
224
|
+
'todo' => pending_count
|
225
|
+
}
|
226
|
+
}
|
227
|
+
return doc
|
228
|
+
end
|
229
|
+
|
230
|
+
# This gets invoked after the summary if option is set to do so.
|
231
|
+
#def dump_pending
|
232
|
+
#end
|
233
|
+
|
234
|
+
def seed(number)
|
235
|
+
@seed = number
|
236
|
+
end
|
237
|
+
|
238
|
+
private
|
239
|
+
|
240
|
+
# Returns a String of source code.
|
241
|
+
def code_snippet(file, line)
|
242
|
+
s = []
|
243
|
+
if File.file?(file)
|
244
|
+
source = source(file)
|
245
|
+
radius = 2 # TODO: make customizable (number of surrounding lines to show)
|
246
|
+
region = [line - radius, 1].max ..
|
247
|
+
[line + radius, source.length].min
|
248
|
+
|
249
|
+
s = region.map do |n|
|
250
|
+
{n => source[n-1].chomp}
|
251
|
+
end
|
252
|
+
end
|
253
|
+
return s
|
254
|
+
end
|
255
|
+
|
256
|
+
# Cache source file text. This is only used if the TAP-Y stream
|
257
|
+
# doesn not provide a snippet and the test file is locatable.
|
258
|
+
def source(file)
|
259
|
+
@_source_cache ||= {}
|
260
|
+
@_source_cache[file] ||= (
|
261
|
+
File.readlines(file)
|
262
|
+
)
|
263
|
+
end
|
264
|
+
|
265
|
+
# Parse source location from caller, caller[0] or an Exception object.
|
266
|
+
def parse_source_location(caller)
|
267
|
+
case caller
|
268
|
+
when Exception
|
269
|
+
trace = caller.backtrace #.reject{ |bt| bt =~ INTERNALS }
|
270
|
+
caller = trace.first
|
271
|
+
when Array
|
272
|
+
caller = caller.first
|
273
|
+
end
|
274
|
+
caller =~ /(.+?):(\d+(?=:|\z))/ or return ""
|
275
|
+
source_file, source_line = $1, $2.to_i
|
276
|
+
return source_file, source_line
|
277
|
+
end
|
278
|
+
|
279
|
+
end
|
280
|
+
|
281
|
+
#
|
282
|
+
class TapY < TapBaseFormatter
|
283
|
+
def initialize(*whatever)
|
284
|
+
require 'yaml'
|
285
|
+
super(*whatever)
|
286
|
+
end
|
287
|
+
def start(example_count)
|
288
|
+
output.puts super(example_count).to_yaml
|
289
|
+
end
|
290
|
+
def example_group_started(example_group)
|
291
|
+
output.puts super(example_group).to_yaml
|
292
|
+
end
|
293
|
+
def example_passed(example)
|
294
|
+
output.puts super(example).to_yaml
|
295
|
+
end
|
296
|
+
def example_pending(example)
|
297
|
+
output.puts super(example).to_yaml
|
298
|
+
end
|
299
|
+
def example_failed(example)
|
300
|
+
output.puts super(example).to_yaml
|
301
|
+
end
|
302
|
+
def dump_summary(duration, example_count, failure_count, pending_count)
|
303
|
+
output.puts super(duration, example_count, failure_count, pending_count).to_yaml
|
304
|
+
output.puts "..."
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
#
|
309
|
+
class TapJ < TapBaseFormatter
|
310
|
+
def initialize(*whatever)
|
311
|
+
require 'json'
|
312
|
+
super(*whatever)
|
313
|
+
end
|
314
|
+
def start(example_count)
|
315
|
+
output.puts super(example_count).to_json
|
316
|
+
end
|
317
|
+
def example_group_started(example_group)
|
318
|
+
output.puts super(example_group).to_json
|
319
|
+
end
|
320
|
+
def example_passed(example)
|
321
|
+
output.puts super(example).to_json
|
322
|
+
end
|
323
|
+
def example_pending(example)
|
324
|
+
output.puts super(example).to_json
|
325
|
+
end
|
326
|
+
def example_failed(example)
|
327
|
+
output.puts super(example).to_json
|
328
|
+
end
|
329
|
+
def dump_summary(duration, example_count, failure_count, pending_count)
|
330
|
+
output.puts super(duration, example_count, failure_count, pending_count).to_json
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
#end
|
335
|
+
#end
|
336
|
+
end
|
data/lib/rspec/tap.rb
ADDED
@@ -0,0 +1,336 @@
|
|
1
|
+
require 'rspec/core/formatters/base_formatter'
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
#module Core
|
5
|
+
#module Formatters
|
6
|
+
|
7
|
+
#
|
8
|
+
class TapBaseFormatter < Core::Formatters::BaseFormatter
|
9
|
+
|
10
|
+
# TAP-Y/J Revision
|
11
|
+
REVISION = 3
|
12
|
+
|
13
|
+
attr_accessor :example_group_stack
|
14
|
+
|
15
|
+
def initialize(output)
|
16
|
+
super(output)
|
17
|
+
@example_group_stack = []
|
18
|
+
end
|
19
|
+
|
20
|
+
# This method is invoked before any examples are run, right after
|
21
|
+
# they have all been collected. This can be useful for special
|
22
|
+
# formatters that need to provide progress on feedback (graphical ones)
|
23
|
+
#
|
24
|
+
# This will only be invoked once, and the next one to be invoked
|
25
|
+
# is #example_group_started
|
26
|
+
def start(example_count)
|
27
|
+
super(example_count)
|
28
|
+
|
29
|
+
@start_time = Time.now
|
30
|
+
|
31
|
+
doc = {
|
32
|
+
'type' => 'suite',
|
33
|
+
'start' => @start_time.strftime('%Y-%m-%d %H:%M:%S'),
|
34
|
+
'count' => example_count,
|
35
|
+
'seed' => @seed,
|
36
|
+
'rev' => REVISION
|
37
|
+
}
|
38
|
+
return doc
|
39
|
+
end
|
40
|
+
|
41
|
+
# This method is invoked at the beginning of the execution of each example group.
|
42
|
+
# +example_group+ is the example_group.
|
43
|
+
#
|
44
|
+
# The next method to be invoked after this is +example_passed+,
|
45
|
+
# +example_pending+, or +example_finished+
|
46
|
+
def example_group_started(example_group)
|
47
|
+
super(example_group) #@example_group = example_group
|
48
|
+
doc = {
|
49
|
+
'type' => 'case',
|
50
|
+
'subtype' => 'describe',
|
51
|
+
'label' => "#{example_group.description}",
|
52
|
+
'level' => @example_group_stack.size
|
53
|
+
}
|
54
|
+
@example_group_stack << example_group
|
55
|
+
return doc
|
56
|
+
end
|
57
|
+
|
58
|
+
# This method is invoked at the end of the execution of each example group.
|
59
|
+
# +example_group+ is the example_group.
|
60
|
+
def example_group_finished(example_group)
|
61
|
+
@example_group_stack.pop
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
def example_started(example)
|
66
|
+
examples << example
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
def example_passed(example)
|
71
|
+
super(example)
|
72
|
+
|
73
|
+
file, line = example.location.split(':')
|
74
|
+
file = self.class.relative_path(file)
|
75
|
+
line = line.to_i
|
76
|
+
|
77
|
+
doc = {
|
78
|
+
'type' => 'test',
|
79
|
+
'subtype' => 'it',
|
80
|
+
'status' => 'pass',
|
81
|
+
#'setup': foo instance
|
82
|
+
'label' => "#{example.description}",
|
83
|
+
#'expected' => 2
|
84
|
+
#'returned' => 2
|
85
|
+
'file' => file,
|
86
|
+
'line' => line,
|
87
|
+
'source' => source(file)[line-1].strip,
|
88
|
+
'snippet' => code_snippet(file, line),
|
89
|
+
#'coverage' => {
|
90
|
+
# file: lib/foo.rb
|
91
|
+
# line: 11..13
|
92
|
+
# code: Foo#*
|
93
|
+
#}
|
94
|
+
'time' => Time.now - @start_time
|
95
|
+
}
|
96
|
+
return doc
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
def example_pending(example)
|
101
|
+
super(example)
|
102
|
+
|
103
|
+
file, line = example.location.split(':')
|
104
|
+
file = self.class.relative_path(file)
|
105
|
+
line = line.to_i
|
106
|
+
|
107
|
+
doc = {
|
108
|
+
'type' => 'test',
|
109
|
+
'subtype' => 'it',
|
110
|
+
'status' => 'todo',
|
111
|
+
#'setup': foo instance
|
112
|
+
'label' => "#{example.description}",
|
113
|
+
'file' => file,
|
114
|
+
'line' => line,
|
115
|
+
'source' => source(file)[line-1].strip,
|
116
|
+
'snippet' => code_snippet(file, line),
|
117
|
+
#'coverage' => {
|
118
|
+
# file: lib/foo.rb
|
119
|
+
# line: 11..13
|
120
|
+
# code: Foo#*
|
121
|
+
#}
|
122
|
+
'time' => Time.now - @start_time
|
123
|
+
}
|
124
|
+
return doc
|
125
|
+
end
|
126
|
+
|
127
|
+
def example_failed(example)
|
128
|
+
super(example)
|
129
|
+
|
130
|
+
file, line = example.location.split(':')
|
131
|
+
|
132
|
+
file = self.class.relative_path(file)
|
133
|
+
line = line.to_i
|
134
|
+
|
135
|
+
if RSpec::Expectations::ExpectationNotMetError === example.exception
|
136
|
+
status = 'fail'
|
137
|
+
if md = /expected:\s*(.*?)\n\s*got:\s*(.*?)\s+/.match(example.exception.to_s)
|
138
|
+
expected, returned = md[1], md[2]
|
139
|
+
else
|
140
|
+
expected, returned = nil, nil
|
141
|
+
end
|
142
|
+
else
|
143
|
+
status = 'error'
|
144
|
+
end
|
145
|
+
|
146
|
+
backtrace = format_backtrace(example.exception.backtrace, example)
|
147
|
+
|
148
|
+
efile, eline = parse_source_location(backtrace)
|
149
|
+
|
150
|
+
doc = {
|
151
|
+
'type' => 'test',
|
152
|
+
'subtype' => 'it',
|
153
|
+
'status' => status,
|
154
|
+
'label' => "#{example.description}",
|
155
|
+
#'setup' => "foo instance",
|
156
|
+
'file' => file,
|
157
|
+
'line' => line,
|
158
|
+
'source' => source(file)[line-1].strip,
|
159
|
+
'snippet' => code_snippet(file, line),
|
160
|
+
#'coverage' =>
|
161
|
+
#{
|
162
|
+
# 'file' => lib/foo.rb
|
163
|
+
# 'line' => 11..13
|
164
|
+
# 'code' => Foo#*
|
165
|
+
#}
|
166
|
+
}
|
167
|
+
|
168
|
+
if expected or returned
|
169
|
+
doc.update(
|
170
|
+
'expected' => expected,
|
171
|
+
'returned' => returned,
|
172
|
+
)
|
173
|
+
end
|
174
|
+
|
175
|
+
doc.update(
|
176
|
+
'exception' => {
|
177
|
+
'message' => example.exception.to_s.strip,
|
178
|
+
'class' => example.exception.class.name,
|
179
|
+
'file' => efile,
|
180
|
+
'line' => eline,
|
181
|
+
'source' => source(efile)[eline-1].strip,
|
182
|
+
'snippet' => code_snippet(efile, eline),
|
183
|
+
'backtrace' => backtrace
|
184
|
+
},
|
185
|
+
'time' => Time.now - @start_time
|
186
|
+
)
|
187
|
+
|
188
|
+
return doc
|
189
|
+
end
|
190
|
+
|
191
|
+
# @todo Is this a note?
|
192
|
+
def message(message)
|
193
|
+
end
|
194
|
+
|
195
|
+
#def stop
|
196
|
+
#end
|
197
|
+
|
198
|
+
# This method is invoked after the dumping of examples and failures.
|
199
|
+
def dump_summary(duration, example_count, failure_count, pending_count)
|
200
|
+
super(duration, example_count, failure_count, pending_count)
|
201
|
+
|
202
|
+
error_count = 0
|
203
|
+
|
204
|
+
@failed_examples.each do |e|
|
205
|
+
if RSpec::Expectations::ExpectationNotMetError === e.exception
|
206
|
+
#failure_count += 1
|
207
|
+
else
|
208
|
+
failure_count -= 1
|
209
|
+
error_count += 1
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
passing_count = example_count - failure_count - error_count - pending_count
|
214
|
+
|
215
|
+
doc = {
|
216
|
+
'type' => 'final',
|
217
|
+
'time' => duration,
|
218
|
+
'counts' => {
|
219
|
+
'total' => example_count,
|
220
|
+
'pass' => passing_count,
|
221
|
+
'fail' => failure_count,
|
222
|
+
'error' => error_count,
|
223
|
+
'omit' => 0,
|
224
|
+
'todo' => pending_count
|
225
|
+
}
|
226
|
+
}
|
227
|
+
return doc
|
228
|
+
end
|
229
|
+
|
230
|
+
# This gets invoked after the summary if option is set to do so.
|
231
|
+
#def dump_pending
|
232
|
+
#end
|
233
|
+
|
234
|
+
def seed(number)
|
235
|
+
@seed = number
|
236
|
+
end
|
237
|
+
|
238
|
+
private
|
239
|
+
|
240
|
+
# Returns a String of source code.
|
241
|
+
def code_snippet(file, line)
|
242
|
+
s = []
|
243
|
+
if File.file?(file)
|
244
|
+
source = source(file)
|
245
|
+
radius = 2 # TODO: make customizable (number of surrounding lines to show)
|
246
|
+
region = [line - radius, 1].max ..
|
247
|
+
[line + radius, source.length].min
|
248
|
+
|
249
|
+
s = region.map do |n|
|
250
|
+
{n => source[n-1].chomp}
|
251
|
+
end
|
252
|
+
end
|
253
|
+
return s
|
254
|
+
end
|
255
|
+
|
256
|
+
# Cache source file text. This is only used if the TAP-Y stream
|
257
|
+
# doesn not provide a snippet and the test file is locatable.
|
258
|
+
def source(file)
|
259
|
+
@_source_cache ||= {}
|
260
|
+
@_source_cache[file] ||= (
|
261
|
+
File.readlines(file)
|
262
|
+
)
|
263
|
+
end
|
264
|
+
|
265
|
+
# Parse source location from caller, caller[0] or an Exception object.
|
266
|
+
def parse_source_location(caller)
|
267
|
+
case caller
|
268
|
+
when Exception
|
269
|
+
trace = caller.backtrace #.reject{ |bt| bt =~ INTERNALS }
|
270
|
+
caller = trace.first
|
271
|
+
when Array
|
272
|
+
caller = caller.first
|
273
|
+
end
|
274
|
+
caller =~ /(.+?):(\d+(?=:|\z))/ or return ""
|
275
|
+
source_file, source_line = $1, $2.to_i
|
276
|
+
return source_file, source_line
|
277
|
+
end
|
278
|
+
|
279
|
+
end
|
280
|
+
|
281
|
+
#
|
282
|
+
class TapY < TapBaseFormatter
|
283
|
+
def initialize(*whatever)
|
284
|
+
require 'yaml'
|
285
|
+
super(*whatever)
|
286
|
+
end
|
287
|
+
def start(example_count)
|
288
|
+
output.puts super(example_count).to_yaml
|
289
|
+
end
|
290
|
+
def example_group_started(example_group)
|
291
|
+
output.puts super(example_group).to_yaml
|
292
|
+
end
|
293
|
+
def example_passed(example)
|
294
|
+
output.puts super(example).to_yaml
|
295
|
+
end
|
296
|
+
def example_pending(example)
|
297
|
+
output.puts super(example).to_yaml
|
298
|
+
end
|
299
|
+
def example_failed(example)
|
300
|
+
output.puts super(example).to_yaml
|
301
|
+
end
|
302
|
+
def dump_summary(duration, example_count, failure_count, pending_count)
|
303
|
+
output.puts super(duration, example_count, failure_count, pending_count).to_yaml
|
304
|
+
output.puts "..."
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
#
|
309
|
+
class TapJ < TapBaseFormatter
|
310
|
+
def initialize(*whatever)
|
311
|
+
require 'json'
|
312
|
+
super(*whatever)
|
313
|
+
end
|
314
|
+
def start(example_count)
|
315
|
+
output.puts super(example_count).to_json
|
316
|
+
end
|
317
|
+
def example_group_started(example_group)
|
318
|
+
output.puts super(example_group).to_json
|
319
|
+
end
|
320
|
+
def example_passed(example)
|
321
|
+
output.puts super(example).to_json
|
322
|
+
end
|
323
|
+
def example_pending(example)
|
324
|
+
output.puts super(example).to_json
|
325
|
+
end
|
326
|
+
def example_failed(example)
|
327
|
+
output.puts super(example).to_json
|
328
|
+
end
|
329
|
+
def dump_summary(duration, example_count, failure_count, pending_count)
|
330
|
+
output.puts super(duration, example_count, failure_count, pending_count).to_json
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
#end
|
335
|
+
#end
|
336
|
+
end
|
data/lib/rspec/tap_j.rb
ADDED
data/lib/rspec/tap_y.rb
ADDED
data/spec/demonstrate.rb
ADDED
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rspec-ontap
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Trans
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-14 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &16983840 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *16983840
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: tapout
|
27
|
+
requirement: &16982400 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *16982400
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: detroit
|
38
|
+
requirement: &16980480 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *16980480
|
47
|
+
description: RSpecOnTap is a TAP-Y/J formatter for RSpec.
|
48
|
+
email:
|
49
|
+
- transfire@gmail.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files:
|
53
|
+
- COPYING.rdoc
|
54
|
+
- HISTORY.md
|
55
|
+
- README.md
|
56
|
+
files:
|
57
|
+
- .ruby
|
58
|
+
- .yardopts
|
59
|
+
- lib/rspec/ontap.rb
|
60
|
+
- lib/rspec/tap.rb
|
61
|
+
- lib/rspec/tap_j.rb
|
62
|
+
- lib/rspec/tap_y.rb
|
63
|
+
- spec/demonstrate.rb
|
64
|
+
- HISTORY.md
|
65
|
+
- README.md
|
66
|
+
- COPYING.rdoc
|
67
|
+
homepage: http://rubyworks.github.com/rspec-ontap
|
68
|
+
licenses:
|
69
|
+
- BSD-2-Clause
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ! '>='
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
requirements: []
|
87
|
+
rubyforge_project:
|
88
|
+
rubygems_version: 1.8.10
|
89
|
+
signing_key:
|
90
|
+
specification_version: 3
|
91
|
+
summary: RSpec On Tap-Y/J
|
92
|
+
test_files: []
|