Probe.check() joined a check already running, or a finished one nobody had collected. A folder deleted right after configure() woke the probe therefore stayed available until the next probe, which failed the Overview notice spec in the merge queue. It now waits for a running check and starts a new one, still never two at once, and a stuck check still times out. The spec also asserts the fixture's removal took effect.
94 lines
3.1 KiB
Python
94 lines
3.1 KiB
Python
"""The thumbnail folder check answers for the folder as it is now (#320).
|
|
|
|
A check that started before the caller asked can miss what just happened: #592
|
|
hit it in the merge queue, where a folder deleted right after
|
|
``configure`` woke the probe stayed "available" until the next probe.
|
|
"""
|
|
|
|
import sys
|
|
import threading
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
|
|
from py.api import preview_location, preview_store # noqa: E402
|
|
|
|
WAIT = 5
|
|
HERE = (True, None, {"id": "x"})
|
|
GONE = (False, "missing", None)
|
|
KEY = ("/thumbs", "x")
|
|
|
|
|
|
class TestProbeCheck(unittest.TestCase):
|
|
def setUp(self):
|
|
self.probe = preview_store._Probe()
|
|
|
|
def test_a_finished_check_nobody_collected_is_not_the_answer(self):
|
|
with patch.object(preview_location, "folder_state", return_value=HERE):
|
|
with self.probe._lock:
|
|
_key, done, _result = self.probe._start_check(*KEY)
|
|
self.assertTrue(done.wait(WAIT))
|
|
with patch.object(preview_location, "folder_state", return_value=GONE):
|
|
self.assertEqual(self.probe.check(*KEY), GONE)
|
|
|
|
def test_a_check_already_running_is_followed_by_a_new_one(self):
|
|
started = threading.Event()
|
|
release = threading.Event()
|
|
calls = []
|
|
|
|
def state(_path, _expected_id):
|
|
calls.append(threading.current_thread().name)
|
|
if len(calls) == 1:
|
|
started.set()
|
|
self.assertTrue(release.wait(WAIT))
|
|
return HERE
|
|
return GONE
|
|
|
|
answers = []
|
|
with patch.object(preview_location, "folder_state", state):
|
|
with self.probe._lock:
|
|
self.probe._start_check(*KEY)
|
|
self.assertTrue(started.wait(WAIT))
|
|
caller = threading.Thread(
|
|
target=lambda: answers.append(self.probe.check(*KEY))
|
|
)
|
|
caller.start()
|
|
release.set()
|
|
caller.join(WAIT)
|
|
self.assertFalse(caller.is_alive())
|
|
self.assertEqual(answers, [GONE])
|
|
self.assertEqual(len(calls), 2) # one after the other, never two at once
|
|
|
|
def test_a_stuck_check_is_never_doubled(self):
|
|
release = threading.Event()
|
|
self.addCleanup(release.set)
|
|
calls = []
|
|
|
|
def state(_path, _expected_id):
|
|
calls.append(1)
|
|
release.wait(WAIT)
|
|
return HERE
|
|
|
|
with (
|
|
patch.object(preview_location, "folder_state", state),
|
|
patch.object(preview_store, "IO_BUDGET", 0.05),
|
|
):
|
|
with self.probe._lock:
|
|
self.probe._start_check(*KEY)
|
|
with self.assertRaises(TimeoutError):
|
|
self.probe.check(*KEY)
|
|
self.assertEqual(len(calls), 1)
|
|
|
|
def test_an_error_from_the_new_check_reaches_the_caller(self):
|
|
with patch.object(
|
|
preview_location, "folder_state", side_effect=OSError("share gone")
|
|
):
|
|
with self.assertRaises(OSError):
|
|
self.probe.check(*KEY)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|