Skip to content

Commit 9e39f13

Browse files
committed
Add :inspect_opts option for doctest
Closes #14219
1 parent 5610775 commit 9e39f13

File tree

2 files changed

+113
-19
lines changed

2 files changed

+113
-19
lines changed

lib/ex_unit/lib/ex_unit/doc_test.ex

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ defmodule ExUnit.DocTest do
115115
116116
inspect(map.datetime) == "#DateTime<2023-06-26 09:30:00+09:00 JST Asia/Tokyo>"
117117
118+
You can also control `doctest` to use certain inspect options. See the
119+
documentation on the `:inspect_opts` option below.
120+
118121
Alternatively, since doctest results are actually evaluated, you can have
119122
the `DateTime` building expression as the doctest result:
120123
@@ -196,6 +199,10 @@ defmodule ExUnit.DocTest do
196199
197200
* `:tags` - a list of tags to apply to all generated doctests.
198201
202+
* `:inspect_opts` - A keyword list with options for `inspect/2` on opaque
203+
types. This is useful when inspection output on opaque types utilizes
204+
pretty printing and to keep the doctests more readable.
205+
199206
## Examples
200207
201208
defmodule MyModuleTest do
@@ -241,6 +248,10 @@ defmodule ExUnit.DocTest do
241248
242249
* `:tags` - a list of tags to apply to all generated doctests.
243250
251+
* `:inspect_opts` - A keyword list with options for `inspect/2` on opaque
252+
types. This is useful when inspection output on opaque types utilizes
253+
pretty printing and to keep the doctests more readable.
254+
244255
## Examples
245256
246257
defmodule ReadmeTest do
@@ -270,10 +281,15 @@ defmodule ExUnit.DocTest do
270281
doc = File.read!(file)
271282
file = Path.relative_to_cwd(file)
272283
tags = [doctest: file] ++ Keyword.get(opts, :tags, [])
284+
inspect_opts = Keyword.get(opts, :inspect_opts, [])
273285

274286
extract_tests(1, doc, module, :moduledoc)
275287
|> Enum.with_index(fn test, acc ->
276-
{"#{file} (#{acc + 1})", test_content(test, module, false, file), test_tags(test, tags)}
288+
{
289+
"#{file} (#{acc + 1})",
290+
test_content(test, module, false, file, inspect_opts),
291+
test_tags(test, tags)
292+
}
277293
end)
278294
end
279295

@@ -293,6 +309,7 @@ defmodule ExUnit.DocTest do
293309
def __doctests__(module, opts) do
294310
tags = [doctest: module] ++ Keyword.get(opts, :tags, [])
295311
import = Keyword.get(opts, :import, false)
312+
inspect_opts = Keyword.get(opts, :inspect_opts, [])
296313

297314
maybe_source =
298315
if source = module.module_info(:compile)[:source] do
@@ -303,7 +320,7 @@ defmodule ExUnit.DocTest do
303320
|> filter_by_opts(module, opts)
304321
|> Enum.sort_by(& &1.line)
305322
|> Enum.with_index(fn test, index ->
306-
compile_test(test, module, import, index + 1, maybe_source, tags)
323+
compile_test(test, module, import, index + 1, maybe_source, tags, inspect_opts)
307324
end)
308325
end
309326

@@ -349,9 +366,12 @@ defmodule ExUnit.DocTest do
349366

350367
## Compilation of extracted tests
351368

352-
defp compile_test(test, module, do_import, n, maybe_source, tags) do
353-
{test_name(test, module, n), test_content(test, module, do_import, maybe_source),
354-
test_tags(test, tags)}
369+
defp compile_test(test, module, do_import, n, maybe_source, tags, inspect_opts) do
370+
{
371+
test_name(test, module, n),
372+
test_content(test, module, do_import, maybe_source, inspect_opts),
373+
test_tags(test, tags)
374+
}
355375
end
356376

357377
defp test_name(%{fun_arity: :moduledoc}, m, n) do
@@ -362,7 +382,7 @@ defmodule ExUnit.DocTest do
362382
"#{inspect(m)}.#{f}/#{a} (#{n})"
363383
end
364384

365-
defp test_content(%{exprs: exprs, line: line}, module, do_import, maybe_source) do
385+
defp test_content(%{exprs: exprs, line: line}, module, do_import, maybe_source, inspect_opts) do
366386
if multiple_exceptions?(exprs) do
367387
raise Error,
368388
line: line,
@@ -372,7 +392,9 @@ defmodule ExUnit.DocTest do
372392
"please separate your iex> prompts by multiple newlines to start new examples"
373393
end
374394

375-
tests = Enum.map(exprs, fn expr -> test_case_content(expr, module, maybe_source) end)
395+
tests =
396+
Enum.map(exprs, fn expr -> test_case_content(expr, module, maybe_source, inspect_opts) end)
397+
376398
{:__block__, [], test_import(module, do_import) ++ tests}
377399
end
378400

@@ -387,12 +409,17 @@ defmodule ExUnit.DocTest do
387409
end) > 1
388410
end
389411

390-
defp test_case_content(%{expected: :test} = data, module, maybe_source) do
412+
defp test_case_content(%{expected: :test} = data, module, maybe_source, _inspect_opts) do
391413
%{expr: expr, expr_line: expr_line, doctest: doctest} = data
392414
string_to_quoted(module, maybe_source, expr_line, expr, doctest) |> insert_assertions()
393415
end
394416

395-
defp test_case_content(%{expected: {:test, expected}} = data, module, maybe_source) do
417+
defp test_case_content(
418+
%{expected: {:test, expected}} = data,
419+
module,
420+
maybe_source,
421+
_inspect_opts
422+
) do
396423
%{expr: expr, expr_line: expr_line, expected_line: expected_line, doctest: doctest} = data
397424

398425
expr_ast =
@@ -419,7 +446,12 @@ defmodule ExUnit.DocTest do
419446
end
420447
end
421448

422-
defp test_case_content(%{expected: {:inspect, expected}} = data, module, maybe_source) do
449+
defp test_case_content(
450+
%{expected: {:inspect, expected}} = data,
451+
module,
452+
maybe_source,
453+
inspect_opts
454+
) do
423455
%{expr: expr, expr_line: expr_line, doctest: doctest} = data
424456

425457
expr_ast =
@@ -436,12 +468,18 @@ defmodule ExUnit.DocTest do
436468
unquote(inspect(expected)),
437469
unquote(module),
438470
unquote(maybe_source),
439-
unquote(expr_line)
471+
unquote(expr_line),
472+
unquote(inspect_opts)
440473
)
441474
end
442475
end
443476

444-
defp test_case_content(%{expected: {:error, exception, message}} = data, module, maybe_source) do
477+
defp test_case_content(
478+
%{expected: {:error, exception, message}} = data,
479+
module,
480+
maybe_source,
481+
_inspect_opts
482+
) do
445483
%{expr: expr, expr_line: expr_line, doctest: doctest} = data
446484
expr_ast = string_to_quoted(module, maybe_source, expr_line, expr, doctest)
447485

@@ -478,10 +516,20 @@ defmodule ExUnit.DocTest do
478516
end
479517

480518
@doc false
481-
def __inspect__(value, expected, doctest, last_expr, expected_expr, module, maybe_source, line) do
519+
def __inspect__(
520+
value,
521+
expected,
522+
doctest,
523+
last_expr,
524+
expected_expr,
525+
module,
526+
maybe_source,
527+
line,
528+
inspect_opts
529+
) do
482530
result =
483531
try do
484-
inspect(value, safe: false)
532+
inspect(value, Keyword.put(inspect_opts, :safe, false))
485533
rescue
486534
e ->
487535
stack = Enum.drop(__STACKTRACE__, 1)

lib/ex_unit/test/ex_unit/doc_test_test.exs

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -448,14 +448,16 @@ defmodule ExUnit.DocTestTest.Haiku do
448448
def crlf_test, do: :ok
449449

450450
defimpl Inspect do
451-
def inspect(haiku, _opts) do
452-
author = if haiku.author == "", do: "", else: "\n -- #{haiku.author}"
451+
def inspect(haiku, opts) do
452+
indent = Keyword.get(opts.custom_options, :indent, 2)
453+
spaces = String.duplicate(" ", indent)
454+
author = if haiku.author == "", do: "", else: "\n#{spaces}-- #{haiku.author}"
453455

454456
"""
455457
#Haiku<
456-
#{haiku.first_phrase}
457-
#{haiku.second_phrase}
458-
#{haiku.third_phrase}#{author}
458+
#{spaces}#{haiku.first_phrase}
459+
#{spaces}#{haiku.second_phrase}
460+
#{spaces}#{haiku.third_phrase}#{author}
459461
>
460462
"""
461463
|> String.trim_trailing("\n")
@@ -464,6 +466,47 @@ defmodule ExUnit.DocTestTest.Haiku do
464466
end
465467
|> ExUnit.BeamHelpers.write_beam()
466468

469+
defmodule ExUnit.DocTestTest.HaikuIndent4UsingInspectOpts do
470+
@moduledoc """
471+
This module is part of the DocTest test suite,
472+
to ensure that DocTest respect the `:inspect_opts` options.
473+
"""
474+
475+
@doc """
476+
# Simple Haiku, inspect output consists of multiple lines.
477+
iex> ExUnit.DocTestTest.Haiku.new("Haikus are easy", "But sometimes they don't make sense", "Refrigerator")
478+
#Haiku<
479+
Haikus are easy
480+
But sometimes they don't make sense
481+
Refrigerator
482+
>
483+
484+
# Haiku with Unicode characters (Japanese Kanji, em-dash).
485+
iex> ExUnit.DocTestTest.Haiku.new("古池や", "蛙飛びこむ", "水の音", "Matsuo Basho")
486+
#Haiku<
487+
古池や
488+
蛙飛びこむ
489+
水の音
490+
-- Matsuo Basho
491+
>
492+
493+
"""
494+
def indent_4, do: :ok
495+
496+
@doc """
497+
iex> ExUnit.DocTestTest.Haiku.new("古池や", "蛙飛びこむ", "水の音", "Matsuo Basho")
498+
#Haiku<
499+
古池や
500+
蛙飛びこむ
501+
水の音
502+
-- Matsuo Basho
503+
>
504+
"""
505+
|> String.replace("\n", "\r\n")
506+
def crlf_test, do: :ok
507+
end
508+
|> ExUnit.BeamHelpers.write_beam()
509+
467510
defmodule ExUnit.DocTestTest.PatternMatching do
468511
def starting_line, do: __ENV__.line + 2
469512

@@ -528,6 +571,9 @@ defmodule ExUnit.DocTestTest do
528571
doctest ExUnit.DocTestTest.FencedHeredocs
529572
doctest ExUnit.DocTestTest.Haiku
530573

574+
doctest ExUnit.DocTestTest.HaikuIndent4UsingInspectOpts,
575+
inspect_opts: [custom_options: [indent: 4]]
576+
531577
import ExUnit.CaptureIO
532578

533579
test "multiple functions filtered with :only" do

0 commit comments

Comments
 (0)