1defmodule ExUnit.DocTest do 2 @moduledoc """ 3 Extract test cases from the documentation. 4 5 Doctests allow us to generate tests from code examples found 6 in `@moduledoc` and `@doc` attributes. To do this, invoke the 7 `doctest/1` macro from within your test case and ensure your 8 code examples are written according to the syntax and guidelines 9 below. 10 11 ## Syntax 12 13 Every new test starts on a new line, with an `iex>` prefix. 14 Multiline expressions can be used by prefixing subsequent lines 15 with either `...>` (recommended) or `iex>`. 16 17 The expected result should start the line after the `iex>` 18 and `...>` line(s) and be terminated by a newline. 19 20 ## Examples 21 22 To run doctests include them in an ExUnit case with a `doctest` macro: 23 24 defmodule MyModuleTest do 25 use ExUnit.Case, async: true 26 doctest MyModule 27 end 28 29 The `doctest` macro loops through all functions and 30 macros defined in `MyModule`, parsing their documentation in 31 search of code examples. 32 33 A very basic example is: 34 35 iex> 1 + 1 36 2 37 38 Expressions on multiple lines are also supported: 39 40 iex> Enum.map([1, 2, 3], fn x -> 41 ...> x * 2 42 ...> end) 43 [2, 4, 6] 44 45 Multiple results can be checked within the same test: 46 47 iex> a = 1 48 1 49 iex> a + 1 50 2 51 52 If you want to keep any two tests separate, 53 add an empty line between them: 54 55 iex> a = 1 56 1 57 58 iex> a + 1 # will fail with a "undefined function a/0" error 59 2 60 61 If you don't want to assert for every result in a doctest, you can omit 62 the result. You can do so between expressions: 63 64 iex> pid = spawn(fn -> :ok end) 65 iex> is_pid(pid) 66 true 67 68 As well as at the end: 69 70 iex> Mod.do_a_call_that_should_not_raise!(...) 71 72 This is useful when the result is something variable (like a PID in the 73 example above) or when the result is a complicated data structure and you 74 don't want to show it all, but just parts of it or some of its properties. 75 76 Similarly to IEx you can use numbers in your "prompts": 77 78 iex(1)> [1 + 2, 79 ...(1)> 3] 80 [3, 3] 81 82 This is useful in two cases: 83 84 * being able to refer to specific numbered scenarios 85 * copy-pasting examples from an actual IEx session 86 87 You can also select or skip functions when calling 88 `doctest`. See the documentation on the `:except` and `:only` options below 89 for more information. 90 91 ## Opaque types 92 93 Some types' internal structures are kept hidden and instead show a 94 user-friendly structure when inspected. The idiom in 95 Elixir is to print those data types in the format `#Name<...>`. Because those 96 values are treated as comments in Elixir code due to the leading 97 `#` sign, they require special care when being used in doctests. 98 99 Imagine you have a map that contains a MapSet and is printed as: 100 101 %{users: #MapSet<[:foo, :bar]>} 102 103 If you try to match on such an expression, `doctest` will fail to compile. 104 There are two ways to resolve this. 105 106 The first is to rely on the fact that doctest can compare internal 107 structures as long as they are at the root. So one could write: 108 109 iex> map = %{users: Enum.into([:foo, :bar], MapSet.new())} 110 iex> map.users 111 #MapSet<[:foo, :bar]> 112 113 Whenever a doctest starts with "#Name<", `doctest` will perform a string 114 comparison. For example, the above test will perform the following match: 115 116 inspect(map.users) == "#MapSet<[:foo, :bar]>" 117 118 Alternatively, since doctest results are actually evaluated, you can have 119 the MapSet building expression as the doctest result: 120 121 iex> %{users: Enum.into([:foo, :bar], MapSet.new())} 122 %{users: Enum.into([:foo, :bar], MapSet.new())} 123 124 The downside of this approach is that the doctest result is not really 125 what users would see in the terminal. 126 127 ## Exceptions 128 129 You can also showcase expressions raising an exception, for example: 130 131 iex(1)> raise "some error" 132 ** (RuntimeError) some error 133 134 Doctest will looking for a line starting with `** (` and it will parse it 135 accordingly to extract the exception name and message. The exception parser 136 will consider all following lines part of the exception message until there 137 is an empty line or there is a new expression prefixed with `iex>`. 138 Therefore, it is possible to match on multiline messages as long as there 139 are no empty lines on the message itself. 140 141 ## When not to use doctest 142 143 In general, doctests are not recommended when your code examples contain 144 side effects. For example, if a doctest prints to standard output, doctest 145 will not try to capture the output. 146 147 Similarly, doctests do not run in any kind of sandbox. So any module 148 defined in a code example is going to linger throughout the whole test 149 suite run. 150 """ 151 152 @opaque_type_regex ~r/#[\w\.]+</ 153 154 defmodule Error do 155 defexception [:message] 156 157 @impl true 158 def exception(opts) do 159 module = Keyword.fetch!(opts, :module) 160 message = Keyword.fetch!(opts, :message) 161 162 file = module.module_info(:compile)[:source] |> Path.relative_to_cwd() 163 info = Exception.format_file_line(file, opts[:line]) 164 %__MODULE__{message: info <> " " <> message} 165 end 166 end 167 168 @doc """ 169 This macro is used to generate ExUnit test cases for doctests. 170 171 Calling `doctest(Module)` will generate tests for all doctests found 172 in the `module`. 173 174 Options can also be given: 175 176 * `:except` - generates tests for all functions except those listed 177 (list of `{function, arity}` tuples, and/or `:moduledoc`). 178 179 * `:only` - generates tests only for functions listed 180 (list of `{function, arity}` tuples, and/or `:moduledoc`). 181 182 * `:import` - when `true`, one can test a function defined in the module 183 without referring to the module name. However, this is not feasible when 184 there is a clash with a module like `Kernel`. In these cases, `:import` 185 should be set to `false` and a full `Module.function` construct should be 186 used. 187 188 * `:tags` - a list of tags to apply to all generated doctests. 189 190 ## Examples 191 192 doctest MyModule, except: [:moduledoc, trick_fun: 1] 193 194 This macro is auto-imported with every `ExUnit.Case`. 195 """ 196 defmacro doctest(module, opts \\ []) do 197 require = 198 if is_atom(Macro.expand(module, __CALLER__)) do 199 quote do 200 require unquote(module) 201 end 202 end 203 204 tests = 205 quote bind_quoted: [module: module, opts: opts] do 206 env = __ENV__ 207 file = ExUnit.DocTest.__file__(module) 208 209 for {name, test} <- ExUnit.DocTest.__doctests__(module, opts) do 210 if tags = Keyword.get(opts, :tags) do 211 @tag tags 212 end 213 214 @file file 215 doc = ExUnit.Case.register_test(env, :doctest, name, []) 216 def unquote(doc)(_), do: unquote(test) 217 end 218 end 219 220 [require, tests] 221 end 222 223 @doc false 224 def __file__(module) do 225 source = 226 module.module_info(:compile)[:source] || 227 raise "#{inspect(module)} does not have compile-time source information" 228 229 "(for doctest at) " <> Path.relative_to_cwd(source) 230 end 231 232 @doc false 233 def __doctests__(module, opts) do 234 do_import = Keyword.get(opts, :import, false) 235 236 extract(module) 237 |> filter_by_opts(opts) 238 |> Stream.with_index() 239 |> Enum.map(fn {test, acc} -> 240 compile_test(test, module, do_import, acc + 1) 241 end) 242 end 243 244 defp filter_by_opts(tests, opts) do 245 except = Keyword.get(opts, :except, []) 246 247 case Keyword.fetch(opts, :only) do 248 {:ok, []} -> 249 [] 250 251 {:ok, only} -> 252 tests 253 |> Stream.reject(&(&1.fun_arity in except)) 254 |> Stream.filter(&(&1.fun_arity in only)) 255 256 :error -> 257 Stream.reject(tests, &(&1.fun_arity in except)) 258 end 259 end 260 261 ## Compilation of extracted tests 262 263 defp compile_test(test, module, do_import, n) do 264 {test_name(test, module, n), test_content(test, module, do_import)} 265 end 266 267 defp test_name(%{fun_arity: :moduledoc}, m, n) do 268 "module #{inspect(m)} (#{n})" 269 end 270 271 defp test_name(%{fun_arity: {f, a}}, m, n) do 272 "#{inspect(m)}.#{f}/#{a} (#{n})" 273 end 274 275 defp test_content(%{exprs: exprs, line: line}, module, do_import) do 276 file = module.module_info(:compile)[:source] |> Path.relative_to_cwd() 277 location = [line: line, file: file] 278 stack = Macro.escape([{module, :__MODULE__, 0, location}]) 279 280 if multiple_exceptions?(exprs) do 281 raise Error, 282 line: line, 283 module: module, 284 message: 285 "multiple exceptions in the same doctest example are not supported, " <> 286 "please separate your iex> prompts by multiple newlines to start new examples" 287 end 288 289 tests = 290 Enum.map(exprs, fn {expr, expected, formatted} -> 291 test_case_content(expr, expected, location, stack, formatted) 292 end) 293 294 {:__block__, [], test_import(module, do_import) ++ tests} 295 end 296 297 defp multiple_exceptions?(exprs) do 298 Enum.count(exprs, fn 299 {_, {:error, _, _}, _} -> true 300 _ -> false 301 end) > 1 302 end 303 304 defp test_case_content(expr, :test, location, stack, formatted) do 305 string_to_quoted(location, stack, expr, "\n" <> formatted) |> insert_assertions() 306 end 307 308 defp test_case_content(expr, {:test, expected}, location, stack, formatted) do 309 doctest = "\n" <> formatted <> "\n" <> expected 310 expr_ast = string_to_quoted(location, stack, expr, doctest) |> insert_assertions() 311 expected_ast = string_to_quoted(location, stack, expected, doctest) 312 last_expr = Macro.to_string(last_expr(expr_ast)) 313 314 quote do 315 value = unquote(expr_ast) 316 expected = unquote(expected_ast) 317 formatted = unquote(formatted) 318 last_expr = unquote(last_expr) 319 expected_expr = unquote(expected) 320 stack = unquote(stack) 321 322 ExUnit.DocTest.__test__(value, expected, formatted, last_expr, expected_expr, stack) 323 end 324 end 325 326 defp test_case_content(expr, {:inspect, expected}, location, stack, formatted) do 327 doctest = "\n" <> formatted <> "\n" <> expected 328 expr_ast = string_to_quoted(location, stack, expr, doctest) |> insert_assertions() 329 expected_ast = string_to_quoted(location, stack, expected, doctest) 330 last_expr = Macro.to_string(last_expr(expr_ast)) 331 332 quote do 333 value = unquote(expr_ast) 334 expected = unquote(expected_ast) 335 formatted = unquote(formatted) 336 last_expr = unquote(last_expr) 337 expected_expr = unquote(expected) 338 stack = unquote(stack) 339 340 ExUnit.DocTest.__inspect__(value, expected, formatted, last_expr, expected_expr, stack) 341 end 342 end 343 344 defp test_case_content(expr, {:error, exception, message}, location, stack, formatted) do 345 doctest = "\n" <> formatted <> "\n** (#{inspect(exception)}) #{inspect(message)}" 346 expr_ast = string_to_quoted(location, stack, expr, doctest) 347 348 quote do 349 stack = unquote(stack) 350 message = unquote(message) 351 formatted = unquote(formatted) 352 exception = unquote(exception) 353 ExUnit.DocTest.__error__(fn -> unquote(expr_ast) end, message, exception, formatted, stack) 354 end 355 end 356 357 @doc false 358 def __test__(value, expected, formatted, last_expr, expected_expr, stack) do 359 case value do 360 ^expected -> 361 :ok 362 363 _ -> 364 error = [ 365 message: "Doctest failed", 366 doctest: "\n" <> formatted <> "\n" <> expected_expr, 367 expr: "#{last_expr} === #{String.trim(expected_expr)}", 368 left: value, 369 right: expected 370 ] 371 372 reraise ExUnit.AssertionError, error, stack 373 end 374 end 375 376 @doc false 377 def __inspect__(value, expected, formatted, last_expr, expected_expr, parent_stack) do 378 result = 379 try do 380 inspect(value, safe: false) 381 rescue 382 e -> 383 stack = Enum.drop(__STACKTRACE__, 1) 384 {[message: Exception.message(e)], ExUnit.Runner.prune_stacktrace(stack)} 385 else 386 ^expected -> :ok 387 actual -> {[left: actual, right: expected, message: "Doctest failed"], []} 388 end 389 390 case result do 391 :ok -> 392 :ok 393 394 {extra, stack} -> 395 doctest = "\n" <> formatted <> "\n" <> expected_expr 396 expr = "inspect(#{last_expr}) === #{String.trim(expected_expr)}" 397 error = [doctest: doctest, expr: expr] ++ extra 398 reraise ExUnit.AssertionError, error, stack ++ parent_stack 399 end 400 end 401 402 @doc false 403 def __error__(fun, message, exception, formatted, stack) do 404 try do 405 fun.() 406 rescue 407 error -> 408 actual_exception = error.__struct__ 409 actual_message = Exception.message(error) 410 411 failed = 412 cond do 413 actual_exception != exception -> 414 "Doctest failed: expected exception #{inspect(exception)} but got " <> 415 "#{inspect(actual_exception)} with message #{inspect(actual_message)}" 416 417 actual_message != message -> 418 "Doctest failed: wrong message for #{inspect(actual_exception)}\n" <> 419 "expected:\n" <> 420 " #{inspect(message)}\n" <> 421 "actual:\n" <> " #{inspect(actual_message)}" 422 423 true -> 424 nil 425 end 426 427 if failed do 428 doctest = "\n" <> formatted <> "\n** (#{inspect(exception)}) #{inspect(message)}" 429 reraise ExUnit.AssertionError, [message: failed, doctest: doctest], stack 430 end 431 else 432 _ -> 433 doctest = "\n" <> formatted <> "\n** (#{inspect(exception)}) #{inspect(message)}" 434 failed = "Doctest failed: expected exception #{inspect(exception)} but nothing was raised" 435 error = [message: failed, doctest: doctest] 436 reraise ExUnit.AssertionError, error, stack 437 end 438 end 439 440 defp test_import(_mod, false), do: [] 441 defp test_import(mod, _), do: [quote(do: import(unquote(mod)))] 442 443 defp string_to_quoted(location, stack, expr, doctest) do 444 try do 445 Code.string_to_quoted!(expr, location) 446 rescue 447 e -> 448 ex_message = "(#{inspect(e.__struct__)}) #{Exception.message(e)}" 449 message = "Doctest did not compile, got: #{ex_message}" 450 451 message = 452 if e.__struct__ == TokenMissingError and expr =~ @opaque_type_regex do 453 message <> 454 """ 455 \nIf you are planning to assert on the result of an iex> expression \ 456 which contains a value inspected as #Name<...>, please make sure \ 457 the inspected value is placed at the beginning of the expression; \ 458 otherwise Elixir will treat it as a comment due to the leading sign #.\ 459 """ 460 else 461 message 462 end 463 464 opts = 465 if String.valid?(doctest) do 466 [message: message, doctest: doctest] 467 else 468 [message: message] 469 end 470 471 quote do 472 reraise ExUnit.AssertionError, unquote(opts), unquote(stack) 473 end 474 end 475 end 476 477 ## Extraction of the tests 478 479 defp extract(module) do 480 case Code.fetch_docs(module) do 481 {:docs_v1, annotation, _, _, moduledoc, _, docs} -> 482 extract_from_moduledoc(annotation, moduledoc, module) ++ 483 extract_from_docs(Enum.sort(docs), module) 484 485 {:error, reason} -> 486 raise Error, 487 module: module, 488 message: 489 "could not retrieve the documentation for module #{inspect(module)}. " <> 490 explain_docs_error(reason) 491 end 492 end 493 494 defp explain_docs_error(:module_not_found), 495 do: "The BEAM file of the module cannot be accessed" 496 497 defp explain_docs_error(:chunk_not_found), 498 do: "The module was not compiled with documentation" 499 500 defp explain_docs_error({:invalid_chunk, _}), 501 do: "The documentation chunk in the module is invalid" 502 503 defp extract_from_moduledoc(annotation, %{"en" => doc}, module) do 504 for test <- extract_tests(:erl_anno.line(annotation), doc, module) do 505 normalize_test(test, :moduledoc) 506 end 507 end 508 509 defp extract_from_moduledoc(_, _doc, _module), do: [] 510 511 defp extract_from_docs(docs, module) do 512 for doc <- docs, doc <- extract_from_doc(doc, module), do: doc 513 end 514 515 defp extract_from_doc({{_, name, arity}, annotation, _, %{"en" => doc}, _}, module) do 516 line = :erl_anno.line(annotation) 517 518 for test <- extract_tests(line, doc, module) do 519 normalize_test(test, {name, arity}) 520 end 521 end 522 523 defp extract_from_doc(_doc, _module), 524 do: [] 525 526 defp extract_tests(line_no, doc, module) do 527 all_lines = String.split(doc, ["\r\n", "\n"], trim: false) 528 lines = adjust_indent(all_lines, line_no + 1, module) 529 extract_tests(lines, "", "", [], true, module, "") 530 end 531 532 @iex_prompt ["iex>", "iex("] 533 @dot_prompt ["...>", "...("] 534 535 defp adjust_indent(lines, line_no, module) do 536 adjust_indent(:text, lines, line_no, [], 0, module) 537 end 538 539 defp adjust_indent(_kind, [], _line_no, adjusted_lines, _indent, _module) do 540 Enum.reverse(adjusted_lines) 541 end 542 543 defp adjust_indent(:text, [line | rest], line_no, adjusted_lines, indent, module) do 544 case String.starts_with?(String.trim_leading(line), @iex_prompt) do 545 true -> 546 line_indent = get_indent(line, indent) 547 adjust_indent(:prompt, [line | rest], line_no, adjusted_lines, line_indent, module) 548 549 false -> 550 adjust_indent(:text, rest, line_no + 1, adjusted_lines, indent, module) 551 end 552 end 553 554 defp adjust_indent(kind, [line | rest], line_no, adjusted_lines, indent, module) 555 when kind in [:prompt, :after_prompt] do 556 stripped_line = strip_indent(line, indent) 557 558 case String.trim_leading(line) do 559 "" -> 560 :ok 561 562 ^stripped_line -> 563 :ok 564 565 _ -> 566 n_spaces = if indent == 1, do: "#{indent} space", else: "#{indent} spaces" 567 568 raise Error, 569 line: line_no, 570 module: module, 571 message: """ 572 indentation level mismatch on doctest line: #{inspect(line)} 573 574 If you are planning to assert on the result of an `iex>` expression, \ 575 make sure the result is indented at the beginning of `iex>`, which \ 576 in this case is exactly #{n_spaces}. 577 578 If instead you have an `iex>` expression that spans over multiple lines, \ 579 please make sure that each line after the first one begins with `...>`. 580 """ 581 end 582 583 adjusted_lines = [{stripped_line, line_no} | adjusted_lines] 584 585 next = 586 cond do 587 kind == :prompt -> :after_prompt 588 String.starts_with?(stripped_line, @iex_prompt ++ @dot_prompt) -> :after_prompt 589 true -> :code 590 end 591 592 adjust_indent(next, rest, line_no + 1, adjusted_lines, indent, module) 593 end 594 595 defp adjust_indent(:code, [line | rest], line_no, adjusted_lines, indent, module) do 596 stripped_line = strip_indent(line, indent) 597 598 cond do 599 stripped_line == "" -> 600 adjusted_lines = [{stripped_line, line_no} | adjusted_lines] 601 adjust_indent(:text, rest, line_no + 1, adjusted_lines, 0, module) 602 603 String.starts_with?(String.trim_leading(line), @iex_prompt) -> 604 adjust_indent(:prompt, [line | rest], line_no, adjusted_lines, indent, module) 605 606 true -> 607 adjusted_lines = [{stripped_line, line_no} | adjusted_lines] 608 adjust_indent(:code, rest, line_no + 1, adjusted_lines, indent, module) 609 end 610 end 611 612 defp get_indent(line, current_indent) do 613 case :binary.match(line, "iex") do 614 {pos, _len} -> pos 615 :nomatch -> current_indent 616 end 617 end 618 619 defp strip_indent(line, indent) do 620 length = byte_size(line) - indent 621 622 if length > 0 do 623 binary_part(line, indent, length) 624 else 625 "" 626 end 627 end 628 629 @fences ["```", "~~~"] 630 631 defp extract_tests(lines, expr_acc, expected_acc, acc, new_test, module, formatted) 632 633 defp extract_tests([], "", "", [], _, _, _) do 634 [] 635 end 636 637 defp extract_tests([], "", "", acc, _, _, _) do 638 Enum.reverse(acc) 639 end 640 641 # End of input and we've still got a test pending. 642 defp extract_tests([], expr_acc, expected_acc, [test | rest], _, _, formatted) do 643 test = add_expr(test, expr_acc, expected_acc, formatted) 644 Enum.reverse([test | rest]) 645 end 646 647 # We've encountered the next test on an adjacent line. Put them into one group. 648 defp extract_tests( 649 [{"iex>" <> _, _} | _] = list, 650 expr_acc, 651 expected_acc, 652 [test | rest], 653 new_test, 654 module, 655 formatted 656 ) 657 when expr_acc != "" and expected_acc != "" do 658 test = add_expr(test, expr_acc, expected_acc, formatted) 659 extract_tests(list, "", "", [test | rest], new_test, module, "") 660 end 661 662 # Store expr_acc and start a new test case. 663 defp extract_tests( 664 [{"iex>" <> string = line, line_no} | lines], 665 "", 666 expected_acc, 667 acc, 668 true, 669 module, 670 _ 671 ) do 672 test = %{line: line_no, fun_arity: nil, exprs: []} 673 extract_tests(lines, string, expected_acc, [test | acc], false, module, line) 674 end 675 676 # Store expr_acc. 677 defp extract_tests( 678 [{"iex>" <> string = line, _} | lines], 679 "", 680 expected_acc, 681 acc, 682 false, 683 module, 684 _ 685 ) do 686 extract_tests(lines, string, expected_acc, acc, false, module, line) 687 end 688 689 # Still gathering expr_acc. Synonym for the next clause. 690 defp extract_tests( 691 [{"iex>" <> string = line, _} | lines], 692 expr_acc, 693 expected_acc, 694 acc, 695 new_test, 696 module, 697 formatted 698 ) do 699 extract_tests( 700 lines, 701 expr_acc <> "\n" <> string, 702 expected_acc, 703 acc, 704 new_test, 705 module, 706 formatted <> "\n" <> line 707 ) 708 end 709 710 # Still gathering expr_acc. Synonym for the previous clause. 711 defp extract_tests( 712 [{"...>" <> string = line, _} | lines], 713 expr_acc, 714 expected_acc, 715 acc, 716 new_test, 717 module, 718 formatted 719 ) 720 when expr_acc != "" do 721 extract_tests( 722 lines, 723 expr_acc <> "\n" <> string, 724 expected_acc, 725 acc, 726 new_test, 727 module, 728 formatted <> "\n" <> line 729 ) 730 end 731 732 # Expression numbers are simply skipped. 733 defp extract_tests( 734 [{<<"iex(", _>> <> string = line, line_no} | lines], 735 expr_acc, 736 expected_acc, 737 acc, 738 new_test, 739 module, 740 formatted 741 ) do 742 new_line = {"iex" <> skip_iex_number(string, module, line_no, line), line_no} 743 extract_tests([new_line | lines], expr_acc, expected_acc, acc, new_test, module, formatted) 744 end 745 746 # Expression numbers are simply skipped redux. 747 defp extract_tests( 748 [{<<"...(", _>> <> string, line_no} = line | lines], 749 expr_acc, 750 expected_acc, 751 acc, 752 new_test, 753 module, 754 formatted 755 ) do 756 new_line = {"..." <> skip_iex_number(string, module, line_no, line), line_no} 757 extract_tests([new_line | lines], expr_acc, expected_acc, acc, new_test, module, formatted) 758 end 759 760 # Skip empty or documentation line. 761 defp extract_tests([_ | lines], "", "", acc, _, module, _formatted) do 762 extract_tests(lines, "", "", acc, true, module, "") 763 end 764 765 # Encountered end of fenced code block, store pending test 766 defp extract_tests( 767 [{<<fence::3-bytes>> <> _, _} | lines], 768 expr_acc, 769 expected_acc, 770 [test | rest], 771 _new_test, 772 module, 773 formatted 774 ) 775 when fence in @fences and expr_acc != "" do 776 test = add_expr(test, expr_acc, expected_acc, formatted) 777 extract_tests(lines, "", "", [test | rest], true, module, "") 778 end 779 780 # Encountered an empty line, store pending test 781 defp extract_tests( 782 [{"", _} | lines], 783 expr_acc, 784 expected_acc, 785 [test | rest], 786 _new_test, 787 module, 788 formatted 789 ) do 790 test = add_expr(test, expr_acc, expected_acc, formatted) 791 extract_tests(lines, "", "", [test | rest], true, module, "") 792 end 793 794 # Finally, parse expected_acc. 795 defp extract_tests([{expected, _} | lines], expr_acc, "", acc, new_test, module, formatted) do 796 extract_tests(lines, expr_acc, expected, acc, new_test, module, formatted) 797 end 798 799 defp extract_tests( 800 [{expected, _} | lines], 801 expr_acc, 802 expected_acc, 803 acc, 804 new_test, 805 module, 806 formatted 807 ) do 808 extract_tests( 809 lines, 810 expr_acc, 811 expected_acc <> "\n" <> expected, 812 acc, 813 new_test, 814 module, 815 formatted 816 ) 817 end 818 819 defp skip_iex_number(")>" <> string, _module, _line_no, _line) do 820 ">" <> string 821 end 822 823 defp skip_iex_number("", module, line_no, line) do 824 message = 825 "unknown IEx prompt: #{inspect(line)}.\nAccepted formats are: iex>, iex(1)>, ...>, ...(1)>}" 826 827 raise Error, line: line_no, module: module, message: message 828 end 829 830 defp skip_iex_number(<<_>> <> string, module, line_no, line) do 831 skip_iex_number(string, module, line_no, line) 832 end 833 834 defp normalize_test(%{exprs: exprs} = test, fa) do 835 %{test | fun_arity: fa, exprs: Enum.reverse(exprs)} 836 end 837 838 defp add_expr(%{exprs: exprs} = test, expr, expected, formatted) do 839 %{test | exprs: [{expr, tag_expected(expected), formatted} | exprs]} 840 end 841 842 defp tag_expected(string) do 843 case string do 844 "" -> 845 :test 846 847 "** (" <> error -> 848 [mod, message] = :binary.split(error, ")") 849 {:error, Module.concat([mod]), String.trim_leading(message)} 850 851 _ -> 852 if inspectable?(string) do 853 {:inspect, inspect(string)} 854 else 855 {:test, string} 856 end 857 end 858 end 859 860 defp inspectable?(<<?#, char, rest::binary>>) when char in ?A..?Z, do: inspectable_end?(rest) 861 defp inspectable?(_), do: false 862 863 defp inspectable_end?(<<?., char, rest::binary>>) when char in ?A..?Z, 864 do: inspectable_end?(rest) 865 866 defp inspectable_end?(<<char, rest::binary>>) 867 when char in ?A..?Z 868 when char in ?a..?z 869 when char in ?0..?9 870 when char == ?_, 871 do: inspectable_end?(rest) 872 873 defp inspectable_end?(<<?<, _::binary>>), do: true 874 defp inspectable_end?(_), do: false 875 876 defp last_expr({:__block__, _, [_ | _] = block}), do: block |> List.last() |> last_expr() 877 defp last_expr(other), do: other 878 879 defp insert_assertions({:__block__, meta, block}), 880 do: {:__block__, meta, Enum.map(block, &insert_match_assertion/1)} 881 882 defp insert_assertions(ast), 883 do: insert_match_assertion(ast) 884 885 defp insert_match_assertion({:=, _, [{var, _, context}, _]} = ast) 886 when is_atom(var) and is_atom(context), 887 do: ast 888 889 defp insert_match_assertion({:=, meta, [left, right]}), 890 do: {{:., meta, [__MODULE__, :__assert__]}, meta, [{:=, meta, [left, right]}]} 891 892 defp insert_match_assertion(ast), 893 do: ast 894 895 @doc false 896 defmacro __assert__({:=, _, [left, right]} = assertion) do 897 code = Macro.escape(assertion, prune_metadata: true) 898 ExUnit.Assertions.__match__(left, right, code, :ok, __CALLER__) 899 end 900end 901