mirror of
https://github.com/clearml/wexpect-venv
synced 2025-01-31 10:57:07 +00:00
minimal 21
This commit is contained in:
parent
fd30f82dcb
commit
38c6970c73
@ -562,34 +562,11 @@ class Wtty:
|
||||
|
||||
logger.info(f"Fetch child's process and pid...")
|
||||
|
||||
# while True:
|
||||
# msg = win32gui.GetMessage(0, 0, 0)
|
||||
# childPid = msg[1][2]
|
||||
# Sometimes win32gui.GetMessage returns a bogus PID, so keep calling it
|
||||
# until we can successfully connect to the child or timeout is
|
||||
# reached
|
||||
# if childPid:
|
||||
# try:
|
||||
# self.__childProcess = win32api.OpenProcess(
|
||||
# win32con.PROCESS_TERMINATE | win32con.PROCESS_QUERY_INFORMATION, False, childPid)
|
||||
self.__conProcess = win32api.OpenProcess(
|
||||
win32con.PROCESS_TERMINATE | win32con.PROCESS_QUERY_INFORMATION, False, self.conpid)
|
||||
# except pywintypes.error:
|
||||
# if time.time() > ts + self.timeout:
|
||||
# break
|
||||
# else:
|
||||
# self.pid = childPid
|
||||
# break
|
||||
# time.sleep(.05)
|
||||
|
||||
logger.info(f"Child's pid: {self.pid}")
|
||||
|
||||
# if not self.__childProcess:
|
||||
# logger.info('ExceptionPexpect: The process ' + args[0] + ' could not be started.')
|
||||
# raise ExceptionPexpect ('The process ' + args[0] + ' could not be started.')
|
||||
|
||||
|
||||
|
||||
winHandle = int(win32console.GetConsoleWindow())
|
||||
|
||||
self.__switch = True
|
||||
@ -656,7 +633,7 @@ class Wtty:
|
||||
' '.join(pyargs),
|
||||
f"import sys; sys.path = {spath} + sys.path;"
|
||||
f"args = {args}; import wexpect;"
|
||||
f"wexpect.ConsoleReaderPipe(wexpect.join_args(args), {pid}, {tid}, cp={cp}, logdir={logdir}, just_init=True)"
|
||||
f"wexpect.ConsoleReaderPipe(wexpect.join_args(args), {pid}, cp={cp}, just_init=True)"
|
||||
)
|
||||
|
||||
logger.info(f'CreateProcess: {commandLine}')
|
||||
@ -673,297 +650,14 @@ class Wtty:
|
||||
win32api.TerminateProcess(self.__childProcess, 1)
|
||||
# win32api.win32process.TerminateProcess(self.__childProcess, 1)
|
||||
|
||||
class ConsoleReader: # pragma: no cover
|
||||
|
||||
def __init__(self, path, pid, tid, env = None, cp=None, logdir=None):
|
||||
self.logdir = logdir
|
||||
logger.info(f"ConsoleReader started: {self.__class__.__name__}")
|
||||
logger.info('consolepid: {}'.format(os.getpid()))
|
||||
logger.debug("OEM code page: %s" % windll.kernel32.GetOEMCP())
|
||||
logger.debug("ANSI code page: %s" % windll.kernel32.GetACP())
|
||||
logger.debug("Console output code page: %s" % windll.kernel32.GetConsoleOutputCP())
|
||||
if cp:
|
||||
logger.debug("Setting console output code page to %s" % cp)
|
||||
try:
|
||||
win32console.SetConsoleOutputCP(cp)
|
||||
except Exception as e:
|
||||
logger.info(e)
|
||||
else:
|
||||
logger.info("Console output code page: %s" % windll.kernel32.GetConsoleOutputCP())
|
||||
logger.info('Spawning %s' % path)
|
||||
try:
|
||||
try:
|
||||
consout = self.getConsoleOut()
|
||||
self.initConsole(consout)
|
||||
|
||||
si = win32process.GetStartupInfo()
|
||||
self.__childProcess, _, childPid, self.__tid = win32process.CreateProcess(None, path, None, None, False,
|
||||
0, None, None, si)
|
||||
logger.info('childPid: {} host_pid: {}'.format(childPid, pid))
|
||||
except Exception:
|
||||
logger.info(traceback.format_exc())
|
||||
time.sleep(.1)
|
||||
win32api.PostThreadMessage(int(tid), win32con.WM_USER, 0, 0)
|
||||
sys.exit()
|
||||
|
||||
time.sleep(.1)
|
||||
|
||||
win32api.PostThreadMessage(int(tid), win32con.WM_USER, childPid, 0)
|
||||
|
||||
parent = win32api.OpenProcess(win32con.PROCESS_TERMINATE | win32con.PROCESS_QUERY_INFORMATION , 0, int(pid))
|
||||
paused = False
|
||||
|
||||
while True:
|
||||
consinfo = consout.GetConsoleScreenBufferInfo()
|
||||
cursorPos = consinfo['CursorPosition']
|
||||
|
||||
if win32process.GetExitCodeProcess(parent) != win32con.STILL_ACTIVE or win32process.GetExitCodeProcess(self.__childProcess) != win32con.STILL_ACTIVE:
|
||||
time.sleep(.1)
|
||||
try:
|
||||
win32process.TerminateProcess(self.__childProcess, 0)
|
||||
except pywintypes.error as e:
|
||||
# 'Access denied' happens always? Perhaps if not
|
||||
# running as admin (or UAC enabled under Vista/7).
|
||||
# Don't log. Child process will exit regardless when
|
||||
# calling sys.exit
|
||||
if e.args[0] != winerror.ERROR_ACCESS_DENIED:
|
||||
logger.info(e)
|
||||
logger.info('Exiting...')
|
||||
sys.exit()
|
||||
|
||||
if cursorPos.Y > maxconsoleY and not paused:
|
||||
self.suspendThread()
|
||||
paused = True
|
||||
|
||||
if cursorPos.Y <= maxconsoleY and paused:
|
||||
self.resumeThread()
|
||||
paused = False
|
||||
|
||||
time.sleep(.1)
|
||||
except Exception as e:
|
||||
logger.info(e)
|
||||
time.sleep(.1)
|
||||
|
||||
|
||||
def handler(self, sig):
|
||||
logger.info(sig)
|
||||
return False
|
||||
|
||||
def getConsoleOut(self):
|
||||
consout = win32file.CreateFile('CONOUT$',
|
||||
win32con.GENERIC_READ | win32con.GENERIC_WRITE,
|
||||
win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
|
||||
None,
|
||||
win32con.OPEN_EXISTING,
|
||||
0,
|
||||
0)
|
||||
|
||||
return win32console.PyConsoleScreenBufferType(consout)
|
||||
|
||||
def initConsole(self, consout):
|
||||
rect = win32console.PySMALL_RECTType(0, 0, 79, 24)
|
||||
consout.SetConsoleWindowInfo(True, rect)
|
||||
size = win32console.PyCOORDType(80, 16000)
|
||||
consout.SetConsoleScreenBufferSize(size)
|
||||
pos = win32console.PyCOORDType(0, 0)
|
||||
# Use NUL as fill char because it displays as whitespace
|
||||
# (if we interact() with the child)
|
||||
consout.FillConsoleOutputCharacter(screenbufferfillchar, size.X * size.Y, pos)
|
||||
|
||||
def suspendThread(self):
|
||||
"""Pauses the main thread of the child process."""
|
||||
|
||||
handle = windll.kernel32.OpenThread(win32con.THREAD_SUSPEND_RESUME, 0, self.__tid)
|
||||
win32process.SuspendThread(handle)
|
||||
|
||||
def resumeThread(self):
|
||||
"""Un-pauses the main thread of the child process."""
|
||||
|
||||
handle = windll.kernel32.OpenThread(win32con.THREAD_SUSPEND_RESUME, 0, self.__tid)
|
||||
win32process.ResumeThread(handle)
|
||||
class ConsoleReader:
|
||||
pass
|
||||
|
||||
class searcher_string (object):
|
||||
|
||||
"""This is a plain string search helper for the spawn.expect_any() method.
|
||||
|
||||
Attributes:
|
||||
|
||||
eof_index - index of EOF, or -1
|
||||
timeout_index - index of TIMEOUT, or -1
|
||||
|
||||
After a successful match by the search() method the following attributes
|
||||
are available:
|
||||
|
||||
start - index into the buffer, first byte of match
|
||||
end - index into the buffer, first byte after match
|
||||
match - the matching string itself
|
||||
"""
|
||||
|
||||
def __init__(self, strings):
|
||||
|
||||
"""This creates an instance of searcher_string. This argument 'strings'
|
||||
may be a list; a sequence of strings; or the EOF or TIMEOUT types. """
|
||||
|
||||
self.eof_index = -1
|
||||
self.timeout_index = -1
|
||||
self._strings = []
|
||||
for n, s in zip(list(range(len(strings))), strings):
|
||||
if s is EOF:
|
||||
self.eof_index = n
|
||||
continue
|
||||
if s is TIMEOUT:
|
||||
self.timeout_index = n
|
||||
continue
|
||||
self._strings.append((n, s))
|
||||
|
||||
def __str__(self):
|
||||
|
||||
"""This returns a human-readable string that represents the state of
|
||||
the object."""
|
||||
|
||||
ss = [ (ns[0],' %d: "%s"' % ns) for ns in self._strings ]
|
||||
ss.append((-1,'searcher_string:'))
|
||||
if self.eof_index >= 0:
|
||||
ss.append ((self.eof_index,' %d: EOF' % self.eof_index))
|
||||
if self.timeout_index >= 0:
|
||||
ss.append ((self.timeout_index,' %d: TIMEOUT' % self.timeout_index))
|
||||
ss.sort()
|
||||
ss = list(zip(*ss))[1]
|
||||
return '\n'.join(ss)
|
||||
|
||||
def search(self, buffer, freshlen, searchwindowsize=None):
|
||||
|
||||
"""This searches 'buffer' for the first occurence of one of the search
|
||||
strings. 'freshlen' must indicate the number of bytes at the end of
|
||||
'buffer' which have not been searched before. It helps to avoid
|
||||
searching the same, possibly big, buffer over and over again.
|
||||
|
||||
See class spawn for the 'searchwindowsize' argument.
|
||||
|
||||
If there is a match this returns the index of that string, and sets
|
||||
'start', 'end' and 'match'. Otherwise, this returns -1. """
|
||||
|
||||
absurd_match = len(buffer)
|
||||
first_match = absurd_match
|
||||
|
||||
# 'freshlen' helps a lot here. Further optimizations could
|
||||
# possibly include:
|
||||
#
|
||||
# using something like the Boyer-Moore Fast String Searching
|
||||
# Algorithm; pre-compiling the search through a list of
|
||||
# strings into something that can scan the input once to
|
||||
# search for all N strings; realize that if we search for
|
||||
# ['bar', 'baz'] and the input is '...foo' we need not bother
|
||||
# rescanning until we've read three more bytes.
|
||||
#
|
||||
# Sadly, I don't know enough about this interesting topic. /grahn
|
||||
|
||||
for index, s in self._strings:
|
||||
if searchwindowsize is None:
|
||||
# the match, if any, can only be in the fresh data,
|
||||
# or at the very end of the old data
|
||||
offset = -(freshlen+len(s))
|
||||
else:
|
||||
# better obey searchwindowsize
|
||||
offset = -searchwindowsize
|
||||
n = buffer.find(s, offset)
|
||||
if n >= 0 and n < first_match:
|
||||
first_match = n
|
||||
best_index, best_match = index, s
|
||||
if first_match == absurd_match:
|
||||
return -1
|
||||
self.match = best_match
|
||||
self.start = first_match
|
||||
self.end = self.start + len(self.match)
|
||||
return best_index
|
||||
pass
|
||||
|
||||
class searcher_re (object):
|
||||
|
||||
"""This is regular expression string search helper for the
|
||||
spawn.expect_any() method.
|
||||
|
||||
Attributes:
|
||||
|
||||
eof_index - index of EOF, or -1
|
||||
timeout_index - index of TIMEOUT, or -1
|
||||
|
||||
After a successful match by the search() method the following attributes
|
||||
are available:
|
||||
|
||||
start - index into the buffer, first byte of match
|
||||
end - index into the buffer, first byte after match
|
||||
match - the re.match object returned by a succesful re.search
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, patterns):
|
||||
|
||||
"""This creates an instance that searches for 'patterns' Where
|
||||
'patterns' may be a list or other sequence of compiled regular
|
||||
expressions, or the EOF or TIMEOUT types."""
|
||||
|
||||
self.eof_index = -1
|
||||
self.timeout_index = -1
|
||||
self._searches = []
|
||||
for n, s in zip(list(range(len(patterns))), patterns):
|
||||
if s is EOF:
|
||||
self.eof_index = n
|
||||
continue
|
||||
if s is TIMEOUT:
|
||||
self.timeout_index = n
|
||||
continue
|
||||
self._searches.append((n, s))
|
||||
|
||||
def __str__(self):
|
||||
|
||||
"""This returns a human-readable string that represents the state of
|
||||
the object."""
|
||||
|
||||
ss = [ (n,' %d: re.compile("%s")' % (n,str(s.pattern))) for n,s in self._searches]
|
||||
ss.append((-1,'searcher_re:'))
|
||||
if self.eof_index >= 0:
|
||||
ss.append ((self.eof_index,' %d: EOF' % self.eof_index))
|
||||
if self.timeout_index >= 0:
|
||||
ss.append ((self.timeout_index,' %d: TIMEOUT' % self.timeout_index))
|
||||
ss.sort()
|
||||
ss = list(zip(*ss))[1]
|
||||
return '\n'.join(ss)
|
||||
|
||||
def search(self, buffer, freshlen, searchwindowsize=None):
|
||||
|
||||
"""This searches 'buffer' for the first occurence of one of the regular
|
||||
expressions. 'freshlen' must indicate the number of bytes at the end of
|
||||
'buffer' which have not been searched before.
|
||||
|
||||
See class spawn for the 'searchwindowsize' argument.
|
||||
|
||||
If there is a match this returns the index of that string, and sets
|
||||
'start', 'end' and 'match'. Otherwise, returns -1."""
|
||||
|
||||
absurd_match = len(buffer)
|
||||
first_match = absurd_match
|
||||
# 'freshlen' doesn't help here -- we cannot predict the
|
||||
# length of a match, and the re module provides no help.
|
||||
if searchwindowsize is None:
|
||||
searchstart = 0
|
||||
else:
|
||||
searchstart = max(0, len(buffer)-searchwindowsize)
|
||||
for index, s in self._searches:
|
||||
match = s.search(buffer, searchstart)
|
||||
if match is None:
|
||||
continue
|
||||
n = match.start()
|
||||
if n < first_match:
|
||||
first_match = n
|
||||
the_match = match
|
||||
best_index = index
|
||||
if first_match == absurd_match:
|
||||
return -1
|
||||
self.start = first_match
|
||||
self.match = the_match
|
||||
self.end = self.match.end()
|
||||
return best_index
|
||||
|
||||
pass
|
||||
|
||||
def join_args(args):
|
||||
"""Joins arguments into a command line. It quotes all arguments that contain
|
||||
|
Loading…
Reference in New Issue
Block a user