elastic/elasticsearch · error · InvalidUserDataException

// TEST[continued] cannot immediately follow // TEARDOWN: ${

Error message

// TEST[continued] cannot immediately follow // TEARDOWN: ${test}

What it means

This is the third guard inside test.continued(). The source at lines 250-251 checks 'previousTest != null && previousTest.testSetup()' but the message says 'cannot immediately follow // TEARDOWN'. This is a suspected copy-paste defect: the condition likely should call previousTest.testTearDown() to detect a continued snippet following a teardown. As written, it fires under the same condition as the TESTSETUP message (previousTest is a setup), so the TEARDOWN label is misleading. A continued snippet is invalid after a teardown because teardowns close out a test section.

Source

Thrown at build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/doc/RestTestsFromDocSnippetTask.java:251

                previousTest = snippet;
            }
            // Must be an unmarked snippet....
        }

        private void test(Snippet test) {
            setupCurrent(test);

            if (test.continued()) {
                /* Catch some difficult to debug errors with // TEST[continued]
                 * and throw a helpful error message. */
                if (previousTest == null || previousTest.path().equals(test.path()) == false) {
                    throw new InvalidUserDataException("// TEST[continued] " + "cannot be on first snippet in a file: " + test);
                }
                if (previousTest != null && previousTest.testSetup()) {
                    throw new InvalidUserDataException("// TEST[continued] " + "cannot immediately follow // TESTSETUP: " + test);
                }
                if (previousTest != null && previousTest.testSetup()) {
                    throw new InvalidUserDataException("// TEST[continued] " + "cannot immediately follow // TEARDOWN: " + test);
                }
            } else {
                current.println("---");
                if (test.name() != null && test.name().isBlank() == false) {
                    if (names.add(test.name()) == false) {
                        throw new InvalidUserDataException("Duplicated snippet name '" + test.name() + "': " + test);
                    }
                    current.println("\"" + test.name() + "\":");
                } else {
                    current.println("\"line_" + test.start() + "\":");
                }
                /* The Elasticsearch test runner doesn't support quite a few
                 * constructs unless we output this skip. We don't know if
                 * we're going to use these constructs, but we might so we
                 * output the skip just in case. */
                current.println("  - skip:");
                current.println("      features:");
                current.println("        - default_shards");

View on GitHub (pinned to db6a809a66)

Solutions

  1. Add a new base console request snippet between the teardown and the // TEST[continued] snippet.
  2. Remove // TEST[continued] if the snippet should stand alone as a new test.
  3. If you maintain build-tools-internal, consider fixing the condition at line 250 to call testTearDown() so the message matches its true trigger.

Example fix

// before — continued after teardown
// TEARDOWN
DELETE /idx
// CONSOLE
// TEST[continued]
GET /idx/_search

// after — new base request first
// TEARDOWN
DELETE /idx
// CONSOLE
POST /idx2/_doc
{ "a": 1 }
// CONSOLE
// TEST[continued]
GET /idx2/_search
Defensive patterns

Strategy: validation

Validate before calling

// Ensure TEST[continued] does not immediately follow a TEARDOWN (intent of the guard)
// NOTE: source code at line 250 checks testSetup() rather than testTearDown();
// this lint checks the intended condition.
void checkContinuedAfterTeardown(List<SnippetInfo> fileSnippets, String filePath) {
    for (int i = 1; i < fileSnippets.size(); i++) {
        SnippetInfo prev = fileSnippets.get(i - 1);
        SnippetInfo cur = fileSnippets.get(i);
        if (cur.continued && prev.testTearDown) {
            throw new IllegalStateException(
                "// TEST[continued] in " + filePath + " immediately follows TEARDOWN");
        }
    }
}

Prevention

When it happens

Trigger: Per current code: identical trigger to the TESTSETUP message (previousTest is a setup). Per intent: a // TEST[continued] snippet immediately follows a // TEARDOWN snippet in the same file.

Common situations: Reordering snippets so a teardown is followed by a continued test; the continued block has no open test section to append to after a teardown closed it.

Related errors


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