Skip to content

Commit a1c656d

Browse files
committed
fix(runtime): skip a relative import path entry when the working directory is gone
The import hook read the working directory outside its error handling, so after the directory was deleted every import through a relative search path entry raised FileNotFoundError. The entry is skipped again, as before the hook keyed entries by directory. A SQL macro lexer test now names the raw-string prefix rules it uses.
1 parent 9633260 commit a1c656d

4 files changed

Lines changed: 46 additions & 2 deletions

File tree

‎crates/sqlbuild-analysis/src/sql_macros/tests/test_lexical_rules.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ fn given_adapter_lexical_rules_when_finding_sql_macro_calls_then_strings_end_whe
3131
expected_calls: &["order_total"],
3232
},
3333
LexicalRulesCallsTestCase {
34-
description: "a raw string ending in a backslash on BigQuery",
34+
description: "a raw string ending in a backslash under raw-string prefix rules",
3535
syntax: raw_prefix_rules(),
3636
sql: "SELECT r'C:\\' AS path, @sql.order_total(1) AS b",
3737
expected_calls: &["order_total"],

‎src/sqlbuild/runtime/user_code/classes/user_code_finder.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,10 @@ def _is_project_entry(self, entry: str) -> bool:
6060
if roots != self._entry_roots:
6161
self._project_entries = {}
6262
self._entry_roots = roots
63-
directory: str = "" if os.path.isabs(entry) else os.getcwd()
63+
try:
64+
directory: str = "" if os.path.isabs(entry) else os.getcwd()
65+
except OSError:
66+
return False
6467
known: bool | None = self._project_entries.get((entry, directory))
6568
if known is not None:
6669
return known

‎tests/unit/src/sqlbuild/runtime/user_code/classes/_test_types.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,12 @@ class FinderWorkingDirectoryTestCase:
2121
first_directory: str
2222
second_directory: str
2323
expected_source_only: tuple[bool, bool]
24+
25+
26+
@dataclass(frozen=True)
27+
class FinderDeletedDirectoryTestCase:
28+
"""A lookup while the working directory, on the search path as "", has been deleted."""
29+
30+
description: str
31+
module_name: str
32+
expected_spec: None = None

‎tests/unit/src/sqlbuild/runtime/user_code/classes/test_user_code_finder.py‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from sqlbuild.runtime.user_code.classes.source_only_loader import SourceOnlyLoader
1010
from sqlbuild.runtime.user_code.classes.user_code_finder import UserCodeFinder
1111
from tests.unit.src.sqlbuild.runtime.user_code.classes._test_types import (
12+
FinderDeletedDirectoryTestCase,
1213
FinderSpecTestCase,
1314
FinderWorkingDirectoryTestCase,
1415
)
@@ -98,5 +99,36 @@ def test_given_working_directory_entry_when_directory_changes_then_finder_follow
9899
) == test_case.expected_source_only
99100

100101

102+
@pytest.mark.parametrize(
103+
"test_case",
104+
[
105+
FinderDeletedDirectoryTestCase(
106+
description="a project module is left to normal import", module_name="order_rules"
107+
),
108+
FinderDeletedDirectoryTestCase(
109+
description="a standard-library module is left to normal import", module_name="json"
110+
),
111+
],
112+
ids=lambda case: case.description,
113+
)
114+
def test_given_deleted_working_directory_when_finding_spec_then_relative_entry_is_skipped(
115+
test_case: FinderDeletedDirectoryTestCase, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
116+
) -> None:
117+
project: Path = tmp_path / "project"
118+
project.mkdir()
119+
_ = (project / "order_rules.py").write_text("LIMIT: int = 1\n", encoding="utf-8")
120+
deleted: Path = tmp_path / "deleted"
121+
deleted.mkdir()
122+
monkeypatch.setattr(sys, "path", ["", *sys.path])
123+
finder: UserCodeFinder = UserCodeFinder()
124+
finder.roots.add(project.resolve())
125+
monkeypatch.chdir(deleted)
126+
deleted.rmdir()
127+
128+
spec: ModuleSpec | None = finder.find_spec(test_case.module_name)
129+
130+
assert spec == test_case.expected_spec
131+
132+
101133
if __name__ == "__main__":
102134
raise SystemExit(pytest.main([__file__, "-n", "auto", "--dist", "loadfile"]))

0 commit comments

Comments
 (0)