Skip to content

test(UnitTest): handle early local HTTP requests - #14729

Open
alireza787b wants to merge 2 commits into
mavlink:masterfrom
alireza787b:agent/local-http-server-early-request
Open

test(UnitTest): handle early local HTTP requests#14729
alireza787b wants to merge 2 commits into
mavlink:masterfrom
alireza787b:agent/local-http-server-early-request

Conversation

@alireza787b

@alireza787b alireza787b commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Description

LocalHttpTestServer previously had two timing gaps:

  • request bytes could already be buffered before its one-shot readyRead connection was installed
  • the first readyRead could contain only part of an HTTP request, causing the fixture to respond and close before the header was complete

The responder now consumes any already-buffered bytes, accumulates a bounded request header across readyRead notifications, and sends one response only after \r\n\r\n arrives. The regression test deliberately splits the request header across two writes and verifies that the server does not answer the incomplete first fragment.

This is test infrastructure only; application networking is unchanged.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change
  • Documentation update
  • Refactoring
  • CI/Build changes
  • Other: test fixture reliability

Testing

  • Tested locally
  • Added/updated unit tests
  • Tested with simulator (SITL)
  • Tested with hardware

Local checks on exact head e7bb8c40f:

  • both touched translation units pass C++20 syntax checks with Qt 6.4
  • a standalone Qt probe verifies no response to the first request fragment and a complete response after the terminator
  • git diff --check

Exact PR head e7bb8c40f2c179541f4efd91bcd2b51f26e91dfb passed all reported QGC checks.

Platforms Tested

  • Linux
  • Windows
  • macOS
  • Android
  • iOS

Flight Stacks Tested

  • PX4
  • ArduPilot

Not applicable: this changes a unit-test fixture only.

Screenshots

Not applicable; there is no UI change.

Checklist

  • I have read the Contribution Guidelines
  • I have read the Code of Conduct
  • My code follows the project's coding standards
  • I have added tests that prove the fixture behavior
  • New and existing unit tests pass in QGC CI

Related Issues

This fixture correction remains independently reviewable. #14730 carries it as a test-infrastructure prerequisite because its camera-free MJPEG delivery test uses installRawResponder.


By submitting this pull request, I confirm that my contribution is made under the terms of the project's dual license (Apache 2.0 and GPL v3).

@alireza787b
alireza787b force-pushed the agent/local-http-server-early-request branch from 03ee5b0 to 903cd28 Compare July 28, 2026 06:33
@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 32.65%. Comparing base (f29efd3) to head (e7bb8c4).
⚠️ Report is 213 commits behind head on master.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master   #14729      +/-   ##
==========================================
+ Coverage   25.47%   32.65%   +7.18%     
==========================================
  Files         769      783      +14     
  Lines       65912    67513    +1601     
  Branches    30495    31263     +768     
==========================================
+ Hits        16788    22047    +5259     
+ Misses      37285    30606    -6679     
- Partials    11839    14860    +3021     
Flag Coverage Δ
unittests 32.65% <ø> (+7.18%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.
see 476 files with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 94ab3c6...e7bb8c4. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Build Results

Platform Status

Platform Status Details
Linux Passed View
Windows Passed View
MacOS Passed View
Android Passed View

All builds passed.

Pre-commit

Check Status Details
pre-commit Failed (non-blocking) View

Pre-commit hooks: 0 passed, 0 failed, 0 skipped.

Test Results

linux-coverage-integration: 37 passed, 0 skipped
linux-coverage-unit: 115 passed, 0 skipped
linux-sanitizers-integration: 20 passed, 0 skipped
linux-sanitizers-unit: 115 passed, 0 skipped
Total: 287 passed, 0 skipped

Code Coverage

Coverage Baseline Change
69.3% 69.3% +0.1%

Artifact Sizes

Artifact Size Δ from master
QGroundControl 221.78 MB -4.44 MB (decrease)
QGroundControl-aarch64 179.34 MB -0.01 MB (decrease)
QGroundControl-installer-AMD64 137.85 MB -0.44 MB (decrease)
QGroundControl-installer-AMD64-ARM64 80.36 MB -0.22 MB (decrease)
QGroundControl-installer-ARM64 109.12 MB -0.00 MB (decrease)
QGroundControl-linux 87.02 MB -78.72 MB (decrease)
QGroundControl-mac 87.02 MB No change
QGroundControl-x86_64 191.58 MB +0.00 MB (increase)
Total size decreased by 83.83 MB

Updated: 2026-07-28 13:13:00 UTC • Commit: e7bb8c4 • Triggered by: Android

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR improves the reliability of the TestFixtures::LocalHttpTestServer test infrastructure by ensuring already-buffered and fragmented HTTP request headers are fully accumulated before a response is sent, and adds a regression unit test covering the early/fragmented request scenario.

Changes:

  • Adds LocalHttpTestServerTest to validate that the server does not respond to an incomplete HTTP request header fragment.
  • Updates LocalHttpTestServer::installRawResponder to consume already-buffered bytes and wait for \r\n\r\n before responding.
  • Registers the new unit test in the UnitTestFramework Tests CMake target.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
test/UnitTestFramework/Tests/LocalHttpTestServerTest.h Adds a new UnitTest class for LocalHttpTestServer regression coverage.
test/UnitTestFramework/Tests/LocalHttpTestServerTest.cc Implements a regression test that splits an HTTP header across writes and asserts no early response.
test/UnitTestFramework/Tests/CMakeLists.txt Adds the new test sources and registers LocalHttpTestServerTest.
test/UnitTestFramework/Fixtures/LocalHttpTestServer.cc Updates the raw responder to accumulate request data and respond only after the header terminator is received.

Comment on lines +111 to +117
state->request.append(socket->readAll());
if (!state->request.contains(QByteArrayLiteral("\r\n\r\n"))) {
if (state->request.size() > MAX_REQUEST_HEADER_SIZE) {
socket->disconnectFromHost();
}
return;
}
Comment on lines +3 to +17
#include <QtNetwork/QHostAddress>
#include <QtNetwork/QTcpSocket>
#include <QtTest/QSignalSpy>

#include "Fixtures/LocalHttpTestServer.h"

void LocalHttpTestServerTest::_testEarlyRequest()
{
TestFixtures::LocalHttpTestServer server;
QVERIFY(server.listen());
server.installHttpResponder(QByteArrayLiteral("ready"));

QTcpSocket client;
client.connectToHost(QHostAddress::LocalHost, server.port());
QVERIFY(client.waitForConnected(TestTimeout::mediumMs()));
@DonLakeFlyer

Copy link
Copy Markdown
Contributor

Review feedback from a Claude Fable review:

Worth changing

  1. Drop the unrelated reformatting hunks — the httpReasonPhrase switch re-indent, the installHttpResponder string-literal re-wrap, and the } // namespace} // namespace spacing changes touch code the fix doesn't. They double the diff for zero behavior change and obscure the real fix.
  2. Latch the terminal state on the oversized-header path — set state->responseSent = true before disconnectFromHost() when MAX_REQUEST_HEADER_SIZE is exceeded, so later readyRead notifications don't keep appending to state->request while the disconnect is in flight. This also addresses the substance of the existing size-bound review comment without the complexity of a budgeted read, which seems unnecessary for a loopback test fixture.
  3. Restore alphabetical ordering in test/UnitTestFramework/Tests/CMakeLists.txtLocalHttpTestServerTest is inserted after MultiSignalSpyTest in both target_sources and the add_qgc_test list.
  4. Include order in LocalHttpTestServerTest.cc — sibling tests order own header → project headers → Qt headers; the new file puts Qt headers before Fixtures/LocalHttpTestServer.h.

Optional

  1. PR description overstates the first bug — the "request bytes buffered before the one-shot readyRead connection was installed" gap is essentially theoretical: the connect happens synchronously inside the newConnection handler before the event loop spins again, and Qt's read notifier is level-triggered, so already-buffered data still produces a readyRead. The partial-header race is the real bug; the immediate sendResponseWhenRequestComplete() call is fine as defensive hardening, but the description should reflect that.
  2. Merge the two anonymous namespaces in LocalHttpTestServer.cc — the new RawResponderState/MAX_REQUEST_HEADER_SIZE block adds a second anonymous namespace while httpReasonPhrase keeps its own mid-file.

For the record: the core fix is correct and well-tested — the fragmented-header regression test genuinely fails against the old single-shot responder, and the shared-state/lifetime handling (QSharedPointer state, socket-context connection, deleteLater cleanup) is sound.

@DonLakeFlyer

Copy link
Copy Markdown
Contributor

Claude Fable review — assessment of the two Copilot inline comments:

1. IPv4/IPv6 bind mismatch in LocalHttpTestServerTest.cc — agree, should be fixed. The fixture explicitly supports falling back to an IPv6-only bind (listen() tries LocalHostIPv6 second) and url() already handles that case, so a test hardcoding QHostAddress::LocalHost contradicts the fixture's own contract. It will pass in today's CI (IPv4 loopback essentially always binds), but it's a latent spurious failure on IPv6-only configurations, and the fix is one line: derive the host from server.url() instead of hardcoding the address.

2. Size bound enforced after append(readAll()) — technically correct observation, but the suggested budgeted read is over-engineered for this fixture. The worst case is a single transient over-allocation bounded by the kernel socket buffer (a few hundred KB), freed as soon as the socket closes; the bound's real job — keeping a terminator-less test from accumulating forever — is already achieved, just one readyRead late. Reading "at most the remaining header budget +1 byte" and truncating to the terminator adds branching that itself needs testing, which isn't warranted in a loopback test fixture. The minimal fix worth taking is to latch the terminal state on the overflow path (state->responseSent = true before disconnectFromHost()), which stops further accumulation while the disconnect is in flight. I'd decline the rest of the suggestion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants