Support launching with a parameterless main method

Fixes gh-47311
This commit is contained in:
Andy Wilkinson 2025-09-24 15:16:18 +01:00
parent 9a1d9f677b
commit b61e38bef8
2 changed files with 146 additions and 23 deletions

View File

@ -97,9 +97,23 @@ public abstract class Launcher {
protected void launch(ClassLoader classLoader, String mainClassName, String[] args) throws Exception {
Thread.currentThread().setContextClassLoader(classLoader);
Class<?> mainClass = Class.forName(mainClassName, false, classLoader);
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
Method mainMethod = getMainMethod(mainClass);
mainMethod.setAccessible(true);
mainMethod.invoke(null, new Object[] { args });
if (mainMethod.getParameterCount() == 0) {
mainMethod.invoke(null);
}
else {
mainMethod.invoke(null, new Object[] { args });
}
}
private Method getMainMethod(Class<?> mainClass) throws Exception {
try {
return mainClass.getDeclaredMethod("main", String[].class);
}
catch (NoSuchMethodException ex) {
return mainClass.getDeclaredMethod("main");
}
}
/**

View File

@ -41,6 +41,115 @@ import static org.assertj.core.api.Assertions.assertThat;
@AssertFileChannelDataBlocksClosed
class LauncherTests {
/**
* Main method tests.
*
*/
@Nested
@ExtendWith(OutputCaptureExtension.class)
class MainMethod {
@Test
void publicMainMethod(CapturedOutput output) throws Exception {
new MainMethodTestLauncher(PublicMainMethod.class).launch(new String[0]);
assertThat(output).contains("Launched public static void main(String[] args)");
}
@Test
void packagePrivateMainMethod(CapturedOutput output) throws Exception {
new MainMethodTestLauncher(PackagePrivateMainMethod.class).launch(new String[0]);
assertThat(output).contains("Launched static void main(String[] args)");
}
@Test
void publicParameterlessMainMethod(CapturedOutput output) throws Exception {
new MainMethodTestLauncher(PublicParameterlessMainMethod.class).launch(new String[0]);
assertThat(output).contains("Launched public static void main()");
}
@Test
void packagePrivateParameterlessMainMethod(CapturedOutput output) throws Exception {
new MainMethodTestLauncher(PackagePrivateParameterlessMainMethod.class).launch(new String[0]);
assertThat(output).contains("Launched static void main()");
}
@Test
void prefersSingleParameterMainMethod(CapturedOutput output) throws Exception {
new MainMethodTestLauncher(MultipleMainMethods.class).launch(new String[0]);
assertThat(output).contains("Launched static void main(String[] args)");
}
static class MainMethodTestLauncher extends Launcher {
private final Class<?> mainClass;
MainMethodTestLauncher(Class<?> mainClass) {
this.mainClass = mainClass;
}
@Override
protected Archive getArchive() {
return null;
}
@Override
protected String getMainClass() throws Exception {
return this.mainClass.getName();
}
@Override
protected Set<URL> getClassPathUrls() throws Exception {
return Collections.emptySet();
}
}
public static class PublicMainMethod {
public static void main(String[] args) {
System.out.println("Launched public static void main(String[] args)");
}
}
public static class PackagePrivateMainMethod {
public static void main(String[] args) {
System.out.println("Launched static void main(String[] args)");
}
}
public static class PublicParameterlessMainMethod {
public static void main() {
System.out.println("Launched public static void main()");
}
}
public static class PackagePrivateParameterlessMainMethod {
static void main() {
System.out.println("Launched static void main()");
}
}
public static class MultipleMainMethods {
static void main(String[] args) {
System.out.println("Launched static void main(String[] args)");
}
static void main() {
System.out.println("Launched static void main()");
}
}
}
/**
* Jar Mode tests.
*/
@ -61,7 +170,7 @@ class LauncherTests {
@Test
void launchWhenJarModePropertyIsSetLaunchesJarMode(CapturedOutput out) throws Exception {
System.setProperty("jarmode", "test");
new TestLauncher().launch(new String[] { "boot" });
new JarModeTestLauncher().launch(new String[] { "boot" });
assertThat(out).contains("running in test jar mode [boot]");
assertThat(System.getProperty(JarModeRunner.SUPPRESSED_SYSTEM_EXIT_CODE)).isEqualTo("0");
}
@ -69,7 +178,7 @@ class LauncherTests {
@Test
void launchWhenJarModePropertyIsNotAcceptedThrowsException(CapturedOutput out) throws Exception {
System.setProperty("jarmode", "idontexist");
new TestLauncher().launch(new String[] { "boot" });
new JarModeTestLauncher().launch(new String[] { "boot" });
assertThat(out).contains("Unsupported jarmode 'idontexist'");
assertThat(System.getProperty(JarModeRunner.SUPPRESSED_SYSTEM_EXIT_CODE)).isEqualTo("1");
}
@ -77,7 +186,7 @@ class LauncherTests {
@Test
void launchWhenJarModeRunFailsWithErrorExceptionPrintsSimpleMessage(CapturedOutput out) throws Exception {
System.setProperty("jarmode", "test");
new TestLauncher().launch(new String[] { "error" });
new JarModeTestLauncher().launch(new String[] { "error" });
assertThat(out).contains("running in test jar mode [error]");
assertThat(out).contains("Error: error message");
assertThat(System.getProperty(JarModeRunner.SUPPRESSED_SYSTEM_EXIT_CODE)).isEqualTo("1");
@ -86,34 +195,34 @@ class LauncherTests {
@Test
void launchWhenJarModeRunFailsWithErrorExceptionPrintsStackTrace(CapturedOutput out) throws Exception {
System.setProperty("jarmode", "test");
new TestLauncher().launch(new String[] { "fail" });
new JarModeTestLauncher().launch(new String[] { "fail" });
assertThat(out).contains("running in test jar mode [fail]");
assertThat(out).contains("java.lang.IllegalStateException: bad");
assertThat(System.getProperty(JarModeRunner.SUPPRESSED_SYSTEM_EXIT_CODE)).isEqualTo("1");
}
}
private static final class JarModeTestLauncher extends Launcher {
private static final class TestLauncher extends Launcher {
@Override
protected String getMainClass() throws Exception {
throw new IllegalStateException("Should not be called");
}
@Override
protected String getMainClass() throws Exception {
throw new IllegalStateException("Should not be called");
}
@Override
protected Archive getArchive() {
return null;
}
@Override
protected Archive getArchive() {
return null;
}
@Override
protected Set<URL> getClassPathUrls() throws Exception {
return Collections.emptySet();
}
@Override
protected Set<URL> getClassPathUrls() throws Exception {
return Collections.emptySet();
}
@Override
protected void launch(String[] args) throws Exception {
super.launch(args);
}
@Override
protected void launch(String[] args) throws Exception {
super.launch(args);
}
}