pywinpty 3.0.1__cp314-cp314t-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,6 @@
1
+ Metadata-Version: 2.4
2
+ Name: pywinpty
3
+ Version: 3.0.1
4
+ License-File: LICENSE.txt
5
+ Summary: Pseudo terminal support for Windows from Python.
6
+ Requires-Python: >=3.9
@@ -0,0 +1,16 @@
1
+ pywinpty-3.0.1.dist-info/METADATA,sha256=uv-Z7g2o5rjRo8IsY9CRa-o5hsuAXJ7ahffynf82knM,159
2
+ pywinpty-3.0.1.dist-info/WHEEL,sha256=-TKJ7-TyRE7TSpD4y2y1Nzd97blPI9emqtvIMBBpmrM,97
3
+ pywinpty-3.0.1.dist-info/licenses/LICENSE.txt,sha256=-HjUdn-a0uQ9MIPvoAIBsADOk32e6GJuALpccqrJUeI,1088
4
+ winpty/OpenConsole.exe,sha256=6Dg0D2Gj8O-6rBrxtvgb31jYQDGM0fB2GQIlWrtWqmE,1148472
5
+ winpty/__init__.py,sha256=laTi9sLjCLycyaQYO3wMvhZNIK2vh_pOvGTJT2Od4Qs,402
6
+ winpty/conpty.dll,sha256=kliUMMhwG7vUzGvzCV_o8GdlaIh13W7EK6VKWwdHHoU,109624
7
+ winpty/enums.py,sha256=KAm7XJFPk7nMWwUJxvYHlql92cr6y-Y24IHK3fc0JDU,1860
8
+ winpty/ptyprocess.py,sha256=lub-1tL-LVUEY0e8s8J0VEbfWLsRdsx8gHyhagxdb28,12255
9
+ winpty/tests/__init__.py,sha256=fzb9cDnPt2R3b_rWh6sqDgIbiQOZtBlfsV1aq-ULT2Q,53
10
+ winpty/tests/test_pty.py,sha256=f6UKatWALTFM7jvcrqYWsRFCxosT-Aj0ChhvkN97ov4,3850
11
+ winpty/tests/test_ptyprocess.py,sha256=QP0I-gufNV7OaZZFpRIC9J1XD4nq86NrR3oqi9s4OEI,5981
12
+ winpty/winpty-agent.exe,sha256=REZ6g9hrYSe8e4duiy-zP6df3gnZuf8nx_jvhLqWmyk,2627338
13
+ winpty/winpty.cp314t-win_amd64.pyd,sha256=Z6eAqG5QTpUg3O8By8axIY1CBLUCj-0Yxn9K37pygD4,580096
14
+ winpty/winpty.dll,sha256=UXfzIarC-oVDbdbsa_xtQUWdJfW1M6oDNJ71E0YbTHk,2509089
15
+ winpty/winpty.pyi,sha256=N8sy5oYzQGwtekPUSZ8Ai6Z9l3TcpNv-VOMjhRHByhs,1500
16
+ pywinpty-3.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.9.4)
3
+ Root-Is-Purelib: false
4
+ Tag: cp314-cp314t-win_amd64
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 Spyder IDE
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
winpty/OpenConsole.exe ADDED
Binary file
winpty/__init__.py ADDED
@@ -0,0 +1,23 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Pywinpty
4
+ ========
5
+ This package provides low and high level APIs to create
6
+ pseudo terminals in Windows.
7
+ """
8
+
9
+ # Local imports
10
+ from .winpty import PTY, WinptyError, __version__
11
+ from .ptyprocess import PtyProcess
12
+ from .enums import Backend, Encoding, MouseMode, AgentConfig
13
+
14
+
15
+ PTY
16
+ PtyProcess
17
+ Backend
18
+ Encoding
19
+ MouseMode
20
+ AgentConfig
21
+ WinptyError
22
+
23
+ __version__
winpty/conpty.dll ADDED
Binary file
winpty/enums.py ADDED
@@ -0,0 +1,54 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """General constants used to spawn a PTY."""
4
+
5
+
6
+ class Backend:
7
+ """Available PTY backends."""
8
+ ConPTY = 0
9
+ WinPTY = 1
10
+
11
+
12
+ class Encoding:
13
+ """Available byte encodings to communicate with a PTY."""
14
+ UTF8 = 'utf-8'
15
+ UTF16 = 'utf-16'
16
+
17
+
18
+ class MouseMode:
19
+ """Mouse capture settings for the winpty backend."""
20
+
21
+ # QuickEdit mode is initially disabled, and the agent does not send mouse
22
+ # mode sequences to the terminal. If it receives mouse input, though, it
23
+ # still writes MOUSE_EVENT_RECORD values into CONIN.
24
+ WINPTY_MOUSE_MODE_NONE = 0
25
+
26
+ # QuickEdit mode is initially enabled. As CONIN enters or leaves mouse
27
+ # input mode (i.e. where ENABLE_MOUSE_INPUT is on and
28
+ # ENABLE_QUICK_EDIT_MODE is off), the agent enables or disables mouse
29
+ # input on the terminal.
30
+ WINPTY_MOUSE_MODE_AUTO = 1
31
+
32
+ # QuickEdit mode is initially disabled, and the agent enables the
33
+ # terminal's mouse input mode. It does not disable terminal
34
+ # mouse mode (until exit).
35
+ WINPTY_MOUSE_MODE_FORCE = 2
36
+
37
+
38
+ class AgentConfig:
39
+ """General configuration settings for the winpty backend."""
40
+
41
+ # Create a new screen buffer (connected to the "conerr" terminal pipe) and
42
+ # pass it to child processes as the STDERR handle. This flag also prevents
43
+ # the agent from reopening CONOUT$ when it polls -- regardless of whether
44
+ # the active screen buffer changes, winpty continues to monitor the
45
+ # original primary screen buffer.
46
+ WINPTY_FLAG_CONERR = 0x1
47
+
48
+ # Don't output escape sequences.
49
+ WINPTY_FLAG_PLAIN_OUTPUT = 0x2
50
+
51
+ # Do output color escape sequences. These escapes are output by default,
52
+ # but are suppressed with WINPTY_FLAG_PLAIN_OUTPUT.
53
+ # Use this flag to re-enable them.
54
+ WINPTY_FLAG_COLOR_ESCAPES = 0x4
winpty/ptyprocess.py ADDED
@@ -0,0 +1,374 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ # Standard library imports
4
+ import codecs
5
+ import os
6
+ import shlex
7
+ import signal
8
+ import socket
9
+ import subprocess
10
+ import threading
11
+ import time
12
+ from shutil import which
13
+
14
+ # Local imports
15
+ from .winpty import PTY
16
+
17
+
18
+ class PtyProcess(object):
19
+ """This class represents a process running in a pseudoterminal.
20
+
21
+ The main constructor is the :meth:`spawn` classmethod.
22
+ """
23
+
24
+ def __init__(self, pty):
25
+ assert isinstance(pty, PTY)
26
+ self.pty = pty
27
+ self.pid = pty.pid
28
+ # self.fd = pty.fd
29
+ self.argv = None
30
+ self.env = None
31
+ self.launch_dir = None
32
+
33
+ self.read_blocking = bool(int(os.environ.get('PYWINPTY_BLOCK', 1)))
34
+ self.closed = False
35
+ self.flag_eof = False
36
+
37
+ # Used by terminate() to give kernel time to update process status.
38
+ # Time in seconds.
39
+ self.delayafterterminate = 0.1
40
+ # Used by close() to give kernel time to update process status.
41
+ # Time in seconds.
42
+ self.delayafterclose = 0.1
43
+
44
+ # Set up our file reader sockets.
45
+ self._server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
46
+ self._server.bind(("127.0.0.1", 0))
47
+ address = self._server.getsockname()
48
+ self._server.listen(1)
49
+
50
+ # Read from the pty in a thread.
51
+ self._thread = threading.Thread(target=_read_in_thread,
52
+ args=(address, self.pty, self.read_blocking))
53
+ self._thread.daemon = True
54
+ self._thread.start()
55
+
56
+ self.fileobj, _ = self._server.accept()
57
+ self.fd = self.fileobj.fileno()
58
+
59
+ @classmethod
60
+ def spawn(cls, argv, cwd=None, env=None, dimensions=(24, 80),
61
+ backend=None):
62
+ """Start the given command in a child process in a pseudo terminal.
63
+
64
+ This does all the setting up the pty, and returns an instance of
65
+ PtyProcess.
66
+
67
+ Dimensions of the psuedoterminal used for the subprocess can be
68
+ specified as a tuple (rows, cols), or the default (24, 80) will be
69
+ used.
70
+ """
71
+ if isinstance(argv, str):
72
+ argv = shlex.split(argv, posix=False)
73
+
74
+ if not isinstance(argv, (list, tuple)):
75
+ raise TypeError("Expected a list or tuple for argv, got %r" % argv)
76
+
77
+ # Shallow copy of argv so we can modify it
78
+ _argv: list[str] = list(argv[:])
79
+ command = _argv[0]
80
+ env = env or os.environ
81
+
82
+ path = env.get('PATH', os.defpath)
83
+ command_with_path = which(command, path=path)
84
+ if command_with_path is None:
85
+ raise FileNotFoundError(
86
+ 'The command was not found or was not ' +
87
+ 'executable: %s.' % command
88
+ )
89
+ command = command_with_path
90
+ _argv[0] = command
91
+ cmdline = ' ' + subprocess.list2cmdline(_argv[1:])
92
+ cwd = cwd or os.getcwd()
93
+
94
+ backend = backend or os.environ.get('PYWINPTY_BACKEND', None)
95
+ backend = int(backend) if backend is not None else backend
96
+
97
+ proc = PTY(dimensions[1], dimensions[0],
98
+ backend=backend)
99
+
100
+ # Create the environment string.
101
+ envStrs = []
102
+ for (key, value) in env.items():
103
+ envStrs.append('%s=%s' % (key, value))
104
+ env = '\0'.join(envStrs) + '\0'
105
+
106
+ # command = bytes(command, encoding)
107
+ # cwd = bytes(cwd, encoding)
108
+ # cmdline = bytes(cmdline, encoding)
109
+ # env = bytes(env, encoding)
110
+
111
+ if len(_argv) == 1:
112
+ proc.spawn(command, cwd=cwd, env=env)
113
+ else:
114
+ proc.spawn(command, cwd=cwd, env=env, cmdline=cmdline)
115
+
116
+ inst = cls(proc)
117
+ inst._winsize = dimensions
118
+
119
+ # Set some informational attributes
120
+ inst.argv = _argv
121
+ if env is not None:
122
+ inst.env = env
123
+ if cwd is not None:
124
+ inst.launch_dir = cwd
125
+
126
+ return inst
127
+
128
+ @property
129
+ def exitstatus(self):
130
+ """The exit status of the process.
131
+ """
132
+ return self.pty.get_exitstatus()
133
+
134
+ def fileno(self):
135
+ """This returns the file descriptor of the pty for the child.
136
+ """
137
+ return self.fd
138
+
139
+ def close(self, force=False):
140
+ """This closes the connection with the child application. Note that
141
+ calling close() more than once is valid. This emulates standard Python
142
+ behavior with files. Set force to True if you want to make sure that
143
+ the child is terminated (SIGKILL is sent if the child ignores
144
+ SIGINT)."""
145
+ if not self.closed:
146
+ self.fileobj.close()
147
+ self._server.close()
148
+ # Give kernel time to update process status.
149
+ time.sleep(self.delayafterclose)
150
+ if self.isalive():
151
+ if not self.terminate(force):
152
+ raise IOError('Could not terminate the child.')
153
+ self.fd = -1
154
+ self.closed = True
155
+ # del self.pty
156
+
157
+ def __del__(self):
158
+ """This makes sure that no system resources are left open. Python only
159
+ garbage collects Python objects. OS file descriptors are not Python
160
+ objects, so they must be handled explicitly. If the child file
161
+ descriptor was opened outside of this class (passed to the constructor)
162
+ then this does not close it.
163
+ """
164
+ # It is possible for __del__ methods to execute during the
165
+ # teardown of the Python VM itself. Thus self.close() may
166
+ # trigger an exception because os.close may be None.
167
+ try:
168
+ self.close()
169
+ except Exception:
170
+ pass
171
+
172
+ def flush(self):
173
+ """This does nothing. It is here to support the interface for a
174
+ File-like object. """
175
+ pass
176
+
177
+ def isatty(self):
178
+ """This returns True if the file descriptor is open and connected to a
179
+ tty(-like) device, else False."""
180
+ return self.isalive()
181
+
182
+ def read(self, size=1024):
183
+ """Read and return at most ``size`` characters from the pty.
184
+
185
+ Can block if there is nothing to read. Raises :exc:`EOFError` if the
186
+ terminal was closed.
187
+ """
188
+ # try:
189
+ # data = self.pty.read(size, blocking=self.read_blocking)
190
+ # except Exception as e:
191
+ # if "EOF" in str(e):
192
+ # raise EOFError(e) from e
193
+ # return data
194
+ data = self.fileobj.recv(size)
195
+ if not data:
196
+ self.flag_eof = True
197
+ raise EOFError('Pty is closed')
198
+
199
+ if data == b'0011Ignore':
200
+ data = b''
201
+
202
+ err = True
203
+ while err and data:
204
+ try:
205
+ data.decode('utf-8')
206
+ err = False
207
+ except UnicodeDecodeError:
208
+ data += self.fileobj.recv(1)
209
+ return data.decode('utf-8')
210
+
211
+ def readline(self):
212
+ """Read one line from the pseudoterminal as bytes.
213
+
214
+ Can block if there is nothing to read. Raises :exc:`EOFError` if the
215
+ terminal was closed.
216
+ """
217
+ buf = []
218
+ while 1:
219
+ try:
220
+ ch = self.read(1)
221
+ except EOFError:
222
+ return ''.join(buf)
223
+ buf.append(ch)
224
+ if ch == '\n':
225
+ return ''.join(buf)
226
+
227
+ def write(self, s):
228
+ """Write the string ``s`` to the pseudoterminal.
229
+
230
+ Returns the number of bytes written.
231
+ """
232
+ if not self.pty.isalive():
233
+ raise EOFError('Pty is closed')
234
+
235
+ nbytes = self.pty.write(s)
236
+ return nbytes
237
+
238
+ def terminate(self, force=False):
239
+ """This forces a child process to terminate."""
240
+ if not self.isalive():
241
+ return True
242
+ self.kill(signal.SIGINT)
243
+ try:
244
+ self.pty.cancel_io()
245
+ except Exception:
246
+ pass
247
+ time.sleep(self.delayafterterminate)
248
+ if not self.isalive():
249
+ return True
250
+ if force:
251
+ self.kill(signal.SIGTERM)
252
+ time.sleep(self.delayafterterminate)
253
+ if not self.isalive():
254
+ return True
255
+ else:
256
+ return False
257
+
258
+ def wait(self):
259
+ """This waits until the child exits. This is a blocking call. This will
260
+ not read any data from the child.
261
+ """
262
+ while self.isalive():
263
+ time.sleep(0.1)
264
+ return self.exitstatus
265
+
266
+ def isalive(self):
267
+ """This tests if the child process is running or not. This is
268
+ non-blocking. If the child was terminated then this will read the
269
+ exitstatus or signalstatus of the child. This returns True if the child
270
+ process appears to be running or False if not.
271
+ """
272
+ alive = self.pty.isalive()
273
+ self.closed = not alive
274
+ return alive
275
+
276
+ def kill(self, sig):
277
+ """Kill the process with the given signal.
278
+ """
279
+ if self.pid is None:
280
+ return
281
+ os.kill(self.pid, sig)
282
+
283
+ def sendcontrol(self, char):
284
+ '''Helper method that wraps send() with mnemonic access for sending control
285
+ character to the child (such as Ctrl-C or Ctrl-D). For example, to send
286
+ Ctrl-G (ASCII 7, bell, '\a')::
287
+ child.sendcontrol('g')
288
+ See also, sendintr() and sendeof().
289
+ '''
290
+ char = char.lower()
291
+ a = ord(char)
292
+ if 97 <= a <= 122:
293
+ a = a - ord('a') + 1
294
+ byte = bytes([a]).decode("ascii")
295
+ return self.pty.write(byte), byte
296
+ d = {'@': 0, '`': 0,
297
+ '[': 27, '{': 27,
298
+ '\\': 28, '|': 28,
299
+ ']': 29, '}': 29,
300
+ '^': 30, '~': 30,
301
+ '_': 31,
302
+ '?': 127}
303
+ if char not in d:
304
+ return 0, ''
305
+
306
+ byte = bytes([d[char]]).decode("ascii")
307
+ return self.pty.write(byte), byte
308
+
309
+ def sendeof(self):
310
+ """This sends an EOF to the child. This sends a character which causes
311
+ the pending parent output buffer to be sent to the waiting child
312
+ program without waiting for end-of-line. If it is the first character
313
+ of the line, the read() in the user program returns 0, which signifies
314
+ end-of-file. This means to work as expected a sendeof() has to be
315
+ called at the beginning of a line. This method does not send a newline.
316
+ It is the responsibility of the caller to ensure the eof is sent at the
317
+ beginning of a line."""
318
+ # Send control character 4 (Ctrl-D)
319
+ self.pty.write('\x04')
320
+
321
+ def sendintr(self):
322
+ """This sends a SIGINT to the child. It does not require
323
+ the SIGINT to be the first character on a line. """
324
+ # Send control character 3 (Ctrl-C)
325
+ self.pty.write('\x03')
326
+
327
+ def eof(self):
328
+ """This returns True if the EOF exception was ever raised.
329
+ """
330
+ return self.flag_eof
331
+
332
+ def getwinsize(self):
333
+ """Return the window size of the pseudoterminal as a tuple (rows, cols).
334
+ """
335
+ return self._winsize
336
+
337
+ def setwinsize(self, rows, cols):
338
+ """Set the terminal window size of the child tty.
339
+ """
340
+ self._winsize = (rows, cols)
341
+ self.pty.set_size(cols, rows)
342
+
343
+
344
+ def _read_in_thread(address, pty: PTY, blocking: bool):
345
+ """Read data from the pty in a thread.
346
+ """
347
+ client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
348
+ client.connect(address)
349
+
350
+ call = 0
351
+
352
+ while 1:
353
+ try:
354
+ data = pty.read(blocking=blocking) or b'0011Ignore'
355
+ try:
356
+ client.send(bytes(data, 'utf-8'))
357
+ except socket.error:
358
+ break
359
+
360
+ # Handle end of file.
361
+ if pty.iseof():
362
+ try:
363
+ client.send(b'')
364
+ except socket.error:
365
+ pass
366
+ finally:
367
+ break
368
+
369
+ call += 1
370
+ except Exception as e:
371
+ break
372
+ time.sleep(1e-3)
373
+
374
+ client.close()
@@ -0,0 +1,2 @@
1
+ # -*- coding: utf-8 -*-
2
+ """winpty module tests."""
@@ -0,0 +1,161 @@
1
+ # -*- coding: utf-8 -*-
2
+ """winpty wrapper tests."""
3
+
4
+ # Standard library imports
5
+ import os
6
+ import time
7
+
8
+ # Third party imports
9
+ from winpty import PTY, WinptyError
10
+ from winpty.enums import Backend
11
+ from winpty.ptyprocess import which
12
+ import pytest
13
+
14
+
15
+ CMD = which('cmd').lower()
16
+
17
+
18
+ def pty_factory(backend):
19
+ if os.environ.get('CI_RUNNING', None) == '1':
20
+ if backend == Backend.ConPTY:
21
+ os.environ['CONPTY_CI'] = '1'
22
+ elif backend == Backend.WinPTY:
23
+ os.environ.pop('CONPTY_CI', None)
24
+
25
+ @pytest.fixture(scope='function')
26
+ def pty_fixture():
27
+ pty = PTY(80, 20, backend=backend)
28
+ # loc = bytes(os.getcwd(), 'utf8')
29
+ assert pty.spawn(CMD)
30
+ time.sleep(0.3)
31
+ # if backend == Backend.ConPTY:
32
+ # pty.write("\x1b[?1;0c\x1b[0;0R")
33
+ return pty
34
+ return pty_fixture
35
+
36
+
37
+ conpty_provider = pty_factory(Backend.ConPTY)
38
+ winpty_provider = pty_factory(Backend.WinPTY)
39
+
40
+
41
+ @pytest.fixture(scope='module', params=['WinPTY', 'ConPTY'])
42
+ def pty_fixture(request):
43
+ backend = request.param
44
+ if os.environ.get('CI_RUNNING', None) == '1':
45
+ if backend == 'ConPTY':
46
+ os.environ['CI'] = '1'
47
+ os.environ['CONPTY_CI'] = '1'
48
+ if backend == 'WinPTY':
49
+ os.environ.pop('CI', None)
50
+ os.environ.pop('CONPTY_CI', None)
51
+
52
+ backend = getattr(Backend, backend)
53
+ def _pty_factory():
54
+ pty = PTY(80, 25, backend=backend)
55
+ assert pty.spawn(CMD)
56
+ time.sleep(0.3)
57
+ return pty
58
+ return _pty_factory
59
+
60
+
61
+
62
+ # @pytest.fixture(scope='function', params=[
63
+ # pytest.lazy_fixture('conpty_provider'),
64
+ # pytest.lazy_fixture('winpty_provider')])
65
+ # def pty_fixture(request):
66
+ # pty = request.param
67
+ # return pty
68
+
69
+
70
+ def test_read(pty_fixture, capsys):
71
+ pty = pty_fixture()
72
+ loc = os.getcwd()
73
+ readline = ''
74
+
75
+ with capsys.disabled():
76
+ start_time = time.time()
77
+ while loc not in readline:
78
+ if time.time() - start_time > 5:
79
+ break
80
+ readline += pty.read()
81
+ assert loc in readline or 'cmd' in readline
82
+ del pty
83
+
84
+
85
+ def test_write(pty_fixture):
86
+ pty = pty_fixture()
87
+ line = pty.read()
88
+
89
+ str_text = 'Eggs, ham and spam ünicode'
90
+ # text = bytes(str_text, 'utf-8')
91
+ num_bytes = pty.write(str_text)
92
+
93
+ line = ''
94
+ start_time = time.time()
95
+ while str_text not in line:
96
+ if time.time() - start_time > 5:
97
+ break
98
+ line += pty.read()
99
+
100
+ assert str_text in line
101
+ del pty
102
+
103
+
104
+ def test_isalive(pty_fixture):
105
+ pty = pty_fixture()
106
+ pty.write('exit\r\n')
107
+
108
+ text = 'exit'
109
+ line = ''
110
+ while text not in line:
111
+ try:
112
+ line += pty.read()
113
+ except Exception:
114
+ break
115
+
116
+ while pty.isalive():
117
+ try:
118
+ pty.read()
119
+ # continue
120
+ except Exception:
121
+ break
122
+
123
+ assert not pty.isalive()
124
+ del pty
125
+
126
+
127
+ # def test_agent_spawn_fail(pty_fixture):
128
+ # pty = pty_fixture
129
+ # try:
130
+ # pty.spawn(CMD)
131
+ # assert False
132
+ # except WinptyError:
133
+ # pass
134
+
135
+
136
+ # @pytest.mark.parametrize(
137
+ # 'backend_name,backend',
138
+ # [("ConPTY", Backend.ConPTY), ('WinPTY', Backend.WinPTY)])
139
+ # def test_pty_create_size_fail(backend_name, backend):
140
+ # try:
141
+ # PTY(80, -25, backend=backend)
142
+ # assert False
143
+ # except WinptyError:
144
+ # pass
145
+
146
+
147
+ # def test_agent_resize_fail(pty_fixture):
148
+ # pty = pty_fixture()
149
+ # try:
150
+ # pty.set_size(-80, 70)
151
+ # assert False
152
+ # except WinptyError:
153
+ # pass
154
+ # finally:
155
+ # del pty
156
+
157
+
158
+ def test_agent_resize(pty_fixture):
159
+ pty = pty_fixture()
160
+ pty.set_size(80, 70)
161
+ del pty
@@ -0,0 +1,263 @@
1
+ # -*- coding: utf-8 -*-
2
+ """winpty wrapper tests."""
3
+
4
+ # Standard library imports
5
+ import asyncio
6
+ import os
7
+ import signal
8
+ import time
9
+ import sys
10
+ import re
11
+
12
+ # Third party imports
13
+ import pytest
14
+ from flaky import flaky
15
+
16
+ # Local imports
17
+ from winpty.enums import Backend
18
+ from winpty.ptyprocess import PtyProcess, which
19
+
20
+
21
+
22
+ @pytest.fixture(scope='module', params=['WinPTY', 'ConPTY'])
23
+ def pty_fixture(request):
24
+ backend = request.param
25
+ if os.environ.get('CI_RUNNING', None) == '1':
26
+ if backend == 'ConPTY':
27
+ os.environ['CI'] = '1'
28
+ os.environ['CONPTY_CI'] = '1'
29
+ if backend == 'WinPTY':
30
+ os.environ.pop('CI', None)
31
+ os.environ.pop('CONPTY_CI', None)
32
+
33
+ backend = getattr(Backend, backend)
34
+ def _pty_factory(cmd=None, env=None):
35
+ cmd = cmd or 'cmd'
36
+ pty = PtyProcess.spawn(cmd, env=env, backend=backend)
37
+ return pty
38
+ # time.sleep(10)
39
+ _pty_factory.backend = request.param
40
+ return _pty_factory
41
+
42
+
43
+ @flaky(max_runs=40, min_passes=1)
44
+ def test_read(pty_fixture):
45
+ pty = pty_fixture()
46
+ loc = os.getcwd()
47
+ data = ''
48
+ tries = 0
49
+ while loc not in data and tries < 10:
50
+ try:
51
+ data += pty.read()
52
+ except EOFError:
53
+ pass
54
+ tries += 1
55
+ assert loc in data
56
+ pty.terminate()
57
+ time.sleep(2)
58
+
59
+
60
+ @flaky(max_runs=40, min_passes=1)
61
+ def test_write(pty_fixture):
62
+ pty = pty_fixture()
63
+
64
+ text = 'Eggs, ham and spam ünicode'
65
+ pty.write(text)
66
+
67
+ data = ''
68
+ tries = 0
69
+ while text not in data and tries < 10:
70
+ try:
71
+ data += pty.read()
72
+ except EOFError:
73
+ pass
74
+ tries += 1
75
+ assert text in data
76
+ pty.terminate()
77
+
78
+
79
+ @pytest.mark.xfail(reason="It fails sometimes due to long strings")
80
+ @flaky(max_runs=40, min_passes=1)
81
+ def test_isalive(pty_fixture):
82
+ pty = pty_fixture()
83
+ time.sleep(4)
84
+
85
+ pty.write('echo \"foo\"\r\nexit\r\n')
86
+ data = ''
87
+ while True:
88
+ try:
89
+ print('Stuck')
90
+ data += pty.read()
91
+ except EOFError:
92
+ break
93
+
94
+ regex = re.compile(".*foo.*")
95
+ assert regex.findall(data)
96
+ assert not pty.isalive()
97
+ pty.terminate()
98
+
99
+
100
+ @pytest.mark.xfail(reason="It fails sometimes due to long strings")
101
+ @flaky(max_runs=40, min_passes=1)
102
+ def test_readline(pty_fixture):
103
+ env = os.environ.copy()
104
+ env['foo'] = 'bar'
105
+ pty = pty_fixture(env=env)
106
+
107
+ # Ensure that the echo print has its own CRLF
108
+ pty.write('cls\r\n')
109
+ pty.write('echo %foo%\r\n')
110
+
111
+ data = ''
112
+ tries = 0
113
+ while 'bar' not in data and tries < 10:
114
+ data = pty.readline()
115
+ tries += 1
116
+
117
+ assert 'bar' in data
118
+
119
+ pty.terminate()
120
+
121
+
122
+ def test_close(pty_fixture):
123
+ pty = pty_fixture()
124
+ pty.close()
125
+ assert not pty.isalive()
126
+
127
+
128
+ def test_flush(pty_fixture):
129
+ pty = pty_fixture()
130
+ pty.flush()
131
+ pty.terminate()
132
+
133
+
134
+ def test_intr(pty_fixture):
135
+ pty = pty_fixture(cmd=[sys.executable, 'import time; time.sleep(10)'])
136
+ pty.sendintr()
137
+ assert pty.wait() != 0
138
+
139
+
140
+ def test_send_control(pty_fixture):
141
+ pty = pty_fixture(cmd=[sys.executable, 'import time; time.sleep(10)'])
142
+ pty.sendcontrol('d')
143
+ assert pty.wait() != 0
144
+
145
+
146
+ @pytest.mark.skipif(which('cat') is None, reason="Requires cat on the PATH")
147
+ def test_send_eof(pty_fixture):
148
+ cat = pty_fixture('cat')
149
+ cat.sendeof()
150
+ assert cat.wait() == 0
151
+
152
+
153
+ def test_isatty(pty_fixture):
154
+ pty = pty_fixture()
155
+ assert pty.isatty()
156
+ pty.terminate()
157
+ assert not pty.isatty()
158
+
159
+
160
+ def test_wait(pty_fixture):
161
+ pty = pty_fixture(cmd=[sys.executable, '--version'])
162
+ assert pty.wait() == 0
163
+
164
+
165
+ def test_exit_status(pty_fixture):
166
+ pty = pty_fixture(cmd=[sys.executable])
167
+ pty.write('import sys;sys.exit(1)\r\n')
168
+ pty.wait()
169
+ assert pty.exitstatus == 1
170
+
171
+
172
+ # @pytest.mark.timeout(30)
173
+ def test_kill_sigterm(pty_fixture):
174
+ pty = pty_fixture()
175
+ pty.write('echo \"foo\"\r\nsleep 1000\r\n')
176
+ pty.read()
177
+ pty.kill(signal.SIGTERM)
178
+
179
+ while True:
180
+ try:
181
+ pty.read()
182
+ except EOFError:
183
+ break
184
+
185
+ assert not pty.isalive()
186
+ assert pty.exitstatus == signal.SIGTERM
187
+
188
+
189
+ # @pytest.mark.timeout(30)
190
+ def test_terminate(pty_fixture):
191
+ pty = pty_fixture()
192
+ pty.write('echo \"foo\"\r\nsleep 1000\r\n')
193
+ pty.read()
194
+ pty.terminate()
195
+
196
+ while True:
197
+ try:
198
+ pty.read()
199
+ except EOFError:
200
+ break
201
+
202
+ assert not pty.isalive()
203
+ assert pty.closed
204
+
205
+
206
+ # @pytest.mark.timeout(30)
207
+ def test_terminate_force(pty_fixture):
208
+ pty = pty_fixture()
209
+ pty.write('echo \"foo\"\r\nsleep 1000\r\n')
210
+ pty.read()
211
+ pty.terminate(force=True)
212
+
213
+ while True:
214
+ try:
215
+ pty.read()
216
+ except EOFError:
217
+ break
218
+
219
+ assert not pty.isalive()
220
+ assert pty.closed
221
+
222
+
223
+ def test_terminate_loop(pty_fixture):
224
+ pty = pty_fixture()
225
+ loop = asyncio.SelectorEventLoop()
226
+ asyncio.set_event_loop(loop)
227
+
228
+ def reader():
229
+ try:
230
+ data = pty.read()
231
+ except EOFError:
232
+ loop.remove_reader(pty.fd)
233
+ loop.stop()
234
+
235
+ loop.add_reader(pty.fd, reader)
236
+ loop.call_soon(pty.write, 'echo \"foo\"\r\nsleep 1000\r\n')
237
+ loop.call_soon(pty.terminate, True)
238
+
239
+ try:
240
+ loop.run_forever()
241
+ finally:
242
+ loop.close()
243
+
244
+ assert not pty.isalive()
245
+ assert pty.closed
246
+
247
+
248
+ def test_getwinsize(pty_fixture):
249
+ pty = pty_fixture()
250
+ assert pty.getwinsize() == (24, 80)
251
+ pty.terminate()
252
+
253
+
254
+ def test_setwinsize(pty_fixture):
255
+ pty = pty_fixture()
256
+ pty.setwinsize(50, 110)
257
+ assert pty.getwinsize() == (50, 110)
258
+ pty.terminate()
259
+
260
+ pty = PtyProcess.spawn('cmd', dimensions=(60, 120))
261
+ assert pty.getwinsize() == (60, 120)
262
+ pty.terminate()
263
+
Binary file
Binary file
winpty/winpty.dll ADDED
Binary file
winpty/winpty.pyi ADDED
@@ -0,0 +1,58 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """Stub typing declarations for the native PTY object."""
4
+
5
+ # Standard library imports
6
+ from typing import Optional
7
+
8
+ # Local imports
9
+ from .enums import Backend, Encoding, MouseMode, AgentConfig
10
+
11
+ __version__: str
12
+
13
+ class WinptyError(Exception):
14
+ ...
15
+
16
+ class PTY:
17
+ def __init__(self, cols: int, rows: int,
18
+ backend: Optional[int] = None,
19
+ encoding: Optional[str] = Encoding.UTF8,
20
+ mouse_mode: int = MouseMode.WINPTY_MOUSE_MODE_NONE,
21
+ timeout: int = 30000,
22
+ agent_config: int = AgentConfig.WINPTY_FLAG_COLOR_ESCAPES):
23
+ ...
24
+
25
+ def spawn(self,
26
+ appname: str,
27
+ cmdline: Optional[str] = None,
28
+ cwd: Optional[str] = None,
29
+ env: Optional[str] = None) -> bool:
30
+ ...
31
+
32
+ def set_size(self, cols: int, rows: int): ...
33
+
34
+ def read(self,
35
+ length: Optional[int] = 1000,
36
+ blocking: bool = False) -> str:
37
+ ...
38
+
39
+ def read_stderr(self,
40
+ length: Optional[int] = 1000,
41
+ blocking: bool = False) -> str:
42
+ ...
43
+
44
+ def write(self, to_write: str) -> int: ...
45
+
46
+ def isalive(self) -> bool: ...
47
+
48
+ def get_exitstatus(self) -> Optional[int]: ...
49
+
50
+ def iseof(self) -> bool: ...
51
+
52
+ def cancel_io() -> bool: ...
53
+
54
+ @property
55
+ def pid(self) -> Optional[int]: ...
56
+
57
+ @property
58
+ def fd(self) -> Optional[int]: ...