Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
10 changes: 7 additions & 3 deletions caldav/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,13 @@ def read_config(fn, interactive_error=False):
f"config file {fn} is not valid JSON, and pyyaml is not installed"
) from None

except FileNotFoundError:
## File not found
logging.debug(f"config file {fn} not found")
except OSError as e:
## Optional config file is unusable (missing, a directory, no
## permission, ...). Treat any of these as "no config here" rather
## than letting e.g. IsADirectoryError propagate out of an optional
## config-file probe (happens when HOME is unset and the path expands
## to something like '//.config//caldav/calendar.conf').
logging.debug(f"config file {fn} not usable: {e}")
return {}
except ValueError:
# Re-raise ValueError so caller can handle config errors
Expand Down
22 changes: 14 additions & 8 deletions caldav/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,18 +213,24 @@ def stop(self) -> None:

if self.xapp_loop and self.xapp_runner:

async def cleanup_and_stop() -> None:
async def cleanup() -> None:
await self.xapp_runner.cleanup()
self.xapp_loop.stop()

try:
asyncio.run_coroutine_threadsafe(cleanup_and_stop(), self.xapp_loop).result(
timeout=10
)
asyncio.run_coroutine_threadsafe(cleanup(), self.xapp_loop).result(timeout=10)
except Exception:
if self.xapp_loop:
self.xapp_loop.call_soon_threadsafe(self.xapp_loop.stop)
elif self.xapp_loop:
# Best-effort cleanup: we swallow anything it raises (timeout,
# CancelledError, aiohttp errors). The Xandikos server is
# ephemeral per test against a throwaway serverdir, so there is
# no shared state to release and nothing to recover here.
pass
Comment thread
tobixen marked this conversation as resolved.

# Stop the loop from *outside* any coroutine running on it. Calling
# loop.stop() from within a coroutine scheduled via
# run_coroutine_threadsafe can stop the loop before it delivers that
# coroutine's result to the concurrent.futures.Future, so .result()
# would block until its timeout (~10s) on every single stop().
if self.xapp_loop:
self.xapp_loop.call_soon_threadsafe(self.xapp_loop.stop)

if self.thread:
Expand Down
12 changes: 9 additions & 3 deletions tests/test_async_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,15 @@ def check_compatibility_flag(self, flag: str) -> bool:
return flag in getattr(self._features, "_old_flags", [])

@pytest.fixture(scope="class")
def test_server(self) -> TestServer:
"""Get the test server for this class."""
server = self.server
@classmethod
def test_server(cls) -> TestServer:
"""Get the test server for this class.

Defined as a classmethod because pytest deprecates class-scoped
fixtures written as plain instance methods (each test gets a fresh
instance, so per-instance state set here would not be visible anyway).
"""
server = cls.server
server.start()
yield server
# Stop the server to free the port for other test modules
Expand Down
Loading