Testing User Input in GitHub CI/Test Suites?

Hi folks!

How do you test user input for readline() in GitHub CI or your local test suite? I have a snippet of code that asks for user input in my package and am trying to test the input – kinda like this:

function finish_exec()
    print("Would you like to continue? (Y/N)")
    input = readline()
 
    answer = isempty(input) ? error("Bad selection") : input
    # Other code
end

Ignoring the specifics of the function, how would I test functions like this in general which need to accept a manual user input?

On Slack, @jakobnissen suggested: “Is it possible to redefine Base.stdin? You could define it to an IOBuffer, run the test, and then set it back.”

But I am not entirely sure how to do that. I did try setting an IOBuffer but didn’t know how I could have it as the state I wanted to be read from via readline.

Thanks!

~ tcp :deciduous_tree:

1 Like

Maybe pass the IOBuffer in?

julia> function finish_exec(io=stdin)
           print("Would you like to continue? (Y/N)")
           input = readline(io)
           answer = isempty(input) ? error("Bad selection") : input
           # Other code
       end

finish_exec (generic function with 2 methods)

julia> finish_exec()
Would you like to continue? (Y/N)Y
"Y"

julia> b = IOBuffer("Y\n")
IOBuffer(data=UInt8[...], readable=true, writable=false, seekable=true, append=false, size=2, maxsize=Inf, ptr=1, mark=-1)

julia> finish_exec(b)
Would you like to continue? (Y/N)"Y"

julia>
5 Likes

If you don’t want to change your codebase to pass around an io like that, you can also use redirect_stdin around the test itself. It’d be nice if that could take an IOBuffer, but it doesn’t allow that currently. You can write a temporary file to get around it.

2 Likes

I considered that @contradict , but maybe I am misunderstanding: I don’t want to have to define another dispatch with finish_exec(io). Instead, I want to have just the original finish_exec() without passing anything in to the function itself. Does your approach allow for that?

I might be a bit confused here.

EDIT: Also, thanks for the comments and thought here!

Oh so with that approach would I want to:

  1. Write my input to a temporary file “input.txt”
  2. Run redirect_stdin("input.txt")
  3. Execute my test like @test finish_exec() == what_i_check

Or what are you imagining Matt?

Fair enough. I like the explicit interface point for testing, but I understand the objection to more visual noise.

I didn’t think of the temp file trick, I was trying to figure out how to hook an IOBuffer up to a Pipe to get redirect_stdin to work but couldn’t figure it out.

1 Like

The tempfile solution would look like this I think:

mktemp() do fname, f
   write(f, "Y\n")
   seek(f, 0)
   redirect_stdin(f) do
       finish_exec()
   end
end
3 Likes

Base.stdin is a mutable variable, so you can do:

julia> try
           global old_stdin = Base.stdin
           Base.stdin = IOBuffer("Hello!\n")
           input = readline()
           println(input)
       finally
           Base.stdin = old_stdin
       end
Hello!

Sketchy, but it works.

1 Like

Just wanted to say a huge thank you everyone! Just incorporated these tips into my test suite and now the testing works great within CI and CodeCov is happy with testing user input requests in my package!

I ended up using @contradict 's solution here, but will probably try @jakobnissen 's too at some point. Both seem great!

Thanks folks! :smiley: :+1: