Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion mypy/metastore.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,12 @@ def __init__(
if cache_dir_prefix.startswith(os.devnull):
return

os.makedirs(cache_dir_prefix, exist_ok=True)
try:
os.makedirs(cache_dir_prefix, exist_ok=True)
except OSError:
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps a bit too broad, but catching all sort of possible FS read-only states can be difficult, and also this is consistent with what FilesystemMetadataStore was doing..

# Prevent failing on read-only filesystem and such.
return

if num_shards <= 1:
self.dbs.append(
connect_db(os_path_join(cache_dir_prefix, "cache.db"), set_journal_mode)
Expand Down
50 changes: 50 additions & 0 deletions mypy/test/testmetastore.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from __future__ import annotations

import os
import sys
import tempfile
import unittest
from collections.abc import Iterator
from contextlib import contextmanager

from mypy.metastore import SqliteMetadataStore


@contextmanager
def _read_only_dir(path: str) -> Iterator[str]:
original_mode = os.stat(path).st_mode
os.chmod(path, 0o555)
try:
yield path
finally:
os.chmod(path, original_mode)


@unittest.skipIf(
sys.platform == "win32",
"POSIX chmod semantics: os.chmod(dir, 0o555) does not prevent writes on Windows",
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't sure what's better -- excluding test altogether, or checking the same thing after store = SqliteMetadataStore(cache_dir) and returning early/skipping in the test body.

I figured excluding it altogether is less confusing.

)
class TestSqliteMetadataStore(unittest.TestCase):
def test_init_degrades_to_noop_when_cache_dir_not_creatable(self) -> None:
with tempfile.TemporaryDirectory() as parent, _read_only_dir(parent):
cache_dir = os.path.join(parent, "mypy_cache")

# Must not raise.
store = SqliteMetadataStore(cache_dir)

# Degraded to no-op state, matching the os.devnull short-circuit
# and FilesystemMetadataStore's behavior on read-only filesystems.
self.assertEqual(store.dbs, [])
self.assertFalse(store.write("foo.meta.json", b"{}"))
with self.assertRaises(FileNotFoundError):
store.read("foo.meta.json")
with self.assertRaises(FileNotFoundError):
store.getmtime("foo.meta.json")
self.assertEqual(list(store.list_all()), [])
# commit/close must be safe on an empty store
store.commit()
store.close()


if __name__ == "__main__":
unittest.main()
Loading