Skip to content

Send target ids in the request body when fetching coverage, to avoid 414 - #1742

Merged
arcuri82 merged 2 commits into
masterfrom
feature/minimization-refetch-414
Sep 9, 2026
Merged

Send target ids in the request body when fetching coverage, to avoid 414#1742
arcuri82 merged 2 commits into
masterfrom
feature/minimization-refetch-414

Conversation

@suarezrominajulieta

@suarezrominajulieta suarezrominajulieta commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Problem

RemoteControllerImplementation.getTestResults sent the ids of the targets as an ids
query parameter. On a large search there are thousands of them, and the resulting URI does
not fit in the request line: Jetty rejects anything above 8192 bytes with a 414, before
the request ever reaches EMController, so the driver code never runs and cannot do
anything about it.

Measured on the jasper case study (120s, seed 1): during the recomputation stage of the
minimization, EnterpriseFitness refetches descriptive ids for 1554 targets, which is
7302 characters of query string. Together with the path, the other query parameters and
the standard headers, that is over the limit.

On the EvoMaster side the 414 is not an exception. getTestResults returns null, the
caller logs Cannot retrieve coverage with full descriptive ids, and the individual is
dropped along with all of its targets
. 22 individuals were lost this way in a single
jasper run. The failure is silent: nothing in the output says that coverage was thrown away.

Change

  • EMController gains a POST variant of testResults that takes the ids in the body.
    Both variants delegate to the same private computeTestResults, so there is exactly one
    implementation of the logic.
  • An empty (or missing) id list keeps meaning "all targets" in the POST variant, as it
    already does for the empty ids query parameter. Passing an empty set instead of null
    would silently return no target at all.
  • RemoteControllerImplementation now uses the POST variant.
  • The GET variant is kept. It is part of the public contract of the Driver, it is what any
    Core older than this change talks to, and it is the only form usable by hand (eg with
    curl) to inspect a running Driver.

A POST for what is a read is not great REST, but there is no alternative: a GET carrying
a body cannot be sent by the HTTP client in use, as the JDK rewrites the method to POST on
its own as soon as anything is written to the connection.

Test

RemoteControllerManyTargetsTest drives the real client against a real driver and asks for
the 1554 targets that exposed the problem, with ids of realistic magnitude (5 digits, as at
the end of a jasper run). It asserts on the outcome of the API call rather than on how the
ids happen to be transported, so it keeps passing unchanged if the transport moves again,
and it catches a regression if it ever moves back into the URI.

Compatibility

A Core with this change requires a Driver with this change: there is no fallback to the
GET variant if the POST is not there. The opposite direction is fine, since the GET is
still served. Anything that pins the client version needs to be updated together with the
Core — in EMB, evomaster-version in jdk_25_maven/pom.xml.

Effect

Measured on jasper, 300s, 12 successful runs over seeds 1-9, 6 per configuration.
--heuristicsForSQLAdvanced=true was needed in all of them to work around an unrelated
pre-existing problem with table functions in this SUT.

before this change with this change
HTTP 414 present 0 in 12 runs
Cannot retrieve coverage 12 per run 0 in 12 runs
SUT restarts 6 per run 0 in 12 runs
targets lost in the recompute (--minimizeThresholdForLoss=0) 465 0 in 6 of 6

With the loss threshold at 0 the warning fires if a single target is lost, so this is a
categorical result rather than an average.

Minimization now behaves as it should: comparing --minimize=true against --minimize=false
gives +343 targets (+3.0%, Â12 = 0.833) and 29 fewer generated tests (-6.1%). Before,
the reported number went down by 425 when minimizing.

GET vs POST

The obvious alternative is to keep the GET and move the ids into its body. EvoMaster
already does exactly that elsewhere: AbstractRestFitness builds
builder.build("GET", bodyEntity) for GET and DELETE (AbstractRestFitness.kt:1132-1133),
citing RFC 9110 §9.3.1. That works because those calls go through the client from
HttpClientFactory.createTrustingJerseyClient(), which configures ApacheConnectorProvider
(HttpClientFactory.kt:62) precisely for this — its comment says the Jersey default "does not
handle PATCH properly and body payloads in GET/DELETE" — and disables JAX-RS compliance
validation (HttpClientFactory.kt:74).

The problem is that this endpoint is not that connection. Calls to the SUT are made with the
Apache-backed client; the Core-to-Driver connection uses ClientBuilder.newClient()
(RemoteControllerImplementation.kt:61), ie Jersey's default HttpUrlConnector over
java.net.HttpURLConnection, which silently rewrites the method to POST as soon as anything
is written to the connection.

Switching this client to the Apache connector would make a GET with a body technically
possible, but it would make the protocol depend on which connector is actually active at
runtime — and per the comment in HttpClientFactory.kt:80-88, that is not something we can
rely on or even check:

using Jersey is a shitshow... based on classpath misconfiguration, can pick up wrong provider
regardless of what you specify here, doing it silently

check passes on IDE, but then fail in Maven when using shaded client in the E2E

The assertion that would verify the connector is commented out for that reason. So if the
connector silently degrades, the GET becomes a POST against an endpoint that would not
exist, or fails compliance validation — and it breaks every coverage request of every run,
not just a few SUT calls.

Two further reasons not to take that route:

  • Silent version skew. An older Driver's @GET has only @QueryParams and never reads a
    body, so ids would default to empty, which that endpoint documents as "return everything".
    A Core sending the ids in a GET body against an older Driver would silently get all
    targets instead of the requested subset. With POST the same mismatch is an immediate 405.
  • The endpoint is a public contract of the Driver, also implemented by the JavaScript and
    .NET drivers. POST with a JSON body is supported out of the box by every server framework;
    reading a body on GET is not.

@suarezrominajulieta

Copy link
Copy Markdown
Collaborator Author

What I'm not sure about is whether to leave the get method without consumers—other than tests and potential drivers for compatibility—or to remove it and update the tests.

@suarezrominajulieta

suarezrominajulieta commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator Author

should change gets in the tests to new post, but leave get enpoint for backward compatibility - from meeting with @jgaleotti

@jgaleotti
jgaleotti requested a review from arcuri82 September 9, 2026 13:44
@arcuri82
arcuri82 marked this pull request as ready for review September 9, 2026 20:21
@arcuri82
arcuri82 merged commit 593b0e3 into master Sep 9, 2026
31 checks passed
@arcuri82
arcuri82 deleted the feature/minimization-refetch-414 branch September 9, 2026 20:22
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.

3 participants