Skip to content

#925 Spix: Integrate Spix for GUI testing via XML-RPC#929

Open
magnesj wants to merge 17 commits into
devfrom
feature/spix-integration-925
Open

#925 Spix: Integrate Spix for GUI testing via XML-RPC#929
magnesj wants to merge 17 commits into
devfrom
feature/spix-integration-925

Conversation

@magnesj

@magnesj magnesj commented May 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • Integrates Spix to allow remote GUI control of ResInsight via XML-RPC for automated UI testing
  • Adds --spix-port <port> command-line option that starts an AnyRPC server inside RiaGuiApplication when provided
  • Gated behind a new CMake option RESINSIGHT_ENABLE_SPIX (off by default); Spix is fetched via FetchContent and linked directly into ApplicationLibCode
  • Adds a Python smoke test (test_spix_smoke.py) that exercises the bridge using the standard library xmlrpc.client

Test plan

  • Configure with -DRESINSIGHT_ENABLE_SPIX=ON and verify the build succeeds
  • Launch ResInsight with --spix-port 9000 and confirm the RPC server starts
  • Run GrpcInterface/Python/rips/tests/test_spix_smoke.py against the running instance
  • Verify default build (without RESINSIGHT_ENABLE_SPIX) is unchanged

magnesj added 10 commits April 30, 2026 10:22
Wires find_package(Spix CONFIG REQUIRED) and appends Spix::QtWidgets to
THIRD_PARTY_LIBRARIES so both ApplicationLibCode (where the server is
constructed) and ApplicationExeCode pick it up via the existing variable
propagation. Defines ENABLE_SPIX so source files can guard the integration.
Forward-declares spix::AnyRpcServer / spix::QtWidgetsBot, holds them as
unique_ptr members behind ENABLE_SPIX, and constructs them in handleArguments
when --spix-port <n> is supplied. The server is reset before window teardown
so its worker thread joins before the bot is destroyed.
Skips when RESINSIGHT_SPIX_PORT is unset so it stays out of the default CI
suite. Spix exposes an XML-RPC server (anyrpc::XmlHttpServer) rather than
JSON-RPC, so the test calls existsAndVisible("mainWindow") via the stdlib
xmlrpc.client.ServerProxy.
The project's default vcpkg baseline (84bab45d4 from 2025-12-13) predates
the addition of the spix port (5d70180b0, 2026-01-25). Add a per-package
registry override so only spix uses the newer baseline; all other ports
still resolve from the default registry.
The vcpkg spix port pulls in qtbase and qtdeclarative as transitive deps,
which would force vcpkg to build a duplicate Qt6 stack alongside the
system Qt6 the project already uses.

Switch to FetchContent at Spix v0.14 and add_subdirectory it inline so
Spix builds against the project's existing Qt6. Only anyrpc remains a
vcpkg dependency (Spix's CMake calls find_package(AnyRPC REQUIRED)).
SPIX_BUILD_QTQUICK and SPIX_BUILD_EXAMPLES are forced off; only the
QtWidgets scene library is built.
Spix::QtWidgets is an ALIAS target, which is incompatible with the
set_property(TARGET FOLDER ...) walk over THIRD_PARTY_LIBRARIES at the
end of the top-level CMakeLists.txt. Wire the link locally in
ApplicationLibCode (which is where RiaGuiApplication.cpp consumes the
headers); ResInsight.exe picks Spix up transitively through its
existing dependency on ApplicationLibCode.

Also drops the now-unused find_package(AnyRPC CONFIG REQUIRED) at the
top level: the vcpkg anyrpc port ships no CMake config file, so Spix's
own FindAnyRPC.cmake (module mode) handles discovery once add_subdirectory
runs.
existsAndVisible(<path>) requires the target widget to have a Qt
objectName. RiuMainWindow does not currently call setObjectName, so
the previous assertion failed even with the RPC server fully wired.

getErrors() takes no widget path, so it verifies the AnyRPC server is
alive and the bot is responsive without coupling the smoke test to
widget naming. Naming top-level widgets for path-based UI tests can be
added separately when functional UI tests land.
@qodo-code-review

Copy link
Copy Markdown

Review Summary by Qodo

Integrate Spix for automated GUI testing via XML-RPC

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Integrates Spix library for remote GUI testing via XML-RPC
• Adds --spix-port command-line option to start RPC server
• Implements AnyRpcServer and QtWidgetsBot in RiaGuiApplication
• Adds Python smoke test using xmlrpc.client for validation
• Gated behind RESINSIGHT_ENABLE_SPIX CMake option (disabled by default)
Diagram
flowchart LR
  CLI["--spix-port argument"]
  Parser["RiaArgumentParser registers option"]
  Handler["RiaGuiApplication.handleArguments"]
  Server["AnyRpcServer + QtWidgetsBot"]
  Test["test_spix_smoke.py validates RPC"]
  
  CLI --> Parser
  Parser --> Handler
  Handler --> Server
  Server --> Test
Loading

Grey Divider

File Changes

1. CMakeLists.txt ⚙️ Configuration changes +32/-0

Add Spix CMake integration with FetchContent

• Adds RESINSIGHT_ENABLE_SPIX CMake option (default OFF)
• Fetches Spix v0.14 via FetchContent with QtWidgets enabled
• Disables Spix examples, tests, and QtQuick builds
• Defines ENABLE_SPIX preprocessor macro when enabled
• Adds anyrpc to vcpkg dependencies

CMakeLists.txt


2. ApplicationLibCode/CMakeLists.txt ⚙️ Configuration changes +4/-0

Link Spix library to application code

• Links Spix::QtWidgets to ApplicationLibCode when RESINSIGHT_ENABLE_SPIX is ON
• Uses PUBLIC linkage to propagate to ApplicationExeCode

ApplicationLibCode/CMakeLists.txt


3. ApplicationLibCode/Application/RiaGuiApplication.h ✨ Enhancement +17/-0

Add Spix server member variables and method

• Forward-declares spix::AnyRpcServer and spix::QtWidgetsBot behind ENABLE_SPIX
• Adds m_spixBot and m_spixServer unique_ptr members
• Declares startSpixServer(int port) private method

ApplicationLibCode/Application/RiaGuiApplication.h


View more (4)
4. ApplicationLibCode/Application/RiaGuiApplication.cpp ✨ Enhancement +45/-0

Implement Spix server initialization and lifecycle

• Includes Spix headers conditionally behind ENABLE_SPIX
• Tears down Spix server in destructor before window cleanup
• Implements startSpixServer() to construct and start AnyRpcServer and QtWidgetsBot
• Handles server startup errors with logging and cleanup
• Processes --spix-port option in handleArguments() method

ApplicationLibCode/Application/RiaGuiApplication.cpp


5. ApplicationLibCode/Application/Tools/RiaArgumentParser.cpp ✨ Enhancement +4/-0

Register spix-port command-line option

• Registers --spix-port command-line option with <portnumber> argument
• Provides help text describing the option as UI test automation server
• Uses SINGLE_VALUE mode for port number

ApplicationLibCode/Application/Tools/RiaArgumentParser.cpp


6. GrpcInterface/Python/rips/tests/test_spix_smoke.py 🧪 Tests +18/-0

Add Python smoke test for Spix RPC server

• Creates new Python smoke test for Spix integration
• Skips test when RESINSIGHT_SPIX_PORT environment variable is unset
• Uses stdlib xmlrpc.client.ServerProxy to call getErrors() RPC method
• Validates RPC layer is functional by checking response is a list

GrpcInterface/Python/rips/tests/test_spix_smoke.py


7. vcpkg.json Dependencies +1/-0

Add anyrpc vcpkg dependency

• Adds anyrpc as a new dependency for Spix integration

vcpkg.json


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented May 2, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Spix port unvalidated 🐞 Bug ≡ Correctness
Description
RiaGuiApplication::handleArguments parses --spix-port with toInt() and only checks >0, so
non-numeric input becomes 0 (silently ignored) and out-of-range ports can attempt startup without a
clear error/exit status when startup fails.
Code

ApplicationLibCode/Application/RiaGuiApplication.cpp[R585-592]

+#ifdef ENABLE_SPIX
+    if ( cvf::Option o = progOpt->option( "spix-port" ) )
+    {
+        if ( o.valueCount() == 1 )
+        {
+            int port = o.value( 0 ).toInt();
+            if ( port > 0 ) startSpixServer( port );
+        }
Evidence
--threadcount uses strict validation and returns EXIT_WITH_ERROR on invalid input, but --spix-port
does not; additionally, startSpixServer() catches exceptions and only logs, so the app continues
running even when server startup fails.

ApplicationLibCode/Application/RiaGuiApplication.cpp[568-594]
ApplicationLibCode/Application/RiaGuiApplication.cpp[1867-1886]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`--spix-port` is parsed via `toInt()` and only checked for `> 0`, which means invalid strings silently become `0` (server not started, no message), and invalid ranges can proceed to `startSpixServer()`.
### Issue Context
There is already precedent for strict CLI validation (`--threadcount`) returning `EXIT_WITH_ERROR` and logging a clear message. Spix startup failures are currently swallowed (log-only) and the application keeps running.
### Fix Focus Areas
- ApplicationLibCode/Application/RiaGuiApplication.cpp[568-594]
- ApplicationLibCode/Application/RiaGuiApplication.cpp[1867-1886]
### Suggested fix
- Validate that the value is a valid integer and within `[1, 65535]`.
- If invalid, log an error and return `EXIT_WITH_ERROR` (consistent with `--threadcount`).
- Consider changing `startSpixServer` to return `bool` (or throw) so `handleArguments()` can fail fast when startup fails, instead of continuing silently.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Flag ignored without Spix 🐞 Bug ≡ Correctness
Description
--spix-port is always registered in the CLI parser, but its handling is compiled out unless
ENABLE_SPIX is defined, so non-Spix builds accept the flag (and show it in help) but do nothing.
Code

ApplicationLibCode/Application/Tools/RiaArgumentParser.cpp[R76-79]

+    progOpt->registerOption( "spix-port",
+                             "<portnumber>",
+                             "Start Spix UI test automation server on the given HTTP port.",
+                             cvf::ProgramOptions::SINGLE_VALUE );
Evidence
The option is registered unconditionally, but the only runtime handling is inside #ifdef
ENABLE_SPIX. Because it is registered, it also won't be reported as an unknown option by the main
entrypoint's unknown-options check, making the no-op behavior silent.

ApplicationLibCode/Application/Tools/RiaArgumentParser.cpp[73-81]
ApplicationLibCode/Application/RiaGuiApplication.cpp[585-594]
ApplicationExeCode/RiaMain.cpp[121-137]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`--spix-port` is always accepted by the argument parser, but in builds without `ENABLE_SPIX` it is ignored entirely.
### Issue Context
This results in a silent no-op: the flag is considered known (so not caught by unknown-option reporting), but doesn't start any server.
### Fix Focus Areas
- ApplicationLibCode/Application/Tools/RiaArgumentParser.cpp[73-81]
- ApplicationLibCode/Application/RiaGuiApplication.cpp[585-594]
- ApplicationExeCode/RiaMain.cpp[121-137]
### Suggested fix
Choose one behavior:
- **Preferred:** Wrap `registerOption("spix-port", ...)` in `#ifdef ENABLE_SPIX` so it is not advertised/accepted in non-Spix builds.
- **Alternative:** In non-Spix builds, detect `progOpt->option("spix-port")` and log an error like "This build was compiled without Spix support" and return `EXIT_WITH_ERROR`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. XML-RPC test can hang🐞 Bug ☼ Reliability
Description
test_spix_smoke.py creates an xmlrpc.client.ServerProxy without a timeout and then performs a
blocking RPC call, which can hang the pytest run indefinitely if the server is unresponsive.
Code

GrpcInterface/Python/rips/tests/test_spix_smoke.py[R14-17]

+    proxy = xmlrpc.client.ServerProxy(f"http://localhost:{port}/")
+    # getErrors() takes no widget path, so this verifies the RPC layer
+    # is alive without depending on any specific widget's objectName.
+    errors = proxy.getErrors()
Evidence
The test calls proxy.getErrors() with the default XML-RPC transport, which does not set a
request/socket timeout here; a stalled connection can block CI indefinitely.

GrpcInterface/Python/rips/tests/test_spix_smoke.py[1-18]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Spix smoke test can hang indefinitely because the XML-RPC client call has no timeout.
### Issue Context
Hanging tests are particularly painful in CI (they consume the full job timeout and hide the actual failure).
### Fix Focus Areas
- GrpcInterface/Python/rips/tests/test_spix_smoke.py[1-18]
### Suggested fix
- Use a transport with a timeout (e.g., subclass `xmlrpc.client.Transport` and override `make_connection` to set a timeout), or set `socket.setdefaulttimeout(...)` within the test scope.
- Optionally, catch connection errors and fail with a clear message (instead of hanging).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +585 to +592
#ifdef ENABLE_SPIX
if ( cvf::Option o = progOpt->option( "spix-port" ) )
{
if ( o.valueCount() == 1 )
{
int port = o.value( 0 ).toInt();
if ( port > 0 ) startSpixServer( port );
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Spix port unvalidated 🐞 Bug ≡ Correctness

RiaGuiApplication::handleArguments parses --spix-port with toInt() and only checks >0, so
non-numeric input becomes 0 (silently ignored) and out-of-range ports can attempt startup without a
clear error/exit status when startup fails.
Agent Prompt
### Issue description
`--spix-port` is parsed via `toInt()` and only checked for `> 0`, which means invalid strings silently become `0` (server not started, no message), and invalid ranges can proceed to `startSpixServer()`.

### Issue Context
There is already precedent for strict CLI validation (`--threadcount`) returning `EXIT_WITH_ERROR` and logging a clear message. Spix startup failures are currently swallowed (log-only) and the application keeps running.

### Fix Focus Areas
- ApplicationLibCode/Application/RiaGuiApplication.cpp[568-594]
- ApplicationLibCode/Application/RiaGuiApplication.cpp[1867-1886]

### Suggested fix
- Validate that the value is a valid integer and within `[1, 65535]`.
- If invalid, log an error and return `EXIT_WITH_ERROR` (consistent with `--threadcount`).
- Consider changing `startSpixServer` to return `bool` (or throw) so `handleArguments()` can fail fast when startup fails, instead of continuing silently.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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