@@ -78,12 +78,14 @@ defmodule ExUnit.DocTest do
7878 side effects. For example, if a doctest prints to standard output, doctest
7979 will not try to capture the output.
8080
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
8282 defined in a code example is going to linger throughout the whole test suite
8383 run.
8484 """
8585
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: [ ]
8789
8890 @ doc """
8991 This macro is used to generate ExUnit test cases for doctests.
@@ -151,87 +153,124 @@ defmodule ExUnit.DocTest do
151153 :"test doc at #{ inspect m } .#{ f } /#{ a } (#{ n } )"
152154 end
153155
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 ]
157164 location = [ line: line , file: Path . relative_to ( file , System . cwd! ) ]
158165 stack = Macro . escape [ { module , :__MODULE__ , 0 , location } ]
159166
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 )
162171
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
165228
166- try do
167- v = unquote ( expected_ast )
168- case unquote ( expr_ast ) do
169- ^ v -> :ok
170229 actual ->
171230 raise ExUnit.ExpectationError ,
172231 [ 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 " ,
176235 actual: inspect ( actual ) ] ,
177236 unquote ( stack )
178237 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
182250 actual ->
183251 raise ExUnit.ExpectationError ,
184252 [ 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 " ,
188256 actual: inspect ( actual ) ] ,
189257 unquote ( stack )
190258 end
191259 end
192260 end
193261
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 )
201264
202265 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 )
235274 end
236275 end
237276
@@ -286,57 +325,81 @@ defmodule ExUnit.DocTest do
286325
287326 defp extract_tests ( line , doc ) do
288327 lines = String . split ( doc , % r / \n/) |> Enum . map ( function ( String . strip / 1 ) )
289- extract_tests ( lines , line , "" , "" , [ ] )
328+ extract_tests ( lines , line , "" , "" , [ ] , true )
290329 end
291330
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
293339
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 ] )
297344 end
298345
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 )
302350 end
303351
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 )
306361 end
307362
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)
310366 end
311367
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 )
314371 end
315372
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 )
318376 end
319377
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)
322381 end
323382
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)
326386 end
327387
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 )
331392 end
332393
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 )
336398 end
337399
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)
340403 end
341404
342405 defp extract_error( << ") ", t :: binary > > , acc) do
0 commit comments