Uploaded Test files

This commit is contained in:
Batuhan Berk Başoğlu 2020-11-12 11:05:57 -05:00
parent f584ad9d97
commit 2e81cb7d99
16627 changed files with 2065359 additions and 102444 deletions

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
"""
Pywinpty
========
This package provides a Cython wrapper around winpty C++ library.
"""
# yapf: disable
# Local imports
from .ptyprocess import PtyProcess
from .winpty_wrapper import PTY
PTY
PtyProcess
VERSION_INFO = (0, 5, 7)
__version__ = '.'.join(map(str, VERSION_INFO))

View file

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
"""Winpty headers."""

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,357 @@
# -*- coding: utf-8 -*-
# Standard library imports
import codecs
import os
import shlex
import signal
import socket
import subprocess
import threading
import time
try:
from shutil import which
except ImportError:
from backports.shutil_which import which
# Local imports
from .winpty_wrapper import PTY, PY2
class PtyProcess(object):
"""This class represents a process running in a pseudoterminal.
The main constructor is the :meth:`spawn` classmethod.
"""
def __init__(self, pty):
assert isinstance(pty, PTY)
self.pty = pty
self.pid = pty.pid
self.read_blocking = bool(os.environ.get('PYWINPTY_BLOCK', 1))
self.closed = False
self.flag_eof = False
self.decoder = codecs.getincrementaldecoder('utf-8')(errors='strict')
# Used by terminate() to give kernel time to update process status.
# Time in seconds.
self.delayafterterminate = 0.1
# Used by close() to give kernel time to update process status.
# Time in seconds.
self.delayafterclose = 0.1
# Set up our file reader sockets.
self._server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._server.bind(("127.0.0.1", 0))
address = self._server.getsockname()
self._server.listen(1)
# Read from the pty in a thread.
self._thread = threading.Thread(target=_read_in_thread,
args=(address, self.pty, self.read_blocking))
self._thread.setDaemon(True)
self._thread.start()
self.fileobj, _ = self._server.accept()
self.fd = self.fileobj.fileno()
@classmethod
def spawn(cls, argv, cwd=None, env=None, dimensions=(24, 80)):
"""Start the given command in a child process in a pseudo terminal.
This does all the setting up the pty, and returns an instance of
PtyProcess.
Dimensions of the psuedoterminal used for the subprocess can be
specified as a tuple (rows, cols), or the default (24, 80) will be
used.
"""
if isinstance(argv, str):
argv = shlex.split(argv, posix=False)
if not isinstance(argv, (list, tuple)):
raise TypeError("Expected a list or tuple for argv, got %r" % argv)
# Shallow copy of argv so we can modify it
argv = argv[:]
command = argv[0]
env = env or os.environ
path = env.get('PATH', os.defpath)
command_with_path = which(command, path=path)
if command_with_path is None:
raise FileNotFoundError(
'The command was not found or was not ' +
'executable: %s.' % command
)
command = command_with_path
argv[0] = command
cmdline = ' ' + subprocess.list2cmdline(argv[1:])
cwd = cwd or os.getcwd()
proc = PTY(dimensions[1], dimensions[0])
# Create the environemnt string.
envStrs = []
for (key, value) in env.items():
envStrs.append('%s=%s' % (key, value))
env = '\0'.join(envStrs) + '\0'
if PY2:
command = _unicode(command)
cwd = _unicode(cwd)
cmdline = _unicode(cmdline)
env = _unicode(env)
if len(argv) == 1:
proc.spawn(command, cwd=cwd, env=env)
else:
proc.spawn(command, cwd=cwd, env=env, cmdline=cmdline)
inst = cls(proc)
inst._winsize = dimensions
# Set some informational attributes
inst.argv = argv
if env is not None:
inst.env = env
if cwd is not None:
inst.launch_dir = cwd
return inst
@property
def exitstatus(self):
"""The exit status of the process.
"""
return self.pty.exitstatus
def fileno(self):
"""This returns the file descriptor of the pty for the child.
"""
return self.fd
def close(self, force=False):
"""This closes the connection with the child application. Note that
calling close() more than once is valid. This emulates standard Python
behavior with files. Set force to True if you want to make sure that
the child is terminated (SIGKILL is sent if the child ignores
SIGINT)."""
if not self.closed:
self.pty.close()
self.fileobj.close()
self._server.close()
# Give kernel time to update process status.
time.sleep(self.delayafterclose)
if self.isalive():
if not self.terminate(force):
raise IOError('Could not terminate the child.')
self.fd = -1
self.closed = True
del self.pty
self.pty = None
def __del__(self):
"""This makes sure that no system resources are left open. Python only
garbage collects Python objects. OS file descriptors are not Python
objects, so they must be handled explicitly. If the child file
descriptor was opened outside of this class (passed to the constructor)
then this does not close it.
"""
# It is possible for __del__ methods to execute during the
# teardown of the Python VM itself. Thus self.close() may
# trigger an exception because os.close may be None.
try:
self.close()
except Exception:
pass
def flush(self):
"""This does nothing. It is here to support the interface for a
File-like object. """
pass
def isatty(self):
"""This returns True if the file descriptor is open and connected to a
tty(-like) device, else False."""
return self.isalive()
def read(self, size=1024):
"""Read and return at most ``size`` characters from the pty.
Can block if there is nothing to read. Raises :exc:`EOFError` if the
terminal was closed.
"""
data = self.fileobj.recv(size)
if not data:
self.flag_eof = True
raise EOFError('Pty is closed')
return self.decoder.decode(data, final=False)
def readline(self):
"""Read one line from the pseudoterminal as bytes.
Can block if there is nothing to read. Raises :exc:`EOFError` if the
terminal was closed.
"""
buf = []
while 1:
try:
ch = self.read(1)
except EOFError:
return ''.join(buf)
buf.append(ch)
if ch == '\n':
return ''.join(buf)
def write(self, s):
"""Write the string ``s`` to the pseudoterminal.
Returns the number of bytes written.
"""
if not self.isalive():
raise EOFError('Pty is closed')
if PY2:
s = _unicode(s)
success, nbytes = self.pty.write(s)
if not success:
raise IOError('Write failed')
return nbytes
def terminate(self, force=False):
"""This forces a child process to terminate."""
if not self.isalive():
return True
self.kill(signal.SIGINT)
time.sleep(self.delayafterterminate)
if not self.isalive():
return True
if force:
self.kill(signal.SIGKILL)
time.sleep(self.delayafterterminate)
if not self.isalive():
return True
else:
return False
def wait(self):
"""This waits until the child exits. This is a blocking call. This will
not read any data from the child.
"""
while self.isalive():
time.sleep(0.1)
return self.exitstatus
def isalive(self):
"""This tests if the child process is running or not. This is
non-blocking. If the child was terminated then this will read the
exitstatus or signalstatus of the child. This returns True if the child
process appears to be running or False if not.
"""
return self.pty and self.pty.isalive()
def kill(self, sig=None):
"""Kill the process with the given signal.
"""
os.kill(self.pid, sig)
def sendcontrol(self, char):
'''Helper method that wraps send() with mnemonic access for sending control
character to the child (such as Ctrl-C or Ctrl-D). For example, to send
Ctrl-G (ASCII 7, bell, '\a')::
child.sendcontrol('g')
See also, sendintr() and sendeof().
'''
char = char.lower()
a = ord(char)
if 97 <= a <= 122:
a = a - ord('a') + 1
byte = bytes([a])
return self.pty.write(byte.decode('utf-8')), byte
d = {'@': 0, '`': 0,
'[': 27, '{': 27,
'\\': 28, '|': 28,
']': 29, '}': 29,
'^': 30, '~': 30,
'_': 31,
'?': 127}
if char not in d:
return 0, b''
byte = bytes([d[char]])
return self.pty.write(byte.decode('utf-8')), byte
def sendeof(self):
"""This sends an EOF to the child. This sends a character which causes
the pending parent output buffer to be sent to the waiting child
program without waiting for end-of-line. If it is the first character
of the line, the read() in the user program returns 0, which signifies
end-of-file. This means to work as expected a sendeof() has to be
called at the beginning of a line. This method does not send a newline.
It is the responsibility of the caller to ensure the eof is sent at the
beginning of a line."""
# Send control character 4 (Ctrl-D)
self.pty.write('\x04'), '\x04'
def sendintr(self):
"""This sends a SIGINT to the child. It does not require
the SIGINT to be the first character on a line. """
# Send control character 3 (Ctrl-C)
self.pty.write('\x03'), '\x03'
def eof(self):
"""This returns True if the EOF exception was ever raised.
"""
return self.flag_eof
def getwinsize(self):
"""Return the window size of the pseudoterminal as a tuple (rows, cols).
"""
return self._winsize
def setwinsize(self, rows, cols):
"""Set the terminal window size of the child tty.
"""
self._winsize = (rows, cols)
self.pty.set_size(cols, rows)
def _read_in_thread(address, pty, blocking):
"""Read data from the pty in a thread.
"""
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(address)
while 1:
data = pty.read(4096, blocking=blocking)
if not data and not pty.isalive():
while not data and not pty.iseof():
data += pty.read(4096, blocking=blocking)
if not data:
try:
client.send(b'')
except socket.error:
pass
break
try:
client.send(data)
except socket.error:
break
client.close()
def _unicode(s):
"""Ensure that a string is Unicode on Python 2.
"""
if isinstance(s, unicode): # noqa E891
return s
return s.decode('utf-8')

View file

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
"""winpty module tests."""

View file

@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
"""Cywinpty tests."""
# yapf: disable
# Third party imports
from winpty.cywinpty import Agent
from winpty.winpty_wrapper import PY2
from winpty.ptyprocess import which
import pytest
# yapf: disable
CMD = which('cmd')
if PY2:
CMD = unicode(CMD) # noqa
@pytest.fixture(scope='module')
def agent_fixture():
def _agent_factory(cols, rows):
agent = Agent(cols, rows)
return agent
return _agent_factory
def test_agent_spawn(agent_fixture):
agent = agent_fixture(80, 25)
succ = agent.spawn(CMD)
assert succ
del agent
def test_agent_spawn_fail(agent_fixture):
agent = agent_fixture(80, 25)
try:
agent.spawn(CMD)
except RuntimeError:
pass
def test_agent_spawn_size_fail(agent_fixture):
try:
agent_fixture(80, -25)
except RuntimeError:
pass
def test_agent_resize(agent_fixture):
agent = agent_fixture(80, 25)
agent.set_size(80, 70)
del agent
def test_agent_resize_fail(agent_fixture):
agent = agent_fixture(80, 25)
try:
agent.set_size(-80, 70)
except RuntimeError:
pass

View file

@ -0,0 +1,150 @@
# -*- coding: utf-8 -*-
"""winpty wrapper tests."""
# yapf: disable
# Standard library imports
import os
import signal
import sys
# Third party imports
from flaky import flaky
from winpty.ptyprocess import PtyProcess
import pytest
# yapf: enable
@pytest.fixture(scope='module')
def pty_fixture():
def _pty_factory(cmd=None, env=None):
cmd = cmd or 'cmd'
return PtyProcess.spawn(cmd, env=env)
return _pty_factory
@flaky(max_runs=4, min_passes=1)
def test_read(pty_fixture):
pty = pty_fixture()
loc = os.getcwd()
data = ''
while loc not in data:
data += pty.read()
pty.terminate()
def test_write(pty_fixture):
pty = pty_fixture()
text = u'Eggs, ham and spam ünicode'
pty.write(text)
data = ''
while text not in data:
data += pty.read()
pty.terminate()
def test_isalive(pty_fixture):
pty = pty_fixture()
pty.write('exit\r\n')
text = 'exit'
data = ''
while text not in data:
data += pty.read()
while 1:
try:
pty.read()
except EOFError:
break
assert not pty.isalive()
pty.terminate()
def test_readline(pty_fixture):
env = os.environ.copy()
env['foo'] = 'bar'
pty = pty_fixture(env=env)
pty.write('echo %foo%\r\n')
while 'bar' not in pty.readline():
pass
pty.terminate()
def test_close(pty_fixture):
pty = pty_fixture()
pty.close()
assert not pty.isalive()
def test_flush(pty_fixture):
pty = pty_fixture()
pty.flush()
pty.terminate()
def test_intr(pty_fixture):
pty = pty_fixture(cmd=[sys.executable, 'import time; time.sleep(10)'])
pty.sendintr()
assert pty.wait() != 0
def test_send_control(pty_fixture):
pty = pty_fixture(cmd=[sys.executable, 'import time; time.sleep(10)'])
pty.sendcontrol('d')
assert pty.wait() != 0
def test_send_eof(pty_fixture):
cat = pty_fixture('cat')
cat.sendeof()
assert cat.wait() == 0
def test_isatty(pty_fixture):
pty = pty_fixture()
assert pty.isatty()
pty.terminate()
assert not pty.isatty()
def test_wait(pty_fixture):
pty = pty_fixture(cmd=[sys.executable, '--version'])
assert pty.wait() == 0
def test_exit_status(pty_fixture):
pty = pty_fixture(cmd=[sys.executable])
pty.write('import sys;sys.exit(1)\r\n')
pty.wait()
assert pty.exitstatus == 1
def test_kill(pty_fixture):
pty = pty_fixture()
pty.kill(signal.SIGTERM)
assert not pty.isalive()
assert pty.exitstatus == signal.SIGTERM
def test_getwinsize(pty_fixture):
pty = pty_fixture()
assert pty.getwinsize() == (24, 80)
pty.terminate()
def test_setwinsize(pty_fixture):
pty = pty_fixture()
pty.setwinsize(50, 110)
assert pty.getwinsize() == (50, 110)
pty.terminate()
pty = PtyProcess.spawn('cmd', dimensions=(60, 120))
assert pty.getwinsize() == (60, 120)
pty.terminate()

View file

@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
"""winpty wrapper tests."""
# yapf: disable
# Standard library imports
import os
# Third party imports
from flaky import flaky
from winpty.winpty_wrapper import PTY, PY2
from winpty.ptyprocess import which
import pytest
# yapf: enable
CMD = which('cmd')
if PY2:
CMD = unicode(CMD) # noqa
@pytest.fixture(scope='module')
def pty_fixture():
def _pty_factory():
pty = PTY(80, 25)
pty.spawn(CMD)
return pty
return _pty_factory
@flaky(max_runs=4, min_passes=1)
def test_read(pty_fixture):
pty = pty_fixture()
loc = os.getcwd()
line = ''
while loc not in line:
line += pty.read().decode('utf-8')
assert loc in line
pty.close()
del pty
def test_write(pty_fixture):
pty = pty_fixture()
line = pty.read()
while len(line) < 10:
line = pty.read()
text = u'Eggs, ham and spam ünicode'
pty.write(text)
line = u''
while text not in line:
line += pty.read().decode('utf-8')
assert text in line
pty.close()
del pty
def test_isalive(pty_fixture):
pty = pty_fixture()
pty.write(u'exit\r\n')
text = u'exit'
line = u''
while text not in line:
line += pty.read().decode('utf-8')
while pty.isalive():
pty.read()
continue
assert not pty.isalive()
pty.close()
del pty

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,98 @@
# -*- coding: utf-8 -*-
"""Wrap process I/O pipe communication using pywin32."""
# yapf: disable
# Standard library imports
from ctypes import windll
from ctypes.wintypes import DWORD, LPVOID, HANDLE, BOOL, LPCVOID
import ctypes
# Local imports
from .cywinpty import Agent
import sys
PY2 = sys.version_info[0] == 2
# yapf: enable
OPEN_EXISTING = 3
GENERIC_WRITE = 0x40000000
GENERIC_READ = 0x80000000
LARGE_INTEGER = ctypes.c_ulong
PLARGE_INTEGER = ctypes.POINTER(LARGE_INTEGER)
LPOVERLAPPED = LPVOID
# LPDWORD is not in ctypes.wintypes on Python 2
LPDWORD = ctypes.POINTER(DWORD)
ReadFile = windll.kernel32.ReadFile
ReadFile.restype = BOOL
ReadFile.argtypes = [HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED]
WriteFile = windll.kernel32.WriteFile
WriteFile.restype = BOOL
WriteFile.argtypes = [HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED]
class PTY(Agent):
"""
This class provides a pywin32 communication wrapper around winpty process
communication pipes.
Inherits all Cython winpty agent functionality and properties.
"""
def __init__(self, cols, rows):
"""Initialize a new Pseudo Terminal of size ``(cols, rows)``."""
Agent.__init__(self, cols, rows, True)
self.conin_pipe = windll.kernel32.CreateFileW(
self.conin_pipe_name, GENERIC_WRITE, 0, None, OPEN_EXISTING, 0,
None
)
self.conout_pipe = windll.kernel32.CreateFileW(
self.conout_pipe_name, GENERIC_READ, 0, None, OPEN_EXISTING, 0,
None
)
def read(self, length=1000, blocking=False):
"""
Read ``length`` bytes from current process output stream.
Note: This method is not fully non-blocking, however it
behaves like one.
"""
size_p = PLARGE_INTEGER(LARGE_INTEGER(0))
if not blocking:
windll.kernel32.GetFileSizeEx(self.conout_pipe, size_p)
size = size_p[0]
length = min(size, length)
data = ctypes.create_string_buffer(length)
if length > 0:
num_bytes = PLARGE_INTEGER(LARGE_INTEGER(0))
ReadFile(self.conout_pipe, data, length, num_bytes, None)
return data.value
def write(self, data):
"""Write string data to current process input stream."""
data = data.encode('utf-8')
data_p = ctypes.create_string_buffer(data)
num_bytes = PLARGE_INTEGER(LARGE_INTEGER(0))
bytes_to_write = len(data)
success = WriteFile(self.conin_pipe, data_p,
bytes_to_write, num_bytes, None)
return success, num_bytes[0]
def close(self):
"""Close all communication process streams."""
windll.kernel32.CloseHandle(self.conout_pipe)
windll.kernel32.CloseHandle(self.conin_pipe)
def iseof(self):
"""Check if current process streams are still open."""
succ = windll.kernel32.PeekNamedPipe(
self.conout_pipe, None, None, None, None, None
)
return not bool(succ)