Skip to content

Commit 8448600

Browse files
committed
Fix doctest lines when compilation fails, closes #12607
1 parent ea31af5 commit 8448600

File tree

2 files changed

+96
-80
lines changed

2 files changed

+96
-80
lines changed

lib/ex_unit/lib/ex_unit/doc_test.ex

Lines changed: 54 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -376,13 +376,13 @@ defmodule ExUnit.DocTest do
376376
end) > 1
377377
end
378378

379-
defp test_case_content(expr, :test, location, stack, doctest) do
380-
string_to_quoted(location, stack, expr, doctest) |> insert_assertions()
379+
defp test_case_content(expr_lines, :test, location, stack, doctest) do
380+
string_to_quoted(location, stack, expr_lines, doctest) |> insert_assertions()
381381
end
382382

383-
defp test_case_content(expr, {:test, expected}, location, stack, doctest) do
384-
expr_ast = string_to_quoted(location, stack, expr, doctest) |> insert_assertions()
385-
expected_ast = string_to_quoted(location, stack, expected, doctest)
383+
defp test_case_content(expr_lines, {:test, expected}, location, stack, doctest) do
384+
expr_ast = string_to_quoted(location, stack, expr_lines, doctest) |> insert_assertions()
385+
expected_ast = string_to_quoted(update_line(location, expr_lines), stack, expected, doctest)
386386
last_expr = Macro.to_string(last_expr(expr_ast))
387387

388388
quote do
@@ -397,8 +397,8 @@ defmodule ExUnit.DocTest do
397397
end
398398
end
399399

400-
defp test_case_content(expr, {:inspect, expected}, location, stack, doctest) do
401-
expr_ast = string_to_quoted(location, stack, expr, doctest) |> insert_assertions()
400+
defp test_case_content(expr_lines, {:inspect, expected}, location, stack, doctest) do
401+
expr_ast = string_to_quoted(location, stack, expr_lines, doctest) |> insert_assertions()
402402
last_expr = Macro.to_string(last_expr(expr_ast))
403403

404404
quote do
@@ -425,6 +425,10 @@ defmodule ExUnit.DocTest do
425425
end
426426
end
427427

428+
defp update_line(location, lines) do
429+
Keyword.replace_lazy(location, :line, & &1 + length(lines))
430+
end
431+
428432
@doc false
429433
def __test__(value, expected, doctest, last_expr, expected_expr, stack) do
430434
case value do
@@ -510,7 +514,7 @@ defmodule ExUnit.DocTest do
510514

511515
defp string_to_quoted(location, stack, expr, doctest) do
512516
try do
513-
Code.string_to_quoted!(expr, location)
517+
Code.string_to_quoted!(IO.iodata_to_binary(expr), location)
514518
rescue
515519
e ->
516520
ex_message = "(#{inspect(e.__struct__)}) #{Exception.message(e)}"
@@ -591,12 +595,6 @@ defmodule ExUnit.DocTest do
591595
defp extract_from_doc(_doc, _module),
592596
do: []
593597

594-
defp extract_tests(line_no, doc, module) do
595-
all_lines = String.split(doc, ["\r\n", "\n"], trim: false)
596-
lines = adjust_indent(all_lines, line_no + 1, module)
597-
extract_tests(lines, "", "", [], true, module, "")
598-
end
599-
600598
@iex_prompt ["iex>", "iex("]
601599
@dot_prompt ["...>", "...("]
602600

@@ -696,13 +694,19 @@ defmodule ExUnit.DocTest do
696694

697695
@fences ["```", "~~~"]
698696

697+
defp extract_tests(line_no, doc, module) do
698+
all_lines = String.split(doc, ["\r\n", "\n"], trim: false)
699+
lines = adjust_indent(all_lines, line_no + 1, module)
700+
extract_tests(lines, [], [], [], true, module, [])
701+
end
702+
699703
defp extract_tests(lines, expr_acc, expected_acc, acc, new_test, module, formatted)
700704

701-
defp extract_tests([], "", "", [], _, _, _) do
705+
defp extract_tests([], [], [], [], _, _, _) do
702706
[]
703707
end
704708

705-
defp extract_tests([], "", "", acc, _, _, _) do
709+
defp extract_tests([], [], [], acc, _, _, _) do
706710
Enum.reverse(acc)
707711
end
708712

@@ -722,36 +726,36 @@ defmodule ExUnit.DocTest do
722726
module,
723727
formatted
724728
)
725-
when expr_acc != "" and expected_acc != "" do
729+
when expr_acc != [] and expected_acc != [] do
726730
test = add_expr(test, expr_acc, expected_acc, formatted)
727-
extract_tests(list, "", "", [test | rest], new_test, module, "")
731+
extract_tests(list, [], [], [test | rest], new_test, module, [])
728732
end
729733

730734
# Store expr_acc and start a new test case.
731735
defp extract_tests(
732736
[{"iex>" <> string = line, line_no} | lines],
733-
"",
737+
[],
734738
expected_acc,
735739
acc,
736740
true,
737741
module,
738742
_
739743
) do
740744
test = %{line: line_no, fun_arity: nil, exprs: []}
741-
extract_tests(lines, string, expected_acc, [test | acc], false, module, line)
745+
extract_tests(lines, [string], expected_acc, [test | acc], false, module, line)
742746
end
743747

744748
# Store expr_acc.
745749
defp extract_tests(
746750
[{"iex>" <> string = line, _} | lines],
747-
"",
751+
[],
748752
expected_acc,
749753
acc,
750754
false,
751755
module,
752756
_
753757
) do
754-
extract_tests(lines, string, expected_acc, acc, false, module, line)
758+
extract_tests(lines, [string], expected_acc, acc, false, module, line)
755759
end
756760

757761
# Still gathering expr_acc. Synonym for the next clause.
@@ -764,15 +768,9 @@ defmodule ExUnit.DocTest do
764768
module,
765769
formatted
766770
) do
767-
extract_tests(
768-
lines,
769-
expr_acc <> "\n" <> string,
770-
expected_acc,
771-
acc,
772-
new_test,
773-
module,
774-
formatted <> "\n" <> line
775-
)
771+
expr_acc = add_line(expr_acc, string)
772+
formatted = add_line(formatted, line)
773+
extract_tests(lines, expr_acc, expected_acc, acc, new_test, module, formatted)
776774
end
777775

778776
# Still gathering expr_acc. Synonym for the previous clause.
@@ -785,16 +783,10 @@ defmodule ExUnit.DocTest do
785783
module,
786784
formatted
787785
)
788-
when expr_acc != "" do
789-
extract_tests(
790-
lines,
791-
expr_acc <> "\n" <> string,
792-
expected_acc,
793-
acc,
794-
new_test,
795-
module,
796-
formatted <> "\n" <> line
797-
)
786+
when expr_acc != [] do
787+
expr_acc = add_line(expr_acc, string)
788+
formatted = add_line(formatted, line)
789+
extract_tests(lines, expr_acc, expected_acc, acc, new_test, module, formatted)
798790
end
799791

800792
# Expression numbers are simply skipped.
@@ -826,8 +818,8 @@ defmodule ExUnit.DocTest do
826818
end
827819

828820
# Skip empty or documentation line.
829-
defp extract_tests([_ | lines], "", "", acc, _, module, _formatted) do
830-
extract_tests(lines, "", "", acc, true, module, "")
821+
defp extract_tests([_ | lines], [], [], acc, _, module, _formatted) do
822+
extract_tests(lines, [], [], acc, true, module, [])
831823
end
832824

833825
# Encountered end of fenced code block, store pending test
@@ -840,9 +832,9 @@ defmodule ExUnit.DocTest do
840832
module,
841833
formatted
842834
)
843-
when fence in @fences and expr_acc != "" do
835+
when fence in @fences and expr_acc != [] do
844836
test = add_expr(test, expr_acc, expected_acc, formatted)
845-
extract_tests(lines, "", "", [test | rest], true, module, "")
837+
extract_tests(lines, [], [], [test | rest], true, module, [])
846838
end
847839

848840
# Encountered an empty line, store pending test
@@ -856,11 +848,11 @@ defmodule ExUnit.DocTest do
856848
formatted
857849
) do
858850
test = add_expr(test, expr_acc, expected_acc, formatted)
859-
extract_tests(lines, "", "", [test | rest], true, module, "")
851+
extract_tests(lines, [], [], [test | rest], true, module, [])
860852
end
861853

862854
# Finally, parse expected_acc.
863-
defp extract_tests([{expected, _} | lines], expr_acc, "", acc, new_test, module, formatted) do
855+
defp extract_tests([{expected, _} | lines], expr_acc, [], acc, new_test, module, formatted) do
864856
extract_tests(lines, expr_acc, expected, acc, new_test, module, formatted)
865857
end
866858

@@ -873,15 +865,12 @@ defmodule ExUnit.DocTest do
873865
module,
874866
formatted
875867
) do
876-
extract_tests(
877-
lines,
878-
expr_acc,
879-
expected_acc <> "\n" <> expected,
880-
acc,
881-
new_test,
882-
module,
883-
formatted
884-
)
868+
expected_acc = add_line(expected_acc, expected)
869+
extract_tests(lines, expr_acc, expected_acc, acc, new_test, module, formatted)
870+
end
871+
872+
defp add_line(acc, line) do
873+
[acc, [?\n, line]]
885874
end
886875

887876
defp skip_iex_number(")>" <> string, _module, _line_no, _line) do
@@ -903,13 +892,14 @@ defmodule ExUnit.DocTest do
903892
%{test | fun_arity: fa, exprs: Enum.reverse(exprs)}
904893
end
905894

906-
defp add_expr(%{exprs: exprs} = test, expr, expected, formatted) do
907-
doctest = "\n" <> formatted <> "\n" <> expected
908-
%{test | exprs: [{expr, tag_expected(expected), doctest} | exprs]}
895+
defp add_expr(%{exprs: exprs} = test, expr_lines, expected_lines, formatted_lines) do
896+
expected = IO.iodata_to_binary(expected_lines)
897+
doctest = IO.iodata_to_binary([?\n, formatted_lines, ?\n, expected])
898+
%{test | exprs: [{expr_lines, tag_expected(expected), doctest} | exprs]}
909899
end
910900

911-
defp tag_expected(string) do
912-
case string do
901+
defp tag_expected(expected) do
902+
case expected do
913903
"" ->
914904
:test
915905

@@ -918,10 +908,10 @@ defmodule ExUnit.DocTest do
918908
{:error, Module.concat([mod]), String.trim_leading(message)}
919909

920910
_ ->
921-
if inspectable?(string) do
922-
{:inspect, string}
911+
if inspectable?(expected) do
912+
{:inspect, expected}
923913
else
924-
{:test, string}
914+
{:test, expected}
925915
end
926916
end
927917
end

lib/ex_unit/test/ex_unit/doc_test_test.exs

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,13 @@ defmodule ExUnit.DocTestTest.Invalid do
232232
1
233233
"""
234234
@type t :: any()
235+
236+
@doc """
237+
iex> :foo
238+
iex> :bar
239+
1 + * 1
240+
"""
241+
def result, do: :ok
235242
end
236243
|> ExUnit.BeamHelpers.write_beam()
237244

@@ -630,6 +637,8 @@ defmodule ExUnit.DocTestTest do
630637

631638
assert output =~
632639
"test/ex_unit/doc_test_test.exs:#{starting_line + 28}: ExUnit.DocTestTest.Failure (module)"
640+
641+
assert output =~ "8 doctests, 8 failures"
633642
end
634643

635644
test "doctest invalid" do
@@ -678,10 +687,10 @@ defmodule ExUnit.DocTestTest do
678687
assert output =~ """
679688
3) doctest ExUnit.DocTestTest.Invalid.indented_not_enough/0 (3) (ExUnit.DocTestTest.InvalidCompiled)
680689
test/ex_unit/doc_test_test.exs:#{doctest_line}
681-
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 14}:1: unexpected token: "`" (column 1, code point U+0060)
682-
#{line_placeholder(starting_line + 14)} |
683-
#{starting_line + 14} | `
684-
#{line_placeholder(starting_line + 14)} | ^
690+
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 15}:1: unexpected token: "`" (column 1, code point U+0060)
691+
#{line_placeholder(starting_line + 15)} |
692+
#{starting_line + 15} | `
693+
#{line_placeholder(starting_line + 15)} | ^
685694
doctest:
686695
iex> 1 + 2
687696
3
@@ -693,10 +702,10 @@ defmodule ExUnit.DocTestTest do
693702
assert output =~ """
694703
4) doctest ExUnit.DocTestTest.Invalid.indented_too_much/0 (4) (ExUnit.DocTestTest.InvalidCompiled)
695704
test/ex_unit/doc_test_test.exs:#{doctest_line}
696-
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 22}:3: unexpected token: "`" (column 3, code point U+0060)
697-
#{line_placeholder(starting_line + 22)} |
698-
#{starting_line + 22} | ```
699-
#{line_placeholder(starting_line + 22)} | ^
705+
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 23}:3: unexpected token: "`" (column 3, code point U+0060)
706+
#{line_placeholder(starting_line + 23)} |
707+
#{starting_line + 23} | ```
708+
#{line_placeholder(starting_line + 23)} | ^
700709
doctest:
701710
iex> 1 + 2
702711
3
@@ -708,10 +717,10 @@ defmodule ExUnit.DocTestTest do
708717
assert output =~ """
709718
5) doctest ExUnit.DocTestTest.Invalid.dedented_past_fence/0 (5) (ExUnit.DocTestTest.InvalidCompiled)
710719
test/ex_unit/doc_test_test.exs:#{doctest_line}
711-
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 30}:5: unexpected token: "`" (column 5, code point U+0060)
712-
#{line_placeholder(starting_line + 30)} |
713-
#{starting_line + 30} | ```
714-
#{line_placeholder(starting_line + 30)} | ^
720+
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 31}:5: unexpected token: "`" (column 5, code point U+0060)
721+
#{line_placeholder(starting_line + 31)} |
722+
#{starting_line + 31} | ```
723+
#{line_placeholder(starting_line + 31)} | ^
715724
doctest:
716725
iex> 1 + 2
717726
3
@@ -731,10 +740,10 @@ defmodule ExUnit.DocTestTest do
731740
assert output =~ """
732741
7) doctest ExUnit.DocTestTest.Invalid.misplaced_opaque_type/0 (7) (ExUnit.DocTestTest.InvalidCompiled)
733742
test/ex_unit/doc_test_test.exs:#{doctest_line}
734-
Doctest did not compile, got: (TokenMissingError) test/ex_unit/doc_test_test.exs:#{starting_line + 42}:7: missing terminator: } (for "{" starting at line #{starting_line + 42})
735-
#{line_placeholder(starting_line + 42)} |
736-
#{starting_line + 42} | {:ok, #Inspect<[]>}
737-
#{line_placeholder(starting_line + 42)} | ^
743+
Doctest did not compile, got: (TokenMissingError) test/ex_unit/doc_test_test.exs:#{starting_line + 43}:7: missing terminator: } (for "{" starting at line #{starting_line + 43})
744+
#{line_placeholder(starting_line + 43)} |
745+
#{starting_line + 43} | {:ok, #Inspect<[]>}
746+
#{line_placeholder(starting_line + 43)} | ^
738747
If you are planning to assert on the result of an iex> expression which contains a value inspected as #Name<...>, please make sure the inspected value is placed at the beginning of the expression; otherwise Elixir will treat it as a comment due to the leading sign #.
739748
doctest:
740749
iex> {:ok, :oops}
@@ -756,6 +765,23 @@ defmodule ExUnit.DocTestTest do
756765
stacktrace:
757766
test/ex_unit/doc_test_test.exs:#{starting_line + 48}: ExUnit.DocTestTest.Invalid (module)
758767
"""
768+
769+
assert output =~ """
770+
9) doctest ExUnit.DocTestTest.Invalid.result/0 (9) (ExUnit.DocTestTest.InvalidCompiled)
771+
test/ex_unit/doc_test_test.exs:#{doctest_line}
772+
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 56}:5: syntax error before: '*'
773+
#{line_placeholder(starting_line + 56)} |
774+
#{starting_line + 56} | 1 + * 1
775+
#{line_placeholder(starting_line + 56)} | ^
776+
doctest:
777+
iex> :foo
778+
iex> :bar
779+
1 + * 1
780+
stacktrace:
781+
test/ex_unit/doc_test_test.exs:#{starting_line + 54}: ExUnit.DocTestTest.Invalid (module)
782+
"""
783+
784+
assert output =~ "9 doctests, 9 failures"
759785
end
760786

761787
test "pattern matching assertions in doctests" do

0 commit comments

Comments
 (0)