Files
ubuntu de978187d0
ci / lint (ruff) (push) Successful in 1m22s
ci / pytest (py3.10) (push) Successful in 9m41s
ci / pytest (py3.11) (push) Successful in 9m38s
ci / pytest (py3.12) (push) Successful in 9m25s
ci / pytest (py3.13) (push) Successful in 9m42s
fix(ci): align runs-on labels with gitea/act_runner defaults — advertise ubuntu-22.04 instead of self-hosted
2026-07-08 08:19:55 +00:00

8.1 KiB
Raw Permalink Blame History

Sviluppo

Setup locale, pytest, ruff, e come estendere il client.


Setup

git clone https://192.168.0.183:8085/truetype/viandante.git
cd viandante

python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

Lo sviluppo quotidiano è Python puro; nessun build system esterno (setuptools è trasparente via pyproject.toml).


Test

pytest                              # 66+ test (telnet + ansi + deadsouls + mapper + logger)
pytest tests/test_logger.py -v      # solo il logger (più verboso)
pytest -k "test_rolling_parser"     # filtrato per nome
pytest --tb=short                   # traceback corti

I test usano pytest.ini_options = ["-q"] di default in pyproject.toml.

Cosa è coperto

File Copre
test_ansi.py stripper ANSI + visible_len
test_telnet.py IACParser (state machine completa) + TelnetConnection (mock asyncio)
test_deadsouls.py parse_exits + RollingRoomParser scenario multi‑chunk
test_mapper.py Mapper.visit, link, layout BFS, orphan placement, render
test_logger.py EventLogger round‑trip JSONL, MD rendering per ogni kind, redazione password, idempotenza close(), lone‑ESC safety

Cosa non è coperto

  • cli.py end‑to‑end (richiede telnet server; il vero test è una smoke run contro il docker locale di Dead Souls).
  • mapper._render_vertical_row (casi degeneri con celle non continue; non aggiunti perché non osservati in pratica).
  • Re‑entrata del logger in un worker thread (lock difensivo è documentato ma non testato).

Lint

ruff check src tests
ruff format src tests

Config in pyproject.toml:

[tool.ruff]
line-length = 100
target-version = "py310"

(Aggiungi regole strict select = ["E", "F", "I", "B", "UP"] se vuoi enforcement.)


Smoke test end‑to‑end

Dead Souls docker su localhost:6666:

# 2 secondi, log JSONL + MD
LOGJSON=$(mktemp /tmp/mud-json.XXXXXX.jsonl)
LOGMD=$(mktemp /tmp/mud-md.XXXXXX.md)
timeout 2 python -m viandante --host localhost --port 6666 \
    --log-json "$LOGJSON" --log-md "$LOGMD" </dev/null >/dev/null 2>&1 || true
wc -l "$LOGJSON" "$LOGMD"
head -3 "$LOGJSON"
head -8 "$LOGMD"

Aspettative: almeno 2‑3 righe JSONL (session_start, text, session_end) e un header MD con host/port/user.


Come aggiungere un modulo

Esempio: nuovo modulo directions.py che assorbe EXIT_ALIASES da deadsouls.py. Procedura:

  1. Crea src/viandante/directions.py con le costanti e normalize_exits().
  2. Aggiorna deadsouls.py → re‑import da directions per back‑compat.
  3. Aggiorna gmcp.py, mapper.py, cli.py → import da directions.
  4. Scrivi tests/test_directions.py.
  5. Aggiorna questo file (docs/development.md) e docs/modules.md.
  6. pytest + ruff check.

Principi

  • Foglia pura sopra orchestratore. Solo cli.py può dipendere da più moduli. Mai il contrario.
  • No mutazione cross‑module. Ogni modulo possiede il suo stato.
  • Mai dipendere dal payload GMCP da mapper o deadsouls. Sono moduli di testo puro.
  • Mai import cli da moduli bassi. Solo cli sa di prompt_toolkit.

Debugging tipico

Sintomo Tool
IAC parser perso un byte pytest tests/test_telnet.py -v + aggiungere print in _byte()
GMCP module non riconosciuto leggere gmcp.parse_gmcp con data=b"..." in REPL
Stanze non rilevate senza GMCP stampare state.parser._buf dopo feed_text
Mapper collassa in [mappa troppo larga] abbassare max_cols/max_rows in render(...)
Stanza GMCP arriva ma render non aggiorna state.mapper.current_room punta ancora al vecchio?
Password finisce nel log grep --log-md per hunter2/*** ratio
Sessione hung dopo quit conn.close() chiamato? finally cleanup?

Aggiungere un nuovo comando slash

  1. In cli.py:_handle_local_command, aggiungi un ramo elif cmd == "foo":.
  2. Se modifica stato → metti la logica prima del if state.logger is not None: state.logger.local_cmd(...).
  3. Se inoltra al MUD → usa await conn.send_line("foo").
  4. Aggiungi voce nella tabella di docs/usage.md e _print_help().
  5. Test: aggiungi in tests/test_cli.py (futuro; ancora non esiste).

Versioning

__version__ in __init__.py (0.1.0 in v0.2). Bump:

  • patch (0.1.x): bug fix, no nuovi flag.
  • minor (0.x.0): nuovi flag, nuove feature senza break della CLI.
  • major (x.0.0): break di --log-* schema eventi o rename simboli pubblici dei moduli.

CI/CD — Gitea Actions

I workflow vivono in .gitea/workflows/:

  • ci.yml — ruff + matrix pytest su Python 3.10/3.11/3.12, trigger su push e pull_request verso main. concurrency.cancel-in-progress annulla i run sorpassati (utile per push consecutivi su PR).
  • release.yml — gate (ruff + pytest) → build (python -m build, sdist + wheel) su tag v*. Gli artifact viandante-*.tar.gz e viandante-*.whl finiscono nella pagina del job, scaricabili.

Stato operativit\u00e0: l'infrastruttura YAML \u00e8 committata e pushata (v0.2.0 \u2192 b20f3c0f). Finch\u00e9 non registri un runner sul Gitea, i job restano in queued e il badge [![ci](...)](...) rimane grigio. Senza runner la pipeline non gira.

Setup del runner (una tantum)

Sulla macchina che eseguir\u00e0 i job serve Docker (l'immagine ubuntu-22.04 parte in un container):

  1. Genera il token di registrazione dalla UI: http://192.168.0.183:8085/-/admin/runners \u2192 "Crea nuovo runner" (NON \u00e8 il token account, \u00e8 un one-shot scelto da te).

  2. Al primo avvio usa --rm -it per vedere in chiaro eventuali errori di registrazione (token sbagliato, permessi su /var/run/docker.sock):

    docker run --rm -it --name act_runner \
      -e GITEA_INSTANCE_URL=http://192.168.0.183:8085 \
      -e GITEA_RUNNER_REGISTRATION_TOKEN=<TOKEN_DEL_PUNTO_1> \
      -e GITEA_RUNNER_NAME=homelab-runner \
      -v /var/run/docker.sock:/var/run/docker.sock \
      gitea/act_runner:latest
    
  3. Quando vedi Runner is now online nel log, sostituisci con la versione daemonizzata (-d, --restart unless-stopped). Un act_runner Docker (gitea/act_runner:latest) di default pubblica le label ubuntu-latest, ubuntu-24.04, ubuntu-22.04; i workflow di viandante usano runs-on: ubuntu-22.04 (in .gitea/workflows/*.yml) che le matcha — ogni job gira in un container pulito basato su Ubuntu 22.04 (Docker‑in‑Docker).

NB sul token: l'URL /-/admin/runners genera un runner globale (visibile a tutti i repo del Gitea). Per limitare il runner a un solo repo, il token si legge da /-/settings/actions/runners di quel repo (la riga registrata appare nella sezione "Runners" del repo specifico, non nella pagina admin globale).

Egress verso github.com (possibile blocker)

Senza egress dal runner host a github.com, le action composite (actions/checkout@v4, actions/setup-python@v5, actions/upload-artifact@v4) non possono essere scaricate e il primo step fallisce con failed to download action. Workaround:

  • Ridireziona il download delle action in app.ini del server (chiave DEFAULT_ACTIONS_URL, introdotta in Gitea 1.21+, default https://github.com):

    [actions]
    DEFAULT_ACTIONS_URL = https://<your-mirror-url>
    

    NB: nomi pi\u00f9 vecchi tipo ENABLE_GITHUB_ACTIONS_MIRROR / ACTIONS_DEFAULT_BASE_URL non esistono in 1.26 \u2014 usa solo DEFAULT_ACTIONS_URL.

  • Oppure bundle locale: pre-popolare le action in un image di runner.

Note pratiche

  • Cache pip: cache: pip + cache-dependency-path: pyproject.toml. Cache key = python-version + hash(pyproject.toml), quindi 3.10/3.11/3.12 hanno cache separate (no cross-version poisoning).
  • Tag release pattern: trigger su v* (v0.2.0, v0.3, v1.0.0-rc1). Workflow di release: git tag vX.Y.Z && git push origin vX.Y.Z.
  • Badge [![version](https://img.shields.io/...)](...) in README.md \u00e8 hardcoded a v0.2.0: aggiornalo a mano a ogni release (vedi checklist in docs/gitea.md).