Randomize test order

This commit is contained in:
Joakim Hellsén 2026-03-07 06:43:32 +01:00
commit dcd86eff69
Signed by: Joakim Hellsén
SSH key fingerprint: SHA256:/9h/CsExpFp+PRhsfA0xznFx2CGfTT5R/kpuFfUgEQk
6 changed files with 114 additions and 4 deletions

View file

@ -44,7 +44,6 @@ type TAG_VALUE = (
| list[str | int | float | bool | dict[str, Any] | list[Any] | None] | list[str | int | float | bool | dict[str, Any] | list[Any] | None]
| None | None
) )
"""Type alias for the value of a feed tag, which can be a nested structure of dicts and lists, or None."""
# Tags that are exported per-feed (empty values are omitted). # Tags that are exported per-feed (empty values are omitted).
_FEED_TAGS: tuple[str, ...] = ( _FEED_TAGS: tuple[str, ...] = (

View file

@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import os
import typing import typing
from functools import lru_cache from functools import lru_cache
from pathlib import Path from pathlib import Path
@ -12,7 +13,12 @@ from reader import make_reader
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from reader.types import JSONType from reader.types import JSONType
data_dir: str = user_data_dir(appname="discord_rss_bot", appauthor="TheLovinator", roaming=True, ensure_exists=True) data_dir: str = os.getenv("DISCORD_RSS_BOT_DATA_DIR", "").strip() or user_data_dir(
appname="discord_rss_bot",
appauthor="TheLovinator",
roaming=True,
ensure_exists=True,
)
# TODO(TheLovinator): Add default things to the database and make the edible. # TODO(TheLovinator): Add default things to the database and make the edible.

View file

@ -22,7 +22,7 @@ dependencies = [
] ]
[dependency-groups] [dependency-groups]
dev = ["djlint", "pytest"] dev = ["djlint", "pytest", "pytest-randomly", "pytest-xdist"]
[build-system] [build-system]
requires = ["poetry-core>=1.0.0"] requires = ["poetry-core>=1.0.0"]
@ -87,6 +87,7 @@ lint.ignore = [
"tests/*" = ["S101", "D103", "PLR2004"] "tests/*" = ["S101", "D103", "PLR2004"]
[tool.pytest.ini_options] [tool.pytest.ini_options]
addopts = "-n 5 --dist loadfile"
filterwarnings = [ filterwarnings = [
"ignore::bs4.GuessedAtParserWarning", "ignore::bs4.GuessedAtParserWarning",
"ignore:functools\\.partial will be a method descriptor in future Python versions; wrap it in staticmethod\\(\\) if you want to preserve the old behavior:FutureWarning", "ignore:functools\\.partial will be a method descriptor in future Python versions; wrap it in staticmethod\\(\\) if you want to preserve the old behavior:FutureWarning",

40
tests/conftest.py Normal file
View file

@ -0,0 +1,40 @@
from __future__ import annotations
import os
import shutil
import sys
import tempfile
from contextlib import suppress
from pathlib import Path
from typing import Any
def pytest_configure() -> None:
"""Isolate persistent app state per xdist worker to avoid cross-worker test interference."""
worker_id: str = os.environ.get("PYTEST_XDIST_WORKER", "gw0")
worker_data_dir: Path = Path(tempfile.gettempdir()) / "discord-rss-bot-tests" / worker_id
# Start each worker from a clean state.
shutil.rmtree(worker_data_dir, ignore_errors=True)
worker_data_dir.mkdir(parents=True, exist_ok=True)
os.environ["DISCORD_RSS_BOT_DATA_DIR"] = str(worker_data_dir)
# If modules were imported before this hook (unlikely), force them to use
# the worker-specific location.
settings_module: Any = sys.modules.get("discord_rss_bot.settings")
if settings_module is not None:
settings_module.data_dir = str(worker_data_dir)
get_reader: Any = getattr(settings_module, "get_reader", None)
if get_reader is not None and hasattr(get_reader, "cache_clear"):
get_reader.cache_clear()
main_module: Any = sys.modules.get("discord_rss_bot.main")
if main_module is not None and settings_module is not None:
with suppress(Exception):
current_reader = getattr(main_module, "reader", None)
if current_reader is not None:
current_reader.close()
get_reader: Any = getattr(settings_module, "get_reader", None)
if callable(get_reader):
main_module.reader = get_reader()

View file

@ -81,6 +81,14 @@ def test_add_webhook() -> None:
def test_create_feed() -> None: def test_create_feed() -> None:
"""Test the /create_feed page.""" """Test the /create_feed page."""
# Ensure webhook exists for this test regardless of test order.
client.post(url="/delete_webhook", data={"webhook_url": webhook_url})
response: Response = client.post(
url="/add_webhook",
data={"webhook_name": webhook_name, "webhook_url": webhook_url},
)
assert response.status_code == 200, f"Failed to add webhook: {response.text}"
# Remove the feed if it already exists before we run the test. # Remove the feed if it already exists before we run the test.
feeds: Response = client.get(url="/") feeds: Response = client.get(url="/")
if feed_url in feeds.text: if feed_url in feeds.text:
@ -99,6 +107,14 @@ def test_create_feed() -> None:
def test_get() -> None: def test_get() -> None:
"""Test the /create_feed page.""" """Test the /create_feed page."""
# Ensure webhook exists for this test regardless of test order.
client.post(url="/delete_webhook", data={"webhook_url": webhook_url})
response: Response = client.post(
url="/add_webhook",
data={"webhook_name": webhook_name, "webhook_url": webhook_url},
)
assert response.status_code == 200, f"Failed to add webhook: {response.text}"
# Remove the feed if it already exists before we run the test. # Remove the feed if it already exists before we run the test.
feeds: Response = client.get("/") feeds: Response = client.get("/")
if feed_url in feeds.text: if feed_url in feeds.text:
@ -144,6 +160,14 @@ def test_get() -> None:
def test_pause_feed() -> None: def test_pause_feed() -> None:
"""Test the /pause_feed page.""" """Test the /pause_feed page."""
# Ensure webhook exists for this test regardless of test order.
client.post(url="/delete_webhook", data={"webhook_url": webhook_url})
response: Response = client.post(
url="/add_webhook",
data={"webhook_name": webhook_name, "webhook_url": webhook_url},
)
assert response.status_code == 200, f"Failed to add webhook: {response.text}"
# Remove the feed if it already exists before we run the test. # Remove the feed if it already exists before we run the test.
feeds: Response = client.get(url="/") feeds: Response = client.get(url="/")
if feed_url in feeds.text: if feed_url in feeds.text:
@ -152,6 +176,7 @@ def test_pause_feed() -> None:
# Add the feed. # Add the feed.
response: Response = client.post(url="/add", data={"feed_url": feed_url, "webhook_dropdown": webhook_name}) response: Response = client.post(url="/add", data={"feed_url": feed_url, "webhook_dropdown": webhook_name})
assert response.status_code == 200, f"Failed to add feed: {response.text}"
# Unpause the feed if it is paused. # Unpause the feed if it is paused.
feeds: Response = client.get(url="/") feeds: Response = client.get(url="/")
@ -171,6 +196,14 @@ def test_pause_feed() -> None:
def test_unpause_feed() -> None: def test_unpause_feed() -> None:
"""Test the /unpause_feed page.""" """Test the /unpause_feed page."""
# Ensure webhook exists for this test regardless of test order.
client.post(url="/delete_webhook", data={"webhook_url": webhook_url})
response: Response = client.post(
url="/add_webhook",
data={"webhook_name": webhook_name, "webhook_url": webhook_url},
)
assert response.status_code == 200, f"Failed to add webhook: {response.text}"
# Remove the feed if it already exists before we run the test. # Remove the feed if it already exists before we run the test.
feeds: Response = client.get("/") feeds: Response = client.get("/")
if feed_url in feeds.text: if feed_url in feeds.text:
@ -179,6 +212,7 @@ def test_unpause_feed() -> None:
# Add the feed. # Add the feed.
response: Response = client.post(url="/add", data={"feed_url": feed_url, "webhook_dropdown": webhook_name}) response: Response = client.post(url="/add", data={"feed_url": feed_url, "webhook_dropdown": webhook_name})
assert response.status_code == 200, f"Failed to add feed: {response.text}"
# Pause the feed if it is unpaused. # Pause the feed if it is unpaused.
feeds: Response = client.get(url="/") feeds: Response = client.get(url="/")
@ -198,6 +232,14 @@ def test_unpause_feed() -> None:
def test_remove_feed() -> None: def test_remove_feed() -> None:
"""Test the /remove page.""" """Test the /remove page."""
# Ensure webhook exists for this test regardless of test order.
client.post(url="/delete_webhook", data={"webhook_url": webhook_url})
response: Response = client.post(
url="/add_webhook",
data={"webhook_name": webhook_name, "webhook_url": webhook_url},
)
assert response.status_code == 200, f"Failed to add webhook: {response.text}"
# Remove the feed if it already exists before we run the test. # Remove the feed if it already exists before we run the test.
feeds: Response = client.get(url="/") feeds: Response = client.get(url="/")
if feed_url in feeds.text: if feed_url in feeds.text:
@ -206,6 +248,7 @@ def test_remove_feed() -> None:
# Add the feed. # Add the feed.
response: Response = client.post(url="/add", data={"feed_url": feed_url, "webhook_dropdown": webhook_name}) response: Response = client.post(url="/add", data={"feed_url": feed_url, "webhook_dropdown": webhook_name})
assert response.status_code == 200, f"Failed to add feed: {response.text}"
# Remove the feed. # Remove the feed.
response: Response = client.post(url="/remove", data={"feed_url": feed_url}) response: Response = client.post(url="/remove", data={"feed_url": feed_url})
@ -374,6 +417,14 @@ def test_show_more_entries_button_visible_when_many_entries() -> None:
def test_show_more_entries_button_not_visible_when_few_entries() -> None: def test_show_more_entries_button_not_visible_when_few_entries() -> None:
"""Test that the 'Show more entries' button is not visible when there are 20 or fewer entries.""" """Test that the 'Show more entries' button is not visible when there are 20 or fewer entries."""
# Ensure webhook exists for this test regardless of test order.
client.post(url="/delete_webhook", data={"webhook_url": webhook_url})
response: Response = client.post(
url="/add_webhook",
data={"webhook_name": webhook_name, "webhook_url": webhook_url},
)
assert response.status_code == 200, f"Failed to add webhook: {response.text}"
# Use a feed with very few entries # Use a feed with very few entries
small_feed_url = "https://lovinator.space/rss_test_small.xml" small_feed_url = "https://lovinator.space/rss_test_small.xml"

View file

@ -58,8 +58,21 @@ def test_per_feed_update_interval() -> None:
def test_reset_feed_update_interval() -> None: def test_reset_feed_update_interval() -> None:
"""Test resetting feed update interval to global default.""" """Test resetting feed update interval to global default."""
# Ensure feed/webhook setup exists regardless of test order
client.post(url="/delete_webhook", data={"webhook_url": webhook_url})
client.post(url="/remove", data={"feed_url": feed_url})
response: Response = client.post(
url="/add_webhook",
data={"webhook_name": webhook_name, "webhook_url": webhook_url},
)
assert response.status_code == 200, f"Failed to add webhook: {response.text}"
response = client.post(url="/add", data={"feed_url": feed_url, "webhook_dropdown": webhook_name})
assert response.status_code == 200, f"Failed to add feed: {response.text}"
# First set a custom interval # First set a custom interval
response: Response = client.post("/set_update_interval", data={"feed_url": feed_url, "interval_minutes": "15"}) response = client.post("/set_update_interval", data={"feed_url": feed_url, "interval_minutes": "15"})
assert response.status_code == 200, f"Failed to set feed interval: {response.text}" assert response.status_code == 200, f"Failed to set feed interval: {response.text}"
# Reset to global default # Reset to global default