Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Insufficient test coverage for Stdin::Read on Windows "console mode" #93055

Open
Patrick-Poitras opened this issue Jan 19, 2022 · 3 comments
Open
Labels
A-code-coverage Area: Source-based code coverage (-Cinstrument-coverage) A-io Area: `std::io`, `std::fs`, `std::net` and `std::path` C-bug Category: This is a bug. E-hard Call for participation: Hard difficulty. Experience needed to fix: A lot. E-needs-test Call for participation: An issue has been fixed and does not reproduce, but no test has been added. O-windows Operating system: Windows T-libs Relevant to the library team, which will review and decide on the PR/issue.

Comments

@Patrick-Poitras
Copy link
Contributor

While implementing #91754, we came across a lack of test coverage for Stdin::Read. The function has two code paths, one of which is triggered when input is piped in, and the other is triggered when the user is calling the executable from the console. This "console mode" code path is, as far as I'm able to tell, entirely ignored by automated tests and the CI.

I was unable to get anything near automated to actually use the console mode. However, manually feeding it inputs is easy, and has been the main method of testing that code path since the path's creation. This is sub-optimal, and should be remedied.

We have a couple approaches that could be attempted, but I seek larger input from the community, since this is not a trivial problem.

  • Finding a way to mock the console mode in Windows
  • Have a toggleable flag that would allow for the console mode path to be diverted during testing. (This one I could implement, but I'd need to make sure it respects policy)
  • Not doing anything. As of now, this approach seems to not have caused very many issues.
@Patrick-Poitras
Copy link
Contributor Author

Patrick-Poitras commented Jan 19, 2022

@rustbot label +A-code-coverage +A-io +O-windows +C-bug +E-needs-test +E-hard +T-libs

@rustbot rustbot added A-code-coverage Area: Source-based code coverage (-Cinstrument-coverage) A-io Area: `std::io`, `std::fs`, `std::net` and `std::path` C-bug Category: This is a bug. E-needs-test Call for participation: An issue has been fixed and does not reproduce, but no test has been added. O-windows Operating system: Windows labels Jan 19, 2022
@Patrick-Poitras
Copy link
Contributor Author

Patrick-Poitras commented Jan 19, 2022

Relevant code bits: (in library/std/src/sys/windows/stdio.rs)

fn is_console(handle: c::HANDLE) -> bool {
    // `GetConsoleMode` will return false (0) if this is a pipe (we don't care about the reported
    // mode). This will only detect Windows Console, not other terminals connected to a pipe like
    // MSYS. Which is exactly what we need, as only Windows Console needs a conversion to UTF-16.
    let mut mode = 0;
    unsafe { c::GetConsoleMode(handle, &mut mode) != 0 }
}
impl io::Read for Stdin {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let handle = get_handle(c::STD_INPUT_HANDLE)?;
        if !is_console(handle) {
            unsafe {
                let handle = Handle::from_raw_handle(handle);
                let ret = handle.read(buf);
                handle.into_raw_handle(); // Don't close the handle
                return ret;
            }
        }
        // If there are bytes in the incomplete utf-8, start with those.
        // (No-op if there is nothing in the buffer.)
        let mut bytes_copied = self.incomplete_utf8.read(buf);

        if bytes_copied == buf.len() {
            return Ok(bytes_copied);
        } else if buf.len() - bytes_copied < 4 {
            // Not enough space to get a UTF-8 byte. We will use the incomplete UTF8.
            let mut utf16_buf = [0u16; 1];
            // Read one u16 character.
            let read = read_u16s_fixup_surrogates(handle, &mut utf16_buf, 1, &mut self.surrogate)?;
            // Read bytes, using the (now-empty) self.incomplete_utf8 as extra space.
            let read_bytes = utf16_to_utf8(&utf16_buf[..read], &mut self.incomplete_utf8.bytes)?;

            // Read in the bytes from incomplete_utf8 until the buffer is full.
            self.incomplete_utf8.len = read_bytes as u8;
            // No-op if no bytes.
            bytes_copied += self.incomplete_utf8.read(&mut buf[bytes_copied..]);
            Ok(bytes_copied)
        } else {
            let mut utf16_buf = [0u16; MAX_BUFFER_SIZE / 2];
            // In the worst case, a UTF-8 string can take 3 bytes for every `u16` of a UTF-16. So
            // we can read at most a third of `buf.len()` chars and uphold the guarantee no data gets
            // lost.
            let amount = cmp::min(buf.len() / 3, utf16_buf.len());
            let read =
                read_u16s_fixup_surrogates(handle, &mut utf16_buf, amount, &mut self.surrogate)?;

            match utf16_to_utf8(&utf16_buf[..read], buf) {
                Ok(value) => return Ok(bytes_copied + value),
                Err(e) => return Err(e),
            }
        }
    }
}

@rustbot rustbot added E-hard Call for participation: Hard difficulty. Experience needed to fix: A lot. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Jan 19, 2022
@ChrisDenton
Copy link
Member

ChrisDenton commented Jul 2, 2023

The way forward here would likely be to create a pseudoconsole. Because this involves spawning a process, this will probably need to be a UI test (so in tests/ui/std or maybe tests/ui/process).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-code-coverage Area: Source-based code coverage (-Cinstrument-coverage) A-io Area: `std::io`, `std::fs`, `std::net` and `std::path` C-bug Category: This is a bug. E-hard Call for participation: Hard difficulty. Experience needed to fix: A lot. E-needs-test Call for participation: An issue has been fixed and does not reproduce, but no test has been added. O-windows Operating system: Windows T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

3 participants