Skip to content

fix: Python codebase review: bugs (B1-B9), security (S1, S3, S4), performance (P1-P4), cleanups (C5, C7, C9) - #383

Merged
timdegroot1996 merged 2 commits into
mainfrom
fix/python-review-bugs
Oct 18, 2026
Merged

timdegroot1996 merged 2 commits into
mainfrom
fix/python-review-bugs

Conversation

@timdegroot1996

Copy link
Copy Markdown
Collaborator

Fixes from a line-by-line review of robotframework_dashboard/*.py. Independent of #382 (no shared files except one line in duration_deviation.js).

Bugs

  • Log linking (B1): an uploaded log.html was always linked to the oldest run whose path contained output.xml, so with the listener's default names newer runs never got their log. Now: exact file name, newest run first. Known limit, not changed here: default names still overwrite robot_logs/log.html on every upload.
  • Half-stored runs (B2): each table was committed separately and errors were only printed. A failed insert left a run row behind that was then rejected as a duplicate forever. Now one transaction per output, and the error is reported.
  • age=6mo meant 6 minutes (B3): the pattern now has to match the whole value; 6mo and 10days are rejected.
  • -t "Tim's board" broke the whole dashboard (B4): the title is now escaped separately for the JS string and for <title>.
  • -o a.xml b.xml dropped b.xml (B5), same for -f and -r (the docs example -f ./results:version_1.1 ./results2:... was affected).
  • Errors exited with 0 (B7): every error now exits with code 1.
  • Duplicate check was a prefix match (B8): a run at exactly .000000 seconds was skipped as a duplicate of any run in that second.
  • Small ones (B9):
    • tag=prod no longer removes preprod / production.
    • Test tags keep their inner spaces.
    • age= reads offset-less run_start values as local time.
    • NULL ids become "".
    • --quantity must be ≥ 1.
    • --timezone must be +HH:MM.
    • The run alias only strips the output_ prefix and .xml.
    • Config files and the RF 6 header are read as UTF-8.
  • Python 3.9 minimum (B6): removeprefix was already used. Set in python_requires, the docs and the coding-standards skill.

Security / robustness

  • S1: a non-ASCII server password gave a 500 on every request. FastAPI decodes Basic auth as ASCII, so such credentials could never work; they are now rejected at startup, and the comparison is on bytes (401 instead of 500).
  • S3:
    • --jsonconfig must be valid JSON.
    • </ is escaped in the JSON and message config, so a value cannot end the inline <script>.
  • S4:
    • /add-log writes <name>.part, updates the database, then renames. A failed update leaves an existing log untouched.
    • Upload success is decided by the update_output_path result only, not by ERROR anywhere in the console text (a folder named ERRORS failed good uploads).
    • Gzip uploads are capped at 1 GiB once decompressed.
    • /remove-log all skips folders.
    • --server: the password may contain :, IPv6 hosts work as [::1]:port, and the credentials can come from ROBOTDASHBOARD_SERVER_USER / ROBOTDASHBOARD_SERVER_PASSWORD to keep them out of the process list.

Performance (measured on 504 runs / 41.7k tests)

  • Inserting 504 outputs: 11.0 → 2.6 s. 83% of the time was the five commits per output.
  • An output already in the database is skipped after reading only the <robot generated="..."> header. Before, the whole XML was parsed first. For 18 files the check takes 3 ms; a full parse was about 50 ms per file.
  • The visitors skip what they don't use, and one walk feeds both the keyword and the exception processor: parse + visit 1.32 → 1.16 s for the 18 fixtures, identical output.
  • run_start_exists is one SQL query, and the JS/CSS/offline blocks are cached keyed on every source file's mtime and size, so edits are still picked up while the server runs.

Cleanups

  • C5: database messages go through one _print_console helper instead of print + console += copies.
  • C7: RobotDashboard opens the database in a context manager that always closes it.
  • C9: the per-run selects moved to queries.py. CLAUDE.md and the js-bundling skill now say every js/ file is bundled and imports only set the order.

Behaviour changes for the changelog

  • Non-zero exit codes on errors.
  • tag= matches the whole tag.
  • age=, --timezone, --quantity and --jsonconfig are validated.
  • Python ≥ 3.9.
  • Non-ASCII server credentials are rejected.

Tests

  • Python unit: 524 passed. New tests cover every item above.
  • Robot (Docker): 143 passed. 00_cli.robot now uses -q 25 instead of -q -25.
  • JS unit: 481 passed.
  • Two server tests that expected an ERROR string from create_dashboard to fail the upload now make it raise, since the real one raises on failure.

🤖 Generated with Claude Code

timdegroot1996 and others added 2 commits October 18, 2026 21:14
- update_output_path matches the whole file name and prefers the newest
  run, so a log.html upload no longer always relinks the oldest run
- insert_output_data runs in one transaction and re-raises, a failed
  insert no longer leaves a run that is rejected as duplicate forever
  (also 5x fewer commits per output)
- age= uses fullmatch: "6mo" was read as 6 minutes
- dashboard title is escaped for the JS string and the <title> tag, a
  quote in -t no longer breaks the whole script
- -o/-f/-r use every value after the flag instead of only the first
- error exits use code 1 instead of 0
- run_start_exists compares without the offset instead of a prefix
  match, which skipped runs at exactly .000000 seconds
- non-ASCII server credentials: compare bytes (401, not 500) and reject
  them at startup, FastAPI decodes Basic auth as ASCII
- tag=/limit=;tag= match whole run tags, test tags keep inner spaces,
  age= reads offset-less run_starts as local time, NULL ids become "",
  --quantity is an int >= 1, --timezone must be +HH:MM, run alias only
  strips the output_ prefix and .xml suffix, config files and the RF 6
  header are read as UTF-8
- Python 3.9 is the minimum (removeprefix was already used)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Security/robustness (S3, S4):
- --jsonconfig must be valid JSON; "</" is escaped in the JSON and
  message config so a value cannot end the inline script
- /add-log writes a .part file, updates the database, then renames, so
  a failed update leaves an existing log untouched
- upload success is decided by the update_output_path result only, not
  by "ERROR" anywhere in the console
- gzip uploads are decompressed with a 1 GiB cap
- /remove-log all skips folders
- --server keeps everything after the user as password (may contain
  ':'), accepts [ipv6]:port, and reads ROBOTDASHBOARD_SERVER_USER /
  ROBOTDASHBOARD_SERVER_PASSWORD when it has no credentials

Performance (P1-P4):
- 504 inserts 11.0 -> 2.6 s (one commit per output, previous commit)
- an output already in the database is skipped after reading only the
  <robot generated="..."> header instead of a full XML parse
- suite/test processors skip what they do not use and one walk feeds
  the keyword and exception processors (identical output on fixtures)
- run_start_exists is a single query, label sets in get_data, and the
  JS/CSS/offline blocks are cached keyed on source file mtime and size

Structure (C5, C7, C9):
- database messages go through one _print_console helper
- RobotDashboard opens the database in a context manager that always
  closes it
- per-run selects moved to queries.py; CLAUDE.md and the js-bundling
  skill now say every js/ file is bundled and imports only set order

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@timdegroot1996
timdegroot1996 merged commit 67d9e63 into main Oct 18, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant