Skip to content

Commit 514fe38

Browse files
committed
Refactor conftest.py: Move to integration directory and use pytest hooks
- Move tests/conftest.py to tests/integration/conftest.py for better scope - Replace fixture-based setup with pytest_sessionstart/sessionfinish hooks - Setup containers BEFORE test names are printed for cleaner output - Add devcontainer protection (vsc-*, _devcontainer suffix) in cleanup logic - Implement selenium container reuse between test runs for faster execution - Add health checking for existing containers with automatic recovery - Separate cleanup logic: app containers removed, selenium kept for reuse - Add environment variables: APP_LEAVE_RUNNING, SELENIUM_LEAVE_RUNNING
1 parent 9246345 commit 514fe38

File tree

13 files changed

+436
-317
lines changed

13 files changed

+436
-317
lines changed

.devcontainer/Makefile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ initialize:
1515
@echo "SEMANTIC_VERSION=$(SEMANTIC_VERSION)" >> .devcontainer/.env
1616

1717

18+
19+
# One-step container setup invoked by devcontainer postCreateCommand
20+
setup-container:
21+
@echo "Running entrypoint scripts..."
22+
@for script in .devcontainer/entrypoint.d/*.sh; do \
23+
echo "Running $$script"; \
24+
bash "$$script" || echo "entrypoint script failed: $$script"; \
25+
done
26+
@echo "Entrypoint scripts completed."
27+
28+
1829
# Install development dependencies (only if pyproject.toml changed)
1930
uv.lock:
2031
uv lock

.devcontainer/devcontainer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
}
2121
},
2222
"initializeCommand": "make initialize",
23-
"postCreateCommand": "sudo chown -R ${USER}:${USER} ${PROJECT_PATH} && uv sync --dev && ./.devcontainer/setup-dotfiles.sh",
23+
"postCreateCommand": "make setup-container",
2424
"runArgs": [
2525
"--name","${localWorkspaceFolderBasename}_devcontainer",
2626
"--hostname","${localWorkspaceFolderBasename}",
File renamed without changes.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
set -o errexit # abort on nonzero exitstatus
3+
set -o nounset # abort on unbound variable
4+
set -o pipefail # don't hide errors within pipes
5+
# set -x # Uncomment for debugging
6+
7+
sudo chown -R ${USER}:${USER} ${PROJECT_PATH}
8+
sudo chown -R ${USER}:${USER} ${HOME}
9+
sudo chown -R ${USER}:${USER} /var/run/docker.sock
10+
11+
uv sync --dev

.vscode/settings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@
4040
"date +%F": true,
4141
"ls": true,
4242
"/^uv run (pytest|black|isort|behave|python).*/": true,
43-
"uv sync": true,
43+
"/^uv sync .*/": true,
4444
"/^make (?:run|format|lint|test(?:-[\\w-]+)?)$/": true,
4545
"/^git (add|log|status|show\\b.*).*/": true,
46-
"docker build": true,
46+
"/^docker (build|inspect|logs|ps).*/": true,
4747
"behave": true
4848
},
4949

Dockerfile

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -67,32 +67,42 @@ RUN sed -i 's/UID_MAX .*/UID_MAX 100000/' /etc/login.defs && \
6767

6868
COPY --chmod=755 <<-"EOF" /usr/local/bin/docker-entrypoint.sh
6969
#!/bin/bash
70-
set -o errexit # abort on nonzero exitstatus
71-
set -o nounset # abort on unbound variable
72-
set -o pipefail # do not hide errors within pipes
73-
if [ -v DOCKER_ENTRYPOINT_DEBUG ] && [ "$DOCKER_ENTRYPOINT_DEBUG" == 1 ]; then
70+
# Bash entrypoint: use bash for richer debugging and reliable `pipefail` support.
71+
# Use strict flags that are bash-compatible.
72+
set -euo pipefail
73+
if [ "${DOCKER_ENTRYPOINT_DEBUG:-}" = "1" ]; then
7474
set -x
75-
set -o xtrace
7675
fi
7776

7877
# If running as root, adjust the ${USER} user's UID/GID and drop to that user
7978
if [ "$(id -u)" = "0" ]; then
8079
groupmod -o -g ${PGID:-1000} ${USER} 2>&1 >/dev/null|| true
8180
usermod -o -u ${PUID:-1000} ${USER} 2>&1 >/dev/null|| true
8281

83-
# Ensure docker.sock is owned by the target user when running as root
84-
chown ${USER}:${USER} /var/run/docker.sock >/dev/null 2>&1 || true
85-
chown -R ${USER}:${USER} ${PROJECT_PATH}
82+
# Run optional devcontainer entrypoint hooks if present. Development-only
83+
# scripts placed in ${PROJECT_PATH}/.devcontainer/entrypoint.d/ will be
84+
# executed here at container start. Production images don't include this
85+
# directory so changes there won't affect production image layer caching.
86+
if [ -d "${PROJECT_PATH}/.devcontainer/entrypoint.d" ]; then
87+
for hook in "${PROJECT_PATH}"/.devcontainer/entrypoint.d/*.sh; do
88+
if [ -f "$hook" ]; then
89+
echo "devcontainer hook START: $hook at $(date) PID=$$"
90+
set +e # Allow the hook to fail without aborting startup
91+
bash "$hook"
92+
rc=$?
93+
set -e
94+
echo "devcontainer hook FINISH: $hook at $(date) rc=${rc}"
95+
fi
96+
done
97+
fi
98+
99+
# Ensure project path ownership for the runtime user
100+
# Only touch the docker socket if it exists (socket or file) - one-liner
101+
( [ -S /var/run/docker.sock ] || [ -e /var/run/docker.sock ] ) && chown ${USER}:${USER} /var/run/docker.sock || true
102+
chown -R ${USER}:${USER} ${PROJECT_PATH} || true
86103

87104
echo "Running as user ${USER}: $@"
88105
exec gosu ${USER} "$@"
89-
else
90-
# If not running as root, attempt to chown docker.sock using sudo if available
91-
if command -v sudo >/dev/null 2>&1; then
92-
sudo chown ${USER}:${USER} /var/run/docker.sock >/dev/null 2>&1 || true
93-
sudo chown -R ${USER}:${USER} ${HOME}
94-
sudo chown -R ${USER}:${USER} ${PROJECT_PATH}
95-
fi
96106
fi
97107

98108
echo "Running: $@"
@@ -255,7 +265,7 @@ RUN --mount=type=cache,target=/tmp/.cache/uv \
255265
--mount=type=cache,target=/root/.cache/pip \
256266
--mount=type=cache,target=/root/.cache/uv \
257267
uv sync --dev && \
258-
chown -R $USER:$USER /usr/local/lib/python3.12/site-packages/ && \
268+
chown -R $USER:$USER /usr/local/lib/python*/site-packages/ && \
259269
chown -R $USER:$USER /usr/local/bin
260270

261271

app/services/bookmark_bar_manager.py

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import json
22
import logging
33
import os
4+
import time
45

56
from app.models.utils import pwd
67
from app.services.favicon_store import FaviconStore
@@ -42,10 +43,62 @@ def bookmarks_list(self, bookmarks, urls=[]):
4243

4344
def reload(self):
4445
logger.debug("Beginning Bookmark Bar reload...")
45-
with open(self.bookmark_bar_path, "r") as file:
46-
self._bar = json.load(file)
47-
self.last_reload = self.mtime
4846

49-
self.favicon_store.fetch_favicons_from(self.bookmarks_list(self._bar))
47+
try:
48+
# Read file content first so we can back it up if parsing fails
49+
with open(self.bookmark_bar_path, "r", encoding="utf-8") as file:
50+
content = file.read()
5051

51-
logger.debug("Completed Bookmark Bar reload!")
52+
try:
53+
new_bar = json.loads(content)
54+
except json.JSONDecodeError as e:
55+
# Backup the corrupt file for inspection and keep previous bar in memory
56+
logger.error("Failed to parse bookmark bar JSON: %s", e)
57+
try:
58+
backup_path = f"{self.bookmark_bar_path}.corrupt.{int(time.time())}"
59+
with open(backup_path, "w", encoding="utf-8") as bf:
60+
bf.write(content)
61+
logger.warning(
62+
"Backed up corrupt bookmarks file to %s", backup_path
63+
)
64+
except Exception as be:
65+
logger.exception("Failed to write corrupt backup file: %s", be)
66+
67+
if hasattr(self, "_bar") and self._bar:
68+
logger.info(
69+
"Keeping previous bookmark bar in memory after parse failure."
70+
)
71+
# Don't update last_reload so is_modified remains True until a valid file is present
72+
return
73+
else:
74+
logger.info(
75+
"No previous bookmark bar available; falling back to empty list."
76+
)
77+
new_bar = []
78+
79+
# Assign the parsed content and update mtime
80+
self._bar = new_bar
81+
self.last_reload = self.mtime
82+
83+
# Fetch favicons but don't let favicon failures crash the app
84+
try:
85+
self.favicon_store.fetch_favicons_from(self.bookmarks_list(self._bar))
86+
except Exception:
87+
logger.exception("Failed to fetch favicons during bookmark bar reload")
88+
89+
logger.debug("Completed Bookmark Bar reload!")
90+
91+
except FileNotFoundError:
92+
logger.warning(
93+
"Bookmark bar file not found at %s; using empty bar",
94+
self.bookmark_bar_path,
95+
)
96+
# Ensure we have a bar to render
97+
if not hasattr(self, "_bar") or self._bar is None:
98+
self._bar = []
99+
self.last_reload = 0
100+
except Exception:
101+
# Catch-all to prevent reload failures from bubbling up into requests
102+
logger.exception("Unexpected error while reloading bookmark bar")
103+
if not hasattr(self, "_bar") or self._bar is None:
104+
self._bar = []

0 commit comments

Comments
 (0)