I just looked into how one would test code that used IO.error (by using the 
patch I linked to in the original post).


Currently, if your macro code throws an error like this:
raise MyError, "Something broke"

Then your test code probably looks something like:
assert_raise MyError, fn ->
  Code.require_file("fixtures/my_file.exs")
end)


With IO.error your macro code would instead have:
IO.error("Something broke")

And to test it, you would call:
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
  Code.require_file("fixtures/my_file.exs")
end) =~ "Something broke"


While the code is indeed testable, I'm not sure if it is ideal. We may want 
to add another macro to ExUnit.Assertions to help test code that uses 
IO.warn and IO.error. Or maybe string matching in tests is good enough? 
Thoughts?


On Tuesday, May 12, 2020 at 7:49:26 PM UTC-6, Dallin Osmun wrote:
>
> Whoops, I failed to speak to that point. Thanks for asking!
>
> If not invoked during the compilation step, I would expect IO.error to log 
> an error and stacktrace to the console. No exceptions would be raised and 
> your program or remote session would continue as normal. It makes sense to 
> me that IO.warn and IO.error would be as symmetrical as possible:
>
> IO.warn prints out a warning and a stacktrace to the console. If 
> compiling, IO.warn also emits a warning compiler diagnostic.
>
> IO.error prints out an error and a stacktrace to the console. If 
> compiling, IO.error also emits an error compiler diagnostic (which is what 
> causes the compilation to fail).
>
> Is that what you would expect? I'm definitely open to alternatives.
>
>
> On Tuesday, May 12, 2020 at 4:30:26 PM UTC-6, Ben Wilson wrote:
>>
>> Perhaps you addressed this, but how would IO.error behave when not 
>> compiling things? How would it behave if you remote console into a running 
>> system and call IO.error?
>>
>> On Monday, May 11, 2020 at 11:33:40 PM UTC-4, Dallin Osmun wrote:
>>>
>>> I understand at first glance this proposal might not seem necessary. I 
>>> wanted to give some insight into how I got here. I'd like to outline a 
>>> couple of the alternatives I tried and some of the issues I ran into while 
>>> doing so.
>>>
>>> As a reminder: the goal is to emit multiple error diagnostics during the 
>>> compilation step and fail the compilation.
>>>
>>> *Let's stick with `raise/1`*
>>> Everyone is already using raise to emit errors from macros. The compiler 
>>> diagnostic that is emitted from raise is missing a position but it does 
>>> have a stacktrace. While the original frame in that trace does point to 
>>> your macro, it probably still isn't the correct line. Take the following 
>>> pseudocode. raise will cause a stacktrace that points to `query` on line 1 
>>> when your actual error is on line 3. Currently, raise does not and cannot 
>>> know exactly where your error is.
>>>
>>> ```
>>> 1. query Thing do
>>> 2.   id
>>> 3.   error_here
>>> 4. end
>>> ```
>>>
>>> *So add `raise/2`*
>>> Maybe we enhance raise so it takes an optional stacktrace as a second 
>>> argument like IO.warn does. While I think this is a great idea, it doesn't 
>>> meet one of the two criteria above (emit multiple errors). For the record 
>>> though, I do think there are cases out there where rather than let your 
>>> macro get into an inconsistent state you would want to raise an error and 
>>> stop compilation. If we allowed a custom stacktrace to be passed in to 
>>> raise then the error diagnostic it emitted would be more useful.
>>>
>>> *Why not use IO.warn/2 with the `warnings_as_errors` flag?*
>>> This solution does indeed solve both my criteria: multiple errors are 
>>> emitted and the build fails. But the developer experience is not ideal:
>>> - I am forcing my users to add a compiler flag to their project. It's 
>>> one more thing to remember when using my library.
>>> - As a macro author I would like to emit both warnings and errors. If I 
>>> can only emit warnings (which are treated as errors) then I am unable to 
>>> distinguish between the two.
>>> - This forces ALL warnings in your project to be treated as errors which 
>>> may not be desirable in some cases.
>>>
>>> *How about IO.warn/2 and return a raise if you hit any warnings?*
>>> So let's imagine then that I use IO.warn to report all of my errors. If 
>>> I had to report any errors then I'll make my macro output an AST for `raise 
>>> "broken macro: check the logs"`. I don't have to force the 
>>> `warnings_as_errors` flag on my users this way and I am able to emit 
>>> multiple errors. But now the compilation is successful. I have to rely on 
>>> my users actually exercising their code or having a good test suite to find 
>>> out that the output of the macro isn't actually going to work.
>>>
>>>
>>> On Monday, May 4, 2020 at 5:37:14 PM UTC-6, Dallin Osmun wrote:
>>>>
>>>> I propose that we add `IO.error/2` which matches the signature of 
>>>> `IO.warn/2`. It emits error diagnostics/messages instead of warnings and 
>>>> it 
>>>> causes the compile step to finish with :error.
>>>>
>>>>
>>>> *Reasoning*
>>>>
>>>> Often when building a macro you'll do some validation and `raise` an 
>>>> error if something isn't right. This creates a poor experience for the 
>>>> person using the macro for two reasons:
>>>> 1. You can only raise one error at a time
>>>> 2. You don't get a good picture of where the error is because your 
>>>> whole editor turns red
>>>>
>>>> You can solve both of those problems by using `IO.warn/2`. You can emit 
>>>> multiple warnings in a single compile step and you can pass in a 
>>>> stacktrace 
>>>> which gets turned into a Compiler Diagnostic that in turn, creates good 
>>>> editor hints. But now the compilation succeeds and you're left in a bad 
>>>> state.
>>>>
>>>> I think it is useful to see multiple errors at a time because it 
>>>> shortens the feedback loop. It also gives more context and can help you 
>>>> realize where the root cause of your issue lies.
>>>>
>>>> I think it would be useful to have a function that shared the 
>>>> properties of `IO.warn/2` and `raise/1`:
>>>> - I can emit multiple messages during a single compilation run
>>>> - These messages are output as Compiler Diagnostic errors
>>>> - Invoking this function will ultimately cause the compilation step to 
>>>> result in an :error
>>>>
>>>>
>>>> *Examples*
>>>>
>>>> Today in Ecto Query, this snippet will cause the entire file to turn 
>>>> red because we mistakenly used sort_by instead of order_by.
>>>> query p in Product, sort_by: p.price
>>>>
>>>> Lets assume we forgot to load the :user type in this Absinthe Schema. 
>>>> We have two errors but only one gets displayed. Once again, the entire 
>>>> editor turns red. If instead we saw each line that referenced :user turn 
>>>> red it might remind us more quickly that we forgot to call `import_types 
>>>> MyApp.UserTypes`.
>>>> query name: "Query" do
>>>>   field :user, :user, resolve: &MyApp.Users.get_user/3
>>>>   ...
>>>>   field :users, list_of(:user), resolve: &MyApp.Users.list_user/3
>>>>   ...
>>>> end
>>>>
>>>> I'm currently working on a library to create GraphQL queries. I can 
>>>> detect three errors in the following snippet. Today I can only report one 
>>>> at a time and am greeted with the wall of red. If I switch to `IO.warn/2` 
>>>> I 
>>>> can report them all and see nice editor hints but my user is left with 
>>>> broken code after compile.
>>>> query do
>>>>   user(id: "should_be_a_number") do # wrong type for id
>>>>     firstNam # misspelled, should be firstName
>>>>     lastName do # lastName should not have a do...end block
>>>>       name
>>>>     end
>>>>   end
>>>> end
>>>>
>>>>
>>>> *Code*
>>>>
>>>> Here is a patch that adds `IO.error/2`:
>>>> https://github.com/elixir-lang/elixir/compare/master...numso:io-error
>>>>
>>>>

-- 
You received this message because you are subscribed to the Google Groups 
"elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to elixir-lang-core+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/elixir-lang-core/484a9531-f114-4966-8150-64bc5cc06c01%40googlegroups.com.

Reply via email to