Has anyone writing doctests found a way to update their examples that produce ArgumentErrors to capture the new multiline format produced in Elixir 1.12.0? I had some trouble updating an error expectation today because of the double newline.
Here’s what succeeded in Elixir 1.11.4:
@doc ~S"""
Returns `map` with its keys as atoms, if those atoms already exist.
Raises `ArgumentError` otherwise.
## Examples
iex> atomize_keys!(%{"oh" => "ooh", "noo" => "noooo"})
** (ArgumentError) argument error
"""
But updating to Elixir 1.12.0’s reported actual results in this test failure:
Doctest failed: wrong message for ArgumentError
expected:
"errors were found at the given arguments:\\n\\n * 1st argument: invalid UTF8 encoding\\n"
actual:
"errors were found at the given arguments:\n\n * 1st argument: invalid UTF8 encoding\n"
And if I update the doc tag to interpret escaped characters (i.e., @doc """), the test just fails in the opposite direction:
Doctest failed: wrong message for ArgumentError
expected:
"errors were found at the given arguments:"
actual:
"errors were found at the given arguments:\n\n * 1st argument: invalid UTF8 encoding\n"
…And, yes, escaping the escape fails to improve the situation:
Doctest failed: wrong message for ArgumentError
expected:
"errors were found at the given arguments:\\n\\n * 1st argument: invalid UTF8 encoding\\n"
actual:
"errors were found at the given arguments:\n\n * 1st argument: invalid UTF8 encoding\n"
Me @ this point:
I implemented a new exception as a workaround for my use case, but it feels like a circumstance that does deserve a more generally flexible fix. I mean, what if a library developer does want to use an example of a multiline exception with double newlines in one of their doctests? Clearly, there are cases now where at least ArgumentErrors produce such exceptions, and those cases shouldn’t fail in the test runs. At least, it doesn’t feel like they should ._.
Doctests do not support multi-line exceptions because it is hard for it to know when the exception is over and when you have a new paragraph (as we typically avoid relying on indentation). However, we definitely need to come up with a mechanism to do so… or at least for a subtext match. Please open up an issue!
workaround for me was to use assert_raise without any output check, not ideal to use asserts in doctests but does the job: iex> assert_raise ArgumentError, fn -> atomize_keys!(%{"oh" => "ooh", "noo" => "noooo"}) end
I thought several times about making it a prefix match on the error message, but then it means including less information in the example. Also, if we remove the empty lines, we will definitely receive pull requests adding the lines back. I am not quite sure how to address this.
Hmm, maybe a pragmatic solution is in a doctest that is checking for an exception, only match the first line. So this would be a valid passing doctest:
iex> hd([])
** (ArgumentError) errors were found at the given arguments:
I’m not using doctests often so I’m not super familiar with them. But shouldn’t it be possible to use the actual exception message to check if that full message appears in the doctest?
Now that I think about it. That’s probably hard because of the way doctests are parsed?
i use doctests all the time now. especially for advent of code. Not having to define tests in different file is so nice. Not having to even open repl. Just mix.watch --only etc for a tagged doctest. (for ex mix test.watch --only day:2024.d12.p1)
I ususally have to define multiline input in another module. And i have to be super careful of not having space between the tests, if i have one doc test and then a new line … the other tests are not run