clikernel 0.1.1__tar.gz → 0.1.2__tar.gz
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.
- {clikernel-0.1.1 → clikernel-0.1.2}/CHANGELOG.md +7 -0
- {clikernel-0.1.1/clikernel.egg-info → clikernel-0.1.2}/PKG-INFO +1 -1
- clikernel-0.1.2/clikernel/__init__.py +3 -0
- {clikernel-0.1.1 → clikernel-0.1.2}/clikernel/cli.py +18 -2
- {clikernel-0.1.1 → clikernel-0.1.2/clikernel.egg-info}/PKG-INFO +1 -1
- {clikernel-0.1.1 → clikernel-0.1.2}/tests/test_cli.py +96 -1
- clikernel-0.1.1/clikernel/__init__.py +0 -2
- {clikernel-0.1.1 → clikernel-0.1.2}/LICENSE +0 -0
- {clikernel-0.1.1 → clikernel-0.1.2}/MANIFEST.in +0 -0
- {clikernel-0.1.1 → clikernel-0.1.2}/README.md +0 -0
- {clikernel-0.1.1 → clikernel-0.1.2}/clikernel.egg-info/SOURCES.txt +0 -0
- {clikernel-0.1.1 → clikernel-0.1.2}/clikernel.egg-info/dependency_links.txt +0 -0
- {clikernel-0.1.1 → clikernel-0.1.2}/clikernel.egg-info/entry_points.txt +0 -0
- {clikernel-0.1.1 → clikernel-0.1.2}/clikernel.egg-info/requires.txt +0 -0
- {clikernel-0.1.1 → clikernel-0.1.2}/clikernel.egg-info/top_level.txt +0 -0
- {clikernel-0.1.1 → clikernel-0.1.2}/pyproject.toml +0 -0
- {clikernel-0.1.1 → clikernel-0.1.2}/setup.cfg +0 -0
|
@@ -75,10 +75,24 @@ def _disable_echo():
|
|
|
75
75
|
def _restore_echo(state):
|
|
76
76
|
if state: termios.tcsetattr(state[0], termios.TCSADRAIN, state[1])
|
|
77
77
|
|
|
78
|
+
def _disable_output_newline_translation():
|
|
79
|
+
if not sys.__stdout__.isatty(): return None
|
|
80
|
+
fd = sys.__stdout__.fileno()
|
|
81
|
+
attrs = termios.tcgetattr(fd)
|
|
82
|
+
new_attrs = attrs[:]
|
|
83
|
+
new_attrs[1] &= ~termios.ONLCR
|
|
84
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, new_attrs)
|
|
85
|
+
return fd, attrs
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _restore_termios(state):
|
|
89
|
+
if state: termios.tcsetattr(state[0], termios.TCSADRAIN, state[1])
|
|
90
|
+
|
|
78
91
|
|
|
79
92
|
def main():
|
|
80
|
-
echo_state = _disable_echo()
|
|
81
93
|
shell = _make_shell()
|
|
94
|
+
output_state = _disable_output_newline_translation()
|
|
95
|
+
echo_state = _disable_echo()
|
|
82
96
|
delim = _new_delim()
|
|
83
97
|
print("loading complete. first delimiter:", flush=True)
|
|
84
98
|
_write_response(delim)
|
|
@@ -96,7 +110,9 @@ def main():
|
|
|
96
110
|
except BaseException: outputs = _format_error("internal-error", traceback.format_exc())
|
|
97
111
|
_write_response(delim, outputs)
|
|
98
112
|
if _should_exit(shell): break
|
|
99
|
-
finally:
|
|
113
|
+
finally:
|
|
114
|
+
_restore_echo(echo_state)
|
|
115
|
+
_restore_termios(output_state)
|
|
100
116
|
|
|
101
117
|
|
|
102
118
|
if __name__ == "__main__": main()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import os,re,select,shutil,subprocess,tempfile,time
|
|
1
|
+
import os,pty,re,select,shutil,subprocess,tempfile,time
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
4
|
|
|
@@ -55,10 +55,31 @@ def start_kernel(tmp_path):
|
|
|
55
55
|
return proc
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
def start_kernel_pty(tmp_path):
|
|
59
|
+
cmd = shutil.which("clikernel")
|
|
60
|
+
assert cmd, "clikernel console script is not on PATH"
|
|
61
|
+
env = os.environ.copy()
|
|
62
|
+
state = os.path.join(tempfile.gettempdir(), f"clikernel-{os.getuid()}")
|
|
63
|
+
env["CLIKERNEL_STATE_DIR"] = str(tmp_path / "state")
|
|
64
|
+
env.setdefault("IPYTHONDIR", os.path.join(state, "ipython"))
|
|
65
|
+
env.setdefault("MPLCONFIGDIR", os.path.join(state, "matplotlib"))
|
|
66
|
+
env.setdefault("MPLBACKEND", "Agg")
|
|
67
|
+
env["PYTHONUNBUFFERED"] = "1"
|
|
68
|
+
master, slave = pty.openpty()
|
|
69
|
+
try: proc = subprocess.Popen([cmd], stdin=slave, stdout=slave, stderr=subprocess.PIPE, env=env, close_fds=True)
|
|
70
|
+
finally: os.close(slave)
|
|
71
|
+
proc._pty_master = master
|
|
72
|
+
proc._pty_buffer = bytearray()
|
|
73
|
+
return proc
|
|
74
|
+
|
|
75
|
+
|
|
58
76
|
def stop_kernel(proc):
|
|
59
77
|
if proc.stdin is not None:
|
|
60
78
|
try: proc.stdin.close()
|
|
61
79
|
except OSError: pass
|
|
80
|
+
if hasattr(proc, "_pty_master"):
|
|
81
|
+
try: os.close(proc._pty_master)
|
|
82
|
+
except OSError: pass
|
|
62
83
|
if proc.poll() is None:
|
|
63
84
|
try: proc.wait(timeout=1)
|
|
64
85
|
except subprocess.TimeoutExpired:
|
|
@@ -77,6 +98,55 @@ def send(proc, text, timeout=TIMEOUT):
|
|
|
77
98
|
return read_until_ready(proc, timeout)
|
|
78
99
|
|
|
79
100
|
|
|
101
|
+
def _read_ptyline(proc, timeout):
|
|
102
|
+
buf = proc._pty_buffer
|
|
103
|
+
while b"\n" not in buf:
|
|
104
|
+
ready, _, _ = select.select([proc._pty_master], [], [], timeout)
|
|
105
|
+
if not ready: raise AssertionError(f"timed out waiting for clikernel pty output{_failure_detail(proc)}")
|
|
106
|
+
chunk = os.read(proc._pty_master, 4096)
|
|
107
|
+
assert chunk, _failure_detail(proc)
|
|
108
|
+
buf.extend(chunk)
|
|
109
|
+
line, _, rest = buf.partition(b"\n")
|
|
110
|
+
proc._pty_buffer = bytearray(rest)
|
|
111
|
+
return line.decode("utf-8", "replace").rstrip("\r") + "\n"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def read_pty_until_ready(proc, timeout=TIMEOUT):
|
|
115
|
+
lines = []
|
|
116
|
+
deadline = time.monotonic() + timeout
|
|
117
|
+
while True:
|
|
118
|
+
remaining = deadline - time.monotonic()
|
|
119
|
+
if remaining <= 0: raise AssertionError(f"timed out waiting for ready delimiter{_failure_detail(proc)}")
|
|
120
|
+
line = _read_ptyline(proc, remaining)
|
|
121
|
+
s = line.rstrip("\n")
|
|
122
|
+
if DELIM_RE.fullmatch(s): return "".join(lines), s
|
|
123
|
+
lines.append(line)
|
|
124
|
+
|
|
125
|
+
def _read_pty_rawline(proc, timeout):
|
|
126
|
+
buf = proc._pty_buffer
|
|
127
|
+
while b"\n" not in buf:
|
|
128
|
+
ready, _, _ = select.select([proc._pty_master], [], [], timeout)
|
|
129
|
+
if not ready: raise AssertionError(f"timed out waiting for clikernel pty output{_failure_detail(proc)}")
|
|
130
|
+
chunk = os.read(proc._pty_master, 4096)
|
|
131
|
+
assert chunk, _failure_detail(proc)
|
|
132
|
+
buf.extend(chunk)
|
|
133
|
+
line, _, rest = buf.partition(b"\n")
|
|
134
|
+
proc._pty_buffer = bytearray(rest)
|
|
135
|
+
return bytes(line + b"\n")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def read_pty_raw_until_ready(proc, timeout=TIMEOUT):
|
|
139
|
+
body = bytearray()
|
|
140
|
+
deadline = time.monotonic() + timeout
|
|
141
|
+
while True:
|
|
142
|
+
remaining = deadline - time.monotonic()
|
|
143
|
+
if remaining <= 0: raise AssertionError(f"timed out waiting for ready delimiter{_failure_detail(proc)}")
|
|
144
|
+
line = _read_pty_rawline(proc, remaining)
|
|
145
|
+
s = line.rstrip(b"\r\n").decode("utf-8", "replace")
|
|
146
|
+
if DELIM_RE.fullmatch(s): return bytes(body), s, line
|
|
147
|
+
body.extend(line)
|
|
148
|
+
|
|
149
|
+
|
|
80
150
|
@pytest.fixture
|
|
81
151
|
def kernel(tmp_path):
|
|
82
152
|
proc = start_kernel(tmp_path)
|
|
@@ -100,6 +170,31 @@ def test_single_line_request_returns_result_and_same_delimiter(kernel):
|
|
|
100
170
|
assert next_delim == delim
|
|
101
171
|
|
|
102
172
|
|
|
173
|
+
def test_tty_input_is_not_echoed_after_startup(tmp_path):
|
|
174
|
+
proc = start_kernel_pty(tmp_path)
|
|
175
|
+
try:
|
|
176
|
+
body, delim = read_pty_until_ready(proc)
|
|
177
|
+
assert body == "please wait, loading...\nloading complete. first delimiter:\n"
|
|
178
|
+
os.write(proc._pty_master, b"1+1\n")
|
|
179
|
+
assert _read_ptyline(proc, TIMEOUT) == ".\n"
|
|
180
|
+
body, next_delim = read_pty_until_ready(proc)
|
|
181
|
+
assert body == "2\n"
|
|
182
|
+
assert next_delim == delim
|
|
183
|
+
finally: stop_kernel(proc)
|
|
184
|
+
|
|
185
|
+
def test_tty_output_keeps_newlines_as_lf(tmp_path):
|
|
186
|
+
proc = start_kernel_pty(tmp_path)
|
|
187
|
+
try:
|
|
188
|
+
_, delim = read_pty_until_ready(proc)
|
|
189
|
+
os.write(proc._pty_master, b"print('hello')\n")
|
|
190
|
+
body, next_delim, raw_delim = read_pty_raw_until_ready(proc)
|
|
191
|
+
assert body == b".\nhello\n"
|
|
192
|
+
assert raw_delim == next_delim.encode() + b"\n"
|
|
193
|
+
assert b"\r" not in body + raw_delim
|
|
194
|
+
assert next_delim == delim
|
|
195
|
+
finally: stop_kernel(proc)
|
|
196
|
+
|
|
197
|
+
|
|
103
198
|
def test_request_ack_is_written_before_result(kernel):
|
|
104
199
|
proc, _, delim = kernel
|
|
105
200
|
assert proc.stdin is not None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|