Repository: quarkusio/quarkus
Issue: testNative tag ignores binary name generated by using quarkus.package.output-name/add-runner-suffix

### Describe the bug

If the properties `quarkus.package.output-name=<binary-name>` or `quarkus.package.add-runner-suffix=false` are used to modify the binary name generated by the `quarkusBuild`, the `testNative` task fails to find the executable to run.

Once both properties are removed from the configuration, the `testNative` task works again.

### Expected behavior

The `testNative` task correctly finds the native binary with the custom name and is able to run the native tests.

### Actual behavior

The `testNative` tasks fails. After opening the generated test results report, the following exception is listed: 

```
java.lang.RuntimeException: java.io.IOException: Cannot run program "/private/tmp/test/build/test-1.0.0-SNAPSHOT-runner": error=2, No such file or directory
	at io.quarkus.test.junit.QuarkusIntegrationTestExtension.throwBootFailureException(QuarkusIntegrationTestExtension.java:337)
	at io.quarkus.test.junit.QuarkusIntegrationTestExtension.beforeEach(QuarkusIntegrationTestExtension.java:110)
        [...]
```

### How to Reproduce?

Steps to reproduce:
1. Create a simple quarkus application. The default example comes with a native test: `quarkus create app --gradle test`.
2. Verify native tests work by default by running: `./gradlew -Dquarkus.package.type=native build testNative`.
3. Make sure to clean the previous build so the currently generated binary is deleted, by running: `./gradlew clean`.
4. Use any of the two properties `quarkus.package.output-name` or `quarkus.package.add-runner-suffix` while building: `./gradlew -Dquarkus.package.type=native -Dquarkus.package.output-name=test -Dquarkus.package.add-runner-suffix=false build testNative`.
5. Open the generated native test report to see the exception: `file:///private/tmp/test/build/reports/tests/testNative/index.html`

### Output of `uname -a` or `ver`

Darwin rrivas-mac 22.1.0 Darwin Kernel Version 22.1.0: Sun Oct  9 20:14:54 PDT 2022; root:xnu-8792.41.9~2/RELEASE_X86_64 x86_64

### Output of `java -version`

openjdk version "17.0.5" 2022-10-18 OpenJDK Runtime Environment GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08) OpenJDK 64-Bit Server VM GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08, mixed mode, sharing)

### GraalVM version (if different from Java)

_No response_

### Quarkus version or git rev

2.15.1.Final

### Build tool (ie. output of `mvnw --version` or `gradlew --version`)

Gradle 7.5.1

### Additional information

Although I chose `gradle` for the reproduction steps, it can be reproduced with both `gradle` and `maven`.

Changed files:
devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java
devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/extension/QuarkusPluginExtension.java
integration-tests/gradle/src/test/java/io/quarkus/gradle/nativeimage/NativeIntegrationTestIT.java
integration-tests/gradle/src/test/java/io/quarkus/gradle/nativeimage/BasicJavaNativeBuildIT.java

diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/extension/QuarkusPluginExtension.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/extension/QuarkusPluginExtension.java
index 925eec91f09..ee4fd9f4100 100644
--- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/extension/QuarkusPluginExtension.java
+++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/extension/QuarkusPluginExtension.java
@@ -7,6 +7,7 @@
 import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Properties;
 import java.util.Set;
 import java.util.StringJoiner;
 import java.util.stream.Collectors;
@@ -85,7 +86,8 @@ public void beforeTest(Test task) {
             task.environment(BootstrapConstants.TEST_TO_MAIN_MAPPINGS, fileList);
             project.getLogger().debug("test dir mapping - {}", fileList);
 
-            final String nativeRunner = task.getProject().getBuildDir().toPath().resolve(finalName() + "-runner")
+            final String nativeRunner = task.getProject().getBuildDir().toPath()
+                    .resolve(buildNativeRunnerName(props))
                     .toAbsolutePath()
                     .toString();
             props.put("native.image.path", nativeRunner);
@@ -94,6 +96,30 @@ public void beforeTest(Test task) {
         }
     }
 
+    public String buildNativeRunnerName(final Map<String, Object> taskSystemProps) {
+        Properties properties = new Properties(taskSystemProps.size());
+        properties.putAll(taskSystemProps);
+        quarkusBuildProperties.entrySet()
+                .forEach(buildEntry -> properties.putIfAbsent(buildEntry.getKey(), buildEntry.getValue()));
+        System.getProperties().entrySet()
+                .forEach(propEntry -> properties.putIfAbsent(propEntry.getKey(), propEntry.getValue()));
+        System.getenv().entrySet().forEach(
+                envEntry -> properties.putIfAbsent(envEntry.getKey(), envEntry.getValue()));
+        StringBuilder nativeRunnerName = new StringBuilder();
+
+        if (properties.containsKey("quarkus.package.output-name")) {
+            nativeRunnerName.append(properties.get("quarkus.package.output-name"));
+        } else {
+            nativeRunnerName.append(finalName());
+        }
+        if (!properties.containsKey("quarkus.package.add-runner-suffix")
+                || (properties.containsKey("quarkus.package.add-runner-suffix")
+                        && Boolean.parseBoolean((String) properties.get("quarkus.package.add-runner-suffix")))) {
+            nativeRunnerName.append("-runner");
+        }
+        return nativeRunnerName.toString();
+    }
+
     public Property<String> getFinalName() {
         return finalName;
     }
diff --git a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java
index ff7f3372ccd..5013ce21a66 100644
--- a/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java
+++ b/devtools/gradle/gradle-application-plugin/src/main/java/io/quarkus/gradle/tasks/QuarkusBuild.java
@@ -129,12 +129,12 @@ public QuarkusBuild manifest(Action<Manifest> action) {
 
     @OutputFile
     public File getRunnerJar() {
-        return new File(getProject().getBuildDir(), extension().finalName() + "-runner.jar");
+        return new File(getProject().getBuildDir(), String.format("%s.jar", extension().buildNativeRunnerName(Map.of())));
     }
 
     @OutputFile
     public File getNativeRunner() {
-        return new File(getProject().getBuildDir(), extension().finalName() + "-runner");
+        return new File(getProject().getBuildDir(), extension().buildNativeRunnerName(Map.of()));
     }
 
     @OutputDirectory
diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/nativeimage/BasicJavaNativeBuildIT.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/nativeimage/BasicJavaNativeBuildIT.java
index c61f9c8135d..b846cb793f5 100644
--- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/nativeimage/BasicJavaNativeBuildIT.java
+++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/nativeimage/BasicJavaNativeBuildIT.java
@@ -48,6 +48,76 @@ public void shouldBuildNativeImage() throws Exception {
 
     }
 
+    @Test
+    public void shouldBuildNativeImageWithCustomName() throws Exception {
+        final File projectDir = getProjectDir("basic-java-native-module");
+
+        final BuildResult build = runGradleWrapper(projectDir, "clean", "quarkusBuild", "-Dquarkus.package.type=native",
+                "-Dquarkus.package.output-name=test");
+
+        assertThat(build.getTasks().get(":quarkusBuild")).isEqualTo(BuildResult.SUCCESS_OUTCOME);
+        final String buildOutput = build.getOutput();
+        // make sure the output log during the build contains some expected logs from the native-image process
+        CharSequence[] expectedOutput;
+        if (buildOutput.contains("Version info:")) { // Starting with 22.0 the native-image output changed
+            expectedOutput = new CharSequence[] { "Initializing...", "Performing analysis...",
+                    "Finished generating 'test-runner' in" };
+        } else {
+            expectedOutput = new CharSequence[] { "(clinit):", "(typeflow):", "[total]:" };
+        }
+        assertThat(buildOutput)
+                .withFailMessage("native-image build log is missing certain expected log messages: \\n\\n %s", buildOutput)
+                .contains(expectedOutput)
+                .doesNotContain("Finished generating '" + NATIVE_IMAGE_NAME + "' in");
+        Path nativeImagePath = projectDir.toPath().resolve("build").resolve("test-runner");
+        assertThat(nativeImagePath).exists();
+        Process nativeImageProcess = runNativeImage(nativeImagePath.toAbsolutePath().toString());
+        try {
+            final String response = DevModeTestUtils.getHttpResponse("/hello");
+            assertThat(response)
+                    .withFailMessage("Response %s for /hello was expected to contain the hello, but didn't", response)
+                    .contains("hello");
+        } finally {
+            nativeImageProcess.destroy();
+        }
+
+    }
+
+    @Test
+    public void shouldBuildNativeImageWithCustomNameWithoutSuffix() throws Exception {
+        final File projectDir = getProjectDir("basic-java-native-module");
+
+        final BuildResult build = runGradleWrapper(projectDir, "clean", "quarkusBuild", "-Dquarkus.package.type=native",
+                "-Dquarkus.package.output-name=test", "-Dquarkus.package.add-runner-suffix=false");
+
+        assertThat(build.getTasks().get(":quarkusBuild")).isEqualTo(BuildResult.SUCCESS_OUTCOME);
+        final String buildOutput = build.getOutput();
+        // make sure the output log during the build contains some expected logs from the native-image process
+        CharSequence[] expectedOutput;
+        if (buildOutput.contains("Version info:")) { // Starting with 22.0 the native-image output changed
+            expectedOutput = new CharSequence[] { "Initializing...", "Performing analysis...",
+                    "Finished generating 'test' in" };
+        } else {
+            expectedOutput = new CharSequence[] { "(clinit):", "(typeflow):", "[total]:" };
+        }
+        assertThat(buildOutput)
+                .withFailMessage("native-image build log is missing certain expected log messages: \\n\\n %s", buildOutput)
+                .contains(expectedOutput)
+                .doesNotContain("Finished generating '" + NATIVE_IMAGE_NAME + "' in");
+        Path nativeImagePath = projectDir.toPath().resolve("build").resolve("test");
+        assertThat(nativeImagePath).exists();
+        Process nativeImageProcess = runNativeImage(nativeImagePath.toAbsolutePath().toString());
+        try {
+            final String response = DevModeTestUtils.getHttpResponse("/hello");
+            assertThat(response)
+                    .withFailMessage("Response %s for /hello was expected to contain the hello, but didn't", response)
+                    .contains("hello");
+        } finally {
+            nativeImageProcess.destroy();
+        }
+
+    }
+
     private Process runNativeImage(String nativeImage) throws IOException {
         final ProcessBuilder processBuilder = new ProcessBuilder(nativeImage);
         processBuilder.inheritIO();
diff --git a/integration-tests/gradle/src/test/java/io/quarkus/gradle/nativeimage/NativeIntegrationTestIT.java b/integration-tests/gradle/src/test/java/io/quarkus/gradle/nativeimage/NativeIntegrationTestIT.java
index b387532b8ab..b22b4eab2df 100644
--- a/integration-tests/gradle/src/test/java/io/quarkus/gradle/nativeimage/NativeIntegrationTestIT.java
+++ b/integration-tests/gradle/src/test/java/io/quarkus/gradle/nativeimage/NativeIntegrationTestIT.java
@@ -19,4 +19,22 @@ public void nativeTestShouldRunIntegrationTest() throws Exception {
         assertThat(testResult.getTasks().get(":testNative")).isEqualTo(BuildResult.SUCCESS_OUTCOME);
     }
 
+    @Test
+    public void runNativeTestsWithOutputName() throws Exception {
+        final File projectDir = getProjectDir("it-test-basic-project");
+
+        final BuildResult testResult = runGradleWrapper(projectDir, "clean", "testNative",
+                "-Dquarkus.package.output-name=test");
+        assertThat(testResult.getTasks().get(":testNative")).isEqualTo(BuildResult.SUCCESS_OUTCOME);
+    }
+
+    @Test
+    public void runNativeTestsWithoutRunnerSuffix() throws Exception {
+        final File projectDir = getProjectDir("it-test-basic-project");
+
+        final BuildResult testResult = runGradleWrapper(projectDir, "clean", "testNative",
+                "-Dquarkus.package.add-runner-suffix=false");
+        assertThat(testResult.getTasks().get(":testNative")).isEqualTo(BuildResult.SUCCESS_OUTCOME);
+    }
+
 }