Files
romm/DEVELOPER_SETUP.md
T
7d3e3ec1ab perf(tests): faster database setup and cleanup in the backend suite (#5073)
* perf(tests): skip empty tables in clear_database and run migration tests first

clear_database issued 20 DELETEs after every test. One EXISTS query now finds which tables hold rows and only those are cleared. The slow migration tests are scheduled first so they overlap the rest of the suite under xdist.

* perf(tests): clone a migrated template into each MariaDB xdist worker

Replaying every migration in each worker at once was most of a cold run. The template database is migrated once under a server lock and copied into each empty worker database. PostgreSQL keeps running migrations per worker.

* fix(tests): address pr-ready code review

Key the MariaDB template on a digest of the migrations and the helpers
they import, so an edited revision or a template left at another
branch's head or half-migrated is rebuilt instead of reused or failing.
Copy alembic_version last and reset a worker database that has tables
but no version row, so a clone cut short is redone on the next run.

* refactor(tests): pr-ready simplify pass

* style(tests): pr-ready polish

* style(tests): pr-ready humanizer pass

* test(tests): cover the template copy and empty-table cleanup

Addresses the Greptile finding that the new database setup paths had no
assertions:
- test_copy_schema_reproduces_the_migrated_database copies the migrated
  worker database with _copy_schema and checks tables, views, triggers,
  row checksums and the alembic version match the source (MariaDB only).
- test_clear_tables_deletes_only_from_tables_with_rows checks that cleanup
  issues a DELETE only for tables holding rows. The fixture body moves into
  _clear_tables so the test can call it.

* fix(tests): address pr-ready code review (xhigh)

Hash all of models/ and utils/ into the template digest, warn when the
clone falls back to migrating, defer the digest past the early returns,
and dedupe the DEFINER regex, statement capture and schema-name queries.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* refactor(tests): pr-ready simplify pass

Read tables and views in one query, strip DEFINER once, use the
inspector for the version check, always recreate an unversioned worker
DB, and pull the server lock and template build into helpers.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* style(tests): pr-ready polish

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* test(db): clear every model table between tests

Derive the cleared tables from BaseModel.metadata.sorted_tables, children
first, instead of a hand-kept list of 20 models. Views and the
permission tables the migrations seed are skipped. A new model is now
cleared without editing the fixture.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* perf(tests): build the MariaDB template once in the xdist controller

pytest_configure_node checks each worker's database on the controller
and, for one with no version row, builds the template (once per session)
and hands its name over through workerinput. Workers copy it without
taking the lock, so copies run in parallel, and a broken migration
fails once instead of once per waiting worker. The template is shared
by every checkout and ROMM_TEST_DB_TAG run, since its digest pins the
schema. DEVELOPER_SETUP.md documents it.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

---------

Co-authored-by: Georges-Antoine Assi <[email protected]>
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-03 12:52:09 -04:00

8.0 KiB

Setting up RomM for development

Option 1: Using Docker

If you prefer to use Docker for development, you can set up RomM using the provided Docker Compose configuration. This method simplifies the setup process by encapsulating all dependencies within Docker containers.

Environment setup

Create the mock structure with at least one rom and empty config for manual testing

mkdir -p romm_mock/library/roms/switch
touch romm_mock/library/roms/switch/metroid.xci
mkdir -p romm_mock/resources
mkdir -p romm_mock/assets
mkdir -p romm_mock/config
touch romm_mock/config/config.yml

Copy env.template to .env and fill the variables

cp env.template .env
ROMM_BASE_PATH=/app/romm
DEV_MODE=true

Build the image

docker compose build  # or `docker compose build --no-cache` to rebuild from scratch

Spin up the Docker containers

docker compose up -d

And you're done! You can access the app at http://localhost:3000. Any changes made to the code will be automatically reflected in the app thanks to the volume mounts.

Optional stacks

Two extra stacks sit in their own files, since most work needs neither. Both join the dev stack's network, so bring docker compose up -d up first.

docker compose -f docker-compose.oidc.yml up -d       # Authentik, for OIDC work
docker compose -f docker-compose.streaming.yml up -d  # webstation, for streaming work

Authentik listens on http://localhost:9001; point RomM at it with the OIDC_* variables in .env. The webstation image is amd64-only and several GB; it reads romm_mock/webstation for emulator configs and BIOS, and its BROKER_SECRET follows STREAMING_BROKER_SECRET from .env.

Option 2: Manual setup

Environment setup

- Create the mock structure with at least one rom and empty config for manual testing

mkdir -p romm_mock/library/roms/switch
touch romm_mock/library/roms/switch/metroid.xci
mkdir -p romm_mock/resources
mkdir -p romm_mock/assets
mkdir -p romm_mock/config
touch romm_mock/config/config.yml

- Copy env.template to .env and fill the variables

cp env.template .env

- Install system dependencies

# https://mariadb.com/docs/skysql-previous-release/connect/programming-languages/c/install/#Installation_via_Package_Repository_(Linux):
sudo apt install libmariadb3 libmariadb-dev libpq-dev

# Build and configure RAHasher (optional)
# This is only required to calculate RA hashes
# Users on macOS can skip this step as RAHasher is not supported
git clone --recursive https://github.com/RetroAchievements/RALibretro.git
cd ./RALibretro
git checkout 1.8.3
git submodule update --init --recursive
make HAVE_CHD=1 -f ./Makefile.RAHasher
cp ./bin64/RAHasher /usr/bin/RAHasher

- Install python dependencies

scripts/dev-setup.sh installs the pinned uv, Python, and Node versions plus the backend and frontend dependencies in one step, and is safe to rerun. On systems without apt-get, install the MariaDB and PostgreSQL client headers yourself first. To do it by hand instead, follow the steps below.

You'll need uv installed

https://docs.astral.sh/uv/getting-started/installation/

curl -LsSf https://astral.sh/uv/install.sh | sh

Then create the virtual environment and install the dependencies using uv:

uv venv
source .venv/bin/activate
uv sync --all-extras --dev

- Spin up the database and other services

docker compose up -d

- Run the backend

Migrations will be run automatically when running the backend.

cd backend
uv run python3 main.py

- Run the task workers

A manual task run is refused, and a scheduled job stays queued, until an RQ worker listens on its queue; scans and streaming teardowns each have a worker of their own. Each command is a foreground process, so run them in separate terminals. -c config reads the Redis connection from the same REDIS_* settings in .env the backend uses.

cd backend
uv run rq worker -c config --worker-class handler.rq_worker.RomMWorker --with-scheduler high default low
cd backend
uv run rq worker -c config --worker-class handler.rq_worker.RomMWorker --with-scheduler scans
cd backend
uv run rq worker -c config --worker-class handler.rq_worker.PreloadingWorker --with-scheduler streaming

--with-scheduler releases delayed jobs, such as the rescans the filesystem watcher waits out, so each worker needs it. The recurring schedule is registered by the RQ cron process, which the workers then execute:

cd backend
uv run rq cron -c config tasks.cron_config

Setting up the frontend

- Install node.js dependencies

cd frontend
# npm version >= 9 needed
npm install

Vite serves cover art, screenshots and manuals from assets/romm/resources. The link target resolves from the link's own folder, hence the three levels up:

mkdir -p assets/romm
ln -s ../../../romm_mock/resources assets/romm/resources

- Run the frontend

npm run dev

- Storybook (v2 components)

Component docs and visual QA for frontend/src/v2/ (port 6006):

npm run storybook
npm run storybook:test   # composeStories + play() + a11y (every v2 story)

For responsive layouts, use the viewport toolbar presets from .storybook/rommViewports.ts.

- Optional: local frontend against a remote RomM

To run npm run dev or npm run preview against another RomM instance (for example a home server with a full library), set DEV_PROXY_TARGET in the repo-root .env to its origin, such as https://romm.example.com. Vite then proxies /api, /ws and /assets/romm there instead of the local backend. Leave it empty to keep the default http://127.0.0.1:${DEV_PORT}. The remote's TLS certificate must be valid. Sign in with a username and password: OIDC login does not work through the proxy, because the identity provider redirects to the remote's OIDC_REDIRECT_URI, so the session never reaches localhost.

Setting up the linter

We use Trunk for linting, which combines multiple linters and formatters with sensible defaults and a single configuration file. You'll need to install the Trunk CLI to use it.

- Install the Trunk CLI

curl https://get.trunk.io -fsSL | bash

Alternative installation methods can be found in their docs. On commit, the linter will run automatically. To run it manually, use the following commands:

trunk fmt
trunk check

Type checking runs separately from Trunk, against the project's own environment. CI type-checks the whole backend on every backend change; to run it locally:

cd backend
uv run mypy --config-file ../.trunk/configs/mypy.ini .

Failing to install and run the linter will result in a failed CI check, which won't allow us to merge your PR.

Test setup

- Create the test user and database with root user

docker exec -i romm-db-dev mariadb -uroot -p<root password> < backend/romm_test/setup.sql

- Run tests

Migrations will be run automatically when running the tests.

cd backend
# path or test file can be passed as argument to test only a subset
uv run pytest [path/file]
# or run the following command to run all tests, one worker per CPU core
uv run pytest -n auto

Each -n worker gets its own database (romm_test_gw0, romm_test_gw1, ...), created on demand; setup.sql grants the test user the rights to create them. On MariaDB, a new worker database is copied from romm_test_template_<digest>, which the run migrates once and keeps for later runs. The digest covers alembic/, models/ and utils/, so editing any of them builds a new template. To migrate from scratch, drop both the worker databases and the template. On a machine with many cores, cap the workers with --maxprocesses 4 (CI's count) to keep the load on the database down. A serial run of the whole suite takes 20+ minutes. Drop -n auto when debugging with --pdb or -s, which don't work under parallel workers.