rdx 0.9.0.pre
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 +7 -0
- data/.rdx +20 -0
- data/README +19 -0
- data/bin/rdx +7 -0
- data/examples/minimal/.rdx +8 -0
- data/examples/minimal/README +10 -0
- data/examples/minimal/lib/other_conventions.rb +64 -0
- data/examples/minimal/lib/the_basics.rb +94 -0
- data/examples/minimal/lib/using_directives.rb +66 -0
- data/examples/minimal/rakefile +27 -0
- data/examples/ruby-2.0.0-p0/README +7 -0
- data/examples/ruby-2.0.0-p0/install/core/.rdx +6 -0
- data/examples/ruby-2.0.0-p0/install/core/README +19 -0
- data/examples/ruby-2.0.0-p0/install/core/Rakefile +61 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/array.c.diff +166 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/bignum.c.diff +11 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/class.c.diff +36 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/compar.c.diff +11 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/complex.c.diff +301 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/cont.c.diff +65 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/dir.c.diff +147 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/re.rdoc.diff +328 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/security.rdoc.diff +8 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/standard_library.rdoc.diff +0 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax.rdoc.diff +0 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/assignment.rdoc.diff +160 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/calling_methods.rdoc.diff +130 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/control_expressions.rdoc.diff +254 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/exceptions.rdoc.diff +0 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/literals.rdoc.diff +54 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/methods.rdoc.diff +157 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/miscellaneous.rdoc.diff +91 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/modules_and_classes.rdoc.diff +161 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/precedence.rdoc.diff +8 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/refinements.rdoc.diff +146 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/encoding.c.diff +276 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/enum.c.diff +281 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/enumerator.c.diff +479 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/error.c.diff +143 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/eval.c.diff +47 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/eval_jump.c.diff +23 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/file.c.diff +752 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/gc.c.diff +195 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/hash.c.diff +84 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/iseq.c.diff +354 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/load.c.diff +53 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/marshal.c.diff +98 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/math.c.diff +110 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/numeric.c.diff +103 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/object.c.diff +295 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/pack.c.diff +18 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/parse.y.diff +23 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/proc.c.diff +155 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/random.c.diff +126 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/range.c.diff +49 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/rational.c.diff +312 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/re.c.diff +207 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/ruby.c.diff +21 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/signal.c.diff +67 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/sprintf.c.diff +29 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/string.c.diff +73 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/struct.c.diff +20 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/time.c.diff +691 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/transcode.c.diff +435 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/variable.c.diff +62 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/vm_backtrace.c.diff +164 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/vm_eval.c.diff +99 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/vm_method.c.diff +17 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/vm_trace.c.diff +393 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/.rdx +6 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/README +19 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/Rakefile +53 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/abbrev.rb.diff +77 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/base64.rb.diff +42 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/benchmark.rb.diff +144 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/cmath.rb.diff +52 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/forwardable.rb.diff +150 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/mathn.rb.diff +58 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/matrix.rb.diff +657 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/observer.rb.diff +31 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/optparse.rb.diff +147 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/ostruct.rb.diff +78 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/prime.rb.diff +52 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/pstore.rb.diff +110 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/scanf.rb.diff +100 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/securerandom.rb.diff +144 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/set.rb.diff +637 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/shellwords.rb.diff +66 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/singleton.rb.diff +37 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/tempfile.rb.diff +104 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/thread.rb.diff +38 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/time.rb.diff +140 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/tmpdir.rb.diff +52 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/uri.rb.diff +39 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/uri/common.rb.diff +237 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/weakref.rb.diff +36 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/yaml/store.rb.diff +27 -0
- data/examples/ruby-2.0.0-p0/rakefile +165 -0
- data/lib/rdx.rb +331 -0
- data/lib/rdx/assertions.rb +484 -0
- data/lib/rdx/binding.rb +151 -0
- data/lib/rdx/code_object.rb +598 -0
- data/lib/rdx/comment.rb +338 -0
- data/lib/rdx/convention.rb +1174 -0
- data/lib/rdx/directive.rb +1432 -0
- data/lib/rdx/example.rb +679 -0
- data/lib/rdx/generator.rb +112 -0
- data/lib/rdx/generator/rdoc.rb +1006 -0
- data/lib/rdx/options.rb +359 -0
- data/lib/rdx/plain_text.rb +65 -0
- data/lib/rdx/reporter.rb +421 -0
- data/lib/rdx/ruby_lex.rb +324 -0
- data/lib/rdx/runner.rb +309 -0
- data/lib/rdx/source_file.rb +94 -0
- data/lib/rdx/specification.rb +194 -0
- data/lib/rdx/statement.rb +248 -0
- data/lib/rdx/store.rb +119 -0
- data/lib/rdx/task.rb +361 -0
- data/lib/rdx/text.rb +688 -0
- data/lib/rdx/version.rb +15 -0
- data/rakefile +64 -0
- metadata +203 -0
data/lib/rdx/comment.rb
ADDED
@@ -0,0 +1,338 @@
|
|
1
|
+
|
2
|
+
module RDX
|
3
|
+
|
4
|
+
#
|
5
|
+
# Represents a comment in a source file.
|
6
|
+
#
|
7
|
+
class Comment < CodeObject::Runnable::Startable
|
8
|
+
|
9
|
+
# The +String+ containing the text.
|
10
|
+
attr_writer :text
|
11
|
+
|
12
|
+
# Absolute line number in the source file.
|
13
|
+
attr_writer :line_no
|
14
|
+
|
15
|
+
# This is +true+ if the comment will appear on the documentation output, +false+ otherwise.
|
16
|
+
attr_accessor :documenting
|
17
|
+
|
18
|
+
# The SourceFile this comment is into.
|
19
|
+
attr_reader :source_file
|
20
|
+
alias parent source_file
|
21
|
+
|
22
|
+
# Returns +self+.
|
23
|
+
def comment
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
# The +Array+ of CodeObject contained (may contain PlainText, Example and Directive objects).
|
28
|
+
attr_reader :contents
|
29
|
+
|
30
|
+
# The +Array+ of Example objects contained.
|
31
|
+
def examples
|
32
|
+
contents.grep(Example)
|
33
|
+
end
|
34
|
+
|
35
|
+
# The +Array+ of PlainText objects contained.
|
36
|
+
def plain_texts
|
37
|
+
contents.grep(PlainText)
|
38
|
+
end
|
39
|
+
|
40
|
+
# The +Array+ of Directive objects contained.
|
41
|
+
def directives
|
42
|
+
contents.grep(Directive)
|
43
|
+
end
|
44
|
+
|
45
|
+
# The +Array+ of Directive objects contained that are +implicit?+.
|
46
|
+
def implicit_directives
|
47
|
+
directives.select(&:implicit?)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Example and PlainText objects are collectively +sections+ in the comment.
|
51
|
+
def sections
|
52
|
+
@sections || contents.reject{ |c| c.is_a?(Directive) }
|
53
|
+
end
|
54
|
+
|
55
|
+
# The comment for the documentation container.
|
56
|
+
#--
|
57
|
+
# TODO: not always clear...
|
58
|
+
attr_reader :enclosing_comment
|
59
|
+
|
60
|
+
# The +Array+ of comments in this documentation container (contains +self+).
|
61
|
+
#--
|
62
|
+
# same limitations as above
|
63
|
+
attr_reader :enclosed_comments
|
64
|
+
|
65
|
+
# The Example that are +active?+.
|
66
|
+
def children
|
67
|
+
examples.select(&:active?)
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# :category: API
|
72
|
+
#
|
73
|
+
# Disables the directives of the given _names_ that are +implicit?+ in _section_.
|
74
|
+
# If no name is given, all of them are disabled.
|
75
|
+
#
|
76
|
+
def disable_implicit_directives_in section, *names
|
77
|
+
implicit_dirs = directives.select{ |dir| dir.implicit?(section) }
|
78
|
+
unless names.empty?
|
79
|
+
names.map!{ |dir_name| store.directive(dir_name) } # directive classes
|
80
|
+
implicit_dirs.select!{ |dir| names.find{ |type| dir.is_a?(type) } }
|
81
|
+
end
|
82
|
+
implicit_dirs.each(&:deactivate!)
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# :category: API
|
87
|
+
#
|
88
|
+
# Disables the directives of the given _names_ that are +implicit?+ in this comment.
|
89
|
+
# If no name is given, all of them are disabled.
|
90
|
+
#
|
91
|
+
def disable_implicit_directives! *names
|
92
|
+
disable_implicit_directives_in self, *names
|
93
|
+
end
|
94
|
+
|
95
|
+
# :stopdoc:
|
96
|
+
|
97
|
+
def empty?
|
98
|
+
text.nil? || text.empty?
|
99
|
+
end
|
100
|
+
|
101
|
+
def file_name= file_name
|
102
|
+
@source_file.remove_comment(self) if @source_file
|
103
|
+
@source_file = if file_name
|
104
|
+
store.find_source_file(file_name).add_comment(self) unless empty?
|
105
|
+
else
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def initialize text=nil, file_name=nil, line_no=nil
|
111
|
+
super(text,nil)
|
112
|
+
self.file_name = file_name
|
113
|
+
@line_no = line_no
|
114
|
+
@documenting = false
|
115
|
+
@normalized = false
|
116
|
+
@enclosing_comment = nil
|
117
|
+
@enclosed_comments = [self] # for convenience
|
118
|
+
@contents = []
|
119
|
+
clear_run_hooks
|
120
|
+
end
|
121
|
+
|
122
|
+
def inspect
|
123
|
+
parent_info = enclosing_comment && enclosing_comment.location
|
124
|
+
parent_info &&= "(<#{parent_info})"
|
125
|
+
content = text
|
126
|
+
if content.size > 60
|
127
|
+
content = content[0..60] + '...'
|
128
|
+
end
|
129
|
+
"<%s:0x%x(%s)@%s%s;%p>" % [self.class, __id__, documenting, location, parent_info, content]
|
130
|
+
end
|
131
|
+
|
132
|
+
def enclosing_comment= comment
|
133
|
+
return self if comment.nil?
|
134
|
+
raise unless comment.is_a?(Comment)
|
135
|
+
@enclosing_comment = comment
|
136
|
+
comment.enclosed_comments << self
|
137
|
+
self
|
138
|
+
end
|
139
|
+
|
140
|
+
attr_accessor :setup
|
141
|
+
protected :setup, :setup=
|
142
|
+
|
143
|
+
attr_accessor :teardown
|
144
|
+
protected :teardown, :teardown=
|
145
|
+
|
146
|
+
def def_setup example
|
147
|
+
enclosed_comments.each{ |cmt| cmt.setup = example }
|
148
|
+
end
|
149
|
+
|
150
|
+
def def_teardown example
|
151
|
+
enclosed_comments.each{ |cmt| cmt.teardown = example }
|
152
|
+
end
|
153
|
+
|
154
|
+
def clear_run_hooks
|
155
|
+
self.setup = self.teardown = nil
|
156
|
+
self
|
157
|
+
end
|
158
|
+
|
159
|
+
def run_setup starter
|
160
|
+
return unless setup
|
161
|
+
setup.binding = starter.binding
|
162
|
+
setup.whole_run(starter)
|
163
|
+
end
|
164
|
+
|
165
|
+
def run_teardown starter
|
166
|
+
return unless teardown
|
167
|
+
teardown.binding = starter.binding
|
168
|
+
teardown.whole_run(starter)
|
169
|
+
end
|
170
|
+
|
171
|
+
attr_reader :normalized
|
172
|
+
|
173
|
+
def normalize
|
174
|
+
return self if normalized
|
175
|
+
@text = Text.normalize_comment(text,true) # remove consecutive #
|
176
|
+
@normalized = true
|
177
|
+
self
|
178
|
+
end
|
179
|
+
|
180
|
+
def build
|
181
|
+
super
|
182
|
+
store.add_test self
|
183
|
+
end
|
184
|
+
|
185
|
+
private
|
186
|
+
|
187
|
+
def build_self
|
188
|
+
normalize
|
189
|
+
determine_contents
|
190
|
+
execute_directives
|
191
|
+
check_missing_conventions
|
192
|
+
end
|
193
|
+
|
194
|
+
def extract_directives
|
195
|
+
directives = []
|
196
|
+
text.gsub! generator.directive_rgxp do |matched|
|
197
|
+
md = Regexp.last_match
|
198
|
+
init_nl = matched[/\A\s*/].count("\n")
|
199
|
+
dir,param = md[1].downcase,md[2]
|
200
|
+
if dir == 'rdx'
|
201
|
+
l_no = 1 + md.pre_match.count("\n") + init_nl
|
202
|
+
directives << [param.strip, l_no]
|
203
|
+
end
|
204
|
+
Text.collect_newlines(matched) # remove it
|
205
|
+
end
|
206
|
+
text.replace Text.flush_left(text) # The directives haven't effect on indentation
|
207
|
+
directives.map{ |cnt| [:directive,*cnt] }
|
208
|
+
end
|
209
|
+
|
210
|
+
def divide_examples_texts
|
211
|
+
examples = []
|
212
|
+
texts = []
|
213
|
+
lines = text.lines("\n")
|
214
|
+
generator.recognize_examples(text).each do |ex,line_no|
|
215
|
+
examples << [ex,line_no]
|
216
|
+
line_no_f = line_no + ex.count("\n") - 1
|
217
|
+
line_no.upto(line_no_f){ |i| lines[i-1] = nil }
|
218
|
+
end
|
219
|
+
chunks = lines.chunk(&:nil?).map(&:last)
|
220
|
+
chunks.reduce(1) do |line_no,lines|
|
221
|
+
unless lines.first.nil?
|
222
|
+
txt = lines.join ''
|
223
|
+
texts << [txt,line_no]
|
224
|
+
end
|
225
|
+
line_no + lines.size
|
226
|
+
end
|
227
|
+
examples.map!{ |cnt| [:example,*cnt] }
|
228
|
+
texts.map!{ |cnt| [:text,*cnt] }
|
229
|
+
[examples,texts]
|
230
|
+
end
|
231
|
+
|
232
|
+
def determine_contents
|
233
|
+
directives = extract_directives
|
234
|
+
examples,texts = divide_examples_texts
|
235
|
+
pseudo_contents = (examples+texts).sort_by(&:last)
|
236
|
+
directives.each do |directive|
|
237
|
+
l_no = directive.last
|
238
|
+
index = (pseudo_contents.find_index{ |_,_,n| n > l_no } || 0) - 1
|
239
|
+
pseudo_content = pseudo_contents[index]
|
240
|
+
pseudo_contents[index,1] = split_content(pseudo_content,directive,true)
|
241
|
+
end
|
242
|
+
pseudo_contents = add_implicit_directives pseudo_contents
|
243
|
+
finalize_contents pseudo_contents
|
244
|
+
end
|
245
|
+
|
246
|
+
def split_content content, directive_cnt, remove_line
|
247
|
+
type,text,l_no_before = content
|
248
|
+
lines = text.lines "\n"
|
249
|
+
l_no_after = directive_cnt[2]
|
250
|
+
l_no_after += 1 if remove_line # exclude the line with directive - not for implicit ones
|
251
|
+
cnt_before = lines.take(directive_cnt[2]-l_no_before).join ''
|
252
|
+
cnt_after = lines.drop(l_no_after-l_no_before).join ''
|
253
|
+
result = []
|
254
|
+
result << [type,cnt_before,l_no_before] if cnt_before =~ /\S/
|
255
|
+
result << directive_cnt
|
256
|
+
result << [type,cnt_after,l_no_after] if cnt_after =~ /\S/
|
257
|
+
return result
|
258
|
+
end
|
259
|
+
|
260
|
+
def add_implicit_directives pseudo_contents
|
261
|
+
result = []
|
262
|
+
pseudo_contents.each do |pseudo_content|
|
263
|
+
result << pseudo_content
|
264
|
+
type,text,line_no = pseudo_content
|
265
|
+
next if type == :directive
|
266
|
+
store.scan_for_implicit_directives(type,text).each do |directive|
|
267
|
+
directive[2] += line_no - 1
|
268
|
+
result.concat split_content(result.pop,directive,false)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
return result
|
272
|
+
end
|
273
|
+
|
274
|
+
def finalize_contents pseudo_contents
|
275
|
+
pseudo_contents.each do |type,txt,l_no|
|
276
|
+
case type
|
277
|
+
when :example
|
278
|
+
contents << Example.new(self,txt,l_no)
|
279
|
+
when :text
|
280
|
+
contents << PlainText.new(self,txt,l_no)
|
281
|
+
when :directive
|
282
|
+
contents << new_directive(txt,l_no)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
@sections = self.sections
|
286
|
+
end
|
287
|
+
|
288
|
+
def new_directive dir_string, l_no
|
289
|
+
# dir_string: rdx string directive, containing its own directive and param
|
290
|
+
dir,param = Directive.split(dir_string)
|
291
|
+
directive = nil
|
292
|
+
trace_execution location(l_no) do
|
293
|
+
directive = store.directive(dir)
|
294
|
+
end
|
295
|
+
directive.new(self,param,l_no)
|
296
|
+
end
|
297
|
+
|
298
|
+
# :startdoc:
|
299
|
+
|
300
|
+
#
|
301
|
+
# :section: Internal
|
302
|
+
#
|
303
|
+
|
304
|
+
#
|
305
|
+
# Executes the directives.
|
306
|
+
# If the comment won't be part of the documentation output only explicit directives
|
307
|
+
# get executed: a warning will be generated for recognized implicit ones.
|
308
|
+
#
|
309
|
+
def execute_directives # :doc:
|
310
|
+
prepare_binding
|
311
|
+
if documenting
|
312
|
+
examples.each(&:activate)
|
313
|
+
else
|
314
|
+
implicit_directives.each do |dir|
|
315
|
+
dir.warn "Found pattern for implicit directive `#{dir.name}' in non-documenting comment"
|
316
|
+
dir.deactivate!
|
317
|
+
end
|
318
|
+
end
|
319
|
+
directives.each(&:execute)
|
320
|
+
end
|
321
|
+
|
322
|
+
#
|
323
|
+
# Every commment seen by the parser is checked for the presence of conventions:
|
324
|
+
# if they are in places where they will not be executed a warning will be generated.
|
325
|
+
# This is done in text sections (see PlainText#check_missing_conventions) and in
|
326
|
+
# examples that won't be part of the documentation output (see Example#check_non_documenting).
|
327
|
+
#
|
328
|
+
def check_missing_conventions # :doc:
|
329
|
+
examples.each(&:check_non_documenting)
|
330
|
+
plain_texts.each do |text|
|
331
|
+
text.check_missing_conventions
|
332
|
+
end
|
333
|
+
return self
|
334
|
+
end
|
335
|
+
|
336
|
+
end
|
337
|
+
|
338
|
+
end
|
@@ -0,0 +1,1174 @@
|
|
1
|
+
|
2
|
+
module RDX
|
3
|
+
|
4
|
+
#
|
5
|
+
# == Introduction
|
6
|
+
#
|
7
|
+
# Let's start taking an example:
|
8
|
+
#
|
9
|
+
# my_array = [1,2,3]
|
10
|
+
# my_array.pop # => 3
|
11
|
+
# my_array # => [1,2]
|
12
|
+
#
|
13
|
+
# One of the main advantages of *examples* is that they <b>are written as ruby code ready to work:
|
14
|
+
# the effort made by the user to understand and reuse them is often minimum</b>.
|
15
|
+
# The key point here is that the significant data of a statement - such as result
|
16
|
+
# returned, produced output, raised exception - is pointed out with a comment at its end.
|
17
|
+
# In this way the example can keep its whole consistence: since expectations are commented out
|
18
|
+
# they don't interfere with the logical flux of statements.
|
19
|
+
#
|
20
|
+
# The *conventions* really constitute the heart of RDX: they can <b>map specific patterns into
|
21
|
+
# those comments to a way of processing code</b>. In other words, they <b>are the bridge
|
22
|
+
# between documentation and testing</b>. Choosing intuitive and commonly accepted patterns
|
23
|
+
# will lead to examples very easy to write and understand, which are also consistent.
|
24
|
+
#
|
25
|
+
# == A tour on the existing conventions
|
26
|
+
#
|
27
|
+
# The most important *default* conventions (and some derivates for literals) currently are:
|
28
|
+
# * ::result_eval: checks the result
|
29
|
+
# [1,2,3].pop(2) # => [2,3]
|
30
|
+
# * ::output_literal: checks the output
|
31
|
+
# puts "Hello world!" # prints: Hello world!
|
32
|
+
# * ::error_literal: checks the raised exception
|
33
|
+
# "a string" + 1 # raises TypeError: no implicit conversion of Fixnum into String
|
34
|
+
# These are the most common, so often they are more than enough for your needs.
|
35
|
+
# The list of default conventions is available in Specification#default_conventions.
|
36
|
+
#
|
37
|
+
# RDX defines other *useful* conventions (but they are not used by default,
|
38
|
+
# since there isn't a common acceptance). Some of these are:
|
39
|
+
# * ::result_inspect:
|
40
|
+
# /\d/.match "x = 3" # > #<MatchData "3">
|
41
|
+
# * ::result_indicative:
|
42
|
+
# rand # -> 0.78215412345
|
43
|
+
# * ::output_eval:
|
44
|
+
# puts "Hello world!" # >> "Hello world!\n"
|
45
|
+
# * ::error_eval:
|
46
|
+
# "a string" + 1 # ~> TypeError: "no implicit conversion of Fixnum into String"
|
47
|
+
# All the conventions already defined are documented as Convention's class methods
|
48
|
+
# (even if technically they aren't) in the relative sections.
|
49
|
+
#
|
50
|
+
# If these aren't enough, you can *edit* them or even *define* your own conventions
|
51
|
+
# in the Specification.
|
52
|
+
#
|
53
|
+
# But before this you'll need to know how the conventions work:
|
54
|
+
# this is explained in the next section.
|
55
|
+
#
|
56
|
+
# == How conventions work
|
57
|
+
#
|
58
|
+
# Let's start from an Example:
|
59
|
+
#
|
60
|
+
# (1..10).all? do |i|
|
61
|
+
# i > 0 and i < 15
|
62
|
+
# end #=> true
|
63
|
+
#
|
64
|
+
# Here is the background:
|
65
|
+
# :rdx: no_warnings conventions
|
66
|
+
# * First the example is *parsed* and subdivided into Statement objects
|
67
|
+
# (in RubyLex#extract_statements). Here we have only one statement.
|
68
|
+
# * For each statement is *extracted* the comment at its end
|
69
|
+
# (in RubyLex#extract_comments_from_statement).
|
70
|
+
# In this case we have the +String+ <tt>"#=> true"</tt>.
|
71
|
+
# * This comment is then *normalized* (through Text#normalize_comment).
|
72
|
+
# For this example we end up with <tt>"=> true"</tt>.
|
73
|
+
#
|
74
|
+
# Then the *conventions* come into play. There are four +Proc+ objects that does the full work:
|
75
|
+
# * The first step is to *associate* a convention to that comment.
|
76
|
+
# This is done in #process_comment, which is called for every convention in use until
|
77
|
+
# the right one is found: now we have an Expectation.
|
78
|
+
# In this case it's recognized (and removed) the prompt <tt>=></tt> of the
|
79
|
+
# ::result_eval convention, leaving <tt>"true"</tt> in the +expected_data+.
|
80
|
+
# * Then the code of the *statement* is processed through #process_statement of the
|
81
|
+
# determined convention (which often simply evaluates it). In this case the string
|
82
|
+
# <tt>"(1..10).all? do |i|\n i > 0 and i < 15\nend"</tt> is evalued, returning +true+.
|
83
|
+
# This is the +actual_value+.
|
84
|
+
# * Next the useful data of the *expectation* extracted previously is send to
|
85
|
+
# #process_expectation: the result is the +expected_value+.
|
86
|
+
# In this case we obtain +true+ (because +result_eval+ _evaluates_ the expected data).
|
87
|
+
# * Finally we can do the *assertion*: the actual and expected values (in this order)
|
88
|
+
# are sent to #process_assertion, which provides full access to Minitest's assertions.
|
89
|
+
# Since +result_eval+ does an +assert_equal+ we see that this assertion passes.
|
90
|
+
#
|
91
|
+
# To sum up, the ::result_eval convention maps the <tt>=></tt>
|
92
|
+
# prompt in documentation examples to an +assert_equal+ of the +Minitest+ framework.
|
93
|
+
#
|
94
|
+
# According to the defined conventions RDX builds the tests for the user,
|
95
|
+
# which only has to check the report.
|
96
|
+
#
|
97
|
+
class Convention
|
98
|
+
|
99
|
+
include Text
|
100
|
+
extend Text
|
101
|
+
|
102
|
+
def self.define_processor name # :nodoc:
|
103
|
+
ivar = "@#{name}".freeze
|
104
|
+
define_method name do |&definition|
|
105
|
+
return instance_variable_get(ivar) unless definition
|
106
|
+
instance_variable_set(ivar,definition)
|
107
|
+
end
|
108
|
+
define_method "dont_#{name}" do
|
109
|
+
instance_variable_set(ivar,false)
|
110
|
+
end
|
111
|
+
define_method "call_#{name}" do |context,*args|
|
112
|
+
processor = instance_variable_get(ivar)
|
113
|
+
if processor
|
114
|
+
if context
|
115
|
+
context.instance_exec(*args,&processor)
|
116
|
+
else
|
117
|
+
processor.call(*args)
|
118
|
+
end
|
119
|
+
else
|
120
|
+
args.size==1 ? args.first : args
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
private_class_method :define_processor
|
125
|
+
|
126
|
+
##
|
127
|
+
# :call-seq:
|
128
|
+
# process_comment{ |normalized_comment| ... }
|
129
|
+
# process_comment -> proc or nil
|
130
|
+
#
|
131
|
+
# If a block is given, it's registered as the processor.
|
132
|
+
# Otherwise returns the registered +Proc+ (or +nil+ if it hasn't been registered yet).
|
133
|
+
#
|
134
|
+
# The +Proc+ will receive the normalized comment at the end of a statement as a parameter
|
135
|
+
# and processes it. What happens next depends on the result of the block:
|
136
|
+
# * If it returns +false+ or +nil+ this convention isn't associated with the statement
|
137
|
+
# (and other conventions are tried in turn);
|
138
|
+
# * otherwise this convention is associated with the statement and we have an _expectation_.
|
139
|
+
# The result (often the string containing the useful portion of that comment)
|
140
|
+
# will be successively sent to #process_expectation.
|
141
|
+
#
|
142
|
+
# Often a simple +Regexp+ is enough to extract text from the comment:
|
143
|
+
# use #pattern= for this.
|
144
|
+
#
|
145
|
+
define_processor :process_comment
|
146
|
+
|
147
|
+
#
|
148
|
+
# Builds #process_comment so that it matches the given +Regexp+ _pattern_.
|
149
|
+
# What will be send to #process_expectation depends on the number of capturing group of the pattern:
|
150
|
+
# * 0 => the portion of string matched by the full +Regexp+ (<tt>$&</tt>);
|
151
|
+
# * 1 => the portion of string matched by the capturing group (<tt>$1</tt>);
|
152
|
+
# * more => the array of captured groups (<tt>$~.captures</tt>).
|
153
|
+
#
|
154
|
+
# Often the pattern just consists of some prompts and a sequence of other patterns:
|
155
|
+
# use #build_pattern for this.
|
156
|
+
#
|
157
|
+
def pattern= pattern
|
158
|
+
process_comment do |text|
|
159
|
+
next false unless md = text.match(pattern)
|
160
|
+
case md.size
|
161
|
+
when 1
|
162
|
+
next md[0]
|
163
|
+
when 2
|
164
|
+
next md[1]
|
165
|
+
else
|
166
|
+
next md.captures
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
#
|
172
|
+
# Builds and sets (through #pattern=) a +Regexp+ that matches one of the given
|
173
|
+
# _prompts_ and the other _parts_ in sequence. First the array _prompts_ is sent to Text#build_prompt;
|
174
|
+
# this result, along with the other _parts_ are sent to Text#build_pattern.
|
175
|
+
#
|
176
|
+
# If the last part is <tt>:STATEMENT</tt> the data extracted is the first statement:
|
177
|
+
# it can span across multiple lines and additional ones can contain arbitrary text;
|
178
|
+
# this is useful if we want to evaluate the expectation.
|
179
|
+
#
|
180
|
+
# If all that matters is what follows the prompt you can use #set_prompts.
|
181
|
+
#
|
182
|
+
def build_pattern prompts, *parts
|
183
|
+
prompts = Array(prompts).flatten
|
184
|
+
return dont_process_comment if prompts.empty?
|
185
|
+
prompt = build_prompt(prompts)
|
186
|
+
if parts.last == :STATEMENT
|
187
|
+
stmnt = true
|
188
|
+
parts[-1] = /(.*)/m
|
189
|
+
end
|
190
|
+
pattern = super(prompt,*parts)
|
191
|
+
unless stmnt
|
192
|
+
self.pattern = pattern
|
193
|
+
else
|
194
|
+
process_comment do |text|
|
195
|
+
md = text.match pattern
|
196
|
+
next unless md
|
197
|
+
begin
|
198
|
+
RubyLex.extract_first_statement md[-1]
|
199
|
+
rescue ParseError
|
200
|
+
text # the error will be found later...
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
#
|
207
|
+
# Uses #build_pattern so that the rest of line after one of the given _prompts_ is captured.
|
208
|
+
#
|
209
|
+
def set_prompts *prompts
|
210
|
+
build_pattern prompts, /(.*)/
|
211
|
+
end
|
212
|
+
|
213
|
+
#
|
214
|
+
# Calls #set_prompts with a single _prompt_
|
215
|
+
#
|
216
|
+
def set_prompt prompt
|
217
|
+
set_prompts prompt
|
218
|
+
end
|
219
|
+
|
220
|
+
##
|
221
|
+
# :call-seq:
|
222
|
+
# process_statement{ |statement_code| ... }
|
223
|
+
# process_statement -> proc or nil
|
224
|
+
# dont_process_statement -> false
|
225
|
+
#
|
226
|
+
# If a block is given, it's registered as the processor.
|
227
|
+
# Otherwise returns the registered +Proc+ (or +nil+ if it hasn't been registered yet).
|
228
|
+
#
|
229
|
+
# The block is executed in the context of (with +self+ as) that Statement, receiving
|
230
|
+
# its code as a parameter.
|
231
|
+
# The result of the block is the _actual_ value, that will be the first parameter to
|
232
|
+
# #process_assertion.
|
233
|
+
#
|
234
|
+
# +dont_process_statement+ leaves the parameter unchanged, making it directly the actual value:
|
235
|
+
# it's like using <tt>process_statement{ |statement_code| statement_code }</tt>.
|
236
|
+
#
|
237
|
+
define_processor :process_statement
|
238
|
+
|
239
|
+
##
|
240
|
+
# :call-seq:
|
241
|
+
# process_expectation{ |expectation_code| ... }
|
242
|
+
# process_expectation -> proc or nil
|
243
|
+
# dont_process_expectation -> false
|
244
|
+
#
|
245
|
+
# If a block is given, it's registered as the processor.
|
246
|
+
# Otherwise returns the registered +Proc+ (or +nil+ if it hasn't been registered yet).
|
247
|
+
#
|
248
|
+
# The block is executed in the context of (with +self+ as) that Expectation, receiving
|
249
|
+
# the _expectation_code_ (result of #process_comment) as a parameter.
|
250
|
+
# The result of the block is the _expected_ value, that will be the second parameter to
|
251
|
+
# #process_assertion.
|
252
|
+
#
|
253
|
+
# +dont_process_expectation+ leaves the parameter unchanged, making it directly the expected value:
|
254
|
+
# <tt>process_expectation{ |expectation_code| expectation_code }</tt>.
|
255
|
+
#
|
256
|
+
define_processor :process_expectation
|
257
|
+
|
258
|
+
##
|
259
|
+
# :call-seq:
|
260
|
+
# process_assertion{ |actual,expected| ... }
|
261
|
+
# process_assertion -> proc or nil
|
262
|
+
# dont_process_assertion -> false
|
263
|
+
#
|
264
|
+
# If a block is given, it's registered as the processor.
|
265
|
+
# Otherwise returns the registered Proc (or +nil+ if it hasn't been registered yet).
|
266
|
+
# +dont_process_assertion+ does nothing, it's like using <tt>process_assertion{ |*| }</tt>.
|
267
|
+
#
|
268
|
+
# The block will receive the _actual_ and _expected_ values (obtained respectively from
|
269
|
+
# #process_statement and #process_expectation) and is executed in the context of
|
270
|
+
# (with +self+ as) the corresponding Example - class that includes the Assertion module,
|
271
|
+
# providing full access to Minitest framework. This allows to customize the assertion methods
|
272
|
+
# for only some examples (through some directives), but mantains an unified and coherent
|
273
|
+
# behaviour between statements inside it.
|
274
|
+
#
|
275
|
+
define_processor :process_assertion
|
276
|
+
|
277
|
+
#
|
278
|
+
# :category: Internal
|
279
|
+
#
|
280
|
+
# Registers the given block as a configuration for the convention:
|
281
|
+
# this centralizes the building procedure, allowing the user to supply
|
282
|
+
# (through #use_with) only the desired parameters - often the prompts.
|
283
|
+
#
|
284
|
+
# After this #default_configuration is called.
|
285
|
+
#
|
286
|
+
# The configuration is defined for built-in conventions;
|
287
|
+
# user-defined ones often do not need this step.
|
288
|
+
#
|
289
|
+
def configure &configuration
|
290
|
+
@configuration = configuration
|
291
|
+
default_configuration
|
292
|
+
end
|
293
|
+
|
294
|
+
#
|
295
|
+
# Restores the default configuration for this convention calling
|
296
|
+
# the block supplied to #configure without parameters
|
297
|
+
# (so its arity should allow this).
|
298
|
+
#
|
299
|
+
def default_configuration
|
300
|
+
use_with()
|
301
|
+
end
|
302
|
+
|
303
|
+
#
|
304
|
+
# Calls the configuration +Proc+ (if registered through #configure) with the given arguments.
|
305
|
+
# This method can be called from the Specification.
|
306
|
+
#
|
307
|
+
# Returns +self+, allowing to chain other methods.
|
308
|
+
#
|
309
|
+
def use_with(*args,&blk)
|
310
|
+
if @configuration
|
311
|
+
@configuration.call(*args,&blk)
|
312
|
+
elsif !args.empty? || blk
|
313
|
+
raise "#{self} does not accept a configuration"
|
314
|
+
end
|
315
|
+
return self
|
316
|
+
end
|
317
|
+
alias with use_with
|
318
|
+
|
319
|
+
# Name of the convention as a +Symbol+.
|
320
|
+
attr_reader :name
|
321
|
+
attr_writer :name # :nodoc:
|
322
|
+
protected :name= # :nodoc:
|
323
|
+
|
324
|
+
# +Hash+ of arbitrary metadata
|
325
|
+
attr_reader :metadata
|
326
|
+
attr_writer :metadata # :nodoc:
|
327
|
+
protected :metadata= # :nodoc:
|
328
|
+
|
329
|
+
#
|
330
|
+
# Priority of the convention (0 by default). The higher it is, the earlier this convention
|
331
|
+
# will be recognized in the comments.
|
332
|
+
#
|
333
|
+
# If a comment can be accepted by more than one convention - for example when one pattern
|
334
|
+
# is a subset of the other - it's better to make their priorities differ.
|
335
|
+
# Otherwise the convention defined lastly takes the precedence.
|
336
|
+
#
|
337
|
+
attr_accessor :priority
|
338
|
+
|
339
|
+
#
|
340
|
+
# Creates a +Convention+ object with the +name+ _name_.
|
341
|
+
# The block is supplied to #edit and must define the convention behaviour
|
342
|
+
# (through #process_comment, #process_statement, #process_expectation, #process_assertion).
|
343
|
+
#
|
344
|
+
def initialize(name,&blk)
|
345
|
+
@name = name.to_sym
|
346
|
+
@metadata = {}
|
347
|
+
@priority = 0
|
348
|
+
edit &blk
|
349
|
+
verify_status
|
350
|
+
end
|
351
|
+
|
352
|
+
#
|
353
|
+
# :call-seq:
|
354
|
+
# edit{ || ... }
|
355
|
+
# edit{ |conv| ... }
|
356
|
+
#
|
357
|
+
# The block given can edit the convention behaviour through its methods.
|
358
|
+
# If it takes no arguments that block is executed in the context of +self+,
|
359
|
+
# otherwise it is yielded this convention object.
|
360
|
+
#
|
361
|
+
# Returns +self+, allowing to chain other methods.
|
362
|
+
#
|
363
|
+
def edit &blk
|
364
|
+
return self unless blk
|
365
|
+
if blk.arity == 0
|
366
|
+
instance_eval(&blk)
|
367
|
+
else
|
368
|
+
blk.call(self)
|
369
|
+
end
|
370
|
+
return self
|
371
|
+
end
|
372
|
+
|
373
|
+
# :stopdoc:
|
374
|
+
|
375
|
+
def verify_status
|
376
|
+
process_comment.nil? and
|
377
|
+
raise "Convention behaviour not defined (use `process_comment{ ... }')"
|
378
|
+
process_statement.nil? and
|
379
|
+
raise "Convention behaviour not defined (use `process_statement{ ... }')"
|
380
|
+
process_expectation.nil? and
|
381
|
+
raise "Convention behaviour not defined (use `process_expectation{ ... }')"
|
382
|
+
process_assertion.nil? and
|
383
|
+
raise "Convention behaviour not defined (use `process_assertion{ ... }')"
|
384
|
+
end
|
385
|
+
private :verify_status
|
386
|
+
|
387
|
+
def copy new_name, &edit
|
388
|
+
conv = self.dup
|
389
|
+
conv.name = new_name.to_sym
|
390
|
+
conv.metadata = self.metadata.dup
|
391
|
+
conv.edit(&edit)
|
392
|
+
end
|
393
|
+
|
394
|
+
def to_s
|
395
|
+
"#<#{self.class}:#{name}>"
|
396
|
+
end
|
397
|
+
alias inspect to_s
|
398
|
+
|
399
|
+
# :startdoc:
|
400
|
+
|
401
|
+
#
|
402
|
+
# This module is included into the Specification and allows the interaction
|
403
|
+
# with the Convention objects in use: you can define new ones, remove
|
404
|
+
# those already in use or simply modify some of their features.
|
405
|
+
#
|
406
|
+
# It also extends the Convention class, so that the built-in conventions
|
407
|
+
# are defined in the same way.
|
408
|
+
# This provides a good starting point: just look at the core definitions
|
409
|
+
# to learn something very quickly. Anyway in the Convention class
|
410
|
+
# you can find the details on how conventions work.
|
411
|
+
#
|
412
|
+
module SpecificationHelper
|
413
|
+
|
414
|
+
# :stopdoc:
|
415
|
+
|
416
|
+
attr_reader :conventions
|
417
|
+
private :conventions
|
418
|
+
|
419
|
+
def sort_conventions
|
420
|
+
conventions.sort_by!.with_index{ |cnv,i| [-cnv.priority,-i] }
|
421
|
+
end
|
422
|
+
private :sort_conventions
|
423
|
+
|
424
|
+
def convention_list
|
425
|
+
sort_conventions
|
426
|
+
conventions.dup
|
427
|
+
end
|
428
|
+
|
429
|
+
# :startdoc:
|
430
|
+
|
431
|
+
# Returns the Convention with the given _name_ or +nil+ if it isn't defined.
|
432
|
+
def convention_defined? name
|
433
|
+
name = name.to_sym
|
434
|
+
conventions.find{ |conv| conv.name == name }
|
435
|
+
end
|
436
|
+
|
437
|
+
# Returns the Convention with the given _name_ or raises an exception if it isn't defined.
|
438
|
+
def convention name
|
439
|
+
convention_defined?(name) or
|
440
|
+
raise Error, "Undefined convention `#{name}'"
|
441
|
+
end
|
442
|
+
|
443
|
+
#
|
444
|
+
# Defines a convention with the given _name_ and the block _definition_ (sent to Convention.new).
|
445
|
+
# If _template_ (name or Convention object) is given, it is copied and used
|
446
|
+
# as the starting point of definition.
|
447
|
+
# Returns the defined convention.
|
448
|
+
#
|
449
|
+
def define_convention name, template=nil, &definition
|
450
|
+
convention_defined?(name) and
|
451
|
+
raise "Convention `#{name}' already defined!"
|
452
|
+
conv = if template.nil?
|
453
|
+
Convention.new(name,&definition)
|
454
|
+
else
|
455
|
+
template = convention(template) unless template.is_a?(Convention)
|
456
|
+
template.copy(name,&definition)
|
457
|
+
end
|
458
|
+
conventions << conv
|
459
|
+
sort_conventions
|
460
|
+
return conv
|
461
|
+
end
|
462
|
+
|
463
|
+
#
|
464
|
+
# Removes _conv_ (name or Convention object) from the list of defined conventions.
|
465
|
+
# Returns +true+ if it was defined, +false+ otherwise.
|
466
|
+
#
|
467
|
+
def remove_convention conv
|
468
|
+
conv = convention(conv) unless conv.is_a?(Convention)
|
469
|
+
!!conventions.delete(conv)
|
470
|
+
end
|
471
|
+
alias undefine_convention remove_convention
|
472
|
+
|
473
|
+
end
|
474
|
+
|
475
|
+
@conventions = []
|
476
|
+
extend SpecificationHelper
|
477
|
+
class << self # :nodoc:
|
478
|
+
alias list convention_list
|
479
|
+
alias get convention
|
480
|
+
alias [] convention
|
481
|
+
end
|
482
|
+
|
483
|
+
#
|
484
|
+
# :section: Conventions: Result
|
485
|
+
#
|
486
|
+
|
487
|
+
##
|
488
|
+
# :singleton-method:
|
489
|
+
#
|
490
|
+
# This is the most common convention. Historically introduced by +IRB+ as the result
|
491
|
+
# prompt <tt>"=>"</tt>, it's now used in almost all documentation examples
|
492
|
+
# in the flavour of _hash_-rocket notation <tt>#=></tt>.
|
493
|
+
#
|
494
|
+
# Expectation format::
|
495
|
+
# <tt>=></tt> _result_code_
|
496
|
+
#
|
497
|
+
# Description::
|
498
|
+
# Evaluates the statement and the (first statement of) expectation;
|
499
|
+
# these values are compared through +assert_equal+.
|
500
|
+
# Regexp.compile '///' #=> %r%///% # equivalent to /\/\/\// but more readable
|
501
|
+
# options = {}
|
502
|
+
# options[:a] = 1
|
503
|
+
# options[:b] = 2
|
504
|
+
# options
|
505
|
+
# # => {
|
506
|
+
# # :a => 1,
|
507
|
+
# # :b => 2
|
508
|
+
# # }
|
509
|
+
# # statement complete; this comment is ignored
|
510
|
+
# [5,4,3,2,1].reverse #=> (1..5).to_a
|
511
|
+
# animals = ["monkey", "cat", "ant", "lion", "bee", "dog"]
|
512
|
+
# animals.select{ |s| s.length == 3 }.sort # => %w[ ant bee cat dog ]
|
513
|
+
#
|
514
|
+
# Note::
|
515
|
+
# Since the expectation is evalued we can include additional details with comments,
|
516
|
+
# it can can span across multiple lines and once the statement is complete the rest is ignored.
|
517
|
+
#
|
518
|
+
# Configuration::
|
519
|
+
# The user can supply its chosen prompts.
|
520
|
+
#
|
521
|
+
define_convention :result_eval do
|
522
|
+
configure do |*prompts|
|
523
|
+
prompts << '=>' if prompts.empty?
|
524
|
+
build_pattern prompts, :STATEMENT
|
525
|
+
end
|
526
|
+
process_statement{ |code| eval(code) }
|
527
|
+
process_expectation &process_statement
|
528
|
+
process_assertion{ |act,exp| assert_equal(exp,act) }
|
529
|
+
self.priority = 100 # most common => tried early for efficiency
|
530
|
+
end
|
531
|
+
|
532
|
+
##
|
533
|
+
# :singleton-method:
|
534
|
+
#
|
535
|
+
# Derived from ::result_eval::
|
536
|
+
# Consider the following:
|
537
|
+
#
|
538
|
+
# a = "A string"
|
539
|
+
# b = "Another string"
|
540
|
+
# b.slice! /nother/
|
541
|
+
# :rdx: off
|
542
|
+
# b #=> a
|
543
|
+
#
|
544
|
+
# Both the strings +a+ and +b+ have the same content;
|
545
|
+
# if the <tt>=></tt> prompt always maps to an +assert_equal+ the assertion would pass...
|
546
|
+
# But it's really what we mean, what we want to bring out from the example?
|
547
|
+
# That sounds much more like "it returns the object _labelled_ +a+"
|
548
|
+
# then "it returns an object _equal_ _to_ +a+".
|
549
|
+
#
|
550
|
+
# Expectation format::
|
551
|
+
# <tt>=></tt> _fetch_instruction_
|
552
|
+
#
|
553
|
+
# Description::
|
554
|
+
# This convention recognizes a plain "fetching" (according to Text::REGEXP::FETCH)
|
555
|
+
# expectation - in the most common case a local variable - and compares the values
|
556
|
+
# through +assert_same+.
|
557
|
+
#
|
558
|
+
# Configuration::
|
559
|
+
# The user can supply its chosen prompts.
|
560
|
+
#
|
561
|
+
define_convention :result_same, :result_eval do
|
562
|
+
configure do |*prompts|
|
563
|
+
prompts << '=>' if prompts.empty?
|
564
|
+
build_pattern prompts, :CAPTURE_FETCH, :END_CODE_IN_LINE
|
565
|
+
end
|
566
|
+
process_assertion{ |act,exp| assert_same(exp,act) }
|
567
|
+
self.priority = 110
|
568
|
+
end
|
569
|
+
|
570
|
+
##
|
571
|
+
# :singleton-method:
|
572
|
+
#
|
573
|
+
# Derived from ::result_eval::
|
574
|
+
# Consider how Numerics are *obtained*:
|
575
|
+
#
|
576
|
+
# 1.quo 2 #=> 1/2
|
577
|
+
# Complex(1,1) #=> 1+1i
|
578
|
+
#
|
579
|
+
# * The first expectation evals to 0 (unless +mathn+ is required),
|
580
|
+
# but that doesn't seem its goal: there is no need to point out an
|
581
|
+
# Integer as a ratio between two literal numbers.
|
582
|
+
# * The second expectation would raise a SyntaxError (due to +i+):
|
583
|
+
# of course this isn't what we mean.
|
584
|
+
#
|
585
|
+
# The point is that _evaluate_ a string isn't the best way to get
|
586
|
+
# a number: the _conversion_ is usually better.
|
587
|
+
#
|
588
|
+
# Now consider how numeric are *compared*:
|
589
|
+
#
|
590
|
+
# :rdx: off -
|
591
|
+
# 1.0 + 2.0 #=> 3
|
592
|
+
# Math.log(14) #=> 2.6390573296152584
|
593
|
+
#
|
594
|
+
# * The result of the first statement is the Float <tt>3.0</tt>, but the expectation
|
595
|
+
# alludes to a Fixnum... Can we still consider those numbers _equals_?
|
596
|
+
# * Are all of those float digits really necessary? In most situations the answer is no,
|
597
|
+
# however the floating-point comparison does care about it - and the result is often
|
598
|
+
# machine-depending.
|
599
|
+
#
|
600
|
+
# This means that in these situations +assert_equal+ is inappropriate: we have to
|
601
|
+
# enforce type checking and loosen the accuracy for inexact floats.
|
602
|
+
#
|
603
|
+
# Expectation format::
|
604
|
+
# <tt>=></tt> _literal_numeric_ (obtainable with Numeric#to_s)
|
605
|
+
#
|
606
|
+
# Description::
|
607
|
+
# Obtains the Numeric through Text#numeric_from_str and makes the assertion with
|
608
|
+
# Assertions#assert_equal_numeric (the default relative error allowed
|
609
|
+
# for floats is <tt>1e-6</tt>, value stored in the #metadata as +:epsilon+).
|
610
|
+
#
|
611
|
+
# Note::
|
612
|
+
# This is suitable enough for the vast majority of the examples;
|
613
|
+
# if all digits are important we should move to more accurate types - such as +BigDecimal+.
|
614
|
+
#
|
615
|
+
# If the value isn't literal (but calculated, for example) the +float+ directive
|
616
|
+
# can - among other things - force the use of this approximation in the comparison
|
617
|
+
# (see Directive::float for an example).
|
618
|
+
#
|
619
|
+
# Configuration::
|
620
|
+
# The user can supply its chosen prompts and the epsilon.
|
621
|
+
#
|
622
|
+
define_convention :result_numeric, :result_eval do
|
623
|
+
md = self.metadata
|
624
|
+
configure do |*args|
|
625
|
+
md[:epsilon] = args.find{ |arg| arg.is_a?(Numeric) } || 1e-6
|
626
|
+
args.delete(md[:epsilon])
|
627
|
+
args << '=>' if args.empty?
|
628
|
+
build_pattern args, :CAPTURE_NUMERIC, :END_CODE_IN_LINE
|
629
|
+
end
|
630
|
+
process_expectation &Text.method(:numeric_from_str)
|
631
|
+
process_assertion do |act,exp|
|
632
|
+
assert_equal_numeric exp, act, md[:epsilon]
|
633
|
+
end
|
634
|
+
self.priority = 110
|
635
|
+
end
|
636
|
+
|
637
|
+
##
|
638
|
+
# :singleton-method:
|
639
|
+
#
|
640
|
+
# Derived from ::result_eval::
|
641
|
+
# Consider the following (from <tt>encoding.c</tt>):
|
642
|
+
#
|
643
|
+
# string = "R\xC3\xA9sum\xC3\xA9"
|
644
|
+
# string.encoding #=> Encoding::UTF_8
|
645
|
+
# string.force_encoding(Encoding::ISO_8859_1)
|
646
|
+
# #=> "R\xC3\xA9sum\xC3\xA9"
|
647
|
+
# # same bytes!
|
648
|
+
#
|
649
|
+
# The point is that with a literal string we have no way to specify its encoding
|
650
|
+
# (other than the magic comment, but it's a little odd at the top of the example
|
651
|
+
# if used only for this reason). Therefore the intention is to show the actual bytes
|
652
|
+
# in the string, regardless of the encoding in which it's represented. This is good
|
653
|
+
# as a general rule: if it's not the case it should be pointed out - like in some
|
654
|
+
# examples in <tt>transcode.c</tt>:
|
655
|
+
#
|
656
|
+
# ec = Encoding::Converter.new("utf-8", "iso-2022-jp")
|
657
|
+
# ec.convert("\xE3") #=> "".force_encoding("ISO-2022-JP")
|
658
|
+
# ec.convert("\x81") #=> "".force_encoding("ISO-2022-JP")
|
659
|
+
# ec.convert("\x82") #=> "\e$B$\"".force_encoding("ISO-2022-JP")
|
660
|
+
# ec.finish #=> "\e(B".force_encoding("ISO-2022-JP")
|
661
|
+
#
|
662
|
+
# Expectation format::
|
663
|
+
# <tt>=></tt> _literal_string_
|
664
|
+
#
|
665
|
+
# Description::
|
666
|
+
# The string encoding is ignored (converted to +BINARY+) in the comparison.
|
667
|
+
#
|
668
|
+
# Note::
|
669
|
+
# This is actually true for all types of literal strings,
|
670
|
+
# however only double-quoted ones (without interpolation) are recognized.
|
671
|
+
# This is because here we can insert actual bytes easily.
|
672
|
+
#
|
673
|
+
# Configuration::
|
674
|
+
# The user can supply its chosen prompts.
|
675
|
+
#
|
676
|
+
define_convention :result_string, :result_eval do
|
677
|
+
configure do |*prompts|
|
678
|
+
prompts << '=>' if prompts.empty?
|
679
|
+
build_pattern prompts, :CAPTURE_DQ_STRING, :END_CODE_IN_LINE
|
680
|
+
end
|
681
|
+
level = lambda do |obj|
|
682
|
+
obj.dup.force_encoding('BINARY') if obj.is_a?(String)
|
683
|
+
end
|
684
|
+
process_assertion{ |act,exp| assert_equal_but level, exp, act }
|
685
|
+
self.priority = 110
|
686
|
+
end
|
687
|
+
|
688
|
+
# This way RDoc recognizes the ghost method properly...
|
689
|
+
undefine_convention :result_string unless Object.const_defined?(:Encoding)
|
690
|
+
|
691
|
+
##
|
692
|
+
# :singleton-method:
|
693
|
+
#
|
694
|
+
# Derived from ::result_eval::
|
695
|
+
# Consider the following:
|
696
|
+
#
|
697
|
+
# class Klass
|
698
|
+
# # ...
|
699
|
+
# end
|
700
|
+
# inst = Klass.new
|
701
|
+
# inst #=> #<Klass:0x00074532>
|
702
|
+
#
|
703
|
+
# This notation is due to the fact that +IRB+ uses the +inspect+ method and usually
|
704
|
+
# it returns a string formatted in that way. Since it's unlikely to start a statement
|
705
|
+
# (intended to be evalued) like that, we can interpret it differently.
|
706
|
+
# The only thing we want to exalt is that +inst+ is an *instance* of +Klass+.
|
707
|
+
#
|
708
|
+
# Expectation format::
|
709
|
+
# <tt>=> #<</tt>_class_ [:_object_adress_] [_other_data_]<tt>></tt>
|
710
|
+
#
|
711
|
+
# Description::
|
712
|
+
# The class is extracted from the expectation; it's then send to
|
713
|
+
# +assert_instance_of+ along with the result of the statement.
|
714
|
+
#
|
715
|
+
# Note::
|
716
|
+
# The _object_adress_ is optional. If there is other extraneous data a warning will
|
717
|
+
# be generated (because it won't be used): to check that data ::result_inspect
|
718
|
+
# is probably the solution.
|
719
|
+
#
|
720
|
+
# Note::
|
721
|
+
# We don't want to rely on the +inspect+ method (either via explicit call or
|
722
|
+
# by the ::result_inspect convention), since that string can carry on other
|
723
|
+
# data or may even not contain the class at all (like +Time+ objects).
|
724
|
+
#
|
725
|
+
# Configuration::
|
726
|
+
# The user can supply its chosen prompts.
|
727
|
+
#
|
728
|
+
define_convention :result_instance_of, :result_eval do
|
729
|
+
configure do |*prompts|
|
730
|
+
prompts << '=>' if prompts.empty?
|
731
|
+
build_pattern prompts, /#?<(.*)>/m, :END_CODE_IN_LINE
|
732
|
+
end
|
733
|
+
process_expectation do |content|
|
734
|
+
# Validation:
|
735
|
+
md = content.match /^
|
736
|
+
( #{REGEXP::CLASS} ) # Literal class
|
737
|
+
(?: :0[Xx]\h+ )? # Optional object id - discarded
|
738
|
+
( (?m:.*) ) # All the rest: should be empty
|
739
|
+
/ox
|
740
|
+
raise 'Missing literal class!' unless md
|
741
|
+
klass_name,other_content = md.captures
|
742
|
+
klass = toplevel_scope.const_get(klass_name)
|
743
|
+
warn "Data unused by this convention: #{other_content.inspect}" unless other_content.empty?
|
744
|
+
klass
|
745
|
+
end
|
746
|
+
process_assertion{ |obj,klass| assert_instance_of(klass,obj) }
|
747
|
+
self.priority = 110
|
748
|
+
end
|
749
|
+
|
750
|
+
##
|
751
|
+
# :singleton-method:
|
752
|
+
#
|
753
|
+
# This convention is for values that are uncertain.
|
754
|
+
# It does a sort of _weak_ _assertion_, testing that values are "similar".
|
755
|
+
# Fields of application include randomness, time objects, system and context-dependent issues.
|
756
|
+
#
|
757
|
+
# Expectation format::
|
758
|
+
# <tt>-></tt> _result_code_
|
759
|
+
#
|
760
|
+
# Description::
|
761
|
+
# Both the statement and the expectation are evalued; the respective results are
|
762
|
+
# compared through Assertions#assert_same_type.
|
763
|
+
#
|
764
|
+
# An example (from <tt>random.c</tt>):
|
765
|
+
#
|
766
|
+
# rand(-100) # -> 87
|
767
|
+
# rand(-0.5) # -> 0.8130921818028143
|
768
|
+
# rand(1.9) # => 0 # equivalent to rand(1), which is always 0
|
769
|
+
#
|
770
|
+
# Note::
|
771
|
+
# When an indicative test makes sense, it's surely better than nothing:
|
772
|
+
# try this convention before leaving to the ::eval one.
|
773
|
+
#
|
774
|
+
# Configuration::
|
775
|
+
# The user can supply its chosen prompts.
|
776
|
+
#
|
777
|
+
define_convention :result_indicative do
|
778
|
+
configure do |*prompts|
|
779
|
+
prompts << '->' if prompts.empty?
|
780
|
+
build_pattern prompts, :STATEMENT
|
781
|
+
end
|
782
|
+
process_statement{ |code| eval(code) }
|
783
|
+
process_expectation &process_statement
|
784
|
+
process_assertion{ |act,exp| assert_same_type(exp,act) }
|
785
|
+
end
|
786
|
+
|
787
|
+
##
|
788
|
+
# :singleton-method:
|
789
|
+
#
|
790
|
+
# This convention is highly coupled with the ::result_eval one:
|
791
|
+
# since in some situations they lead to the same result,
|
792
|
+
# the difference between the two conventions may be a little subtle
|
793
|
+
# (see the Note for details).
|
794
|
+
#
|
795
|
+
# Expectation format::
|
796
|
+
# <tt>></tt> _string_representation_
|
797
|
+
#
|
798
|
+
# Description::
|
799
|
+
# Evaluates the statement and treats the expectation as its literal string representation
|
800
|
+
# obtained with +inspect+. The strings are stripped and object ids are generalized,
|
801
|
+
# so that +assert_equal+ sees only the essential differences.
|
802
|
+
#
|
803
|
+
# /\d+\.\d+/.match "pi = 3.17" # > #<MatchData "3.17">
|
804
|
+
# # inline comments not allowed (the string is taken literally)
|
805
|
+
# # Rubyday 2015:
|
806
|
+
# Time.utc 2015, 11, 13 # > 2015-11-13 00:00:00 UTC
|
807
|
+
# 1/0.0 # > Infinity
|
808
|
+
# o = Object.new
|
809
|
+
# o.instance_variable_set :@a, 1
|
810
|
+
# o # > #<Object:0x000045a8 @a=1>
|
811
|
+
# # Put the adress that you want, it doesn't matter...
|
812
|
+
#
|
813
|
+
# Note::
|
814
|
+
# +result_eval+'s approach is to assert <tt>object == eval(string)</tt>,
|
815
|
+
# while +result_inspect+ asserts <tt>object.inspect == string</tt>.
|
816
|
+
# These are the two sides of the same coin if +eval+ is the _inverse_
|
817
|
+
# of +inspect+ for that +object+.
|
818
|
+
#
|
819
|
+
# In fact, there is an issue hidden under the rug: the <tt>=></tt> prompt in +IRB+
|
820
|
+
# actually shows the string representation of an object (the result of a statement)!
|
821
|
+
# However that prompt isn't associated with the +result_inspect+ convention
|
822
|
+
# for a matter of convenience.
|
823
|
+
#
|
824
|
+
# Examples must be written and read in the simplest - but formal - way,
|
825
|
+
# without loosing time on tiny and unnecessary details.
|
826
|
+
# The fact that the expectation is evalued offers some advantages in terms of freedom:
|
827
|
+
# * we can be more concise and clear by using <b>alternative syntax</b> and *expressions*;
|
828
|
+
# * we can insert <b>values at runtime</b> - depending on the context - rather then
|
829
|
+
# hard-coding them in a predetermined way.
|
830
|
+
# The next example is composed by statements that will fail if +result_inspect+ would have
|
831
|
+
# the <tt>=></tt> prompt:
|
832
|
+
#
|
833
|
+
# [1,2] #=> [1,2] # missing space: [1, 2]
|
834
|
+
# "a string".inspect #=> %Q{"a string"} # inspect uses double quotes: "\"a string\""
|
835
|
+
# Hash('a'=>2,'b'=>1) #=> {
|
836
|
+
# # 'b' => 1,
|
837
|
+
# # 'a' => 2
|
838
|
+
# # } # does these spaces really matter? And the order in which pairs are listed?
|
839
|
+
# File.expand_path 'a' #=> "#{Dir.pwd}/a" # interpolation of values not allowed
|
840
|
+
# (1..1000).first 100 #=> (1..100).to_a # shorthands not allowed
|
841
|
+
# "abc \0\0".unpack('a3a3') #=> ["abc", " \000\000"]
|
842
|
+
# # the second string should be: " \0\0"
|
843
|
+
#
|
844
|
+
# Because such situations are far from being rare - the first twos explain
|
845
|
+
# 43 failures only in <tt>array.c</tt> - the choice is quite clear:
|
846
|
+
# this convention is associated to a different prompt, leaving the hash-rocket
|
847
|
+
# for +result_eval+.
|
848
|
+
#
|
849
|
+
# For these reasons, if these two alternatives are equivalent +result_eval+ is often preferred.
|
850
|
+
# Otherwise - for objects whose +eval+ isn't the inverse of +inspect+ - this convention turns
|
851
|
+
# handy: showing the results above without will explicitly force the use of +inspect+
|
852
|
+
# on the receiver (and the expected string is evalued). These may divert attention
|
853
|
+
# from the true aim of the example to the behaviour of the +inspect+ method.
|
854
|
+
#
|
855
|
+
# Configuration::
|
856
|
+
# The user can supply its chosen prompts.
|
857
|
+
#
|
858
|
+
define_convention :result_inspect do
|
859
|
+
configure do |*prompts|
|
860
|
+
prompts << '>' if prompts.empty?
|
861
|
+
set_prompts prompts
|
862
|
+
end
|
863
|
+
process_statement{ |code| eval(code).inspect }
|
864
|
+
dont_process_expectation
|
865
|
+
level = lambda{ |txt| generalize_object_ids(txt.strip) }
|
866
|
+
process_assertion{ |act,exp| assert_equal_but(level,exp,act) }
|
867
|
+
end
|
868
|
+
|
869
|
+
##
|
870
|
+
# :singleton-method:
|
871
|
+
#
|
872
|
+
# Derived from ::result_inspect::
|
873
|
+
# The aim is to reduce discrepances due to float calculations and representations,
|
874
|
+
# one of the things that ::result_numeric does for ::result_eval (but
|
875
|
+
# here this is done in strings).
|
876
|
+
#
|
877
|
+
# Expectation format::
|
878
|
+
# <tt>></tt> _numeric_representation_ (obtainable with Numeric#inspect)
|
879
|
+
#
|
880
|
+
# Description::
|
881
|
+
# At most 6 (value stored in the #metadata as +:digits+) digits are significant -
|
882
|
+
# thus considered - for floats.
|
883
|
+
#
|
884
|
+
# Complex.polar(2, 3) # > (-1.9799849932008908+0.2822400161197344i)
|
885
|
+
# # same as:
|
886
|
+
# Complex.polar(2, 3) # > (-1.9799849+0.2822400i)
|
887
|
+
#
|
888
|
+
# Configuration::
|
889
|
+
# The user can supply its chosen prompts and the digits.
|
890
|
+
#
|
891
|
+
define_convention :numeric_inspect, :result_inspect do
|
892
|
+
md = self.metadata
|
893
|
+
configure do |*args|
|
894
|
+
md[:digits] = args.find{ |arg| arg.is_a?(Fixnum) } || 6
|
895
|
+
args.delete(md[:digits])
|
896
|
+
args << '>' if args.empty?
|
897
|
+
build_pattern args, :CAPTURE_NUMERIC_P, /$/ # no comment allowed for coherence
|
898
|
+
end
|
899
|
+
level = lambda{ |txt| Text.normalize_floats(txt,md[:digits]) }
|
900
|
+
process_assertion{ |act,exp| assert_equal_but(level,exp,act) }
|
901
|
+
self.priority = 10
|
902
|
+
end
|
903
|
+
|
904
|
+
#
|
905
|
+
# :section: Conventions: Output
|
906
|
+
#
|
907
|
+
|
908
|
+
##
|
909
|
+
# :singleton-method:
|
910
|
+
#
|
911
|
+
# Expectation format::
|
912
|
+
# <tt>prints:</tt> _output_
|
913
|
+
#
|
914
|
+
# Description::
|
915
|
+
# Indicates the output (send to <tt>$stdout</tt>) of the statement; this output is
|
916
|
+
# taken _literally_, without any evaluation, and sent to +assert_output+. Because of this,
|
917
|
+
# in order to avoid failures for minor issues, the comparison is made through Text#normalize_output.
|
918
|
+
#
|
919
|
+
# print "Hello!" # prints: Hello!
|
920
|
+
# # literal == straightforward:
|
921
|
+
# print "\"A quoted string\"" # prints: "A quoted string"
|
922
|
+
# # lost of (often meaningless) information:
|
923
|
+
# print " spaces\n\n" # prints: spaces
|
924
|
+
#
|
925
|
+
# Note::
|
926
|
+
# This convention is really powerful when expressing strings containing many special characters,
|
927
|
+
# that have otherwise to be escaped - but beware of Text#normalize_output.
|
928
|
+
# This is illustrated by the following example (from <tt>re.c</tt>):
|
929
|
+
#
|
930
|
+
# puts Regexp.escape('\*?{}.') # prints: \\\*\?\{\}\.
|
931
|
+
#
|
932
|
+
# Here are some alternatives, clearly more confusing because we have to consider also the
|
933
|
+
# escaping step for the string itself:
|
934
|
+
#
|
935
|
+
# str = Regexp.escape('\*?{}.')
|
936
|
+
# str # => '\\\\\*\?\{\}\.' # not to talk about "\\\\\*\\?\\{\\}\\."
|
937
|
+
# str # =>
|
938
|
+
# # <<'EOS'.strip
|
939
|
+
# # \\\*\?\{\}\.
|
940
|
+
# # EOS
|
941
|
+
#
|
942
|
+
# Note::
|
943
|
+
# The main limitation for this convention is that the visible output cannot fill more
|
944
|
+
# than one line (otherwise we may include extraneous comment, like the ones in the first example):
|
945
|
+
# this issue can be solved by the use of Directive::stdout, which also does the
|
946
|
+
# same levelling on the output.
|
947
|
+
#
|
948
|
+
# Note::
|
949
|
+
# Simplicity means less exactness. This often isn't a problem, but sometimes we may
|
950
|
+
# want more precision. A more formal approach is offered by
|
951
|
+
# ::output_eval which - among other things - avoids the levelling through evaluation.
|
952
|
+
#
|
953
|
+
# Configuration::
|
954
|
+
# The user can supply its chosen prompts.
|
955
|
+
#
|
956
|
+
define_convention :output_literal do
|
957
|
+
configure do |*prompts|
|
958
|
+
prompts << /[Pp]rints:?/ if prompts.empty?
|
959
|
+
set_prompts prompts
|
960
|
+
end
|
961
|
+
process_statement{ |code| lambda{ eval(code) } }
|
962
|
+
dont_process_expectation
|
963
|
+
level = method(:normalize_output).to_proc
|
964
|
+
process_assertion do |verbose_proc,stdout|
|
965
|
+
assert_output_but level, stdout, &verbose_proc
|
966
|
+
end
|
967
|
+
end
|
968
|
+
|
969
|
+
##
|
970
|
+
# :singleton-method:
|
971
|
+
#
|
972
|
+
# Expectation format::
|
973
|
+
# <tt>>></tt> _output_code_
|
974
|
+
#
|
975
|
+
# Description::
|
976
|
+
# Indicates the output (send to <tt>$stdout</tt>) of the statement; this output is
|
977
|
+
# _evalued_ and sent to +assert_output+. Since there cannot be ambiguity
|
978
|
+
# the comparison is by default made without applying any trick, allowing a more formal
|
979
|
+
# approach in contrast to ::output_literal.
|
980
|
+
#
|
981
|
+
# print "a string" # >> "a string" # as is (comment allowed)
|
982
|
+
# puts "a string" # >> "a string\n" # newline appended
|
983
|
+
# puts "a string\n" # >> "a string\n" # newline not appended if already there
|
984
|
+
# p "a string" # >> %Q("a string"\n) # calls inspect
|
985
|
+
# puts "This is ", "the time"
|
986
|
+
# # >> "This is \n" +
|
987
|
+
# # "the time\n"
|
988
|
+
# # multiline expectation
|
989
|
+
# alphabet = "abcdefghijklmnopqrstuvwxyz"
|
990
|
+
# print alphabet # >> ('a'..'z').to_a.join # Expressions allowed
|
991
|
+
#
|
992
|
+
# Note::
|
993
|
+
# The evaluation makes also possible to return a +Regexp+. In this way we
|
994
|
+
# can use the full power of +assert_output+:
|
995
|
+
# puts "c"*500 # >> /\A C{500} \Z/ix # Regexp match assertion
|
996
|
+
#
|
997
|
+
# Configuration::
|
998
|
+
# The user can supply its chosen prompts.
|
999
|
+
#
|
1000
|
+
define_convention :output_eval do
|
1001
|
+
configure do |*prompts|
|
1002
|
+
prompts << '>>' if prompts.empty? # Inspired by "Using JRuby", Pragmatic Programmers
|
1003
|
+
build_pattern prompts, :STATEMENT
|
1004
|
+
end
|
1005
|
+
process_statement{ |code| lambda{ eval(code) } }
|
1006
|
+
process_expectation{ |code| eval(code) }
|
1007
|
+
process_assertion do |verbose_proc,output|
|
1008
|
+
assert_output output, &verbose_proc
|
1009
|
+
end
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
#
|
1013
|
+
# :section: Conventions: Error
|
1014
|
+
#
|
1015
|
+
|
1016
|
+
##
|
1017
|
+
# :singleton-method:
|
1018
|
+
#
|
1019
|
+
# Expectation format::
|
1020
|
+
# <tt>raises</tt> [_exception_class_] [<tt>:</tt>] [_exception_message_]
|
1021
|
+
#
|
1022
|
+
# Description::
|
1023
|
+
# Recognizes the class and the message of the exception raised by the statement.
|
1024
|
+
# The message is interpreted literally, without any evaluation:
|
1025
|
+
# to remove little discrepances Text#normalize_exception_message is used.
|
1026
|
+
# The class (if given), message and +false+ are sent to Assertions#assert_msg_raised.
|
1027
|
+
#
|
1028
|
+
# 1.odd?(true) # raises ArgumentError: wrong number of arguments (1 for 0)
|
1029
|
+
# "a string".meth # raises NoMethodError: undefined method `meth'
|
1030
|
+
# # the message continues with 'for "a string":String', but that doesn't add much...
|
1031
|
+
# "a string" + 1 # Raises! # it doesn't matter if it's a TypeError or ArgumentError,
|
1032
|
+
# # as long as the action is illegal.
|
1033
|
+
# [1].freeze.shift # raises: frozen
|
1034
|
+
# # this works, but it's probably too concise...
|
1035
|
+
#
|
1036
|
+
# Configuration::
|
1037
|
+
# The user can supply its chosen prompts.
|
1038
|
+
#
|
1039
|
+
define_convention :error_literal do
|
1040
|
+
configure do |*prompts|
|
1041
|
+
prompts << /[Rr]aises[:!]?/ if prompts.empty?
|
1042
|
+
build_pattern prompts, /(#{REGEXP::CLASS})?:?/o, /#{REGEXP::END_CODE_IN_LINE}|(.*)/o
|
1043
|
+
end
|
1044
|
+
process_statement{ |code| lambda{ eval(code) } }
|
1045
|
+
process_expectation do |(cls,msg)|
|
1046
|
+
cls &&= toplevel_scope.const_get(cls)
|
1047
|
+
[cls,msg]
|
1048
|
+
end
|
1049
|
+
level = method(:normalize_exception_message).to_proc
|
1050
|
+
process_assertion do |raising_proc,(exc_class,exc_message)|
|
1051
|
+
assert_msg_raised_but level, exc_message, false, *exc_class, &raising_proc
|
1052
|
+
end
|
1053
|
+
end
|
1054
|
+
|
1055
|
+
##
|
1056
|
+
# :singleton-method:
|
1057
|
+
#
|
1058
|
+
# Expectation format::
|
1059
|
+
# <tt>~></tt> [_exception_class_] [<tt>:</tt> _code_message_]
|
1060
|
+
#
|
1061
|
+
# Description::
|
1062
|
+
# Recognizes the class and the message of the exception raised by the statement.
|
1063
|
+
# The message is evalued, thus avoiding any sort of levellation: this enables
|
1064
|
+
# a more formal approach in contrast to ::error_literal.
|
1065
|
+
# The class (if given) and message are sent to Assertions#assert_msg_raised.
|
1066
|
+
#
|
1067
|
+
# [1].freeze.shift # ~> /frozen/
|
1068
|
+
# 1.odd?(true) # ~> ArgumentError: "wrong number of arguments (1 for 0)"
|
1069
|
+
# "a string".meth # ~> NoMethodError: %q{undefined method `meth' for "a string":String}
|
1070
|
+
#
|
1071
|
+
# Configuration::
|
1072
|
+
# The user can supply its chosen prompts.
|
1073
|
+
#
|
1074
|
+
define_convention :error_eval do
|
1075
|
+
configure do |*prompts|
|
1076
|
+
prompts << '~>' if prompts.empty? # Inspired by "Using JRuby", Pragmatic Programmers
|
1077
|
+
build_pattern prompts, /(.+)/m
|
1078
|
+
end
|
1079
|
+
process_statement{ |code| lambda{ eval(code) } }
|
1080
|
+
process_expectation do |content|
|
1081
|
+
cls,msg = Text.split_exception_string(content,:class_message)
|
1082
|
+
# :message_class is not allowed, since a valid statement can end with a parhentesis...
|
1083
|
+
cls &&= toplevel_scope.const_get(cls)
|
1084
|
+
msg &&= eval(msg)
|
1085
|
+
[cls,msg]
|
1086
|
+
end
|
1087
|
+
process_assertion do |raising_proc,(exc_class,exc_message)|
|
1088
|
+
assert_msg_raised exc_message, *exc_class, &raising_proc
|
1089
|
+
end
|
1090
|
+
end
|
1091
|
+
|
1092
|
+
#
|
1093
|
+
# :section: Conventions: Others
|
1094
|
+
#
|
1095
|
+
|
1096
|
+
##
|
1097
|
+
# :singleton-method:
|
1098
|
+
#
|
1099
|
+
# Expectation format::
|
1100
|
+
# <tt>Throws</tt> _symbol_
|
1101
|
+
#
|
1102
|
+
# Description::
|
1103
|
+
# The statement must +throw+ the given _symbol_.
|
1104
|
+
#
|
1105
|
+
# def this_will_throw sym
|
1106
|
+
# throw sym
|
1107
|
+
# end
|
1108
|
+
# this_will_throw :symbol # Throws :symbol
|
1109
|
+
#
|
1110
|
+
# Configuration::
|
1111
|
+
# The user can supply its chosen prompts.
|
1112
|
+
#
|
1113
|
+
define_convention :throws do
|
1114
|
+
configure do |*prompts|
|
1115
|
+
prompts << /[Tt]hrows:?/ if prompts.empty?
|
1116
|
+
build_pattern prompts, /:(\w+)/, :END_CODE_IN_LINE
|
1117
|
+
end
|
1118
|
+
process_statement{ |code| lambda{ eval(code) } }
|
1119
|
+
process_expectation &:to_sym
|
1120
|
+
process_assertion do |throwing_proc,catch|
|
1121
|
+
assert_throws catch, &throwing_proc
|
1122
|
+
end
|
1123
|
+
end
|
1124
|
+
|
1125
|
+
##
|
1126
|
+
# :singleton-method:
|
1127
|
+
#
|
1128
|
+
# This convention is triggered as the last resource, if any of the others isn't.
|
1129
|
+
#
|
1130
|
+
# Description::
|
1131
|
+
# Evaluates the statement. Nothing else.
|
1132
|
+
#
|
1133
|
+
# Note::
|
1134
|
+
# This convention generates a warning if the expectation begins with a non-word
|
1135
|
+
# character, which is probably part of a prompt whose convention is not defined.
|
1136
|
+
#
|
1137
|
+
# Note::
|
1138
|
+
# Basically, this convention kicks in when:
|
1139
|
+
# * we don't care about the result obtained, but we have anyhow to evaluate the code;
|
1140
|
+
# * we do care about it, but other conventions aren't suitable in these circumstances.
|
1141
|
+
#
|
1142
|
+
# The latter situation is more intresting.
|
1143
|
+
# Consider the following (from <tt>time.c</tt>):
|
1144
|
+
#
|
1145
|
+
# t = Time.now # 2007-11-19 08:18:31 -0600
|
1146
|
+
# t.gmt? #=> false
|
1147
|
+
# t.gmtime # 2007-11-19 14:18:31 UTC
|
1148
|
+
# t.gmt? #=> true
|
1149
|
+
#
|
1150
|
+
# Since time and local zone may vary, it's hard to write an expectation.
|
1151
|
+
# On the other hand not writing a comment at all may leave some issues:
|
1152
|
+
# is the time converted or simply interpreted differently?
|
1153
|
+
# Of course, the documentation explains this issue - but then the example
|
1154
|
+
# becomes quite useless...
|
1155
|
+
#
|
1156
|
+
# Remember that <b>the aim of the examples is to be clear and straightforward,
|
1157
|
+
# not to build solid but incomprehensible tests</b>: for this reason
|
1158
|
+
# a plain comment can sometimes suffice this need.
|
1159
|
+
#
|
1160
|
+
# Configuration::
|
1161
|
+
# Not allowed.
|
1162
|
+
#
|
1163
|
+
define_convention :eval do
|
1164
|
+
set_prompts []
|
1165
|
+
process_statement{ |code| eval(code) }
|
1166
|
+
process_expectation do |code|
|
1167
|
+
warn "Unknown prompt, #{convention} triggered" if code =~ /\A\s*\W/
|
1168
|
+
end
|
1169
|
+
dont_process_assertion
|
1170
|
+
end
|
1171
|
+
|
1172
|
+
end
|
1173
|
+
|
1174
|
+
end
|