elastic/elasticsearch · error · InvalidUserDataException

// TEST[continued] cannot immediately follow // TESTSETUP: $

Error message

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

What it means

Inside the test.continued() block, after verifying a same-file previousTest exists, the code checks if previousTest.testSetup() is true and throws this message. // TEST[continued] appends into the previous snippet's YAML test section, but a TESTSETUP snippet writes into the 'setup:' section — continuing from it would merge request steps into setup, which is invalid. NOTE: the source at line 247-248 checks testSetup() and labels it TESTSETUP; a near-identical check at line 250-251 (which throws the TEARDOWN message) also calls testSetup() rather than testTearDown(), which appears to be a copy-paste bug — the TEARDOWN branch likely intends to check testTearDown().

Source

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

            }
            if (snippet.test() || snippet.language().equals("console")) {
                test(snippet);
                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. */

View on GitHub (pinned to db6a809a66)

Solutions

  1. Insert a normal (non-continued, non-setup) console request snippet between the TESTSETUP and the // TEST[continued] snippet.
  2. Remove // TEST[continued] from the snippet if it should run as part of setup instead.

Example fix

// before — continued right after setup
// TESTSETUP
POST /idx/_doc
{ "a": 1 }
// CONSOLE
// TEST[continued]
GET /idx/_search

// after — base request separates them
// TESTSETUP
POST /idx/_doc
{ "a": 1 }
// CONSOLE
GET /idx/_search
// CONSOLE
// TEST[continued]
GET /idx/_search?q=a
Defensive patterns

Strategy: validation

Validate before calling

// Ensure TEST[continued] does not immediately follow a TESTSETUP
void checkContinuedAfterSetup(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.testSetup) {
            throw new IllegalStateException(
                "// TEST[continued] in " + filePath + " immediately follows TESTSETUP");
        }
    }
}

Prevention

When it happens

Trigger: A // TEST[continued] snippet immediately follows a // TESTSETUP snippet in the same file, so previousTest.testSetup() is true.

Common situations: Restructuring a doc so a TESTSETUP is the last snippet before a continued test; the continued test has no real base request to append to.

Related errors


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