blob: b66c2ff299519878de31b4ae866779cae7428cfe [file]
from __future__ import annotations
import contextlib
import threading
from typing import TYPE_CHECKING
from unittest.mock import patch
import pytest
from watchdog.events import FileModifiedEvent, FileSystemEventHandler
from watchdog.observers.api import BaseObserver, EventEmitter
if TYPE_CHECKING:
from collections.abc import Iterator
@pytest.fixture
def observer() -> Iterator[BaseObserver]:
obs = BaseObserver(EventEmitter)
yield obs
obs.stop()
with contextlib.suppress(RuntimeError):
obs.join()
@pytest.fixture
def observer2():
obs = BaseObserver(EventEmitter)
yield obs
obs.stop()
with contextlib.suppress(RuntimeError):
obs.join()
def test_schedule_should_start_emitter_if_running(observer):
observer.start()
observer.schedule(None, "")
(emitter,) = observer.emitters
assert emitter.is_alive()
def test_schedule_should_not_start_emitter_if_not_running(observer):
observer.schedule(None, "")
(emitter,) = observer.emitters
assert not emitter.is_alive()
def test_start_should_start_emitter(observer):
observer.schedule(None, "")
observer.start()
(emitter,) = observer.emitters
assert emitter.is_alive()
def test_stop_should_stop_emitter(observer):
observer.schedule(None, "")
observer.start()
(emitter,) = observer.emitters
assert emitter.is_alive()
observer.stop()
observer.join()
assert not observer.is_alive()
assert not emitter.is_alive()
def test_unschedule_self(observer):
"""
Tests that unscheduling a watch from within an event handler correctly
correctly unregisters emitter and handler without deadlocking.
"""
class EventHandler(FileSystemEventHandler):
def on_modified(self, event):
observer.unschedule(watch)
unschedule_finished.set()
unschedule_finished = threading.Event()
watch = observer.schedule(EventHandler(), "")
observer.start()
(emitter,) = observer.emitters
emitter.queue_event(FileModifiedEvent(""))
assert unschedule_finished.wait()
assert len(observer.emitters) == 0
def test_schedule_after_unschedule_all(observer):
observer.start()
observer.schedule(None, "")
assert len(observer.emitters) == 1
observer.unschedule_all()
assert len(observer.emitters) == 0
observer.schedule(None, "")
assert len(observer.emitters) == 1
def test_2_observers_on_the_same_path(observer, observer2):
assert observer is not observer2
observer.schedule(None, "")
assert len(observer.emitters) == 1
observer2.schedule(None, "")
assert len(observer2.emitters) == 1
def test_start_failure_should_not_prevent_further_try(observer):
observer.schedule(None, "")
emitters = observer.emitters
assert len(emitters) == 1
# Make the emitter to fail on start()
def mocked_start():
raise OSError("Mock'ed!")
emitter = next(iter(emitters))
with patch.object(emitter, "start", new=mocked_start), pytest.raises(OSError, match="Mock'ed!"):
observer.start()
# The emitter should be removed from the list
assert len(observer.emitters) == 0
# Restoring the original behavior should work like there never be emitters
observer.start()
assert len(observer.emitters) == 0
# Re-scheduling the watch should work
observer.schedule(None, "")
assert len(observer.emitters) == 1
def test_schedule_failure_should_not_prevent_future_schedules(observer):
observer.start()
# Make the emitter fail on start(), and subsequently the observer to fail on schedule()
def bad_start(_):
raise OSError("Mock'ed!")
with patch.object(EventEmitter, "start", new=bad_start), pytest.raises(OSError, match="Mock'ed!"):
observer.schedule(None, "")
# The emitter should not be in the list
assert not observer.emitters
# Re-scheduling the watch should work
observer.schedule(None, "")
assert len(observer.emitters) == 1