adb: win32: test for Unicode console output

Unit test for adb on Windows writing Unicode to an actual console
Window, as opposed to a pipe or file.

Test: Ran test on Ubuntu and Windows 10, tested version of adb with
incorrect Unicode handling and verified that test failed

Change-Id: Ibdda46d0fee83004537bcbb48a5c2fd6d3e1d593
Signed-off-by: Spencer Low <CompareAndSwap@gmail.com>
This commit is contained in:
Spencer Low 2018-08-11 00:16:16 -07:00
parent 6d5b931466
commit 69d8c39502
1 changed files with 200 additions and 0 deletions

View File

@ -1303,6 +1303,206 @@ class DeviceOfflineTest(DeviceTest):
self.assertEqual(stdout, "\0" * length + "foo\n")
if sys.platform == "win32":
# From https://stackoverflow.com/a/38749458
import os
import contextlib
import msvcrt
import ctypes
from ctypes import wintypes
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
GENERIC_READ = 0x80000000
GENERIC_WRITE = 0x40000000
FILE_SHARE_READ = 1
FILE_SHARE_WRITE = 2
CONSOLE_TEXTMODE_BUFFER = 1
INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value
STD_OUTPUT_HANDLE = wintypes.DWORD(-11)
STD_ERROR_HANDLE = wintypes.DWORD(-12)
def _check_zero(result, func, args):
if not result:
raise ctypes.WinError(ctypes.get_last_error())
return args
def _check_invalid(result, func, args):
if result == INVALID_HANDLE_VALUE:
raise ctypes.WinError(ctypes.get_last_error())
return args
if not hasattr(wintypes, 'LPDWORD'): # Python 2
wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD)
wintypes.PSMALL_RECT = ctypes.POINTER(wintypes.SMALL_RECT)
class COORD(ctypes.Structure):
_fields_ = (('X', wintypes.SHORT),
('Y', wintypes.SHORT))
class CONSOLE_SCREEN_BUFFER_INFOEX(ctypes.Structure):
_fields_ = (('cbSize', wintypes.ULONG),
('dwSize', COORD),
('dwCursorPosition', COORD),
('wAttributes', wintypes.WORD),
('srWindow', wintypes.SMALL_RECT),
('dwMaximumWindowSize', COORD),
('wPopupAttributes', wintypes.WORD),
('bFullscreenSupported', wintypes.BOOL),
('ColorTable', wintypes.DWORD * 16))
def __init__(self, *args, **kwds):
super(CONSOLE_SCREEN_BUFFER_INFOEX, self).__init__(
*args, **kwds)
self.cbSize = ctypes.sizeof(self)
PCONSOLE_SCREEN_BUFFER_INFOEX = ctypes.POINTER(
CONSOLE_SCREEN_BUFFER_INFOEX)
LPSECURITY_ATTRIBUTES = wintypes.LPVOID
kernel32.GetStdHandle.errcheck = _check_invalid
kernel32.GetStdHandle.restype = wintypes.HANDLE
kernel32.GetStdHandle.argtypes = (
wintypes.DWORD,) # _In_ nStdHandle
kernel32.CreateConsoleScreenBuffer.errcheck = _check_invalid
kernel32.CreateConsoleScreenBuffer.restype = wintypes.HANDLE
kernel32.CreateConsoleScreenBuffer.argtypes = (
wintypes.DWORD, # _In_ dwDesiredAccess
wintypes.DWORD, # _In_ dwShareMode
LPSECURITY_ATTRIBUTES, # _In_opt_ lpSecurityAttributes
wintypes.DWORD, # _In_ dwFlags
wintypes.LPVOID) # _Reserved_ lpScreenBufferData
kernel32.GetConsoleScreenBufferInfoEx.errcheck = _check_zero
kernel32.GetConsoleScreenBufferInfoEx.argtypes = (
wintypes.HANDLE, # _In_ hConsoleOutput
PCONSOLE_SCREEN_BUFFER_INFOEX) # _Out_ lpConsoleScreenBufferInfo
kernel32.SetConsoleScreenBufferInfoEx.errcheck = _check_zero
kernel32.SetConsoleScreenBufferInfoEx.argtypes = (
wintypes.HANDLE, # _In_ hConsoleOutput
PCONSOLE_SCREEN_BUFFER_INFOEX) # _In_ lpConsoleScreenBufferInfo
kernel32.SetConsoleWindowInfo.errcheck = _check_zero
kernel32.SetConsoleWindowInfo.argtypes = (
wintypes.HANDLE, # _In_ hConsoleOutput
wintypes.BOOL, # _In_ bAbsolute
wintypes.PSMALL_RECT) # _In_ lpConsoleWindow
kernel32.FillConsoleOutputCharacterW.errcheck = _check_zero
kernel32.FillConsoleOutputCharacterW.argtypes = (
wintypes.HANDLE, # _In_ hConsoleOutput
wintypes.WCHAR, # _In_ cCharacter
wintypes.DWORD, # _In_ nLength
COORD, # _In_ dwWriteCoord
wintypes.LPDWORD) # _Out_ lpNumberOfCharsWritten
kernel32.ReadConsoleOutputCharacterW.errcheck = _check_zero
kernel32.ReadConsoleOutputCharacterW.argtypes = (
wintypes.HANDLE, # _In_ hConsoleOutput
wintypes.LPWSTR, # _Out_ lpCharacter
wintypes.DWORD, # _In_ nLength
COORD, # _In_ dwReadCoord
wintypes.LPDWORD) # _Out_ lpNumberOfCharsRead
@contextlib.contextmanager
def allocate_console():
allocated = kernel32.AllocConsole()
try:
yield allocated
finally:
if allocated:
kernel32.FreeConsole()
@contextlib.contextmanager
def console_screen(ncols=None, nrows=None):
info = CONSOLE_SCREEN_BUFFER_INFOEX()
new_info = CONSOLE_SCREEN_BUFFER_INFOEX()
nwritten = (wintypes.DWORD * 1)()
hStdOut = kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
kernel32.GetConsoleScreenBufferInfoEx(
hStdOut, ctypes.byref(info))
if ncols is None:
ncols = info.dwSize.X
if nrows is None:
nrows = info.dwSize.Y
elif nrows > 9999:
raise ValueError('nrows must be 9999 or less')
fd_screen = None
hScreen = kernel32.CreateConsoleScreenBuffer(
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
None, CONSOLE_TEXTMODE_BUFFER, None)
try:
fd_screen = msvcrt.open_osfhandle(
hScreen, os.O_RDWR | os.O_BINARY)
kernel32.GetConsoleScreenBufferInfoEx(
hScreen, ctypes.byref(new_info))
new_info.dwSize = COORD(ncols, nrows)
new_info.srWindow = wintypes.SMALL_RECT(
Left=0, Top=0, Right=(ncols - 1),
Bottom=(info.srWindow.Bottom - info.srWindow.Top))
kernel32.SetConsoleScreenBufferInfoEx(
hScreen, ctypes.byref(new_info))
kernel32.SetConsoleWindowInfo(hScreen, True,
ctypes.byref(new_info.srWindow))
kernel32.FillConsoleOutputCharacterW(
hScreen, u'\0', ncols * nrows, COORD(0,0), nwritten)
kernel32.SetConsoleActiveScreenBuffer(hScreen)
try:
yield fd_screen
finally:
kernel32.SetConsoleScreenBufferInfoEx(
hStdOut, ctypes.byref(info))
kernel32.SetConsoleWindowInfo(hStdOut, True,
ctypes.byref(info.srWindow))
kernel32.SetConsoleActiveScreenBuffer(hStdOut)
finally:
if fd_screen is not None:
os.close(fd_screen)
else:
kernel32.CloseHandle(hScreen)
def read_screen(fd):
hScreen = msvcrt.get_osfhandle(fd)
csbi = CONSOLE_SCREEN_BUFFER_INFOEX()
kernel32.GetConsoleScreenBufferInfoEx(
hScreen, ctypes.byref(csbi))
ncols = csbi.dwSize.X
pos = csbi.dwCursorPosition
length = ncols * pos.Y + pos.X + 1
buf = (ctypes.c_wchar * length)()
n = (wintypes.DWORD * 1)()
kernel32.ReadConsoleOutputCharacterW(
hScreen, buf, length, COORD(0,0), n)
lines = [buf[i:i+ncols].rstrip(u'\0')
for i in range(0, n[0], ncols)]
return u'\n'.join(lines)
@unittest.skipUnless(sys.platform == "win32", "requires Windows")
class WindowsConsoleTest(DeviceTest):
def test_unicode_output(self):
"""Test Unicode command line parameters and Unicode console window output.
Bug: https://issuetracker.google.com/issues/111972753
"""
# If we don't have a console window, allocate one. This isn't necessary if we're already
# being run from a console window, which is typical.
with allocate_console() as allocated_console:
# Create a temporary console buffer and switch to it. We could also pass a parameter of
# ncols=len(unicode_string), but it causes the window to flash as it is resized and
# likely unnecessary given the typical console window size.
with console_screen(nrows=1000) as screen:
unicode_string = u'로보카 폴리'
# Run adb and allow it to detect that stdout is a console, not a pipe, by using
# device.shell_popen() which does not use a pipe, unlike device.shell().
process = self.device.shell_popen(['echo', '"' + unicode_string + '"'])
process.wait()
# Read what was written by adb to the temporary console buffer.
console_output = read_screen(screen)
self.assertEqual(unicode_string, console_output)
def main():
random.seed(0)
if len(adb.get_devices()) > 0: