isolate 0.26.5__tar.gz → 0.26.7__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.
- {isolate-0.26.5 → isolate-0.26.7}/.github/workflows/test.yml +2 -2
- {isolate-0.26.5/src/isolate.egg-info → isolate-0.26.7}/PKG-INFO +1 -1
- {isolate-0.26.5 → isolate-0.26.7}/pyproject.toml +5 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/_isolate_version.py +3 -3
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/backends/common.py +6 -4
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/common.py +32 -11
- {isolate-0.26.5 → isolate-0.26.7/src/isolate.egg-info}/PKG-INFO +1 -1
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate.egg-info/SOURCES.txt +1 -0
- {isolate-0.26.5 → isolate-0.26.7}/tests/test_backends.py +1 -0
- isolate-0.26.7/tests/test_logged_io_pipe.py +132 -0
- {isolate-0.26.5 → isolate-0.26.7}/tests/test_serialization.py +36 -0
- {isolate-0.26.5 → isolate-0.26.7}/.github/workflows/claude-code-review.yml +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/.github/workflows/claude.yml +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/.github/workflows/lint.yml +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/.github/workflows/release.yml +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/.gitignore +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/.pre-commit-config.yaml +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/LICENSE +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/README.md +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/setup.cfg +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/__init__.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/_version.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/backends/__init__.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/backends/_base.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/backends/conda.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/backends/container.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/backends/local.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/backends/pyenv.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/backends/remote.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/backends/settings.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/backends/virtualenv.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/common/__init__.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/common/timestamp.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/__init__.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/_local/__init__.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/_local/_base.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/_local/agent_startup.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/__init__.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/_base.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/agent.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/configuration.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/definitions/__init__.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/definitions/agent.proto +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/definitions/agent_pb2.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/definitions/agent_pb2.pyi +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/definitions/agent_pb2_grpc.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/definitions/common.proto +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/definitions/common_pb2.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/definitions/common_pb2.pyi +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/definitions/common_pb2_grpc.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/interface.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/ipc/__init__.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/ipc/_base.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/ipc/agent.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/logger.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/logs.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/py.typed +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/registry.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/__init__.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/definitions/__init__.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/definitions/server.proto +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/definitions/server_pb2.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/definitions/server_pb2.pyi +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/definitions/server_pb2_grpc.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/health/__init__.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/health/health.proto +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/health/health_pb2.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/health/health_pb2.pyi +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/health/health_pb2_grpc.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/health_server.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/interface.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate/server/server.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate.egg-info/dependency_links.txt +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate.egg-info/entry_points.txt +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate.egg-info/requires.txt +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/src/isolate.egg-info/top_level.txt +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/tests/__init__.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/tests/conftest.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/tests/test_agent_backward_compatibility.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/tests/test_concurrency.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/tests/test_connections.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/tests/test_connections_grpc_base.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/tests/test_isolate.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/tests/test_log.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/tests/test_log_masking.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/tests/test_logger.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/tests/test_server.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/tests/test_shutdown.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/tools/Dockerfile +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/tools/agent_requirements.txt +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/tools/isolate_client.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/tools/protobuf-requirements.txt +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/tools/regen_grpc.py +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/tools/requirements.txt +0 -0
- {isolate-0.26.5 → isolate-0.26.7}/tools/test_agent_requirements.txt +0 -0
|
@@ -22,12 +22,12 @@ jobs:
|
|
|
22
22
|
|
|
23
23
|
matrix:
|
|
24
24
|
os: [ubuntu-latest]
|
|
25
|
-
python: ["3.8", "3.9", "3.10", "3.11"]
|
|
25
|
+
python: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
26
26
|
include:
|
|
27
27
|
- os: macos-15-intel
|
|
28
28
|
python: "3.8"
|
|
29
29
|
- os: macos-15-intel
|
|
30
|
-
python: "3.
|
|
30
|
+
python: "3.14"
|
|
31
31
|
steps:
|
|
32
32
|
- uses: actions/checkout@v3
|
|
33
33
|
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.26.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 26,
|
|
31
|
+
__version__ = version = '0.26.7'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 26, 7)
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'g9b3604b86'
|
|
@@ -166,13 +166,15 @@ def _close_fd(fd: int) -> None:
|
|
|
166
166
|
|
|
167
167
|
|
|
168
168
|
def _unblocked_pipe() -> tuple[int, int]:
|
|
169
|
-
"""Create a
|
|
170
|
-
|
|
171
|
-
|
|
169
|
+
"""Create a pipe whose read end is non-blocking, so the reader thread in
|
|
170
|
+
`_io_observer` can `select()` + `read()` without risk of hanging on a
|
|
171
|
+
partial-line edge case. The write end stays blocking so the worker's
|
|
172
|
+
stdout/stderr applies backpressure (waits for the reader to drain the
|
|
173
|
+
kernel pipe buffer) instead of raising BlockingIOError when the buffer
|
|
174
|
+
fills up — which happens reliably with chunky tracebacks on Python 3.14."""
|
|
172
175
|
|
|
173
176
|
read_fd, write_fd = os.pipe()
|
|
174
177
|
os.set_blocking(read_fd, False)
|
|
175
|
-
os.set_blocking(write_fd, False)
|
|
176
178
|
return read_fd, write_fd
|
|
177
179
|
|
|
178
180
|
|
|
@@ -4,6 +4,7 @@ import importlib
|
|
|
4
4
|
import os
|
|
5
5
|
from contextlib import contextmanager
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
+
from types import TracebackType
|
|
7
8
|
from typing import TYPE_CHECKING, Any, Iterator, cast
|
|
8
9
|
|
|
9
10
|
from tblib import Traceback, TracebackParseError
|
|
@@ -28,6 +29,14 @@ class SerializationError(Exception):
|
|
|
28
29
|
message: str
|
|
29
30
|
|
|
30
31
|
|
|
32
|
+
@dataclass
|
|
33
|
+
class ExceptionDeserializationError(SerializationError):
|
|
34
|
+
"""Raised when a remote exception cannot be deserialized locally (e.g. the
|
|
35
|
+
module that defines its type isn't importable here)."""
|
|
36
|
+
|
|
37
|
+
original_traceback: TracebackType | None
|
|
38
|
+
|
|
39
|
+
|
|
31
40
|
# NOTE: tblib's install() will search for BaseException subclasses,
|
|
32
41
|
# so we have to call it after the SerializationError is defined.
|
|
33
42
|
tblib_install()
|
|
@@ -77,8 +86,19 @@ def load_serialized_object(
|
|
|
77
86
|
importlib.import_module(serialization_method)
|
|
78
87
|
)
|
|
79
88
|
|
|
80
|
-
|
|
81
|
-
|
|
89
|
+
try:
|
|
90
|
+
with _step("deserializing the given object"):
|
|
91
|
+
result = serialization_backend.loads(raw_object)
|
|
92
|
+
except SerializationError as exc:
|
|
93
|
+
if was_it_raised:
|
|
94
|
+
# We were trying to reconstruct a remote exception but its type
|
|
95
|
+
# isn't importable here, so loads() failed. Surface the genuine
|
|
96
|
+
# local cause along with the remote traceback.
|
|
97
|
+
raise ExceptionDeserializationError(
|
|
98
|
+
exc.message,
|
|
99
|
+
original_traceback=_prepare_traceback(stringized_traceback),
|
|
100
|
+
) from exc.__cause__
|
|
101
|
+
raise
|
|
82
102
|
|
|
83
103
|
if was_it_raised:
|
|
84
104
|
raise prepare_exc(result, stringized_traceback=stringized_traceback)
|
|
@@ -111,18 +131,19 @@ def validate_entrypoint(entrypoint: str) -> None:
|
|
|
111
131
|
raise ValueError(f"Invalid entrypoint {entrypoint!r}: expected 'module:attr'.")
|
|
112
132
|
|
|
113
133
|
|
|
134
|
+
def _prepare_traceback(stringized_traceback: str | None) -> TracebackType | None:
|
|
135
|
+
if stringized_traceback:
|
|
136
|
+
try:
|
|
137
|
+
return Traceback.from_string(stringized_traceback).as_traceback()
|
|
138
|
+
except TracebackParseError:
|
|
139
|
+
pass
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
|
|
114
143
|
def prepare_exc(
|
|
115
144
|
exc: BaseException,
|
|
116
145
|
*,
|
|
117
146
|
stringized_traceback: str | None = None,
|
|
118
147
|
) -> BaseException:
|
|
119
|
-
|
|
120
|
-
try:
|
|
121
|
-
traceback = Traceback.from_string(stringized_traceback).as_traceback()
|
|
122
|
-
except TracebackParseError:
|
|
123
|
-
traceback = None
|
|
124
|
-
else:
|
|
125
|
-
traceback = None
|
|
126
|
-
|
|
127
|
-
exc.__traceback__ = traceback
|
|
148
|
+
exc.__traceback__ = _prepare_traceback(stringized_traceback)
|
|
128
149
|
return exc
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Regression tests for the pipe wiring used by `logged_io`.
|
|
2
|
+
|
|
3
|
+
Background: `_unblocked_pipe()` is consumed by `logged_io()`, where the
|
|
4
|
+
*read* end is drained from a reader thread (`_io_observer`) using
|
|
5
|
+
`select()` + `os.read()`. Only the read end needs to be non-blocking
|
|
6
|
+
(so the post-`select` read can never hang on a partial-line edge case).
|
|
7
|
+
|
|
8
|
+
Historically both ends were marked non-blocking. That makes the *write*
|
|
9
|
+
end raise `BlockingIOError` (errno EAGAIN) the moment the kernel pipe
|
|
10
|
+
buffer (~64 KB on Linux) fills up before the reader thread drains it —
|
|
11
|
+
which is exactly what happens when a worker emits a chunky traceback.
|
|
12
|
+
With a blocking write end, writers apply backpressure instead.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
import threading
|
|
21
|
+
|
|
22
|
+
import pytest
|
|
23
|
+
from isolate.backends.common import _unblocked_pipe, logged_io
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_unblocked_pipe_write_end_is_blocking():
|
|
27
|
+
"""The write end must be blocking so that worker stdout/stderr applies
|
|
28
|
+
backpressure instead of raising BlockingIOError when the pipe buffer fills."""
|
|
29
|
+
read_fd, write_fd = _unblocked_pipe()
|
|
30
|
+
try:
|
|
31
|
+
assert not os.get_blocking(read_fd), "read end must be non-blocking"
|
|
32
|
+
assert os.get_blocking(write_fd), (
|
|
33
|
+
"write end must be blocking so the worker gets backpressure "
|
|
34
|
+
"instead of BlockingIOError when the pipe buffer fills up"
|
|
35
|
+
)
|
|
36
|
+
finally:
|
|
37
|
+
os.close(read_fd)
|
|
38
|
+
os.close(write_fd)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_writing_more_than_pipe_buffer_does_not_raise_blocking_io_error():
|
|
42
|
+
"""Direct reproducer: write more than the kernel pipe buffer (64 KB on
|
|
43
|
+
Linux) through a Python BufferedWriter on top of the pipe's write end,
|
|
44
|
+
while a reader thread drains it slowly. With a non-blocking write end
|
|
45
|
+
this raises `BlockingIOError`; with a blocking write end the writer
|
|
46
|
+
transparently waits for the reader to make room.
|
|
47
|
+
"""
|
|
48
|
+
read_fd, write_fd = _unblocked_pipe()
|
|
49
|
+
drained = bytearray()
|
|
50
|
+
drain_done = threading.Event()
|
|
51
|
+
|
|
52
|
+
def drain():
|
|
53
|
+
# Slow reader: read in small chunks with a `select` loop, mimicking
|
|
54
|
+
# `_io_observer`'s pattern but slower so the pipe buffer fills up.
|
|
55
|
+
import select as _select
|
|
56
|
+
|
|
57
|
+
while not drain_done.is_set():
|
|
58
|
+
ready, _, _ = _select.select([read_fd], [], [], 0.05)
|
|
59
|
+
if read_fd in ready:
|
|
60
|
+
try:
|
|
61
|
+
chunk = os.read(read_fd, 4096)
|
|
62
|
+
except BlockingIOError:
|
|
63
|
+
continue
|
|
64
|
+
if not chunk:
|
|
65
|
+
return
|
|
66
|
+
drained.extend(chunk)
|
|
67
|
+
|
|
68
|
+
reader = threading.Thread(target=drain)
|
|
69
|
+
reader.start()
|
|
70
|
+
try:
|
|
71
|
+
# 512 KB is well past the 64 KB pipe buffer on Linux.
|
|
72
|
+
payload = b"x" * (512 * 1024)
|
|
73
|
+
with open(write_fd, "wb", closefd=False) as stream:
|
|
74
|
+
stream.write(payload)
|
|
75
|
+
stream.flush()
|
|
76
|
+
# Give the reader a moment to catch up.
|
|
77
|
+
deadline_loops = 50
|
|
78
|
+
while len(drained) < len(payload) and deadline_loops > 0:
|
|
79
|
+
deadline_loops -= 1
|
|
80
|
+
drain_done.wait(0.05)
|
|
81
|
+
finally:
|
|
82
|
+
drain_done.set()
|
|
83
|
+
os.close(write_fd)
|
|
84
|
+
reader.join(timeout=2)
|
|
85
|
+
os.close(read_fd)
|
|
86
|
+
|
|
87
|
+
assert len(drained) == len(
|
|
88
|
+
payload
|
|
89
|
+
), f"reader only drained {len(drained)} of {len(payload)} bytes"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@pytest.mark.skipif(sys.platform == "win32", reason="pipes / pass_fds are POSIX-y")
|
|
93
|
+
def test_logged_io_subprocess_emitting_large_output_does_not_crash():
|
|
94
|
+
"""End-to-end reproducer mirroring how `logged_io` is used in
|
|
95
|
+
`isolate.connections._local._base`: a child process writes a chunky
|
|
96
|
+
payload (~256 KB) to stdout. With a non-blocking write end the child's
|
|
97
|
+
Python runtime raises `BlockingIOError` when flushing stdout; with
|
|
98
|
+
backpressure the child completes cleanly and the parent receives
|
|
99
|
+
every line."""
|
|
100
|
+
|
|
101
|
+
captured: list[str] = []
|
|
102
|
+
lock = threading.Lock()
|
|
103
|
+
|
|
104
|
+
def record(line: str) -> None:
|
|
105
|
+
with lock:
|
|
106
|
+
captured.append(line)
|
|
107
|
+
|
|
108
|
+
n_lines = 4000
|
|
109
|
+
line_template = "trace-{idx:08d}-" + ("y" * 50)
|
|
110
|
+
child_script = (
|
|
111
|
+
"import sys\n"
|
|
112
|
+
f"for i in range({n_lines}):\n"
|
|
113
|
+
f" sys.stdout.write({line_template!r}.format(idx=i) + '\\n')\n"
|
|
114
|
+
"sys.stdout.flush()\n"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
with logged_io(record) as (stdout, stderr, log_fd):
|
|
118
|
+
proc = subprocess.run(
|
|
119
|
+
[sys.executable, "-u", "-c", child_script],
|
|
120
|
+
stdout=stdout,
|
|
121
|
+
stderr=stderr,
|
|
122
|
+
pass_fds=(log_fd,),
|
|
123
|
+
text=True,
|
|
124
|
+
check=False,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
assert proc.returncode == 0, (
|
|
128
|
+
f"child exited with {proc.returncode}; with a non-blocking write end "
|
|
129
|
+
"Python's stdout buffer raises BlockingIOError on large flushes"
|
|
130
|
+
)
|
|
131
|
+
# All `n_lines` should have been captured by the observer thread.
|
|
132
|
+
assert len(captured) == n_lines, f"expected {n_lines} lines, got {len(captured)}"
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import sys
|
|
3
|
+
import traceback
|
|
1
4
|
from functools import partial
|
|
2
5
|
|
|
3
6
|
import pytest
|
|
4
7
|
from isolate.connections.common import (
|
|
8
|
+
ExceptionDeserializationError,
|
|
5
9
|
SerializationError,
|
|
6
10
|
load_serialized_object,
|
|
7
11
|
serialize_object,
|
|
@@ -37,6 +41,38 @@ def test_deserialize_raised_exception():
|
|
|
37
41
|
assert exc_info.value.args == ("some error",)
|
|
38
42
|
|
|
39
43
|
|
|
44
|
+
def test_deserialize_raised_exception_with_unimportable_type_preserves_traceback(
|
|
45
|
+
tmp_path,
|
|
46
|
+
monkeypatch,
|
|
47
|
+
):
|
|
48
|
+
module_name = "remote_only_exc_for_isolate_test"
|
|
49
|
+
module_path = tmp_path / f"{module_name}.py"
|
|
50
|
+
module_path.write_text("class RemoteOnlyError(Exception):\n pass\n")
|
|
51
|
+
monkeypatch.syspath_prepend(str(tmp_path))
|
|
52
|
+
remote_module = importlib.import_module(module_name)
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
raise remote_module.RemoteOnlyError("remote boom")
|
|
56
|
+
except remote_module.RemoteOnlyError as exc:
|
|
57
|
+
serialized = serialize_object("pickle", exc)
|
|
58
|
+
stringized_traceback = traceback.format_exc()
|
|
59
|
+
|
|
60
|
+
sys.modules.pop(module_name, None)
|
|
61
|
+
sys.path.remove(str(tmp_path))
|
|
62
|
+
|
|
63
|
+
with pytest.raises(ExceptionDeserializationError) as exc_info:
|
|
64
|
+
load_serialized_object(
|
|
65
|
+
"pickle",
|
|
66
|
+
serialized,
|
|
67
|
+
was_it_raised=True,
|
|
68
|
+
stringized_traceback=stringized_traceback,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
assert exc_info.value.message == "Error while deserializing the given object"
|
|
72
|
+
assert exc_info.value.original_traceback is not None
|
|
73
|
+
assert isinstance(exc_info.value.__cause__, ModuleNotFoundError)
|
|
74
|
+
|
|
75
|
+
|
|
40
76
|
def error_while_serializing():
|
|
41
77
|
anon = lambda: 2 + 2 # anonymous functions are not # noqa: E731
|
|
42
78
|
# serializable by pickle
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/definitions/agent_pb2_grpc.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{isolate-0.26.5 → isolate-0.26.7}/src/isolate/connections/grpc/definitions/common_pb2_grpc.py
RENAMED
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|