Ensure `Console` is locked and prevent `formatted_traceback` being unexpectedly truncated (#4905)

Co-authored-by: Gyeongjae Choi <def6488@gmail.com>
This commit is contained in:
Muspi Merol 2024-07-02 19:23:01 +08:00 committed by GitHub
parent 5fb36a458b
commit 9428c6f69e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 43 additions and 9 deletions

View File

@ -50,6 +50,13 @@ myst:
in `CodeRunner` and `Console`. in `CodeRunner` and `Console`.
{pr}`4897` {pr}`4897`
- {{ Fix }} Fixed a bug that caused `Console`'s `formatted_traceback` being truncated
unexpectedly when `filename` is specified.
{pr}`4905`
- {{ Fix }} Locked `PyodideConsole.runcode` to block `loadPackagesFromImports`.
{pr}`4905`
### Packages ### Packages
- Upgraded `scikit-learn` to 1.5 {pr}`4823` - Upgraded `scikit-learn` to 1.5 {pr}`4823`

View File

@ -405,18 +405,21 @@ class Console:
res.set_result(fut.result()) res.set_result(fut.result())
res = None res = None
ensure_future(self.runcode(source, code)).add_done_callback(done_cb) ensure_future(self._runcode_with_lock(source, code)).add_done_callback(done_cb)
return res return res
async def _runcode_with_lock(self, source: str, code: CodeRunner) -> Any:
async with self._lock:
return await self.runcode(source, code)
async def runcode(self, source: str, code: CodeRunner) -> Any: async def runcode(self, source: str, code: CodeRunner) -> Any:
"""Execute a code object and return the result.""" """Execute a code object and return the result."""
async with self._lock: with self.redirect_streams():
with self.redirect_streams(): try:
try: return await code.run_async(self.globals)
return await code.run_async(self.globals) finally:
finally: sys.stdout.flush()
sys.stdout.flush() sys.stderr.flush()
sys.stderr.flush()
def formatsyntaxerror(self, e: Exception) -> str: def formatsyntaxerror(self, e: Exception) -> str:
"""Format the syntax error that just occurred. """Format the syntax error that just occurred.
@ -435,7 +438,7 @@ class Console:
kept_frames = 0 kept_frames = 0
# Try to trim out stack frames inside our code # Try to trim out stack frames inside our code
for frame, _ in traceback.walk_tb(tb): for frame, _ in traceback.walk_tb(tb):
keep_frames = keep_frames or frame.f_code.co_filename == "<console>" keep_frames = keep_frames or frame.f_code.co_filename == self.filename
keep_frames = keep_frames or frame.f_code.co_filename == "<exec>" keep_frames = keep_frames or frame.f_code.co_filename == "<exec>"
if keep_frames: if keep_frames:
kept_frames += 1 kept_frames += 1

View File

@ -288,6 +288,7 @@ def test_nonpersistent_redirection(safe_sys_redirections):
asyncio.run(test()) asyncio.run(test())
@pytest.mark.asyncio
async def test_compile_optimize(): async def test_compile_optimize():
from pyodide.console import Console from pyodide.console import Console
@ -300,6 +301,29 @@ async def test_compile_optimize():
assert await console.push("f.__doc__") is None assert await console.push("f.__doc__") is None
@pytest.mark.asyncio
async def test_console_filename():
from pyodide.console import Console
for filename in ("<console>", "<exec>", "other"):
future = Console(filename=filename).push("assert 0")
with pytest.raises(AssertionError):
await future
assert isinstance(future.formatted_error, str)
assert f'File "{filename}", line 1, in <module>' in future.formatted_error
@pytest.mark.skip_refcount_check
@run_in_pyodide
async def test_pyodide_console_runcode_locked(selenium):
from pyodide.console import PyodideConsole
console = PyodideConsole()
console.push("import micropip")
await console.push("micropip")
@pytest.mark.skip_refcount_check @pytest.mark.skip_refcount_check
@run_in_pyodide @run_in_pyodide
async def test_console_imports(selenium): async def test_console_imports(selenium):