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.
Files changed (122) hide show
  1. checksums.yaml +7 -0
  2. data/.rdx +20 -0
  3. data/README +19 -0
  4. data/bin/rdx +7 -0
  5. data/examples/minimal/.rdx +8 -0
  6. data/examples/minimal/README +10 -0
  7. data/examples/minimal/lib/other_conventions.rb +64 -0
  8. data/examples/minimal/lib/the_basics.rb +94 -0
  9. data/examples/minimal/lib/using_directives.rb +66 -0
  10. data/examples/minimal/rakefile +27 -0
  11. data/examples/ruby-2.0.0-p0/README +7 -0
  12. data/examples/ruby-2.0.0-p0/install/core/.rdx +6 -0
  13. data/examples/ruby-2.0.0-p0/install/core/README +19 -0
  14. data/examples/ruby-2.0.0-p0/install/core/Rakefile +61 -0
  15. data/examples/ruby-2.0.0-p0/install/core/diffs/array.c.diff +166 -0
  16. data/examples/ruby-2.0.0-p0/install/core/diffs/bignum.c.diff +11 -0
  17. data/examples/ruby-2.0.0-p0/install/core/diffs/class.c.diff +36 -0
  18. data/examples/ruby-2.0.0-p0/install/core/diffs/compar.c.diff +11 -0
  19. data/examples/ruby-2.0.0-p0/install/core/diffs/complex.c.diff +301 -0
  20. data/examples/ruby-2.0.0-p0/install/core/diffs/cont.c.diff +65 -0
  21. data/examples/ruby-2.0.0-p0/install/core/diffs/dir.c.diff +147 -0
  22. data/examples/ruby-2.0.0-p0/install/core/diffs/doc/re.rdoc.diff +328 -0
  23. data/examples/ruby-2.0.0-p0/install/core/diffs/doc/security.rdoc.diff +8 -0
  24. data/examples/ruby-2.0.0-p0/install/core/diffs/doc/standard_library.rdoc.diff +0 -0
  25. data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax.rdoc.diff +0 -0
  26. data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/assignment.rdoc.diff +160 -0
  27. data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/calling_methods.rdoc.diff +130 -0
  28. data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/control_expressions.rdoc.diff +254 -0
  29. data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/exceptions.rdoc.diff +0 -0
  30. data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/literals.rdoc.diff +54 -0
  31. data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/methods.rdoc.diff +157 -0
  32. data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/miscellaneous.rdoc.diff +91 -0
  33. data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/modules_and_classes.rdoc.diff +161 -0
  34. data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/precedence.rdoc.diff +8 -0
  35. data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/refinements.rdoc.diff +146 -0
  36. data/examples/ruby-2.0.0-p0/install/core/diffs/encoding.c.diff +276 -0
  37. data/examples/ruby-2.0.0-p0/install/core/diffs/enum.c.diff +281 -0
  38. data/examples/ruby-2.0.0-p0/install/core/diffs/enumerator.c.diff +479 -0
  39. data/examples/ruby-2.0.0-p0/install/core/diffs/error.c.diff +143 -0
  40. data/examples/ruby-2.0.0-p0/install/core/diffs/eval.c.diff +47 -0
  41. data/examples/ruby-2.0.0-p0/install/core/diffs/eval_jump.c.diff +23 -0
  42. data/examples/ruby-2.0.0-p0/install/core/diffs/file.c.diff +752 -0
  43. data/examples/ruby-2.0.0-p0/install/core/diffs/gc.c.diff +195 -0
  44. data/examples/ruby-2.0.0-p0/install/core/diffs/hash.c.diff +84 -0
  45. data/examples/ruby-2.0.0-p0/install/core/diffs/iseq.c.diff +354 -0
  46. data/examples/ruby-2.0.0-p0/install/core/diffs/load.c.diff +53 -0
  47. data/examples/ruby-2.0.0-p0/install/core/diffs/marshal.c.diff +98 -0
  48. data/examples/ruby-2.0.0-p0/install/core/diffs/math.c.diff +110 -0
  49. data/examples/ruby-2.0.0-p0/install/core/diffs/numeric.c.diff +103 -0
  50. data/examples/ruby-2.0.0-p0/install/core/diffs/object.c.diff +295 -0
  51. data/examples/ruby-2.0.0-p0/install/core/diffs/pack.c.diff +18 -0
  52. data/examples/ruby-2.0.0-p0/install/core/diffs/parse.y.diff +23 -0
  53. data/examples/ruby-2.0.0-p0/install/core/diffs/proc.c.diff +155 -0
  54. data/examples/ruby-2.0.0-p0/install/core/diffs/random.c.diff +126 -0
  55. data/examples/ruby-2.0.0-p0/install/core/diffs/range.c.diff +49 -0
  56. data/examples/ruby-2.0.0-p0/install/core/diffs/rational.c.diff +312 -0
  57. data/examples/ruby-2.0.0-p0/install/core/diffs/re.c.diff +207 -0
  58. data/examples/ruby-2.0.0-p0/install/core/diffs/ruby.c.diff +21 -0
  59. data/examples/ruby-2.0.0-p0/install/core/diffs/signal.c.diff +67 -0
  60. data/examples/ruby-2.0.0-p0/install/core/diffs/sprintf.c.diff +29 -0
  61. data/examples/ruby-2.0.0-p0/install/core/diffs/string.c.diff +73 -0
  62. data/examples/ruby-2.0.0-p0/install/core/diffs/struct.c.diff +20 -0
  63. data/examples/ruby-2.0.0-p0/install/core/diffs/time.c.diff +691 -0
  64. data/examples/ruby-2.0.0-p0/install/core/diffs/transcode.c.diff +435 -0
  65. data/examples/ruby-2.0.0-p0/install/core/diffs/variable.c.diff +62 -0
  66. data/examples/ruby-2.0.0-p0/install/core/diffs/vm_backtrace.c.diff +164 -0
  67. data/examples/ruby-2.0.0-p0/install/core/diffs/vm_eval.c.diff +99 -0
  68. data/examples/ruby-2.0.0-p0/install/core/diffs/vm_method.c.diff +17 -0
  69. data/examples/ruby-2.0.0-p0/install/core/diffs/vm_trace.c.diff +393 -0
  70. data/examples/ruby-2.0.0-p0/install/stdlib/.rdx +6 -0
  71. data/examples/ruby-2.0.0-p0/install/stdlib/README +19 -0
  72. data/examples/ruby-2.0.0-p0/install/stdlib/Rakefile +53 -0
  73. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/abbrev.rb.diff +77 -0
  74. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/base64.rb.diff +42 -0
  75. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/benchmark.rb.diff +144 -0
  76. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/cmath.rb.diff +52 -0
  77. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/forwardable.rb.diff +150 -0
  78. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/mathn.rb.diff +58 -0
  79. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/matrix.rb.diff +657 -0
  80. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/observer.rb.diff +31 -0
  81. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/optparse.rb.diff +147 -0
  82. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/ostruct.rb.diff +78 -0
  83. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/prime.rb.diff +52 -0
  84. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/pstore.rb.diff +110 -0
  85. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/scanf.rb.diff +100 -0
  86. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/securerandom.rb.diff +144 -0
  87. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/set.rb.diff +637 -0
  88. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/shellwords.rb.diff +66 -0
  89. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/singleton.rb.diff +37 -0
  90. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/tempfile.rb.diff +104 -0
  91. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/thread.rb.diff +38 -0
  92. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/time.rb.diff +140 -0
  93. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/tmpdir.rb.diff +52 -0
  94. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/uri.rb.diff +39 -0
  95. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/uri/common.rb.diff +237 -0
  96. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/weakref.rb.diff +36 -0
  97. data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/yaml/store.rb.diff +27 -0
  98. data/examples/ruby-2.0.0-p0/rakefile +165 -0
  99. data/lib/rdx.rb +331 -0
  100. data/lib/rdx/assertions.rb +484 -0
  101. data/lib/rdx/binding.rb +151 -0
  102. data/lib/rdx/code_object.rb +598 -0
  103. data/lib/rdx/comment.rb +338 -0
  104. data/lib/rdx/convention.rb +1174 -0
  105. data/lib/rdx/directive.rb +1432 -0
  106. data/lib/rdx/example.rb +679 -0
  107. data/lib/rdx/generator.rb +112 -0
  108. data/lib/rdx/generator/rdoc.rb +1006 -0
  109. data/lib/rdx/options.rb +359 -0
  110. data/lib/rdx/plain_text.rb +65 -0
  111. data/lib/rdx/reporter.rb +421 -0
  112. data/lib/rdx/ruby_lex.rb +324 -0
  113. data/lib/rdx/runner.rb +309 -0
  114. data/lib/rdx/source_file.rb +94 -0
  115. data/lib/rdx/specification.rb +194 -0
  116. data/lib/rdx/statement.rb +248 -0
  117. data/lib/rdx/store.rb +119 -0
  118. data/lib/rdx/task.rb +361 -0
  119. data/lib/rdx/text.rb +688 -0
  120. data/lib/rdx/version.rb +15 -0
  121. data/rakefile +64 -0
  122. metadata +203 -0
@@ -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