Writing to stdin from test

Hi, I’m trying to understand why writing to stdin from a test behave weirdly. It’s like it is not writing to the program’s stdin, somehow.

Here is a simple program, an interactive prompt, read to stdin, when user hits Enter, \n is written, stdin.takeDelimiter catch it and program returns.

Running from binary or zig build run, it works as expected.

const std = @import("std");
const print = std.debug.print;

pub fn press_enter(io: std.Io) !void {
    var buf: [1024]u8 = undefined;
    var reader = std.Io.File.stdin().reader(io, &buf);
    const stdin = &reader.interface;

    print("Press enter ", .{});
    const input = try stdin.takeDelimiter('\n');
    _ = input;
    print("\n", .{});
}

test "press_enter" {
    const io = std.testing.io;
    const handle = try std.Thread.spawn(.{}, press_enter, .{io});
    defer handle.join();
    try io.sleep(std.Io.Duration.fromMilliseconds(10), .awake);
    const stdin = std.Io.File.stdin();
    try stdin.writeStreamingAll(io, "123\n");
}

But things get weird when running from the test:

$ zig test src/main.zig
Press enter 123
_ <- cursor position

It hangs at cursor position, it’s like stdin.writeStreamingAll is writing but not to the “right” stdin, or perhaps something from the test runner is interfering?

What’s going on here?

Hi @cornhusk, Welcome to ziggit :slight_smile:

stdin is used from reading only; you cannot write to stdin.
Also stdout and stderr are used for writing only.
Finally these are used by the builder and are not available when running the tester under zig build.

What are you trying to accomplish with your test?

Hi thx.

Basically I’m trying to test interactive prompts, by simulating user typing text and pressing Enter.

But not sure it is possible programmatically within test code. Maybe it would be better to handle that with a shell script and classic piping.

EDIT: that works

zig test src/main.zig < <(sleep 1 && echo '')

Your code should just be taking reader/writers, instead of relying on using a file at all, not to mention specific hard coded files.

That would allow you to use different reader/writers in your tests:

test "press_enter" {
    var input = std.Io.Reader.fixed("123\n");
    try press_enter(&input);
}

Also, spawning a thread just wait for enter is absurdly inefficient.
When reading stdin, the terminal will, by default, block until the user presses enter. That is to say, you get the behaviour you want for free.
might be different when using a cmd prompt on windows(?), but there are terminals and wsl for windows which have the behaviour I described.

Yes makes sens. Actually I started doing this. Thx.