elastic/elasticsearch · error · InvalidUserDataException
${snippet} must follow test setup or be first
Error message
${snippet} must follow test setup or be first What it means
testTearDown fires when a snippet is a TESTSETUP-style teardown (// TEARDOWN block acting as file-level teardown). It checks: if there is a previousTest AND that previousTest was NOT a testSetup AND the snippet is in the same file as the last docs path (lastDocsPath.equals(snippet.path())), it throws. A file-level teardown must either be the first snippet in the file or immediately follow a // TESTSETUP block.
Source
Thrown at build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/doc/RestTestsFromDocSnippetTask.java:315
replaceBlockQuote(response.contents()).lines().forEach(line -> current.println(" " + line));
}
}
private void teardown(final Snippet snippet) {
// insert a teardown defined outside of the docs
for (final String name : snippet.teardown().split(",")) {
final String teardown = getTeardowns().get().get(name);
if (teardown == null) {
throw new InvalidUserDataException("Couldn't find named teardown $name for " + snippet);
}
current.println("# Named teardown " + name);
current.println(teardown);
}
}
private void testTearDown(Snippet snippet) {
if (previousTest != null && previousTest.testSetup() == false && lastDocsPath.equals(snippet.path())) {
throw new InvalidUserDataException(snippet + " must follow test setup or be first");
}
setupCurrent(snippet);
current.println("---");
current.println("teardown:");
body(snippet, true);
}
void emitDo(
String method,
String pathAndQuery,
String body,
String catchPart,
List<String> warnings,
boolean inSetup,
boolean skipShardFailures
) {
String[] tokenized = pathAndQuery.split("\\?");
String path = tokenized[0];View on GitHub (pinned to db6a809a66)
Solutions
- Move the // TEARDOWN snippet to the top of the file (before any console requests) or immediately after a // TESTSETUP snippet.
- If the teardown is meant to run after a specific test, use the per-snippet // TEARDOWN[name] form on a console snippet instead of the file-level block.
Example fix
// before — file-level teardown after a request
// CONSOLE
GET /_search
// TEARDOWN
DELETE /idx
// after — teardown follows setup at top of file
// TESTSETUP
POST /idx/_doc
{ "a": 1 }
// TEARDOWN
DELETE /idx
// CONSOLE
GET /_search Defensive patterns
Strategy: validation
Validate before calling
// Ensure file-level // TEARDOWN is first or follows TESTSETUP, within the same file
void checkFileTeardownOrder(List<SnippetInfo> fileSnippets, String filePath) {
for (int i = 0; i < fileSnippets.size(); i++) {
SnippetInfo s = fileSnippets.get(i);
if (s.isFileLevelTeardown && i > 0) {
SnippetInfo prev = fileSnippets.get(i - 1);
if (!prev.testSetup) {
throw new IllegalStateException(
"File-level TEARDOWN in " + filePath
+ " must be first or follow TESTSETUP");
}
}
}
} Prevention
- Place file-level // TEARDOWN at the top of the file or right after // TESTSETUP.
- Use per-snippet // TEARDOWN[name] for teardowns tied to a specific test, not the file-level block.
- Remember file-level teardown wraps the whole document — it belongs with setup, not after requests.
When it happens
Trigger: A // TEARDOWN (file-level) snippet appears in the same file after a regular console request (previousTest.testSetup() is false), rather than after a TESTSETUP or at the top.
Common situations: Placing a file-level teardown in the middle of a doc after normal request snippets; the teardown is meant to wrap the whole file and must sit alongside setup.
Related errors
- ${snippet}: No paired previous test
- // TEST[continued] cannot immediately follow // TESTSETUP: $
- // TEST[continued] cannot immediately follow // TEARDOWN: ${
- ${snippet}: wasn't first. TESTSETUP can only be used in the
- Extra content ${message} ('${cutOutNoNl}') matching [${patte
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/458c9940e5edc15b.
Report an issue: GitHub.