How to stop IO.gets from reprompting on IO.write from different process?

how-to-question
Tags: #<Tag:0x00007f03a4317d48>

#1

Hello all,

I am working on interactive tests https://github.com/assert-value/assert_value_elixir
The problem I am facing is in IO.puts/IO.gets in the case of async ExUnt tests.

To explain what I mean consider the following code:

Task.async(fn ->
  for _ <- 0..5 do
    :timer.sleep(100)
    IO.write "."
  end
end)

IO.gets("Waiting for input? ")

The output it produces is looking like this:

Waiting for input? .Waiting for input? .Waiting for input? .Waiting for input? .Waiting for input? .Waiting for input? .Waiting for input?

I want it to look like this:

.....Waiting for input?

or like this

Waiting for input?
.....

Of course in that case I can use Task.await but in real case of async tests in ExUnit I don’t know what processes are currently run and what output they produce.

Is there any way to suppress IO output or stop IO processes while
any of the process is waiting for user input?

Thanks,
Serge


#2

I would do it the other way around. Here:

defmodule InputWaiter do

  def waiter(receiver) do
    IO.puts("Waiting for input?")
    rv = IO.gets("")
    send receiver, rv
  end

  def receiver() do
    receive do
      x -> IO.puts("User said: #{x}")
    after
      100 ->
        IO.write(".")
        receiver()
    end
  end

  def userMessage() do 
    pid = self()
    spawn(fn -> InputWaiter.waiter(pid) end)
    receiver()
  end
end

InputWaiter.userMessage()

p.s. I like your idea in that library … would be fantastic to pair it with property based testing… is this what you’re thinking?


#3

It works if you have access to processes. But what I am asking is what to do when you have unknown processes started somewhere?

I did not think about property based testing. We use this library to make maintainable tests based on serialization. So the main test process is:

serialize something ->
   no diff -> 
        pass
   diff ->
      ask user what to do?  ->
        accept 
          -> pass and update the test
        reject
          -> fail

And it works very good for us (especially when software is changing too fast)


#4

How about a custom ExUnit formatter?


#5

This is the way if I will not find another solution. For now the goal is to use ExUnit untouched.


#6

How “unknown”? Do you have its PID? Can it send a message to another process? Because you could always name your process that is producing the wait-status UI and have that “unknown” process send a message to that process when it should stop. You could also have these unknown processes wait on an IO provider of some sort which does the IO.gets() on their behalf with this setup … but I’m not sure what you’d do with some random process you don’t start, don’t know about, don’t have control over its code … though I don’t think that is your case?

Yes, that is evident from the code and, as you note, I can imagine that is quite helpful! Paired with property based testing, it could help fill in the “hole” of testing functions that produce a variety of values (mappings, if you will) for which you need to then capture the correct outputs for. This is especially so for adding in failure cases that generated tests catch. Can see real potential for the removal of hand-work there!


#7

Why are your tests writing to standard input/output while they run? Shouldn’t you use ExUnit.CaptureIO to handle that?


#8

Because I am waiting for real user input to accept/deny test result. I did not find how to do this with ExUnit.CaptureIO.


#9

For others who come across this… https://stackoverflow.com/questions/37715885/how-do-i-fake-io-input-when-testing-with-exunit