Repository: quarkusio/quarkus
Issue: quarkus:test Re-run failed tests does not run failed ParameterizedTest

### Describe the bug

In Continuous Testing mode tests that use `@ParameterizedTest` with `@ValueSource` like this:

```
@QuarkusTest
public class GreetingResourceTest {

    @ParameterizedTest
    @ValueSource(strings = {"fail"})
    public void parametrizedTest(String input) {
        assertEquals("ok", input);
    }

}
```
are not rerun when a "Re-run failed tests" command is issued, but `All tests are now passing` is shown instead.



### Expected behavior

ParameterizedTest tests that fail should be rerun

### Actual behavior

ParameterizedTest tests that fail are declared as passing without having been rerurn

### How to Reproduce?

Declare a failing ParameterizedTest test and try to rerun it in Continuous Testing

### Output of `uname -a` or `ver`

5.13.0-30-generic #33-Ubuntu SMP Fri Feb 4 17:03:31 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

### Output of `java -version`

Java version: 11.0.13, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64

### GraalVM version (if different from Java)

NA

### Quarkus version or git rev

2.7.2

### Build tool (ie. output of `mvnw --version` or `gradlew --version`)

Apache Maven 3.8.4 (9b656c72d54e5bacbed989b64718c159fe39b537)

### Additional information

_No response_

Changed files:
core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestRunListener.java
extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/testrunner/params/TestParameterizedTestCase.java
core/deployment/src/main/java/io/quarkus/deployment/dev/testing/ContinuousTestingSharedStateListener.java
extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/tests/TestsProcessor.java
core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java
core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java
core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestResult.java
core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestConsoleHandler.java

diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/ContinuousTestingSharedStateListener.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/ContinuousTestingSharedStateListener.java
index 85da540feb5..2e965d77246 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/ContinuousTestingSharedStateListener.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/ContinuousTestingSharedStateListener.java
@@ -67,11 +67,6 @@ public void runComplete(TestRunResults testRunResults) {
                 });
             }
 
-            @Override
-            public void noTests(TestRunResults results) {
-                runComplete(results);
-            }
-
             @Override
             public void runAborted() {
                 ContinuousTestingSharedStateManager.setInProgress(false);
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java
index 1bce4eaedfd..a43da33c5d4 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/JunitTestRunner.java
@@ -178,21 +178,6 @@ public FilterResult apply(TestDescriptor testDescriptor) {
             LauncherDiscoveryRequest request = launchBuilder
                     .build();
             TestPlan testPlan = launcher.discover(request);
-            if (!testPlan.containsTests()) {
-                testState.pruneDeletedTests(allDiscoveredIds, dynamicIds);
-                //nothing to see here
-                for (TestRunListener i : listeners) {
-                    i.noTests(new TestRunResults(runId, classScanResult, classScanResult == null, start,
-                            System.currentTimeMillis(), toResultsMap(testState.getCurrentResults())));
-                }
-                quarkusTestClasses.close();
-                return new Runnable() {
-                    @Override
-                    public void run() {
-
-                    }
-                };
-            }
             long toRun = testPlan.countTestIdentifiers(TestIdentifier::isTest);
             for (TestRunListener listener : listeners) {
                 listener.runStarted(toRun);
@@ -226,6 +211,7 @@ public void quarkusStarting() {
                         });
 
                         Map<String, Map<UniqueId, TestResult>> resultsByClass = new HashMap<>();
+                        AtomicReference<TestIdentifier> currentNonDynamicTest = new AtomicReference<>();
                         launcher.execute(testPlan, new TestExecutionListener() {
 
                             @Override
@@ -233,6 +219,10 @@ public void executionStarted(TestIdentifier testIdentifier) {
                                 if (aborted) {
                                     return;
                                 }
+                                boolean dynamic = dynamicIds.contains(UniqueId.parse(testIdentifier.getUniqueId()));
+                                if (!dynamic) {
+                                    currentNonDynamicTest.set(testIdentifier);
+                                }
                                 startTimes.put(testIdentifier, System.currentTimeMillis());
                                 String testClassName = "";
                                 Class<?> testClass = getTestClassFromSource(testIdentifier.getSource());
@@ -260,7 +250,7 @@ public void executionSkipped(TestIdentifier testIdentifier, String reason) {
                                             s -> new HashMap<>());
                                     TestResult result = new TestResult(displayName, testClass.getName(), id,
                                             TestExecutionResult.aborted(null),
-                                            logHandler.captureOutput(), testIdentifier.isTest(), runId, 0);
+                                            logHandler.captureOutput(), testIdentifier.isTest(), runId, 0, true);
                                     results.put(id, result);
                                     if (result.isTest()) {
                                         for (TestRunListener listener : listeners) {
@@ -274,6 +264,9 @@ public void executionSkipped(TestIdentifier testIdentifier, String reason) {
                             @Override
                             public void dynamicTestRegistered(TestIdentifier testIdentifier) {
                                 dynamicIds.add(UniqueId.parse(testIdentifier.getUniqueId()));
+                                for (TestRunListener listener : listeners) {
+                                    listener.dynamicTestRegistered(testIdentifier);
+                                }
                             }
 
                             @Override
@@ -282,6 +275,7 @@ public void executionFinished(TestIdentifier testIdentifier,
                                 if (aborted) {
                                     return;
                                 }
+                                boolean dynamic = dynamicIds.contains(UniqueId.parse(testIdentifier.getUniqueId()));
                                 Set<String> touched = touchedClasses.pop();
                                 Class<?> testClass = getTestClassFromSource(testIdentifier.getSource());
                                 String displayName = getDisplayNameFromIdentifier(testIdentifier, testClass);
@@ -306,18 +300,33 @@ public void executionFinished(TestIdentifier testIdentifier,
                                         testClassUsages.updateTestData(testClassName, id, touched);
                                     }
                                 }
-
                                 Map<UniqueId, TestResult> results = resultsByClass.computeIfAbsent(testClassName,
                                         s -> new HashMap<>());
                                 TestResult result = new TestResult(displayName, testClassName, id,
                                         testExecutionResult,
                                         logHandler.captureOutput(), testIdentifier.isTest(), runId,
-                                        System.currentTimeMillis() - startTimes.get(testIdentifier));
-                                results.put(id, result);
+                                        System.currentTimeMillis() - startTimes.get(testIdentifier), true);
+                                if (!results.containsKey(id)) {
+                                    //if a child has failed we may have already marked the parent failed
+                                    results.put(id, result);
+                                }
                                 if (result.isTest()) {
                                     for (TestRunListener listener : listeners) {
                                         listener.testComplete(result);
                                     }
+                                    if (dynamic && testExecutionResult.getStatus() == TestExecutionResult.Status.FAILED) {
+                                        //if it is dynamic we fail the parent as well for re-runs
+
+                                        RuntimeException failure = new RuntimeException("A child test failed");
+                                        failure.setStackTrace(new StackTraceElement[0]);
+                                        results.put(id,
+                                                new TestResult(currentNonDynamicTest.get().getDisplayName(),
+                                                        result.getTestClass(),
+                                                        currentNonDynamicTest.get().getUniqueIdObject(),
+                                                        TestExecutionResult.failed(failure), List.of(), false, runId, 0,
+                                                        false));
+                                        results.put(UniqueId.parse(currentNonDynamicTest.get().getUniqueId()), result);
+                                    }
                                 } else if (testExecutionResult.getStatus() == TestExecutionResult.Status.FAILED) {
                                     //if a parent fails we fail the children
                                     Set<TestIdentifier> children = testPlan.getChildren(testIdentifier);
@@ -327,7 +336,7 @@ public void executionFinished(TestIdentifier testIdentifier,
                                                 childId,
                                                 testExecutionResult,
                                                 logHandler.captureOutput(), child.isTest(), runId,
-                                                System.currentTimeMillis() - startTimes.get(testIdentifier));
+                                                System.currentTimeMillis() - startTimes.get(testIdentifier), true);
                                         results.put(childId, result);
                                         if (child.isTest()) {
                                             for (TestRunListener listener : listeners) {
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestConsoleHandler.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestConsoleHandler.java
index 764b66944c3..9225b2b8fc4 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestConsoleHandler.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestConsoleHandler.java
@@ -220,9 +220,11 @@ public void runComplete(TestRunResults results) {
                     log.error(statusHeader("TEST REPORT #" + results.getId()));
                     for (Map.Entry<String, TestClassResult> classEntry : results.getCurrentFailing().entrySet()) {
                         for (TestResult test : classEntry.getValue().getFailing()) {
-                            log.error(
-                                    RED + "Test " + test.getDisplayName() + " failed \\n" + RESET,
-                                    test.getTestExecutionResult().getThrowable().get());
+                            if (test.isReportable()) {
+                                log.error(
+                                        RED + "Test " + test.getDisplayName() + " failed \\n" + RESET,
+                                        test.getTestExecutionResult().getThrowable().get());
+                            }
                         }
                     }
                     log.error(
@@ -244,11 +246,6 @@ public void runComplete(TestRunResults results) {
                 }
             }
 
-            @Override
-            public void noTests(TestRunResults results) {
-                runComplete(results);
-            }
-
             @Override
             public void runAborted() {
                 firstRun = false;
@@ -268,6 +265,11 @@ public void testStarted(TestIdentifier testIdentifier, String className) {
                 }
                 testsStatusOutput.setMessage(status);
             }
+
+            @Override
+            public void dynamicTestRegistered(TestIdentifier testIdentifier) {
+                totalNoTests.incrementAndGet();
+            }
         });
 
     }
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestResult.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestResult.java
index 44e278c986e..4250d332492 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestResult.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestResult.java
@@ -18,9 +18,10 @@ public class TestResult {
     final long runId;
     final long time;
     final List<Throwable> problems;
+    final boolean reportable;
 
     public TestResult(String displayName, String testClass, UniqueId uniqueId, TestExecutionResult testExecutionResult,
-            List<String> logOutput, boolean test, long runId, long time) {
+            List<String> logOutput, boolean test, long runId, long time, boolean reportable) {
         this.displayName = displayName;
         this.testClass = testClass;
         this.uniqueId = uniqueId;
@@ -29,6 +30,7 @@ public TestResult(String displayName, String testClass, UniqueId uniqueId, TestE
         this.test = test;
         this.runId = runId;
         this.time = time;
+        this.reportable = reportable;
         List<Throwable> problems = new ArrayList<>();
         if (testExecutionResult.getThrowable().isPresent()) {
             Throwable t = testExecutionResult.getThrowable().get();
@@ -75,4 +77,8 @@ public long getTime() {
     public List<Throwable> getProblems() {
         return problems;
     }
+
+    public boolean isReportable() {
+        return reportable;
+    }
 }
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestRunListener.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestRunListener.java
index 5a817d18576..0f3e8278d40 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestRunListener.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestRunListener.java
@@ -24,7 +24,7 @@ default void testStarted(TestIdentifier testIdentifier, String className) {
 
     }
 
-    default void noTests(TestRunResults results) {
+    default void dynamicTestRegistered(TestIdentifier testIdentifier) {
 
     }
 }
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java
index 1ac18ade9c3..01d76c06184 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/testing/TestSupport.java
@@ -374,20 +374,8 @@ public void testStarted(TestIdentifier testIdentifier, String className) {
                     }
                 }
 
-                @Override
-                public void noTests(TestRunResults results) {
-                    allResults.add(results);
-                    runStarted(0);
-                }
             }));
         }
-        if (testCount.get() == 0) {
-            TestRunResults results = new TestRunResults(runId, classScanResult, classScanResult == null, start,
-                    System.currentTimeMillis(), Collections.emptyMap());
-            for (var i : testRunListeners) {
-                i.noTests(results);
-            }
-        }
         for (var i : testRunListeners) {
             i.runStarted(testCount.get());
         }
diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/tests/TestsProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/tests/TestsProcessor.java
index 3a0ac541a62..5d8ee7d0ce9 100644
--- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/tests/TestsProcessor.java
+++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/tests/TestsProcessor.java
@@ -146,6 +146,7 @@ DevConsoleRouteBuildItem runAllTests(LaunchModeBuildItem launchModeBuildItem) {
             @Override
             public void handle(RoutingContext event) {
                 ts.get().runAllTests();
+                event.response().setStatusCode(204).end();
             }
         });
     }
@@ -178,6 +179,7 @@ DevConsoleRouteBuildItem runFailedTests(LaunchModeBuildItem launchModeBuildItem)
             @Override
             public void handle(RoutingContext event) {
                 ts.get().runFailedTests();
+                event.response().setStatusCode(204).end();
             }
         });
     }
@@ -192,6 +194,7 @@ DevConsoleRouteBuildItem printfailures(LaunchModeBuildItem launchModeBuildItem)
             @Override
             public void handle(RoutingContext event) {
                 ts.get().printFullResults();
+                event.response().setStatusCode(204).end();
             }
         });
     }
@@ -235,6 +238,7 @@ DevConsoleRouteBuildItem forceRestart(LaunchModeBuildItem launchModeBuildItem) {
             @Override
             public void handle(RoutingContext event) {
                 RuntimeUpdatesProcessor.INSTANCE.doScan(true, true);
+                event.response().setStatusCode(204).end();
             }
         });
     }
diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/testrunner/params/TestParameterizedTestCase.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/testrunner/params/TestParameterizedTestCase.java
index 57c14bc6694..1c85ca3dd13 100644
--- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/testrunner/params/TestParameterizedTestCase.java
+++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/testrunner/params/TestParameterizedTestCase.java
@@ -13,6 +13,7 @@
 import io.quarkus.test.ContinuousTestingTestUtils;
 import io.quarkus.test.ContinuousTestingTestUtils.TestStatus;
 import io.quarkus.test.QuarkusDevModeTest;
+import io.restassured.RestAssured;
 
 public class TestParameterizedTestCase {
 
@@ -42,6 +43,13 @@ public void testParameterizedTests() throws InterruptedException {
         Assertions.assertEquals(4L, ts.getTestsPassed());
         Assertions.assertEquals(0L, ts.getTestsSkipped());
 
+        RestAssured.post("q/dev/io.quarkus.quarkus-vertx-http/tests/runfailed");
+
+        ts = utils.waitForNextCompletion();
+
+        Assertions.assertEquals(1L, ts.getTestsFailed());
+        Assertions.assertEquals(3L, ts.getTestsPassed()); //they are all re-run
+        Assertions.assertEquals(0L, ts.getTestsSkipped());
         test.modifyTestSourceFile(ParamET.class, new Function<String, String>() {
             @Override
             public String apply(String s) {