Skip to content

Commit d60be6f

Browse files
Fix UI hang and stabilize tests for unusual tuple/list/dict child classes (#9468)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 64b99d0 commit d60be6f

3 files changed

Lines changed: 70 additions & 10 deletions

File tree

marimo/_utils/flatten.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -292,11 +292,14 @@ def _contains_instance(value: Any) -> bool:
292292
return False
293293
seen.add(id(value))
294294

295-
if isinstance(value, (tuple, list)):
296-
return any(_contains_instance(v) for v in value)
297-
elif isinstance(value, dict):
298-
return any(_contains_instance(v) for v in value.values())
299-
else:
300-
return isinstance(value, instance)
295+
try:
296+
if isinstance(value, (tuple, list)):
297+
return any(_contains_instance(v) for v in value)
298+
elif isinstance(value, dict):
299+
return any(_contains_instance(v) for v in value.values())
300+
except Exception:
301+
# .__iter__() or .values() raised. Cannot probe container.
302+
return False
303+
return isinstance(value, instance)
301304

302305
return _contains_instance(value)

tests/_server/api/endpoints/test_terminal.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,17 +71,25 @@ def test_terminal_ws_wrong_token(client: TestClient) -> None:
7171

7272

7373
class TestCreateShellEnvironment:
74-
def test_create_shell_environment_default_cwd(self) -> None:
74+
def test_create_shell_environment_default_cwd(
75+
self, monkeypatch: pytest.MonkeyPatch
76+
) -> None:
7577
"""Test shell environment creation with default working directory."""
78+
monkeypatch.delenv("SHELL", raising=False)
79+
7680
shell, env = _create_shell_environment()
7781

7882
assert shell in ["/bin/bash", "/bin/zsh", "/bin/sh"]
7983
assert env["TERM"] == "xterm-256color"
8084
assert "LANG" in env
8185
assert "LC_ALL" in env
8286

83-
def test_create_shell_environment_custom_cwd(self) -> None:
87+
def test_create_shell_environment_custom_cwd(
88+
self, monkeypatch: pytest.MonkeyPatch
89+
) -> None:
8490
"""Test shell environment creation with custom working directory."""
91+
monkeypatch.delenv("SHELL", raising=False)
92+
8593
with tempfile.TemporaryDirectory() as temp_dir:
8694
shell, env = _create_shell_environment(cwd=temp_dir)
8795

@@ -91,9 +99,14 @@ def test_create_shell_environment_custom_cwd(self) -> None:
9199
@patch("os.getcwd")
92100
@patch("pathlib.Path.home")
93101
def test_create_shell_environment_fallback_cwd(
94-
self, mock_home: Mock, mock_getcwd: Mock
102+
self,
103+
mock_home: Mock,
104+
mock_getcwd: Mock,
105+
monkeypatch: pytest.MonkeyPatch,
95106
) -> None:
96107
"""Test shell environment creation when getcwd fails."""
108+
monkeypatch.delenv("SHELL", raising=False)
109+
97110
mock_getcwd.side_effect = OSError("No such directory")
98111
mock_home.return_value = Path("/home/user")
99112

@@ -105,9 +118,14 @@ def test_create_shell_environment_fallback_cwd(
105118
@patch("os.getcwd")
106119
@patch("pathlib.Path.home")
107120
def test_create_shell_environment_ultimate_fallback(
108-
self, mock_home: Mock, mock_getcwd: Mock
121+
self,
122+
mock_home: Mock,
123+
mock_getcwd: Mock,
124+
monkeypatch: pytest.MonkeyPatch,
109125
) -> None:
110126
"""Test shell environment creation when both getcwd and home fail."""
127+
monkeypatch.delenv("SHELL", raising=False)
128+
111129
mock_getcwd.side_effect = OSError("No such directory")
112130
mock_home.side_effect = Exception("No home directory")
113131

tests/_utils/test_flatten.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,3 +325,42 @@ class A:
325325
for _ in range(5):
326326
value.append(value)
327327
assert contains_instance(value, A) is True
328+
329+
330+
def test_contains_instance_no_iter_tuple() -> None:
331+
class NoIterTuple(tuple):
332+
def __iter__(self):
333+
raise AttributeError("iter throws")
334+
335+
collection = NoIterTuple((1, 2, 3))
336+
assert contains_instance(collection, NoIterTuple) is False
337+
assert contains_instance(collection, tuple) is False
338+
assert (
339+
contains_instance(collection, int) is False
340+
) # Cannot probe opaque collection
341+
342+
343+
def test_contains_instance_no_iter_list() -> None:
344+
class NoIterList(list):
345+
def __iter__(self):
346+
raise AttributeError("iter throws")
347+
348+
collection = NoIterList([1, 2, 3])
349+
assert contains_instance(collection, NoIterList) is False
350+
assert contains_instance(collection, list) is False
351+
assert (
352+
contains_instance(collection, int) is False
353+
) # Cannot probe opaque collection
354+
355+
356+
def test_contains_instance_no_iter_dict() -> None:
357+
class NoValuesDict(dict):
358+
def values(self):
359+
raise AttributeError("values() throws")
360+
361+
collection = NoValuesDict({"a": 1, "b": 2, "c": 3})
362+
assert contains_instance(collection, NoValuesDict) is False
363+
assert contains_instance(collection, dict) is False
364+
assert (
365+
contains_instance(collection, int) is False
366+
) # Cannot probe opaque collection

0 commit comments

Comments
 (0)