@@ -78,12 +78,14 @@ defmodule ExUnit.DocTest do
78
78
side effects. For example, if a doctest prints to standard output, doctest
79
79
will not try to capture the output.
80
80
81
- Similarly, doctest does not run in any kind of side box . So any module
81
+ Similarly, doctest does not run in any kind of sandbox . So any module
82
82
defined in a code example is going to linger throughout the whole test suite
83
83
run.
84
84
"""
85
85
86
- defrecord Test , fun_arity: nil , line: nil , expr: nil , expected: nil
86
+ defexception Error , message: nil
87
+
88
+ defrecord Test , fun_arity: nil , line: nil , exprs: [ ]
87
89
88
90
@ doc """
89
91
This macro is used to generate ExUnit test cases for doctests.
@@ -151,87 +153,124 @@ defmodule ExUnit.DocTest do
151
153
:"test doc at #{ inspect m } .#{ f } /#{ a } (#{ n } )"
152
154
end
153
155
154
- defp test_content ( Test [ expected : { :test , expected } ] = test , module , do_import ) do
155
- line = test . line
156
- file = module . __info__ ( :compile ) [ :source ]
156
+ defp test_content ( Test [ exprs : exprs , line: line ] , module , do_import ) do
157
+ #IO.puts "Testing tests:"
158
+ #Enum.each exprs, fn { expr, expected } ->
159
+ #IO.puts "test '#{expr}' with expectation #{inspect expected}"
160
+ #end
161
+ #IO.puts ""
162
+
163
+ file = module . __info__ ( :compile ) [ :source ]
157
164
location = [ line: line , file: Path . relative_to ( file , System . cwd! ) ]
158
165
stack = Macro . escape [ { module , :__MODULE__ , 0 , location } ]
159
166
160
- expr_ast = string_to_ast ( module , line , file , test . expr )
161
- expected_ast = string_to_ast ( module , line , file , expected )
167
+ exc_filter_fn = ( function do
168
+ { _ , { :error , _ , _ } } -> true
169
+ _ -> false
170
+ end )
162
171
163
- quote do
164
- unquote_splicing ( test_import ( module , do_import ) )
172
+ exceptions_num = Enum . count exprs , exc_filter_fn
173
+ if exceptions_num > 1 do
174
+ # FIXME: stacktrace pointing to the doctest?
175
+ raise Error , message: "Multiple exceptions in one doctest case are not supported"
176
+ end
177
+
178
+ { tests , whole_expr } = Enum . map_reduce exprs , "" , fn { expr , expected } , acc ->
179
+ { test_case_content ( expr , expected , module , line , file , stack ) , acc <> expr <> "\n " }
180
+ end
181
+ exception_expr = Enum . find ( exprs , exc_filter_fn )
182
+
183
+ if nil? ( exception_expr ) do
184
+ quote do
185
+ unquote_splicing ( test_import ( module , do_import ) )
186
+ try do
187
+ # Put all tests into one context
188
+ unquote_splicing ( tests )
189
+ rescue
190
+ e in [ ExUnit.ExpectationError ] ->
191
+ raise e , [ ] , unquote ( stack )
192
+ actual ->
193
+ raise ExUnit.ExpectationError ,
194
+ [ prelude: "Expected doctest" ,
195
+ description: unquote ( whole_expr ) ,
196
+ expected: "without an exception" ,
197
+ reason: "complete" ,
198
+ actual: inspect ( actual ) ] ,
199
+ unquote ( stack )
200
+ end
201
+ end
202
+ else
203
+ { expr , { :error , exception , message } } = exception_expr
204
+ quote do
205
+ unquote_splicing ( test_import ( module , do_import ) )
206
+ try do
207
+ # Put all tests into one context
208
+ unquote_splicing ( tests )
209
+ rescue
210
+ e in [ ExUnit.ExpectationError ] ->
211
+ case e . reason do
212
+ "evaluate to" ->
213
+ raise e , [ ] , unquote ( stack )
214
+ "raise" ->
215
+ raise ( e )
216
+ end
217
+
218
+ error in [ unquote ( exception ) ] ->
219
+ unless error . message == unquote ( message ) do
220
+ raise ExUnit.ExpectationError ,
221
+ [ prelude: "Expected doctest" ,
222
+ description: unquote ( expr ) ,
223
+ expected: "#{ inspect unquote ( exception ) } with message #{ inspect unquote ( message ) } " ,
224
+ reason: "raise" ,
225
+ actual: inspect ( error ) ] ,
226
+ unquote ( stack )
227
+ end
165
228
166
- try do
167
- v = unquote ( expected_ast )
168
- case unquote ( expr_ast ) do
169
- ^ v -> :ok
170
229
actual ->
171
230
raise ExUnit.ExpectationError ,
172
231
[ prelude: "Expected doctest" ,
173
- description: unquote ( test . expr ) ,
174
- expected: inspect ( v ) ,
175
- reason: "evaluate to " ,
232
+ description: unquote ( whole_expr ) ,
233
+ expected: " #{ inspect unquote ( exception ) } " ,
234
+ reason: "complete or raise " ,
176
235
actual: inspect ( actual ) ] ,
177
236
unquote ( stack )
178
237
end
179
- rescue
180
- e in [ ExUnit.ExpectationError ] ->
181
- raise e , [ ] , unquote ( stack )
238
+ end
239
+ end
240
+ end
241
+
242
+ defp test_case_content ( expr , { :test , expected } , module , line , file , stack ) do
243
+ expr_ast = string_to_ast ( module , line , file , expr )
244
+ expected_ast = string_to_ast ( module , line , file , expected )
245
+
246
+ quote do
247
+ v = unquote ( expected_ast )
248
+ case unquote ( expr_ast ) do
249
+ ^ v -> :ok
182
250
actual ->
183
251
raise ExUnit.ExpectationError ,
184
252
[ prelude: "Expected doctest" ,
185
- description: unquote ( test . expr ) ,
186
- expected: "without an exception" ,
187
- reason: "complete " ,
253
+ description: unquote ( expr ) ,
254
+ expected: inspect ( v ) ,
255
+ reason: "evaluate to " ,
188
256
actual: inspect ( actual ) ] ,
189
257
unquote ( stack )
190
258
end
191
259
end
192
260
end
193
261
194
- defp test_content ( Test [ expected : { :error , exception , message } ] = test , module , do_import ) do
195
- line = test . line
196
- file = module . __info__ ( :compile ) [ :source ]
197
- location = [ line: line , file: Path . relative_to ( file , System . cwd! ) ]
198
- stack = Macro . escape [ { module , :__MODULE__ , 0 , location } ]
199
-
200
- expr_ast = string_to_ast ( module , line , file , test . expr )
262
+ defp test_case_content ( expr , { :error , exception , _ } , module , line , file , stack ) do
263
+ expr_ast = string_to_ast ( module , line , file , expr )
201
264
202
265
quote do
203
- unquote_splicing ( test_import ( module , do_import ) )
204
-
205
- try do
206
- v = unquote ( expr_ast )
207
- raise ExUnit.ExpectationError ,
208
- [ prelude: "Expected doctest" ,
209
- description: unquote ( test . expr ) ,
210
- expected: "#{ inspect unquote ( exception ) } []" ,
211
- reason: "raise" ,
212
- actual: inspect ( v ) ] ,
213
- unquote ( stack )
214
- rescue
215
- e in [ ExUnit.ExpectationError ] -> raise ( e )
216
- error in [ unquote ( exception ) ] ->
217
- unless error . message == unquote ( message ) do
218
- raise ExUnit.ExpectationError ,
219
- [ prelude: "Expected doctest" ,
220
- description: unquote ( test . expr ) ,
221
- expected: "#{ inspect unquote ( exception ) } with message #{ inspect unquote ( message ) } " ,
222
- reason: "raise" ,
223
- actual: inspect ( error ) ] ,
224
- unquote ( stack )
225
- end
226
- error ->
227
- raise ExUnit.ExpectationError ,
228
- [ prelude: "Expected doctest" ,
229
- description: unquote ( test . expr ) ,
230
- expected: "#{ inspect unquote ( exception ) } " ,
231
- reason: "raise" ,
232
- actual: inspect ( error ) ] ,
233
- unquote ( stack )
234
- end
266
+ v = unquote ( expr_ast )
267
+ raise ExUnit.ExpectationError ,
268
+ [ prelude: "Expected doctest" ,
269
+ description: unquote ( expr ) ,
270
+ expected: "#{ inspect unquote ( exception ) } []" ,
271
+ reason: "raise" ,
272
+ actual: inspect ( v ) ] ,
273
+ unquote ( stack )
235
274
end
236
275
end
237
276
@@ -286,57 +325,81 @@ defmodule ExUnit.DocTest do
286
325
287
326
defp extract_tests ( line , doc ) do
288
327
lines = String . split ( doc , % r / \n/) |> Enum . map ( function ( String . strip / 1 ) )
289
- extract_tests ( lines , line , "" , "" , [ ] )
328
+ extract_tests ( lines , line , "" , "" , [ ] , true )
290
329
end
291
330
292
- defp extract_tests ( [ ] , _line , "" , "" , acc ) , do: Enum . reverse ( acc )
331
+ defp extract_tests ( [ ] , _line , "" , "" , [ ] , _ ) do
332
+ [ ]
333
+ end
334
+
335
+ defp extract_tests ( [ ] , _line , "" , "" , [ test = Test [ exprs : exprs ] | t ] , _ ) do
336
+ test = test . exprs ( Enum . reverse ( exprs ) )
337
+ Enum . reverse ( [ test | t ] )
338
+ end
293
339
294
- defp extract_tests ( [ ] , line , expr_acc , expected_acc , acc ) do
295
- test = Test [ expr : expr_acc , line: line , expected: { :test , expected_acc } ]
296
- Enum . reverse ( [ test | acc ] )
340
+ # End of input and we've still got a test pending.
341
+ defp extract_tests ( [ ] , _ , expr_acc , expected_acc , [ test = Test [ exprs : exprs ] | t ] , _ ) do
342
+ test = test . exprs ( Enum . reverse ( [ { expr_acc , { :test , expected_acc } } | exprs ] ) )
343
+ Enum . reverse ( [ test | t ] )
297
344
end
298
345
299
- defp extract_tests( [ << "iex>" , _ :: binary >> | _ ] = list , line, expr_acc, expected_acc, acc) when expr_acc != "" and expected_acc != "" do
300
- test = Test [ expr : expr_acc , line: line , expected: { :test , expected_acc } ]
301
- extract_tests ( list , line , "" , "" , [ test | acc ] )
346
+ # We've encountered the next test on an adjacent line. Put them into one group.
347
+ defp extract_tests( [ << "iex>" , _ :: binary >> | _ ] = list , line, expr_acc, expected_acc, [ test= Test [ exprs : exprs ] | t ] , newtest) when expr_acc != "" and expected_acc != "" do
348
+ test = test . exprs ( [ { expr_acc , { :test , expected_acc } } | exprs ] )
349
+ extract_tests ( list , line , "" , "" , [ test | t ] , newtest )
302
350
end
303
351
304
- defp extract_tests ( [ << "iex>" , string :: binary >> | lines ] , line , expr_acc , expected_acc , acc ) when expr_acc == "" do
305
- extract_tests ( lines , line , string , expected_acc , acc )
352
+ # Store expr_acc.
353
+ defp extract_tests ( [ << "iex>" , string :: binary >> | lines ] , line , "" , expected_acc , acc , newtest ) do
354
+ if newtest do
355
+ if match? ( [ test = Test [ exprs : exprs ] | t ] , acc ) do
356
+ acc = [ test . exprs ( Enum . reverse ( exprs ) ) | t ]
357
+ end
358
+ acc = [ Test [ line : line ] | acc ]
359
+ end
360
+ extract_tests ( lines , line , string , expected_acc , acc , false )
306
361
end
307
362
308
- defp extract_tests( [ << "iex>" , string :: binary >> | lines ] , line, expr_acc, expected_acc, acc) do
309
- extract_tests( lines, line, expr_acc <> "\n " <> string , expected_acc, acc)
363
+ # Still gathering expr_acc. Synonym for the next clause.
364
+ defp extract_tests( [ << "iex>" , string :: binary >> | lines ] , line, expr_acc, expected_acc, acc, newtest) do
365
+ extract_tests( lines, line, expr_acc <> "\n " <> string , expected_acc, acc, newtest)
310
366
end
311
367
312
- defp extract_tests( [ << "...>" , string :: binary >> | lines ] , line, expr_acc, expected_acc, acc) when expr_acc != "" do
313
- extract_tests ( lines , line , expr_acc <> "\n " <> string , expected_acc , acc )
368
+ # Still gathering expr_acc. Synonym for the previous clause.
369
+ defp extract_tests( [ << "...>" , string :: binary >> | lines ] , line, expr_acc, expected_acc, acc, newtest) when expr_acc != "" do
370
+ extract_tests ( lines , line , expr_acc <> "\n " <> string , expected_acc , acc , newtest )
314
371
end
315
372
316
- defp extract_tests ( [ << "iex(" , _ :: 8 , string :: binary >> | lines ] , line , expr_acc , expected_acc , acc ) do
317
- extract_tests ( [ "iex" <> skip_iex_number ( string ) | lines ] , line , expr_acc , expected_acc , acc )
373
+ # Expression numbers are simply skipped.
374
+ defp extract_tests ( [ << "iex(" , _ :: 8 , string :: binary >> | lines ] , line , expr_acc , expected_acc , acc , newtest ) do
375
+ extract_tests ( [ "iex" <> skip_iex_number ( string ) | lines ] , line , expr_acc , expected_acc , acc , newtest )
318
376
end
319
377
320
- defp extract_tests( [ << "...(" , _ :: 8 , string :: binary >> | lines ] , line, expr_acc, expected_acc, acc) do
321
- extract_tests( [ "..." <> skip_iex_number ( string ) | lines ] , line, expr_acc, expected_acc, acc)
378
+ # Expression numbers are simply skipped redux.
379
+ defp extract_tests( [ << "...(" , _ :: 8 , string :: binary >> | lines ] , line, expr_acc, expected_acc, acc, newtest) do
380
+ extract_tests( [ "..." <> skip_iex_number ( string ) | lines ] , line, expr_acc, expected_acc, acc, newtest)
322
381
end
323
382
324
- defp extract_tests( [ _ | lines ] , line, "", "", acc) do
325
- extract_tests( lines, line, "", "", acc)
383
+ # Skip empty or documentation line.
384
+ defp extract_tests( [ _ | lines ] , line, "", "", acc, _) do
385
+ extract_tests( lines, line, "", "", acc, true)
326
386
end
327
387
328
- defp extract_tests( [ "" | lines ] , line, expr_acc, expected_acc, acc) do
329
- test = Test [ expr : expr_acc , line: line , expected: { :test , expected_acc } ]
330
- extract_tests ( lines , line , "" , "" , [ test | acc ] )
388
+ # Encountered an empty line, store pending test
389
+ defp extract_tests( [ "" | lines ] , line, expr_acc, expected_acc, [ test= Test [ exprs : exprs ] | t ] , _) do
390
+ test = test . exprs ( [ { expr_acc , { :test , expected_acc } } | exprs ] )
391
+ extract_tests ( lines , line , "" , "" , [ test | t ] , true )
331
392
end
332
393
333
- defp extract_tests ( [ << "** (" , string :: binary >> | lines ] , line , expr_acc , "" , acc ) do
334
- test = Test [ expr : expr_acc , line: line , expected: extract_error ( string , "" ) ]
335
- extract_tests ( lines , line , "" , "" , [ test | acc ] )
394
+ # Exception test.
395
+ defp extract_tests ( [ << "** (" , string :: binary >> | lines ] , line , expr_acc , "" , [ test = Test [ exprs : exprs ] | t ] , newtest ) do
396
+ test = test . exprs ( [ { expr_acc , extract_error ( string , "" ) } | exprs ] )
397
+ extract_tests ( lines , line , "" , "" , [ test | t ] , newtest )
336
398
end
337
399
338
- defp extract_tests( [ expected | lines ] , line, expr_acc, expected_acc, acc) do
339
- extract_tests( lines, line, expr_acc, expected_acc <> "\n " <> expected , acc)
400
+ # Finally, parse expected_acc.
401
+ defp extract_tests( [ expected | lines ] , line, expr_acc, expected_acc, acc, newtest) do
402
+ extract_tests( lines, line, expr_acc, expected_acc <> "\n " <> expected , acc, newtest)
340
403
end
341
404
342
405
defp extract_error( << ") ", t :: binary > > , acc) do
0 commit comments