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,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