# -*- coding: utf-8 -*- """auth_system 同步栈的读写并发验证:读不持锁、写仍串行(WAL)。""" import sqlite3 import threading import time import pytest import auth_system @pytest.fixture() def temp_site(tmp_path, monkeypatch): monkeypatch.setattr(auth_system.db, 'DATA_DIR', tmp_path) monkeypatch.setattr(auth_system.db, 'DB_PATH', tmp_path / 'site.db') monkeypatch.setattr(auth_system.db, 'BACKUP_DIR', tmp_path / 'backups') monkeypatch.setattr(auth_system.db, 'SECRET_KEY_PATH', tmp_path / 'secret.key') auth_system.init_db() return tmp_path / 'site.db' def test_q_does_not_hold_lock(temp_site): """_q 读不应获取 _DB_LOCK(否则读会被写挡住)。""" # 让另一个线程持有写锁,读仍应在短时间返回(不被阻塞) held = threading.Event() release = threading.Event() def holder(): with auth_system._DB_LOCK: held.set() release.wait(5) t = threading.Thread(target=holder, daemon=True) t.start() held.wait(2) try: start = time.monotonic() rows = auth_system._q('SELECT COUNT(*) AS n FROM users') elapsed = time.monotonic() - start assert rows is not None assert elapsed < 1.0, f'读被写锁阻塞了 {elapsed:.2f}s(WAL 下不应发生)' finally: release.set() t.join(2) def test_concurrent_reads_and_writes(temp_site): """多读线程 + 一个写线程并发:无异常、结果一致。""" errors = [] stop = threading.Event() def writer(): try: for i in range(50): auth_system.set_setting(f'k{i}', str(i)) except Exception as e: errors.append(('w', e)) finally: stop.set() def reader(): while not stop.is_set(): try: auth_system.get_setting('k0') except Exception as e: errors.append(('r', e)) ws = threading.Thread(target=writer, daemon=True) rs = [threading.Thread(target=reader, daemon=True) for _ in range(4)] ws.start() for r in rs: r.start() ws.join(20) for r in rs: r.join(2) assert not errors, f'并发读写异常: {errors[:3]}' assert auth_system.get_setting('k49') == '49' def test_wal_enabled(temp_site): conn = sqlite3.connect(str(temp_site)) try: mode = conn.execute('PRAGMA journal_mode').fetchone()[0] finally: conn.close() assert str(mode).lower() == 'wal'