{"record":{"id":"ca15ab02d80a02b4","repo":"junit-team/junit5","slug":"testplan-must-only-be-executed-once","errorCode":null,"errorMessage":"TestPlan must only be executed once","messagePattern":"TestPlan must only be executed once","errorType":"validation","errorClass":"PreconditionViolationException","httpStatus":null,"severity":"error","filePath":"junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalTestPlan.java","lineNumber":47,"sourceCode":"\tprivate final LauncherDiscoveryResult discoveryResult;\n\tprivate final TestPlan delegate;\n\n\tstatic InternalTestPlan from(LauncherDiscoveryResult discoveryResult) {\n\t\tTestPlan delegate = TestPlan.from(discoveryResult.containsCriticalIssuesOrContainsTests(),\n\t\t\tdiscoveryResult.getEngineTestDescriptors(), discoveryResult.getConfigurationParameters(),\n\t\t\tdiscoveryResult.getOutputDirectoryCreator());\n\t\treturn new InternalTestPlan(discoveryResult, delegate);\n\t}\n\n\tprivate InternalTestPlan(LauncherDiscoveryResult discoveryResult, TestPlan delegate) {\n\t\tsuper(delegate.containsTests(), delegate.getConfigurationParameters(), delegate.getOutputDirectoryCreator());\n\t\tthis.discoveryResult = discoveryResult;\n\t\tthis.delegate = delegate;\n\t}\n\n\tvoid markStarted() {\n\t\tif (!executionStarted.compareAndSet(false, true)) {\n\t\t\tthrow new PreconditionViolationException(\"TestPlan must only be executed once\");\n\t\t}\n\t}\n\n\tLauncherDiscoveryResult getDiscoveryResult() {\n\t\treturn discoveryResult;\n\t}\n\n\tpublic TestPlan getDelegate() {\n\t\treturn delegate;\n\t}\n\n\t@Override\n\tpublic void addInternal(TestIdentifier testIdentifier) {\n\t\tdelegate.addInternal(testIdentifier);\n\t}\n\n\t@Override\n\tpublic void removeInternal(UniqueId uniqueId) {","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/junit-team/junit5/blob/f070c699a08b5d8393df9afd147af5c5e90bb21b/junit-platform-launcher/src/main/java/org/junit/platform/launcher/core/InternalTestPlan.java#L29-L65","documentation":"InternalTestPlan.markStarted uses an AtomicBoolean compareAndSet(false, true); the second caller observes true and throws PreconditionViolationException. A TestPlan is a single-use, stateful object tied to one execution; executing it twice would corrupt listener/state tracking.","triggerScenarios":"Calling Launcher.execute(testPlan) twice with the same InternalTestPlan, or invoking markStarted() a second time. The CAS guards exactly-once execution semantics.","commonSituations":"Reusing a discovered TestPlan across two execute() calls; a listener/wrapper that retries execution on the same plan; test harness code that re-runs a plan for re-run-on-failure features without rediscovering; building a custom launcher flow that re-submits a plan.","solutions":["Rediscover a fresh TestPlan for each execution (call discover() again, or use execute(LauncherDiscoveryRequest) which always starts fresh).","Track whether a plan has been executed in your code and do not reuse it.","For re-run-on-failure, open a new session and rediscover rather than reusing the plan."],"exampleFix":"// before\nTestPlan plan = launcher.discover(request);\nlauncher.execute(plan, listener);\nlauncher.execute(plan, listener); // throws: already started\n// after\nlauncher.execute(request, listener);\nlauncher.execute(request, listener); // fresh execution each time","handlingStrategy":"validation","validationCode":"// Track whether the plan was started; rediscover for each run\nAtomicBoolean started = new AtomicBoolean();\nif (!started.compareAndSet(false, true)) {\n    plan = launcher.discover(request); // fresh plan for the retry\n}","typeGuard":null,"tryCatchPattern":"try {\n    launcher.execute(plan, listener);\n} catch (PreconditionViolationException e) {\n    if (e.getMessage().contains(\"only be executed once\")) {\n        plan = launcher.discover(originalRequest); // rediscover\n        launcher.execute(plan, listener);\n    }\n}","preventionTips":["Rediscover a new TestPlan for each execution rather than reusing.","Prefer execute(LauncherDiscoveryRequest) which is inherently single-use.","Track plan state in your harness to avoid double execution."],"tags":["testplan","once-only","execution","precondition"],"backgroundTag":null,"analyzedSha":"f070c699a08b5d8393df9afd147af5c5e90bb21b","analyzedAt":"2026-08-11T20:31:00.530Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}