|
RTE RBC 2oo2 Test Environment 0.1.0 build 3384d2c
End-to-end Robot Framework test suite + Docker Compose stack for the RBC_GP system
|
A local, developer-only web server and live visualisation page for the rbc_scenario suite. Run it by hand against a local test environment with etc/scripts/runWebRunner.sh [--port N] [--railml PATH] [--open] (default http://localhost:8765/; it wraps python3 backend/server.py); never host it, because it executes real build, start, stop and run subprocesses.
| Path | Role |
|---|---|
| backend/server.py | HTTP and WebSocket server, script picker, build, start, stop and run action runner |
| backend/events.py | Parses the merged log line shape written by ContainerLogAttachListener.py into event dictionaries; the single parser for the live stream and for offline replay |
| backend/site_data.py | Parses ab_site.railml.xml per /api/site request |
| backend/parse_log.py | Offline replay |
| backend/versions.py | Product versions for /api/versions (paths relative to the workspace root) |
| backend/test_web_runner.py | End-to-end regression: starts server.py as a subprocess and speaks real HTTP and WebSocket |
| frontend/live.html | The whole browser-side page |
robot/supportFunctions/ContainerLogAttachListener.py (the Robot listener) is the only producer. Each batch of normalized log lines it emits goes to three places: the terminal, testEnv/logs/_run_stream.txt (durable copy) and, when the run was started by this server (RTE_WEB_RUNNER_PORT), an HTTP POST /api/log_push carrying {"offset": <file offset after the append>, "text": <the exact text>}. The server parses the text at once and broadcasts the events over the WebSocket (/stream), including the derived train and route state, so nothing waits for a file poll. The offset lets the server's file tailer skip exactly those bytes: every byte is processed once, and if the push fails (no route, a proxy, a busy server) the tailer still delivers the same lines from the file within 0.3 s. The push bypasses http_proxy. GET /api/stream_info and the status line on the Wire Event Log tab show whether the file grows, lines parse and browsers are connected.
The listener writes every RBC line with source RBC West or RBC East and puts the originating process in front of the info field ([a-west | GP] promoted to ONLINE); events.py takes the unit (a-west ... c-east) from that tag. Until 2026-10-07 the parser did not accept the nested brackets, so no RBC status type was produced and the A/B nodes stayed grey; the listener also cut every Info= text after its first word. The status types and the RBC lines they come from:
| Type | RBC line | Panel |
|---|---|---|
| negotiation_role | cycle N: negotiation ok (role=ONLINE/STANDBY/FAULTED, ...) (Platform_RTE rte_site_role.c, every cycle while the inter-site link is up) | node role; FAULTED paints the node red |
| rbc_status | Type=STATUS Info=self with role=, site_wait=, site_why= (RBC_GP ab_gp_status.c, also while the link is down) | node role, tooltip "Site role: waits for ..." |
| role | Type=ROLE (promoted, demoted, takeover, dual ONLINE, promotion held back) | node role where the text states one |
| startup_ts | site startup timestamp N ms (own N, sibling N/none, adopted sibling/own kept/sibling not reached within 3000 ms) (RBC_GP ab_gp_channel_negotiate.c) | tooltip "Site startup timestamp" |
| startup_role | start-up: no A/B sibling state, negotiating from timestamps or start-up: A/B sibling reports X, resuming that role | tooltip "Start-up role" |
| sibling_mismatch | site state mismatch with local peer (mismatch count N/3 ...) and Type=NEGOTIATION_MISMATCH (REQ-SITEROLE-014) | tooltip "A/B site state check"; the final event paints the node red |
| takeover | Type=TAKEOVER (silent counterpart, confirmation received or rejected, key state) | tooltip "Last takeover event" |
| agree / disagree | Type=AGREE / Type=DISAGREE (A/B cross-compare) | site nodes green / unit red |
| safe_state | any A/B line containing entering SAFE state (e.g. watchdog clock failed - entering SAFE state) | unit red |
| alarm, lifecycle_gateway | Type=ALARM; Type=LIFECYCLE Info=gateway service starting/shutting down | unit red; C node green |
The A/B node tooltip lists the channels ab-peer, ab-crosscompare, ab-negotiate, ab-relay-*, the C node tooltip c-sim-*, c-relay-*, c-broker-* (RBC_GP etc/config/*.json).
PROJECT_ROOT is test/RBC_Test_Env; the workspace root is two levels above it. Site data comes from application/RBC_SA/etc/site/, GP configuration from application/RBC_GP/etc/config/.
backend/test_web_runner.py must pass after any change to server.py or events.py: it parses one current line of each RBC status type (_RBC_STATUS_LINES, no server needed), then starts server.py and checks the HTTP endpoints, the WebSocket stream, the derived train and route state and the exactly-once push. The page itself is checked by hand against a live local environment.
The environment selector next to the buttons applies to Build, Start, Stop and Run. With docker, Run starts runTests.sh --env docker --stream-logs --no-destructive: the fast Docker suite, or - when a scenario is selected - just that test (--test NAME, same as the local path; 2026-10-07). Container logs stream live the same way as a local run: the Robot listener spawns one docker logs -f -t --tail 0 tail per container and feeds its lines through the same normalizer as the local per-process files (before 2026-10-07 it only scanned testEnv/logs/*.log, which a Docker run never writes, so a Docker run streamed only Robot's own steps). A test whose name matches nothing under env:docker/env:any fails with Robot's own "did not match any test" message. The scenario list shows each test's env:* tag in square brackets.