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

  1. Move the // TEARDOWN snippet to the top of the file (before any console requests) or immediately after a // TESTSETUP snippet.
  2. 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

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


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/458c9940e5edc15b. Report an issue: GitHub.