Skip to content

Claude/route logging diagnostics#22

Open
jgeudens wants to merge 9 commits intomasterfrom
claude/route-logging-diagnostics-4Awjy
Open

Claude/route logging diagnostics#22
jgeudens wants to merge 9 commits intomasterfrom
claude/route-logging-diagnostics-4Awjy

Conversation

@jgeudens
Copy link
Copy Markdown
Member

@jgeudens jgeudens commented Apr 3, 2026

Summary by CodeRabbit

  • New Features

    • Adapter diagnostic notifications now surface severity and message and are routed into application logs.
  • Configuration

    • Modbus adapter defaults updated: connection persistence enabled; device int32 little-endian default enabled.
  • Documentation

    • JSON‑RPC spec documents adapter.diagnostic emission and adds an "error" diagnostic level.
  • Bug Fixes

    • Session shutdown/stop now emits session-stopped only after the adapter process exits (shutdown timing adjusted).
  • Tests

    • New tests for diagnostics, level mapping, malformed params and shutdown/stop timing.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 3, 2026

Walkthrough

AdapterClient gains a handshakeTimeout parameter, handles JSON-RPC adapter.diagnostic notifications (emitting diagnosticReceived), and defers sessionStopped emission until process exit. AdapterProcess stop is non-blocking with a timeout kill. ModbusPoll logs forwarded diagnostics. Tests, docs and logging category were updated; adapter defaults changed.

Changes

Cohort / File(s) Summary
Adapter client
src/ProtocolAdapter/adapterclient.h, src/ProtocolAdapter/adapterclient.cpp
Constructor now accepts handshakeTimeoutMs and stores _handshakeTimeoutMs; added onNotificationReceived(QString, QJsonValue) slot to filter adapter.diagnostic, validate object params, extract level/message and emit diagnosticReceived(QString, QString); adjusted stop/session handshake and sessionStopped emission timing.
Adapter process stop behaviour
src/ProtocolAdapter/adapterprocess.h, src/ProtocolAdapter/adapterprocess.cpp
stop() is now asynchronous: closes write channel and schedules kill() via QTimer::singleShot after cStopTimeoutMs if process still running; header/docs updated to reflect asynchronous contract.
ModbusPoll logging
src/communication/modbuspoll.h, src/communication/modbuspoll.cpp
Connected AdapterClient::diagnosticReceived to new private slot onAdapterDiagnostic(const QString&, const QString&); level-to-Qt-log mapping implemented (debug→qCDebug, info→qCInfo, warning→qCWarning, error/unknown→qCCritical / warning message for unknown).
Logging category
src/util/scopelogging.h, src/util/scopelogging.cpp
Renamed logging category from scope.comm.connection/scopeCommConnection to scope.comm.adapter/scopeAdapter, and updated declarations/usages.
Adapter defaults & docs
adapters/describe.json, adapters/json-rpc-spec.md
Changed defaults: connections[0].persistent: falsetrue, devices[0].int32LittleEndian: falsetrue; documented active adapter.diagnostic emission and added "error" level mapping.
Tests — AdapterClient
tests/ProtocolAdapter/tst_adapterclient.h, tests/ProtocolAdapter/tst_adapterclient.cpp
Added tests for diagnostic notification forwarding, debug-level diagnostics, malformed params (non-object) not emitting signal; added tests verifying shutdown ack behavior and that sessionStopped is emitted only after process exit when appropriate; added MockAdapterProcess::injectProcessFinished().
Tests — ModbusPoll
tests/communication/tst_modbuspoll.h, tests/communication/tst_modbuspoll.cpp, tests/communication/CMakeLists.txt, tests/CMakeLists.txt
Added TestModbusPoll fixture and tests validating level→Qt message mapping; added tst_modbuspoll test target and included communication tests in overall test CMake.
Diagnostic model & tests
src/models/diagnosticmodel.cpp, tests/models/tst_diagnosticmodel.h, tests/models/tst_diagnosticmodel.cpp
Small API-consistent formatting changes; clear() guards against empty removal range; added clearEmpty() unit test.
AdapterProcess tests timing
tests/ProtocolAdapter/tst_adapterprocess.cpp
Inserted short QTest::qWait(500) after process.stop() in two tests to allow asynchronous stop/exit handling to run.

Sequence Diagram(s)

sequenceDiagram
    participant AdapterProc as AdapterProcess
    participant AdapterClient
    participant ModbusPoll
    participant QtLog as Qt Logging

    AdapterProc->>AdapterClient: JSON-RPC notification\n(method: "adapter.diagnostic", params: {level, message})
    activate AdapterClient
    AdapterClient->>AdapterClient: onNotificationReceived()\nif method == "adapter.diagnostic"
    AdapterClient->>AdapterClient: validate params is object\nextract level, message
    AdapterClient->>ModbusPoll: emit diagnosticReceived(level, message)
    deactivate AdapterClient

    activate ModbusPoll
    ModbusPoll->>ModbusPoll: onAdapterDiagnostic()\nmap level -> Qt logging function
    ModbusPoll->>QtLog: log message at mapped level
    deactivate ModbusPoll
Loading

Possibly related PRs

  • Support adapter #5: Overlaps AdapterClient/AdapterProcess integrations and lifecycle handling that this change extends with diagnostic forwarding and handshake timeout parameterisation.
  • Refactor modbus register #18: Touches ModbusPoll internals; may conflict with the newly added diagnostic slot/constructor wiring in ModbusPoll.
  • Update device + remove connection #14: Modifies ModbusPoll and adapter configuration sourcing, creating a strong code-level overlap with the diagnostic handling additions here.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the primary objective: routing adapter diagnostic notifications to logging, which spans multiple file changes including AdapterClient, ModbusPoll, and logging infrastructure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/route-logging-diagnostics-4Awjy

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/communication/tst_modbuspoll.cpp (1)

9-19: Consider resetting captured values at the start of each test.

If a test runs and the target method fails to emit a log message (e.g., due to a regression), the test could incorrectly pass using stale values from a previous test. Resetting the globals before each invocation improves test isolation.

♻️ Proposed fix
     void captureHandler(QtMsgType type, const QMessageLogContext&, const QString& msg)
     {
         g_capturedType = type;
         g_capturedMessage = msg;
     }
+
+    void resetCaptured()
+    {
+        g_capturedType = QtMsgType{};
+        g_capturedMessage.clear();
+    }
 }

Then call resetCaptured() at the start of each test, e.g.:

void TestModbusPoll::diagnosticDebugLevel()
{
    resetCaptured();
    QtMessageHandler previous = qInstallMessageHandler(captureHandler);
    // ...
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/communication/tst_modbuspoll.cpp` around lines 9 - 19, Add a
resetCaptured() helper that sets g_capturedType to a default and clears
g_capturedMessage, and call resetCaptured() at the start of each test that uses
captureHandler (e.g., TestModbusPoll::diagnosticDebugLevel and other
TestModbusPoll tests) so stale global values cannot make a test pass; update
tests to invoke resetCaptured() before qInstallMessageHandler(captureHandler)
and before exercising the code under test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/ProtocolAdapter/adapterclient.cpp`:
- Around line 170-178: The diagnostic handler should validate that params
contains string fields "level" and "message" before emitting; update the code in
adapterclient.cpp where params and QJsonObject obj are used to check
obj.contains("level") && obj.value("level").isString() and similarly for
"message", and if either check fails call qCWarning(scopeComm) with a clear
message (including the raw params) and return without emitting
diagnosticReceived; otherwise call diagnosticReceived with the verified string
values (use toString() only after isString()).

---

Nitpick comments:
In `@tests/communication/tst_modbuspoll.cpp`:
- Around line 9-19: Add a resetCaptured() helper that sets g_capturedType to a
default and clears g_capturedMessage, and call resetCaptured() at the start of
each test that uses captureHandler (e.g., TestModbusPoll::diagnosticDebugLevel
and other TestModbusPoll tests) so stale global values cannot make a test pass;
update tests to invoke resetCaptured() before
qInstallMessageHandler(captureHandler) and before exercising the code under
test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: afce44c4-9607-4aae-9de5-47d21cba23d6

📥 Commits

Reviewing files that changed from the base of the PR and between 180a3bd and c91f778.

📒 Files selected for processing (10)
  • src/ProtocolAdapter/adapterclient.cpp
  • src/ProtocolAdapter/adapterclient.h
  • src/communication/modbuspoll.cpp
  • src/communication/modbuspoll.h
  • tests/CMakeLists.txt
  • tests/ProtocolAdapter/tst_adapterclient.cpp
  • tests/ProtocolAdapter/tst_adapterclient.h
  • tests/communication/CMakeLists.txt
  • tests/communication/tst_modbuspoll.cpp
  • tests/communication/tst_modbuspoll.h

claude and others added 4 commits April 3, 2026 17:21
AdapterClient now connects to AdapterProcess::notificationReceived and
emits diagnosticReceived(level, message) for adapter.diagnostic
notifications. ModbusPoll connects to this signal and routes the message
to the existing scopeComm logging category using the appropriate
qCDebug/qCInfo/qCWarning call, so adapter-side log messages flow through
ScopeLogging into DiagnosticModel alongside host-side entries.

Tests updated: notificationIgnored now also verifies diagnosticReceived
stays silent for non-diagnostic notifications. Two new tests cover
warning and debug level forwarding.

https://claude.ai/code/session_01UhRoTiKzKdkuurvzKQBVjr
Guard against malformed adapter.diagnostic params in
onNotificationReceived: if params is not a JSON object the signal is not
emitted and a warning is logged instead of silently forwarding empty
strings.

Add explicit "warning" branch to onAdapterDiagnostic and an else branch
that logs the unknown level so unrecognised values are visible rather
than being silently promoted.

New test diagnosticMalformedParams verifies no signal is emitted for
non-object params. New tst_modbuspoll covers all four branches of the
level-mapping logic (debug, info, warning, unknown) using a captured
Qt message handler.

https://claude.ai/code/session_01UhRoTiKzKdkuurvzKQBVjr
@jgeudens jgeudens force-pushed the claude/route-logging-diagnostics-4Awjy branch from c91f778 to 602a676 Compare April 3, 2026 18:12
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (1)
src/ProtocolAdapter/adapterclient.cpp (1)

176-178: ⚠️ Potential issue | 🟡 Minor

Validate level and message types before emitting diagnostics.

At the moment, missing/non-string fields silently become empty strings via toString(), which can create misleading downstream logs.

Proposed fix
-    QJsonObject obj = params.toObject();
-    emit diagnosticReceived(obj.value(QStringLiteral("level")).toString(),
-                            obj.value(QStringLiteral("message")).toString());
+    const QJsonObject obj = params.toObject();
+    const QJsonValue levelValue = obj.value(QStringLiteral("level"));
+    const QJsonValue messageValue = obj.value(QStringLiteral("message"));
+    if (!levelValue.isString() || !messageValue.isString())
+    {
+        qCWarning(scopeComm) << "AdapterClient: adapter.diagnostic requires string level and message";
+        return;
+    }
+
+    emit diagnosticReceived(levelValue.toString(), messageValue.toString());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ProtocolAdapter/adapterclient.cpp` around lines 176 - 178, The code
currently reads `params.toObject()` and blindly calls `toString()` on
`obj.value("level")` and `obj.value("message")`; validate that both keys exist
and that `obj.value(QStringLiteral("level")).isString()` and
`obj.value(QStringLiteral("message")).isString()` before converting to QString
and calling `emit diagnosticReceived(... )`; if either check fails, avoid
emitting a potentially misleading empty/invalid diagnostic (log or emit an
explicit malformed-diagnostic warning instead). Ensure you update the logic
around the `QJsonObject obj = params.toObject();` block and the `emit
diagnosticReceived` call so the values are type-checked first and only converted
when valid.
🧹 Nitpick comments (1)
tests/communication/tst_modbuspoll.cpp (1)

72-82: Add a dedicated test for the protocol-defined "error" level.

The suite currently validates an unknown "critical" level path, but it does not assert behaviour for "error", which is now part of the protocol contract.

Based on learnings: Applies to tests/**/*.cpp : When fixing a bug, add a test that reproduces the issue before implementing the fix.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/communication/tst_modbuspoll.cpp` around lines 72 - 82, Add a new unit
test mirroring TestModbusPoll::diagnosticUnknownLevel that invokes the same
onAdapterDiagnostic method on _pModbusPoll but passes QStringLiteral("error") as
the diagnostic level and an appropriate message, installs captureHandler via
qInstallMessageHandler, restores the previous handler, and then asserts the
expected behavior (e.g., QCOMPARE on g_capturedType and QVERIFY on
g_capturedMessage) to validate the protocol-defined "error" level path;
reference TestModbusPoll::diagnosticUnknownLevel and the onAdapterDiagnostic
invocation to implement the new test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@adapters/describe.json`:
- Line 15: The defaults in adapters/describe.json for the fields persistent and
int32LittleEndian were changed to true while adapters/json-rpc-spec.md still
documents them as false, causing an API mismatch for adapter.describe; fix by
making them consistent: either revert the two occurrences of persistent and
int32LittleEndian in adapters/describe.json back to false to match the spec, or
update adapters/json-rpc-spec.md to state the new true defaults—ensure you
update both places where those keys appear in adapters/describe.json and update
the adapter.describe section of the spec accordingly so runtime behavior and
documentation match.

In `@src/communication/modbuspoll.cpp`:
- Around line 123-149: The onAdapterDiagnostic function currently misses
handling the protocol "error" level causing those messages to hit the fallback;
add an else-if branch that checks for level == QStringLiteral("error") and route
it to qCCritical(scopeAdapter) << message (use qCCritical to preserve proper
severity), leaving other branches and the fallback unchanged so unknown levels
still warn; update only the ModbusPoll::onAdapterDiagnostic function and
reference the scopeAdapter logging category when adding the mapping.

In `@src/ProtocolAdapter/adapterclient.h`:
- Around line 117-120: Update the docstring for the signal that is emitted on
adapter.diagnostic notifications (the comment block above the signal declaration
in adapterclient.h) to include the missing "error" severity level; change the
severity list from "debug", "info", or "warning" to include "error" (e.g.,
"debug", "info", "warning", or "error") so the documentation matches the current
protocol.

In `@tests/communication/tst_modbuspoll.cpp`:
- Around line 36-82: Before invoking onAdapterDiagnostic in each TestModbusPoll
test (diagnosticDebugLevel, diagnosticInfoLevel, diagnosticWarningLevel,
diagnosticUnknownLevel), reset the shared capture state g_capturedType and
g_capturedMessage to their default/empty values so prior test output cannot mask
failures; do this immediately before qInstallMessageHandler(captureHandler)
(references: g_capturedType, g_capturedMessage, captureHandler, _pModbusPoll,
onAdapterDiagnostic).
- Line 26: The test enables the wrong logging category so adapter debug messages
never reach the handler; update the QLoggingCategory::setFilterRules call used
in the test setup to include the adapter category (e.g., enable
scope.comm.adapter.debug=true or a wildcard like scope.comm.*.debug=true) so the
messages emitted by onAdapterDiagnostic (category scope.comm.adapter) are
delivered and the debug-level assertions become reliable.

---

Duplicate comments:
In `@src/ProtocolAdapter/adapterclient.cpp`:
- Around line 176-178: The code currently reads `params.toObject()` and blindly
calls `toString()` on `obj.value("level")` and `obj.value("message")`; validate
that both keys exist and that `obj.value(QStringLiteral("level")).isString()`
and `obj.value(QStringLiteral("message")).isString()` before converting to
QString and calling `emit diagnosticReceived(... )`; if either check fails,
avoid emitting a potentially misleading empty/invalid diagnostic (log or emit an
explicit malformed-diagnostic warning instead). Ensure you update the logic
around the `QJsonObject obj = params.toObject();` block and the `emit
diagnosticReceived` call so the values are type-checked first and only converted
when valid.

---

Nitpick comments:
In `@tests/communication/tst_modbuspoll.cpp`:
- Around line 72-82: Add a new unit test mirroring
TestModbusPoll::diagnosticUnknownLevel that invokes the same onAdapterDiagnostic
method on _pModbusPoll but passes QStringLiteral("error") as the diagnostic
level and an appropriate message, installs captureHandler via
qInstallMessageHandler, restores the previous handler, and then asserts the
expected behavior (e.g., QCOMPARE on g_capturedType and QVERIFY on
g_capturedMessage) to validate the protocol-defined "error" level path;
reference TestModbusPoll::diagnosticUnknownLevel and the onAdapterDiagnostic
invocation to implement the new test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a674f537-a009-4533-9b9f-3863c3db0596

📥 Commits

Reviewing files that changed from the base of the PR and between c91f778 and 602a676.

⛔ Files ignored due to path filters (2)
  • adapters/dummymodbusadapter.exe is excluded by !**/*.exe
  • adapters/modbusadapter.exe is excluded by !**/*.exe
📒 Files selected for processing (16)
  • adapters/describe.json
  • adapters/dummymodbusadapter
  • adapters/json-rpc-spec.md
  • adapters/modbusadapter
  • src/ProtocolAdapter/adapterclient.cpp
  • src/ProtocolAdapter/adapterclient.h
  • src/communication/modbuspoll.cpp
  • src/communication/modbuspoll.h
  • src/util/scopelogging.cpp
  • src/util/scopelogging.h
  • tests/CMakeLists.txt
  • tests/ProtocolAdapter/tst_adapterclient.cpp
  • tests/ProtocolAdapter/tst_adapterclient.h
  • tests/communication/CMakeLists.txt
  • tests/communication/tst_modbuspoll.cpp
  • tests/communication/tst_modbuspoll.h
✅ Files skipped from review due to trivial changes (3)
  • tests/CMakeLists.txt
  • src/util/scopelogging.cpp
  • tests/communication/tst_modbuspoll.h
🚧 Files skipped from review as they are similar to previous changes (4)
  • tests/communication/CMakeLists.txt
  • src/communication/modbuspoll.h
  • tests/ProtocolAdapter/tst_adapterclient.h
  • tests/ProtocolAdapter/tst_adapterclient.cpp

"id": 0,
"ip": "127.0.0.1",
"persistent": false,
"persistent": true,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Defaults now diverge from the published contract.

persistent and int32LittleEndian were changed to true, but adapters/json-rpc-spec.md still documents both defaults as false. This creates a behavioural/API mismatch for clients consuming adapter.describe.

Proposed fix (if the default change was unintentional)
-                "persistent": true,
+                "persistent": false,
...
-                "int32LittleEndian": true,
+                "int32LittleEndian": false,

Also applies to: 26-26

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@adapters/describe.json` at line 15, The defaults in adapters/describe.json
for the fields persistent and int32LittleEndian were changed to true while
adapters/json-rpc-spec.md still documents them as false, causing an API mismatch
for adapter.describe; fix by making them consistent: either revert the two
occurrences of persistent and int32LittleEndian in adapters/describe.json back
to false to match the spec, or update adapters/json-rpc-spec.md to state the new
true defaults—ensure you update both places where those keys appear in
adapters/describe.json and update the adapter.describe section of the spec
accordingly so runtime behavior and documentation match.

Comment on lines +117 to +120
/*!
* \brief Emitted when an adapter.diagnostic notification is received from the adapter.
* \param level Severity level string: "debug", "info", or "warning".
* \param message The diagnostic message from the adapter.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Signal docs are stale for diagnostic severity levels.

The comment lists only "debug", "info", and "warning", but the protocol now includes "error". Please update this docstring to avoid misleading consumers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ProtocolAdapter/adapterclient.h` around lines 117 - 120, Update the
docstring for the signal that is emitted on adapter.diagnostic notifications
(the comment block above the signal declaration in adapterclient.h) to include
the missing "error" severity level; change the severity list from "debug",
"info", or "warning" to include "error" (e.g., "debug", "info", "warning", or
"error") so the documentation matches the current protocol.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/ProtocolAdapter/adapterclient.cpp`:
- Around line 117-119: Do not set _state = State::IDLE immediately after calling
_pProcess->stop(); instead leave the state as STOPPING/ERROR until the child
process actually exits, and only transition to State::IDLE inside
onProcessFinished() after processFinished() is observed; update the three
locations that currently clear _state (the Unexpected non-object path emitting
sessionError, the timeout path that emits sessionStopped(), and the other
error/stop branches referenced) to call _pProcess->stop() without resetting
_state, ensure timers/signals are not prematurely stopped so onProcessFinished()
still receives the completion event, and adjust prepareAdapter()/State::STOPPING
handling so fast retries cannot start a new process while the old process is
still running.

In `@src/ProtocolAdapter/adapterprocess.cpp`:
- Around line 51-59: Replace the QTimer::singleShot lambda with a cancellable
timer and a named handler: add an int member (e.g., m_stopTimerId = 0) and use
startTimer(cStopTimeoutMs) instead of QTimer::singleShot to store the returned
timer id; implement a private handler (e.g., void handleStopTimeout() or
override timerEvent and dispatch when timerId == m_stopTimerId) that checks
_pProcess->state() != QProcess::NotRunning and calls _pProcess->kill(); ensure
you call killTimer(m_stopTimerId) and reset m_stopTimerId to 0 from start() and
onProcessFinished() to cancel any pending stop timeout before restarting or
after exit, and remove the lambda to satisfy the "no multi-statement lambda"
guideline.

In `@tests/ProtocolAdapter/tst_adapterprocess.cpp`:
- Around line 47-50: Replace the fixed QTest::qWait(500) sleeps in
tst_adapterprocess.cpp with signal-driven waits: after calling
AdapterProcess::stop() use a QSignalSpy that watches the process finished signal
(the same signal used in processFinishedEmittedOnStop()) and call
spy.wait(cStopTimeoutMs) so the test waits up to cStopTimeoutMs (3000 ms)
instead of a fixed 500ms; similarly, in sendRequestEmitsResponseReceived()
replace the fixed qWait with a QSignalSpy on the response-received signal used
by the test and call spy.wait(cStopTimeoutMs). Ensure you reference the same
signal names the tests currently expect and remove the QTest::qWait calls.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d8fdba55-d8fc-4f30-beca-ec2ac7cbdc41

📥 Commits

Reviewing files that changed from the base of the PR and between 602a676 and 3dfc900.

📒 Files selected for processing (12)
  • src/ProtocolAdapter/adapterclient.cpp
  • src/ProtocolAdapter/adapterclient.h
  • src/ProtocolAdapter/adapterprocess.cpp
  • src/ProtocolAdapter/adapterprocess.h
  • src/communication/modbuspoll.cpp
  • src/models/diagnosticmodel.cpp
  • tests/ProtocolAdapter/tst_adapterclient.cpp
  • tests/ProtocolAdapter/tst_adapterclient.h
  • tests/ProtocolAdapter/tst_adapterprocess.cpp
  • tests/communication/tst_modbuspoll.cpp
  • tests/models/tst_diagnosticmodel.cpp
  • tests/models/tst_diagnosticmodel.h
✅ Files skipped from review due to trivial changes (1)
  • src/ProtocolAdapter/adapterprocess.h
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/communication/modbuspoll.cpp
  • tests/ProtocolAdapter/tst_adapterclient.h

Comment on lines 117 to 119
_state = State::IDLE;
QTimer::singleShot(0, this, [this]() { _pProcess->stop(); });
_pProcess->stop();
emit sessionError(QString("Unexpected non-object result for %1").arg(method));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not switch back to IDLE until the child has actually exited.

AdapterProcess::stop() now returns immediately, but these paths still clear _state early and the timeout branch even emits sessionStopped() before processFinished(). A fast retry can then re-enter prepareAdapter() while the old process is still alive, and the State::STOPPING error path drops its completion signal because the timer is stopped and onProcessFinished() sees IDLE. Keep a non-idle stopping/error state until the process exit is observed, then transition to IDLE.

Also applies to: 130-132, 147-175

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ProtocolAdapter/adapterclient.cpp` around lines 117 - 119, Do not set
_state = State::IDLE immediately after calling _pProcess->stop(); instead leave
the state as STOPPING/ERROR until the child process actually exits, and only
transition to State::IDLE inside onProcessFinished() after processFinished() is
observed; update the three locations that currently clear _state (the Unexpected
non-object path emitting sessionError, the timeout path that emits
sessionStopped(), and the other error/stop branches referenced) to call
_pProcess->stop() without resetting _state, ensure timers/signals are not
prematurely stopped so onProcessFinished() still receives the completion event,
and adjust prepareAdapter()/State::STOPPING handling so fast retries cannot
start a new process while the old process is still running.

Comment on lines 51 to +59
if (_pProcess->state() != QProcess::NotRunning)
{
_pProcess->closeWriteChannel();
if (!_pProcess->waitForFinished(3000))
{
_pProcess->kill();
_pProcess->waitForFinished(1000);
}
QTimer::singleShot(cStopTimeoutMs, this, [this]() {
if (_pProcess->state() != QProcess::NotRunning)
{
_pProcess->kill();
}
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd src/ProtocolAdapter && wc -l adapterprocess.cpp adapterprocess.h

Repository: ModbusScope/NotOnlyModbusScope

Length of output: 130


🏁 Script executed:

cd src/ProtocolAdapter && head -80 adapterprocess.cpp | tail -40

Repository: ModbusScope/NotOnlyModbusScope

Length of output: 1138


🏁 Script executed:

cd src/ProtocolAdapter && grep -n "void.*start\|void.*stop\|void.*onProcessFinished" adapterprocess.cpp | head -20

Repository: ModbusScope/NotOnlyModbusScope

Length of output: 194


🏁 Script executed:

cd src/ProtocolAdapter && cat adapterprocess.h

Repository: ModbusScope/NotOnlyModbusScope

Length of output: 3744


🏁 Script executed:

cd src/ProtocolAdapter && sed -n '20,50p' adapterprocess.cpp

Repository: ModbusScope/NotOnlyModbusScope

Length of output: 1035


🏁 Script executed:

cd src/ProtocolAdapter && sed -n '172,195p' adapterprocess.cpp

Repository: ModbusScope/NotOnlyModbusScope

Length of output: 332


🏁 Script executed:

cd src/ProtocolAdapter && grep -n "QTimer\|_pTimer\|killTimer\|stopTimer" adapterprocess.cpp adapterprocess.h

Repository: ModbusScope/NotOnlyModbusScope

Length of output: 194


Cancel any pending stop timeout before a restart.

The singleShot timer is not cancelled when start() or onProcessFinished() is called. If the child process exits quickly and a new adapter is started within cStopTimeoutMs, the stale callback will fire and kill the fresh process because _pProcess is running again. Store the timer ID returned by singleShot as a member and call killTimer() from start() and onProcessFinished().

Additionally, the lambda contains multiple statements and violates the coding guideline: "Avoid lambda expressions with more than 2 captures or multiple statements; use named functions instead for clarity". Refactor this into a named private slot or function.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ProtocolAdapter/adapterprocess.cpp` around lines 51 - 59, Replace the
QTimer::singleShot lambda with a cancellable timer and a named handler: add an
int member (e.g., m_stopTimerId = 0) and use startTimer(cStopTimeoutMs) instead
of QTimer::singleShot to store the returned timer id; implement a private
handler (e.g., void handleStopTimeout() or override timerEvent and dispatch when
timerId == m_stopTimerId) that checks _pProcess->state() != QProcess::NotRunning
and calls _pProcess->kill(); ensure you call killTimer(m_stopTimerId) and reset
m_stopTimerId to 0 from start() and onProcessFinished() to cancel any pending
stop timeout before restarting or after exit, and remove the lambda to satisfy
the "no multi-statement lambda" guideline.

Comment on lines 47 to +50
process.stop();

/* stop() is non-blocking; give the event loop time to receive the response */
QTest::qWait(500);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "tst_adapterprocess.cpp" -o -name "adapterprocess.cpp" -o -name "adapterprocess.h" | head -20

Repository: ModbusScope/NotOnlyModbusScope

Length of output: 200


🏁 Script executed:

cat -n tests/ProtocolAdapter/tst_adapterprocess.cpp | sed -n '40,85p'

Repository: ModbusScope/NotOnlyModbusScope

Length of output: 1690


🏁 Script executed:

cat -n src/ProtocolAdapter/adapterprocess.cpp | grep -A 10 -B 5 "cStopTimeoutMs\|stop()"

Repository: ModbusScope/NotOnlyModbusScope

Length of output: 1611


🏁 Script executed:

cat -n src/ProtocolAdapter/adapterprocess.h | grep -A 15 "stop()"

Repository: ModbusScope/NotOnlyModbusScope

Length of output: 673


These fixed 500 ms waits are shorter than the stop contract and introduce race conditions.

AdapterProcess::stop() may legitimately take up to 3000 ms before the forced-kill path runs (line 54 in adapterprocess.cpp). Both sleeps at lines 50 and 73 are insufficient and timing-dependent. processFinishedEmittedOnStop() can fail on slower systems during valid graceful shutdown, and sendRequestEmitsResponseReceived() relies on fixed delay rather than signal arrival. Replace these with signal-driven waits: use QSignalSpy::wait() with timeout matching cStopTimeoutMs (3000 ms) instead of QTest::qWait(500).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/ProtocolAdapter/tst_adapterprocess.cpp` around lines 47 - 50, Replace
the fixed QTest::qWait(500) sleeps in tst_adapterprocess.cpp with signal-driven
waits: after calling AdapterProcess::stop() use a QSignalSpy that watches the
process finished signal (the same signal used in processFinishedEmittedOnStop())
and call spy.wait(cStopTimeoutMs) so the test waits up to cStopTimeoutMs (3000
ms) instead of a fixed 500ms; similarly, in sendRequestEmitsResponseReceived()
replace the fixed qWait with a QSignalSpy on the response-received signal used
by the test and call spy.wait(cStopTimeoutMs). Ensure you reference the same
signal names the tests currently expect and remove the QTest::qWait calls.

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.

2 participants