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/example.rb
ADDED
@@ -0,0 +1,679 @@
|
|
1
|
+
|
2
|
+
module RDX
|
3
|
+
|
4
|
+
#
|
5
|
+
# Represents an example in a comment, like the following:
|
6
|
+
#
|
7
|
+
# # This in an example:
|
8
|
+
# def example_method arg
|
9
|
+
# arg + 1
|
10
|
+
# end
|
11
|
+
# example_method(3) #=> 4
|
12
|
+
#
|
13
|
+
class Example < CodeObject::Runnable::Startable
|
14
|
+
|
15
|
+
include Assertions
|
16
|
+
|
17
|
+
# The SourceFile this example is into.
|
18
|
+
def source_file
|
19
|
+
comment.source_file
|
20
|
+
end
|
21
|
+
|
22
|
+
# The Comment this example belongs to.
|
23
|
+
attr_reader :comment
|
24
|
+
alias parent comment
|
25
|
+
|
26
|
+
# Return +self+.
|
27
|
+
def example
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
# The +Array+ of Statement that composes this example.
|
32
|
+
attr_reader :statements
|
33
|
+
alias children statements
|
34
|
+
|
35
|
+
# The code contained in this example.
|
36
|
+
def code
|
37
|
+
text
|
38
|
+
end
|
39
|
+
|
40
|
+
def initialize(comment,text,relative_line_no=1) # :nodoc:
|
41
|
+
@comment = comment
|
42
|
+
super(text,relative_line_no)
|
43
|
+
@statements = []
|
44
|
+
@active = nil
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Has the user seen this example?
|
49
|
+
# This is +true+ if the example will appear on the documentation output or
|
50
|
+
# it won't but is the target of an explicit directive
|
51
|
+
# (according to the limitations of the generator).
|
52
|
+
#
|
53
|
+
def seen?
|
54
|
+
not @active.nil?
|
55
|
+
end
|
56
|
+
|
57
|
+
# Will this example be runned?
|
58
|
+
def active?
|
59
|
+
@active
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# :section: API
|
64
|
+
#
|
65
|
+
|
66
|
+
# Returns +true+. This method should be used instead of _section_<tt>.is_a?(Example)</tt>.
|
67
|
+
def example?
|
68
|
+
true
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns +false+. This method should be used instead of _section_<tt>.is_a?(PlainText)</tt>.
|
72
|
+
def text?
|
73
|
+
false
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# Activates this example so that it will be run.
|
78
|
+
# If _force_ is +true+ this happens even if the example was previously deactivated.
|
79
|
+
#
|
80
|
+
def activate force=false
|
81
|
+
@active = true if !seen? || force
|
82
|
+
return self
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# Deactivates this example so that it won't be run.
|
87
|
+
# If _force_ is +true+ this happens even if the example was previously activated.
|
88
|
+
#
|
89
|
+
def deactivate force=false
|
90
|
+
@active = false if !seen? || force
|
91
|
+
return self
|
92
|
+
end
|
93
|
+
|
94
|
+
# Activates this example; #deactivate will no more have effect.
|
95
|
+
def activate!
|
96
|
+
@active = true
|
97
|
+
define_singleton_method(:deactivate){ |*| }
|
98
|
+
return self
|
99
|
+
end
|
100
|
+
|
101
|
+
# Deactivates this example; #activate will no more have effect.
|
102
|
+
def deactivate!
|
103
|
+
@active = false
|
104
|
+
define_singleton_method(:activate){ |*| }
|
105
|
+
return self
|
106
|
+
end
|
107
|
+
|
108
|
+
#
|
109
|
+
# Skips the execution of this example and reports it.
|
110
|
+
# If _msg_ is given it will be printed in the report.
|
111
|
+
# If _l_no_ is given it will be used in the report instead of #relative_line_no.
|
112
|
+
#
|
113
|
+
def whole_skip msg=nil, type=nil, l_no=nil
|
114
|
+
activate!
|
115
|
+
l_no ||= relative_line_no
|
116
|
+
super msg, type, comment.location(l_no)
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
# Marks this example as deliberately buggy. This means that:
|
121
|
+
# * failures and errors are ordinary: they are not reported;
|
122
|
+
# * successes are unexpected: a warning of type +bug+ will
|
123
|
+
# be reported along with the success.
|
124
|
+
#
|
125
|
+
def is_buggy
|
126
|
+
activate
|
127
|
+
dont_register = lambda{ |*| }
|
128
|
+
define_singleton_method :build_children do
|
129
|
+
super()
|
130
|
+
define_singleton_method :register_failure, dont_register
|
131
|
+
define_singleton_method :register_error, dont_register
|
132
|
+
define_singleton_method :register_success do |*args|
|
133
|
+
warn 'Bug not detected: the assertion passes', :bug
|
134
|
+
super(*args)
|
135
|
+
end
|
136
|
+
statements.each do |stmnt|
|
137
|
+
stmnt.define_singleton_method :register_error, dont_register
|
138
|
+
end
|
139
|
+
statements.flat_map(&:children).each do |expct|
|
140
|
+
expct.define_singleton_method :register_failure, dont_register
|
141
|
+
expct.define_singleton_method :register_success do |*args|
|
142
|
+
warn 'Bug not detected: the assertion passes', :bug if assertion?
|
143
|
+
super(*args)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Activates this example and calls +super+.
|
150
|
+
def run_in_tmpdir
|
151
|
+
activate
|
152
|
+
super
|
153
|
+
end
|
154
|
+
|
155
|
+
# Activates this example and calls #binding=.
|
156
|
+
def set_binding binding
|
157
|
+
activate
|
158
|
+
self.binding = binding
|
159
|
+
end
|
160
|
+
|
161
|
+
#
|
162
|
+
# Activates this example an calls #set_output passing
|
163
|
+
# it _output_ (which may even be another Example) as the
|
164
|
+
# expected result on <tt>$stdout</tt>.
|
165
|
+
#
|
166
|
+
def produces_on_stdout output, l_no=nil
|
167
|
+
self.activate
|
168
|
+
if output.is_a?(Example)
|
169
|
+
output.deactivate!
|
170
|
+
output = output.text
|
171
|
+
end
|
172
|
+
set_output output, nil, l_no
|
173
|
+
end
|
174
|
+
alias produces produces_on_stdout
|
175
|
+
alias outputs produces_on_stdout
|
176
|
+
|
177
|
+
#
|
178
|
+
# Activates this example an calls #set_output passing
|
179
|
+
# it _output_ (which may even be another Example) as the
|
180
|
+
# expected result on <tt>$stderr</tt>.
|
181
|
+
#
|
182
|
+
# #normalize_values is enhanced so that the file name and line number are removed
|
183
|
+
# from warnings: in this way we can have more focus on the content of the message.
|
184
|
+
#
|
185
|
+
def produces_on_stderr output, l_no=nil
|
186
|
+
self.activate
|
187
|
+
if output.is_a?(Example)
|
188
|
+
output.deactivate!
|
189
|
+
output = output.text
|
190
|
+
end
|
191
|
+
orig_level = method(:normalize_values)
|
192
|
+
define_singleton_method :normalize_values do |exp,act,&normalize|
|
193
|
+
if act.is_a?(String)
|
194
|
+
act = act.dup
|
195
|
+
act.gsub! /^#{Regexp.escape file_name}:(\d+): (?=warning: )/ do |matched|
|
196
|
+
line_range.cover?($1.to_i) ? '' : matched
|
197
|
+
end
|
198
|
+
end
|
199
|
+
orig_level.call(exp,act,&normalize)
|
200
|
+
end
|
201
|
+
set_output nil, output, l_no
|
202
|
+
end
|
203
|
+
|
204
|
+
#
|
205
|
+
# Wraps the run of this example into #assert_output_but, passing it
|
206
|
+
# the expected _stdout_ and _stderr_.
|
207
|
+
# The differences are reduced through Text#normalize_output.
|
208
|
+
#
|
209
|
+
# If _l_no_ is given it will be used in the report instead of #relative_line_no.
|
210
|
+
#
|
211
|
+
def set_output stdout, stderr, l_no=nil
|
212
|
+
l_no ||= relative_line_no
|
213
|
+
orig_run = method(:run)
|
214
|
+
normalize = Text.method(:normalize_output).to_proc
|
215
|
+
define_singleton_method :run do
|
216
|
+
trace_assertion :multiline_output_literal, comment.location(l_no) do
|
217
|
+
assert_output_but(normalize,stdout,stderr){ orig_run.call }
|
218
|
+
end
|
219
|
+
end
|
220
|
+
return self
|
221
|
+
end
|
222
|
+
|
223
|
+
#
|
224
|
+
# Activates this example and changes <tt>$stdin</tt> to the given _input_
|
225
|
+
# (which may be a +String+ or another example) during the execution.
|
226
|
+
#
|
227
|
+
def take_input_from input
|
228
|
+
self.activate
|
229
|
+
if input.is_a?(Example)
|
230
|
+
input.deactivate!
|
231
|
+
input = input.text
|
232
|
+
end
|
233
|
+
orig_run = method(:run)
|
234
|
+
define_singleton_method :run do
|
235
|
+
RDX::Util.change_stdin(input){ orig_run.call }
|
236
|
+
end
|
237
|
+
return self
|
238
|
+
end
|
239
|
+
|
240
|
+
#
|
241
|
+
# Activates this example and expects that it raises the exception contained in _arg_.
|
242
|
+
# This may be:
|
243
|
+
# * an Example in which it's described the exception (its +text+ is processed through
|
244
|
+
# Text#split_exception_string using +:class_message+ or +:message_class+ as the _mode_);
|
245
|
+
# * an +Exception+ class (or +String+ containing the class' name);
|
246
|
+
# * an +Array+ containing the class and message.
|
247
|
+
# This method uses #assert_msg_raised_but, normalizing the exception message (if given)
|
248
|
+
# through Text#normalize_exception_message. If the exception class is +nil+ it will not
|
249
|
+
# be sent to the assertion method.
|
250
|
+
#
|
251
|
+
# If _l_no_ is given it will be used in the report instead of #relative_line_no.
|
252
|
+
#
|
253
|
+
def raises arg, l_no=nil
|
254
|
+
self.activate
|
255
|
+
if arg.is_a?(Example)
|
256
|
+
arg.deactivate!
|
257
|
+
arg = arg.text
|
258
|
+
cls,msg = Text.split_exception_string(arg,:class_message)
|
259
|
+
cls,msg = Text.split_exception_string(arg,:message_class) if cls.nil?
|
260
|
+
else
|
261
|
+
cls,msg = arg
|
262
|
+
end
|
263
|
+
cls = toplevel_scope.const_get(cls) if cls.is_a?(String)
|
264
|
+
l_no ||= relative_line_no
|
265
|
+
orig_run = method(:run)
|
266
|
+
normalize = Text.method(:normalize_exception_message).to_proc
|
267
|
+
define_singleton_method :run do
|
268
|
+
trace_assertion :error_literal, comment.location(l_no) do
|
269
|
+
assert_msg_raised_but normalize, msg, false, *cls do
|
270
|
+
orig_run.call
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
return self
|
275
|
+
end
|
276
|
+
|
277
|
+
#
|
278
|
+
# This example will run only if necessary, that is when the documentation output is required;
|
279
|
+
# otherwise this is skipped through #whole_skip.
|
280
|
+
#
|
281
|
+
# This is often used if the example is time-consuming:
|
282
|
+
# when the documentation output isn't required the user just wants to run the tests quickly,
|
283
|
+
# so it's better to bypass this hindrance.
|
284
|
+
#
|
285
|
+
def lazy l_no=nil
|
286
|
+
return activate if options.doc_output
|
287
|
+
whole_skip "turn on documentation output", :LAZY, l_no
|
288
|
+
end
|
289
|
+
|
290
|
+
#
|
291
|
+
# Registers this example as a _setup_ in the #comment, that will run
|
292
|
+
# before every Comment#enclosed_comments.
|
293
|
+
#
|
294
|
+
def use_as_setup
|
295
|
+
deactivate!
|
296
|
+
build
|
297
|
+
comment.def_setup(self)
|
298
|
+
return self
|
299
|
+
end
|
300
|
+
|
301
|
+
#
|
302
|
+
# Registers this example as a _teardown_ in the #comment, that will run
|
303
|
+
# after every Comment#enclosed_commnets.
|
304
|
+
#
|
305
|
+
def use_as_teardown
|
306
|
+
deactivate!
|
307
|
+
build
|
308
|
+
comment.def_teardown(self)
|
309
|
+
return self
|
310
|
+
end
|
311
|
+
|
312
|
+
# Run this exemple in a full independent way from the parent comment.
|
313
|
+
def separate_from_comment
|
314
|
+
deactivate! # will be deleted for sure from the comment
|
315
|
+
build
|
316
|
+
store.add_test self # because it will be run directly
|
317
|
+
return self
|
318
|
+
end
|
319
|
+
|
320
|
+
# Activates this example and run it before requiring the full set of libraries.
|
321
|
+
def run_before_require
|
322
|
+
activate
|
323
|
+
@before_require = true
|
324
|
+
separate_from_comment
|
325
|
+
clear_run_hooks!
|
326
|
+
return self
|
327
|
+
end
|
328
|
+
|
329
|
+
#
|
330
|
+
# Activates this example, runs the whole comment in a temporary directory and
|
331
|
+
# writes the +text+ of this example in the file named _fname_ (avoiding
|
332
|
+
# the whole wrap in a here document and a call to <tt>File.write</tt>).
|
333
|
+
# If a block is given it is yielded the text that will be written, so that
|
334
|
+
# the caller can modify it.
|
335
|
+
#
|
336
|
+
# If _l_no_ is given it will be used in the report instead of #relative_line_no.
|
337
|
+
#
|
338
|
+
# Since this example is not executed #check_convention_patterns is called.
|
339
|
+
#
|
340
|
+
def write_to_file fname, l_no=relative_line_no
|
341
|
+
activate
|
342
|
+
fname = Pathname(fname)
|
343
|
+
raise 'Absolute location not allowed' if fname.absolute?
|
344
|
+
l_no ||= relative_line_no
|
345
|
+
check_convention_patterns 'example not executable'
|
346
|
+
comment.run_in_tmpdir
|
347
|
+
skip_building
|
348
|
+
define_singleton_method :run do
|
349
|
+
trace_execution comment.location(l_no) do
|
350
|
+
content = self.text.dup
|
351
|
+
content = yield(content) if block_given?
|
352
|
+
content.rstrip!
|
353
|
+
content << "\n"
|
354
|
+
fname.dirname.expand_path.mkpath
|
355
|
+
File.write fname, content
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
#
|
361
|
+
# Activates this example, runs the whole comment in a temporary directory and
|
362
|
+
# compares the +text+ of this example against the content of the file named _fname_
|
363
|
+
# (avoiding a call to <tt>File.read</tt>).
|
364
|
+
# Differences are reduced through Text#normalize_output.
|
365
|
+
#
|
366
|
+
# If _l_no_ is given it will be used in the report instead of #relative_line_no.
|
367
|
+
#
|
368
|
+
def check_file_content fname, l_no=nil
|
369
|
+
activate
|
370
|
+
fname = Pathname(fname)
|
371
|
+
raise 'Absolute location not allowed' if fname.absolute?
|
372
|
+
l_no ||= relative_line_no
|
373
|
+
comment.run_in_tmpdir
|
374
|
+
normalize = Text.method(:normalize_output).to_proc
|
375
|
+
skip_building
|
376
|
+
define_singleton_method :run do
|
377
|
+
trace_assertion :file_content, comment.location(l_no) do
|
378
|
+
act = fname.read
|
379
|
+
exp = self.text
|
380
|
+
assert_equal_but normalize, exp, act
|
381
|
+
end
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
#
|
386
|
+
# This example is activated and set into floating point mode with _digits_ precision:
|
387
|
+
# * #assert_equal_numeric uses the specified precision as the default _epsilon_;
|
388
|
+
# * #assert_equal relies to +assert_equal_numeric+ if +Numeric+ are involved;
|
389
|
+
# * #normalize_values is enhanced with Text#normalize_floats if +String+ are involved.
|
390
|
+
#
|
391
|
+
def on_float_mode digits
|
392
|
+
activate
|
393
|
+
eps = 10.0 ** -digits
|
394
|
+
define_singleton_method :assert_equal_numeric do |exp,act,epsilon=eps,*args,&blk|
|
395
|
+
super(exp,act,epsilon,*args,&blk)
|
396
|
+
end
|
397
|
+
define_singleton_method :assert_equal do |exp,act,*args,&blk|
|
398
|
+
if exp.is_a?(Numeric) && act.is_a?(Numeric)
|
399
|
+
assert_equal_numeric exp,act,*args,&blk
|
400
|
+
else
|
401
|
+
super(exp,act,*args,&blk)
|
402
|
+
end
|
403
|
+
end
|
404
|
+
orig_level = method :normalize_values
|
405
|
+
define_singleton_method :normalize_values do |exp,act,&normalize|
|
406
|
+
if exp.is_a?(String) && act.is_a?(String)
|
407
|
+
exp = Text.normalize_floats(exp,digits)
|
408
|
+
act = Text.normalize_floats(act,digits)
|
409
|
+
end
|
410
|
+
orig_level.call(exp,act,&normalize)
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
#
|
415
|
+
# This example is activated and differences in numbers are reduced to minimum.
|
416
|
+
# #normalize_values is enhanced so that:
|
417
|
+
# * +Numeric+ are reduced to <tt>0</tt>;
|
418
|
+
# * Text#generalize_numerics is used on strings.
|
419
|
+
#
|
420
|
+
def on_indicative_numbers_mode
|
421
|
+
activate
|
422
|
+
orig_level = method :normalize_values
|
423
|
+
define_singleton_method :normalize_values do |exp,act,&normalize|
|
424
|
+
if exp.is_a?(Numeric) && act.is_a?(Numeric)
|
425
|
+
exp = act = 0
|
426
|
+
elsif exp.is_a?(String) && act.is_a?(String)
|
427
|
+
exp = Text.generalize_numerics(exp)
|
428
|
+
act = Text.generalize_numerics(act)
|
429
|
+
end
|
430
|
+
orig_level.call(exp,act,&normalize)
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
class Bash # :nodoc:
|
435
|
+
require 'open3'
|
436
|
+
class << self
|
437
|
+
# The system +bash+ command used by Directive::bash.
|
438
|
+
attr_accessor :bash
|
439
|
+
end
|
440
|
+
self.bash = 'bash'
|
441
|
+
attr_reader :example, :default_prompt, :continuation_prompt
|
442
|
+
private :example, :default_prompt, :continuation_prompt
|
443
|
+
def initialize example, default_prompt, continuation_prompt
|
444
|
+
@example = example
|
445
|
+
@default_prompt = build_prompt default_prompt
|
446
|
+
@continuation_prompt = build_prompt continuation_prompt
|
447
|
+
end
|
448
|
+
def parse code
|
449
|
+
result = [] # [input,input_l_no,output,output_l_no]
|
450
|
+
prev_status = nil
|
451
|
+
code.each_line.with_index do |line,l_no|
|
452
|
+
l_no += 1 # line numbers begins from 1
|
453
|
+
current_elem = result.last
|
454
|
+
case status=classify_line(line)
|
455
|
+
when :start_input
|
456
|
+
result << [line,l_no,'',l_no+1]
|
457
|
+
when :cont_input
|
458
|
+
unless [:start_input,:cont_input].include?(prev_status)
|
459
|
+
raise ParseError.new "Default prompt `#{default_prompt}' not detected", l_no
|
460
|
+
end
|
461
|
+
current_elem[0] << line
|
462
|
+
current_elem[3] += 1
|
463
|
+
when :output
|
464
|
+
if nil == prev_status
|
465
|
+
raise ParseError.new "Default prompt `#{default_prompt}' not detected", l_no
|
466
|
+
end
|
467
|
+
current_elem[2] << line
|
468
|
+
end
|
469
|
+
prev_status = status
|
470
|
+
end
|
471
|
+
# Remove useless results:
|
472
|
+
result.delete_if do |elems|
|
473
|
+
elems[0] = '' unless elems[0] =~ /\S/
|
474
|
+
elems[2] = '' unless elems[2] =~ /\S/
|
475
|
+
elems[0].empty? && elems[2].empty?
|
476
|
+
end
|
477
|
+
return result
|
478
|
+
end
|
479
|
+
def run_code code
|
480
|
+
return '' if code.empty?
|
481
|
+
out,err,status = Open3.capture3(Bash.bash, :stdin_data=>code.to_str)
|
482
|
+
out = '' unless out =~ /\S/
|
483
|
+
err = '' unless err =~ /\S/
|
484
|
+
unless status.success?
|
485
|
+
error = SystemCallError.new(status.exitstatus)
|
486
|
+
error.message.insert 0, "In bash input:\n"
|
487
|
+
error.message << "\n#{err}" unless err.empty?
|
488
|
+
raise error
|
489
|
+
end
|
490
|
+
$stderr.print err unless err.empty?
|
491
|
+
return out
|
492
|
+
end
|
493
|
+
private
|
494
|
+
def build_prompt prompt
|
495
|
+
prompt = prompt.is_a?(Regexp) ? prompt.to_s : Regexp.escape(prompt.to_str)
|
496
|
+
return /^#{prompt}/
|
497
|
+
end
|
498
|
+
def classify_line(line)
|
499
|
+
if line.slice!(default_prompt)
|
500
|
+
:start_input
|
501
|
+
elsif line.slice!(continuation_prompt)
|
502
|
+
:cont_input
|
503
|
+
else
|
504
|
+
:output
|
505
|
+
end
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
#
|
510
|
+
# Activates this example, runs the whole comment in a temporary directory and
|
511
|
+
# sets this example in bash mode.
|
512
|
+
#
|
513
|
+
# The execution is interpreted according to the beginning of each line:
|
514
|
+
# * _default_prompt_ starts a new bash command;
|
515
|
+
# * _continuation_prompt_ continues the current command;
|
516
|
+
# * otherwise specifies the output of the current command
|
517
|
+
# (differences are reduced through Text#normalize_output).
|
518
|
+
#
|
519
|
+
def on_bash_mode default_prompt, continuation_prompt
|
520
|
+
activate
|
521
|
+
comment.run_in_tmpdir
|
522
|
+
bash = Bash.new self, default_prompt, continuation_prompt
|
523
|
+
io = nil
|
524
|
+
define_singleton_method :build_self do
|
525
|
+
io = bash.parse text
|
526
|
+
end
|
527
|
+
normalize = Text.method(:normalize_output).to_proc
|
528
|
+
define_singleton_method :run do
|
529
|
+
return unless io
|
530
|
+
io.each do |input,input_l_no,exp_out,out_l_no|
|
531
|
+
begin
|
532
|
+
act_out = nil
|
533
|
+
trace_assertion :bash_input, location(input_l_no) do
|
534
|
+
act_out = bash.run_code(input)
|
535
|
+
end
|
536
|
+
rescue SystemCallError => err
|
537
|
+
raise err if exp_out.empty?
|
538
|
+
register_error err # expectation -> continue evaluation
|
539
|
+
else
|
540
|
+
if exp_out.empty?
|
541
|
+
$stdout.print act_out # will eventually generate a warning
|
542
|
+
else
|
543
|
+
trace_assertion :bash_output, location(out_l_no) do
|
544
|
+
assert_output_but normalize, exp_out do
|
545
|
+
$stdout.print act_out
|
546
|
+
end
|
547
|
+
end
|
548
|
+
end
|
549
|
+
end
|
550
|
+
end
|
551
|
+
end
|
552
|
+
return self
|
553
|
+
end
|
554
|
+
|
555
|
+
#
|
556
|
+
# :section: Internal
|
557
|
+
#
|
558
|
+
|
559
|
+
#
|
560
|
+
# Tries to hide the fact that the code isn't executed in the true toplevel.
|
561
|
+
# This is done removing the fake one (the #toplevel_scope) from the actual and expected
|
562
|
+
# strings (with Text#remove_enclosing_module).
|
563
|
+
#
|
564
|
+
# Note that this doesn't work if those string aren't compared directly -
|
565
|
+
# for example if they are part of an +Array+ -, but these situations are very rare.
|
566
|
+
#
|
567
|
+
# The original method is then called.
|
568
|
+
#
|
569
|
+
def normalize_values exp, act, &normalize
|
570
|
+
act = Text.remove_enclosing_module(act,toplevel_scope) if act.is_a?(String)
|
571
|
+
exp = Text.remove_enclosing_module(exp,toplevel_scope) if exp.is_a?(String)
|
572
|
+
exp, act = comment.normalize_values exp, act
|
573
|
+
super
|
574
|
+
end
|
575
|
+
|
576
|
+
# Filters the backtrace of raised exceptions.
|
577
|
+
def exception_details exc, msg=nil
|
578
|
+
exc.set_backtrace RDX.filter_backtrace(exc.backtrace)
|
579
|
+
super
|
580
|
+
end
|
581
|
+
|
582
|
+
#
|
583
|
+
# If this example hasn't been #seen? calls #check_convention_patterns.
|
584
|
+
# In absence of explicit directives, this means that the comment
|
585
|
+
# won't be part of the documentation output, hence its examples are not executed.
|
586
|
+
#
|
587
|
+
def check_non_documenting
|
588
|
+
return if seen?
|
589
|
+
deactivate
|
590
|
+
check_convention_patterns 'non-documenting comment'
|
591
|
+
end
|
592
|
+
|
593
|
+
# Wraps the original method into #check_uncaptured_output and #capture_exceptions.
|
594
|
+
def whole_run(*)
|
595
|
+
capture_exceptions do
|
596
|
+
self.encoding = from_magic_comment
|
597
|
+
check_uncaptured_output{ super }
|
598
|
+
end
|
599
|
+
end
|
600
|
+
|
601
|
+
private
|
602
|
+
|
603
|
+
#
|
604
|
+
# The example is parsed and subdivided into toplevel statements (by RubyLex#extract_statements).
|
605
|
+
# Each one generates a Statement object.
|
606
|
+
#
|
607
|
+
def build_self # :doc:
|
608
|
+
RubyLex.extract_statements text do |code,l_no|
|
609
|
+
statements << Statement.new(self,code,l_no)
|
610
|
+
end
|
611
|
+
end
|
612
|
+
|
613
|
+
#
|
614
|
+
# Generates a warning for each convention is recognized
|
615
|
+
# in a inner comment (or potential one).
|
616
|
+
#
|
617
|
+
def check_convention_patterns section # :doc:
|
618
|
+
comments = begin
|
619
|
+
RubyLex.extract_comments(text)
|
620
|
+
rescue ParseError
|
621
|
+
RubyLex.extract_potential_comments(text)
|
622
|
+
end
|
623
|
+
comments.each do |text,l_no|
|
624
|
+
text = Text.normalize_comment text
|
625
|
+
store.find_convention_in text do |conv|
|
626
|
+
warn "Convention `#{conv.name}' recognized in #{section}",
|
627
|
+
:conventions, location(l_no)
|
628
|
+
end
|
629
|
+
end
|
630
|
+
return self
|
631
|
+
end
|
632
|
+
|
633
|
+
#
|
634
|
+
# Captures the +Exception+ raised during the execution of this example.
|
635
|
+
# Most of them are rescued and an error is reported, so that it
|
636
|
+
# will not propagate among other examples.
|
637
|
+
#
|
638
|
+
def capture_exceptions # :doc:
|
639
|
+
yield
|
640
|
+
rescue SystemExit => err
|
641
|
+
unless err.backtrace.frozen?
|
642
|
+
# this wasn't generated by evaluation of code
|
643
|
+
raise err
|
644
|
+
end
|
645
|
+
rescue *Assertions::PASSTHROUGH_EXCEPTIONS
|
646
|
+
raise
|
647
|
+
rescue Exception => err
|
648
|
+
# Most types of errors are catched and doesn't stop the others
|
649
|
+
register_error err
|
650
|
+
end
|
651
|
+
|
652
|
+
#
|
653
|
+
# Generates a warning if the output of this example isn't captured -
|
654
|
+
# checked with conventions or directives. This applies to both
|
655
|
+
# <tt>$stdout</tt> and <tt>$stderr</tt>.
|
656
|
+
#
|
657
|
+
def check_uncaptured_output &blk # :doc:
|
658
|
+
stdout,stderr = capture_io &blk
|
659
|
+
warn "Uncaptured stdout:\n#{stdout}", :stdout if stdout =~ /\S/
|
660
|
+
warn "Uncaptured stderr:\n#{stderr}", :stderr if stderr =~ /\S/
|
661
|
+
end
|
662
|
+
|
663
|
+
# :stopdoc:
|
664
|
+
|
665
|
+
# Override Minitest::Assertions#skip
|
666
|
+
define_method :skip, CodeObject.instance_method(:skip)
|
667
|
+
|
668
|
+
def from_magic_comment
|
669
|
+
md = text.match Text::REGEXP::MAGIC_COMMENT
|
670
|
+
return unless md
|
671
|
+
l_no = 1 + text[0...md.begin(1)].count("\n")
|
672
|
+
trace_execution location(l_no) do
|
673
|
+
Kernel.eval("#{md[1]}\n__ENCODING__")
|
674
|
+
end
|
675
|
+
end
|
676
|
+
|
677
|
+
end
|
678
|
+
|
679
|
+
end
|