Skip to content
52 changes: 35 additions & 17 deletions Lib/test/test_msvcrt.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import os
import subprocess
import sys
import unittest
from textwrap import dedent

from test.support import os_helper
from test.support.os_helper import TESTFN, TESTFN_ASCII
Expand All @@ -11,8 +13,6 @@
import _winapi
import msvcrt;

from _testconsole import write_input, flush_console_input_buffer


class TestFileOperations(unittest.TestCase):
def test_locking(self):
Expand Down Expand Up @@ -61,34 +61,52 @@ def test_get_osfhandle(self):


class TestConsoleIO(unittest.TestCase):
def run_in_separated_process(self, code):
cmd = [sys.executable, '-c', code]
subprocess.run(cmd, check=True, capture_output=True,
creationflags=subprocess.CREATE_NEW_CONSOLE)


def test_kbhit(self):
h = msvcrt.get_osfhandle(sys.stdin.fileno())
flush_console_input_buffer(h)
self.assertEqual(msvcrt.kbhit(), 0)
# Run test in a seprated process to avoid stdin conflicts.
# See: gh-110147
code = dedent('''
import msvcrt
assert msvcrt.kbhit() == 0
''')
self.run_in_separated_process(code)

def test_getch(self):
msvcrt.ungetch(b'c')
self.assertEqual(msvcrt.getch(), b'c')

def test_getwch(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_getwche() looks like a copy/paste, you just replaced getwch() with getwche(). Can you rename the function to check_getwch(func), so test_getwch() would call check_getwch("getwch") and test_getwch() would call check_getwch("getwche")?

with open('CONIN$', 'rb', buffering=0) as stdin:
h = msvcrt.get_osfhandle(stdin.fileno())
flush_console_input_buffer(h)

write_input(stdin, c_encoded)
self.assertEqual(msvcrt.getwch(), c)
# Run test in a seprated process to avoid stdin conflicts.
# See: gh-110147
code = dedent(f'''
import msvcrt
from _testconsole import write_input
with open("CONIN$", "rb", buffering=0) as stdin:
write_input(stdin, {repr(c_encoded)})
assert msvcrt.getwch() == "{c}"
''')
self.run_in_separated_process(code)

def test_getche(self):
msvcrt.ungetch(b'c')
self.assertEqual(msvcrt.getche(), b'c')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be safer to run this test in a subprocess with a new console, no?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it would, but it's annoying. We might want to put it behind a resource marker, as the new console will pop up new GUI when it runs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would CREATE_NO_WINDOW flag prevent popups?

The process is a console application that is being run without a console window. Therefore, the console handle for the application is not set. This flag is ignored if the application is not a console application, or if it is used with either CREATE_NEW_CONSOLE or DETACHED_PROCESS.

says: https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems it should work, I'll try it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the console handle for the application is not set

Means no console and the test is irrelevant.

ignored if ... used with either CREATE_NEW_CONSOLE

This is the flag that you'd use to make the test more reliable, and it's incompatible with CREATE_NO_WINDOW.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, and added the @requires_resource('gui') marker to avoid the console pop-up.

For the tests test_getche and test_getch, they uses msvcrt.ungetch instead of the magic write_input, I think this stuff is stable enough, this test file is added and disabled days ago, so I fixed it serveral times, and never saw them failed once. So I think we should keep it (test_getche / test_getch) as current.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the tests test_getche and test_getch, they uses msvcrt.ungetch instead of the magic write_input, I think this stuff is stable enough

Hum, ok. Let's see if CI will agree with you :-) I'm fine with keeping them in the same process for now.


def test_getwche(self):
with open('CONIN$', 'rb', buffering=0) as stdin:
h = msvcrt.get_osfhandle(stdin.fileno())
flush_console_input_buffer(h)

write_input(stdin, c_encoded)
self.assertEqual(msvcrt.getwche(), c)
# Run test in a seprated process to avoid stdin conflicts.
# See: gh-110147
code = dedent(f'''
import msvcrt
from _testconsole import write_input
with open("CONIN$", "rb", buffering=0) as stdin:
write_input(stdin, {repr(c_encoded)})
assert msvcrt.getwche() == "{c}"
''')
self.run_in_separated_process(code)

def test_putch(self):
msvcrt.putch(b'c')
Expand Down