diff --git a/spring-boot-tests/spring-boot-integration-tests/spring-boot-devtools-tests/src/test/java/org/springframework/boot/devtools/tests/DevToolsIntegrationTests.java b/spring-boot-tests/spring-boot-integration-tests/spring-boot-devtools-tests/src/test/java/org/springframework/boot/devtools/tests/DevToolsIntegrationTests.java index 199d3e6328b..91cd63b5ef0 100644 --- a/spring-boot-tests/spring-boot-integration-tests/spring-boot-devtools-tests/src/test/java/org/springframework/boot/devtools/tests/DevToolsIntegrationTests.java +++ b/spring-boot-tests/spring-boot-integration-tests/spring-boot-devtools-tests/src/test/java/org/springframework/boot/devtools/tests/DevToolsIntegrationTests.java @@ -59,13 +59,6 @@ public class DevToolsIntegrationTests { @Rule public JvmLauncher javaLauncher = new JvmLauncher(); - @Parameters(name = "{0}") - public static Object[] parameters() { - return new Object[] { new Object[] { new LocalApplicationLauncher() }, - new Object[] { new ExplodedRemoteApplicationLauncher() }, - new Object[] { new JarFileRemoteApplicationLauncher() } }; - } - public DevToolsIntegrationTests(ApplicationLauncher applicationLauncher) { this.applicationLauncher = applicationLauncher; } @@ -93,20 +86,23 @@ public class DevToolsIntegrationTests { .isEqualTo(HttpStatus.NOT_FOUND); controller("com.example.ControllerOne").withRequestMapping("one") .withRequestMapping("two").build(); + urlBase = "http://localhost:" + awaitServerPort(); assertThat(template.getForObject(urlBase + "/one", String.class)) .isEqualTo("one"); - assertThat(template.getForObject("http://localhost:" + awaitServerPort() + "/two", - String.class)).isEqualTo("two"); + assertThat(template.getForObject(urlBase + "/two", String.class)) + .isEqualTo("two"); } @Test public void removeARequestMappingFromAnExistingController() throws Exception { TestRestTemplate template = new TestRestTemplate(); - assertThat(template.getForObject("http://localhost:" + awaitServerPort() + "/one", - String.class)).isEqualTo("one"); + String urlBase = "http://localhost:" + awaitServerPort(); + assertThat(template.getForObject(urlBase + "/one", String.class)) + .isEqualTo("one"); controller("com.example.ControllerOne").build(); - assertThat(template.getForEntity("http://localhost:" + awaitServerPort() + "/one", - String.class).getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + urlBase = "http://localhost:" + awaitServerPort(); + assertThat(template.getForEntity(urlBase + "/one", String.class).getStatusCode()) + .isEqualTo(HttpStatus.NOT_FOUND); } @Test @@ -118,10 +114,11 @@ public class DevToolsIntegrationTests { assertThat(template.getForEntity(urlBase + "/two", String.class).getStatusCode()) .isEqualTo(HttpStatus.NOT_FOUND); controller("com.example.ControllerTwo").withRequestMapping("two").build(); + urlBase = "http://localhost:" + awaitServerPort(); assertThat(template.getForObject(urlBase + "/one", String.class)) .isEqualTo("one"); - assertThat(template.getForObject("http://localhost:" + awaitServerPort() + "/two", - String.class)).isEqualTo("two"); + assertThat(template.getForObject(urlBase + "/two", String.class)) + .isEqualTo("two"); } @@ -134,15 +131,16 @@ public class DevToolsIntegrationTests { assertThat(template.getForEntity(urlBase + "/two", String.class).getStatusCode()) .isEqualTo(HttpStatus.NOT_FOUND); controller("com.example.ControllerTwo").withRequestMapping("two").build(); + urlBase = "http://localhost:" + awaitServerPort(); assertThat(template.getForObject(urlBase + "/one", String.class)) .isEqualTo("one"); - assertThat(template.getForObject("http://localhost:" + awaitServerPort() + "/two", - String.class)).isEqualTo("two"); + assertThat(template.getForObject(urlBase + "/two", String.class)) + .isEqualTo("two"); controller("com.example.ControllerTwo").withRequestMapping("two") .withRequestMapping("three").build(); - assertThat(template.getForObject( - "http://localhost:" + awaitServerPort() + "/three", String.class)) - .isEqualTo("three"); + urlBase = "http://localhost:" + awaitServerPort(); + assertThat(template.getForObject(urlBase + "/three", String.class)) + .isEqualTo("three"); } @Test @@ -155,32 +153,33 @@ public class DevToolsIntegrationTests { assertThat(template.getForEntity(urlBase + "/two", String.class).getStatusCode()) .isEqualTo(HttpStatus.NOT_FOUND); controller("com.example.ControllerTwo").withRequestMapping("two").build(); + urlBase = "http://localhost:" + awaitServerPort(); assertThat(template.getForObject(urlBase + "/one", String.class)) .isEqualTo("one"); - assertThat(template.getForObject("http://localhost:" + awaitServerPort() + "/two", - String.class)).isEqualTo("two"); + assertThat(template.getForObject(urlBase + "/two", String.class)) + .isEqualTo("two"); controller("com.example.ControllerOne").withRequestMapping("one") .withRequestMapping("three").build(); - int port = awaitServerPort(); - assertThat( - template.getForObject("http://localhost:" + port + "/one", String.class)) - .isEqualTo("one"); - assertThat( - template.getForObject("http://localhost:" + port + "/two", String.class)) - .isEqualTo("two"); - assertThat(template.getForObject("http://localhost:" + port + "/three", - String.class)).isEqualTo("three"); + urlBase = "http://localhost:" + awaitServerPort(); + assertThat(template.getForObject(urlBase + "/one", String.class)) + .isEqualTo("one"); + assertThat(template.getForObject(urlBase + "/two", String.class)) + .isEqualTo("two"); + assertThat(template.getForObject(urlBase + "/three", String.class)) + .isEqualTo("three"); } @Test public void deleteAController() throws Exception { TestRestTemplate template = new TestRestTemplate(); - assertThat(template.getForObject("http://localhost:" + awaitServerPort() + "/one", - String.class)).isEqualTo("one"); + String urlBase = "http://localhost:" + awaitServerPort(); + assertThat(template.getForObject(urlBase + "/one", String.class)) + .isEqualTo("one"); assertThat(new File(this.launchedApplication.getClassesDirectory(), "com/example/ControllerOne.class").delete()).isTrue(); - assertThat(template.getForEntity("http://localhost:" + awaitServerPort() + "/one", - String.class).getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + urlBase = "http://localhost:" + awaitServerPort(); + assertThat(template.getForEntity(urlBase + "/one", String.class).getStatusCode()) + .isEqualTo(HttpStatus.NOT_FOUND); } @@ -193,18 +192,20 @@ public class DevToolsIntegrationTests { assertThat(template.getForEntity(urlBase + "/two", String.class).getStatusCode()) .isEqualTo(HttpStatus.NOT_FOUND); controller("com.example.ControllerTwo").withRequestMapping("two").build(); + urlBase = "http://localhost:" + awaitServerPort(); assertThat(template.getForObject(urlBase + "/one", String.class)) .isEqualTo("one"); - assertThat(template.getForObject("http://localhost:" + awaitServerPort() + "/two", - String.class)).isEqualTo("two"); + assertThat(template.getForObject(urlBase + "/two", String.class)) + .isEqualTo("two"); assertThat(new File(this.launchedApplication.getClassesDirectory(), "com/example/ControllerTwo.class").delete()).isTrue(); - assertThat(template.getForEntity("http://localhost:" + awaitServerPort() + "/two", - String.class).getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); + urlBase = "http://localhost:" + awaitServerPort(); + assertThat(template.getForEntity(urlBase + "/two", String.class).getStatusCode()) + .isEqualTo(HttpStatus.NOT_FOUND); } private int awaitServerPort() throws Exception { - long end = System.currentTimeMillis() + 30000; + long end = System.currentTimeMillis() + 40000; while (this.serverPortFile.length() == 0) { System.out.println("Getting server port " + this.serverPortFile.length()); if (System.currentTimeMillis() > end) { @@ -222,6 +223,8 @@ public class DevToolsIntegrationTests { int port = Integer.valueOf(FileCopyUtils.copyToString(portReader)); this.serverPortFile.delete(); System.out.println("Got port " + port); + this.launchedApplication.restartRemote(port); + Thread.sleep(1000); return port; } @@ -230,6 +233,13 @@ public class DevToolsIntegrationTests { this.launchedApplication.getClassesDirectory()); } + @Parameters(name = "{0}") + public static Object[] parameters() { + return new Object[] { new Object[] { new LocalApplicationLauncher() }, + new Object[] { new ExplodedRemoteApplicationLauncher() }, + new Object[] { new JarFileRemoteApplicationLauncher() } }; + } + private static final class ControllerBuilder { private final List mappings = new ArrayList<>(); diff --git a/spring-boot-tests/spring-boot-integration-tests/spring-boot-devtools-tests/src/test/java/org/springframework/boot/devtools/tests/LaunchedApplication.java b/spring-boot-tests/spring-boot-integration-tests/spring-boot-devtools-tests/src/test/java/org/springframework/boot/devtools/tests/LaunchedApplication.java index c556da91bf6..e8eb2f43364 100644 --- a/spring-boot-tests/spring-boot-integration-tests/spring-boot-devtools-tests/src/test/java/org/springframework/boot/devtools/tests/LaunchedApplication.java +++ b/spring-boot-tests/spring-boot-integration-tests/spring-boot-devtools-tests/src/test/java/org/springframework/boot/devtools/tests/LaunchedApplication.java @@ -17,6 +17,7 @@ package org.springframework.boot.devtools.tests; import java.io.File; +import java.util.function.BiFunction; /** * An application launched by {@link ApplicationLauncher}. @@ -31,18 +32,38 @@ class LaunchedApplication { private final File standardError; - private final Process[] processes; + private final Process localProcess; + + private Process remoteProcess; + + private final BiFunction remoteProcessRestarter; LaunchedApplication(File classesDirectory, File standardOut, File standardError, - Process... processes) { + Process localProcess, Process remoteProcess, + BiFunction remoteProcessRestarter) { this.classesDirectory = classesDirectory; this.standardOut = standardOut; this.standardError = standardError; - this.processes = processes; + this.localProcess = localProcess; + this.remoteProcess = remoteProcess; + this.remoteProcessRestarter = remoteProcessRestarter; + } + + public void restartRemote(int port) throws InterruptedException { + if (this.remoteProcessRestarter != null) { + stop(this.remoteProcess); + this.remoteProcess = this.remoteProcessRestarter.apply(port, + this.classesDirectory); + } } void stop() throws InterruptedException { - for (Process process : this.processes) { + stop(this.localProcess); + stop(this.remoteProcess); + } + + private void stop(Process process) throws InterruptedException { + if (process != null) { process.destroy(); process.waitFor(); } diff --git a/spring-boot-tests/spring-boot-integration-tests/spring-boot-devtools-tests/src/test/java/org/springframework/boot/devtools/tests/LocalApplicationLauncher.java b/spring-boot-tests/spring-boot-integration-tests/spring-boot-devtools-tests/src/test/java/org/springframework/boot/devtools/tests/LocalApplicationLauncher.java index bb2932888ec..e176be122ca 100644 --- a/spring-boot-tests/spring-boot-integration-tests/spring-boot-devtools-tests/src/test/java/org/springframework/boot/devtools/tests/LocalApplicationLauncher.java +++ b/spring-boot-tests/spring-boot-integration-tests/spring-boot-devtools-tests/src/test/java/org/springframework/boot/devtools/tests/LocalApplicationLauncher.java @@ -37,7 +37,7 @@ public class LocalApplicationLauncher implements ApplicationLauncher { LaunchedJvm jvm = jvmLauncher.launch("local", createApplicationClassPath(), "com.example.DevToolsTestApplication", "--server.port=0"); return new LaunchedApplication(new File("target/app"), jvm.getStandardOut(), - jvm.getStandardError(), jvm.getProcess()); + jvm.getStandardError(), jvm.getProcess(), null, null); } protected String createApplicationClassPath() throws Exception { diff --git a/spring-boot-tests/spring-boot-integration-tests/spring-boot-devtools-tests/src/test/java/org/springframework/boot/devtools/tests/RemoteApplicationLauncher.java b/spring-boot-tests/spring-boot-integration-tests/spring-boot-devtools-tests/src/test/java/org/springframework/boot/devtools/tests/RemoteApplicationLauncher.java index 29b8b3b25da..92840d4d5de 100644 --- a/spring-boot-tests/spring-boot-integration-tests/spring-boot-devtools-tests/src/test/java/org/springframework/boot/devtools/tests/RemoteApplicationLauncher.java +++ b/spring-boot-tests/spring-boot-integration-tests/spring-boot-devtools-tests/src/test/java/org/springframework/boot/devtools/tests/RemoteApplicationLauncher.java @@ -20,6 +20,7 @@ import java.io.File; import java.io.FileReader; import java.util.ArrayList; import java.util.List; +import java.util.function.BiFunction; import org.springframework.boot.devtools.RemoteSpringApplication; import org.springframework.boot.devtools.tests.JvmLauncher.LaunchedJvm; @@ -40,26 +41,46 @@ abstract class RemoteApplicationLauncher implements ApplicationLauncher { throws Exception { LaunchedJvm applicationJvm = javaLauncher.launch("app", createApplicationClassPath(), "com.example.DevToolsTestApplication", - "--server.port=12345", "--spring.devtools.remote.secret=secret"); - awaitServerPort(applicationJvm.getStandardOut()); - LaunchedJvm remoteSpringApplicationJvm = javaLauncher.launch( - "remote-spring-application", createRemoteSpringApplicationClassPath(), - RemoteSpringApplication.class.getName(), - "--spring.devtools.remote.secret=secret", "http://localhost:12345"); - awaitRemoteSpringApplication(remoteSpringApplicationJvm.getStandardOut()); + "--server.port=0", "--spring.devtools.remote.secret=secret"); + int port = awaitServerPort(applicationJvm.getStandardOut()); + BiFunction remoteRestarter = getRemoteRestarter( + javaLauncher); return new LaunchedApplication(new File("target/remote"), applicationJvm.getStandardOut(), applicationJvm.getStandardError(), - applicationJvm.getProcess(), remoteSpringApplicationJvm.getProcess()); + applicationJvm.getProcess(), remoteRestarter.apply(port, null), + remoteRestarter); + } + + private BiFunction getRemoteRestarter( + JvmLauncher javaLauncher) { + return (port, classesDirectory) -> { + try { + LaunchedJvm remoteSpringApplicationJvm = javaLauncher.launch( + "remote-spring-application", + createRemoteSpringApplicationClassPath(classesDirectory), + RemoteSpringApplication.class.getName(), + "--spring.devtools.remote.secret=secret", + "http://localhost:" + port); + awaitRemoteSpringApplication(remoteSpringApplicationJvm.getStandardOut()); + return remoteSpringApplicationJvm.getProcess(); + } + catch (Exception ex) { + throw new IllegalStateException(ex); + } + }; } protected abstract String createApplicationClassPath() throws Exception; - private String createRemoteSpringApplicationClassPath() throws Exception { - File remoteDirectory = new File("target/remote"); - FileSystemUtils.deleteRecursively(remoteDirectory); - remoteDirectory.mkdirs(); - FileSystemUtils.copyRecursively(new File("target/test-classes/com"), - new File("target/remote/com")); + private String createRemoteSpringApplicationClassPath(File classesDirectory) + throws Exception { + if (classesDirectory == null) { + File remoteDirectory = new File("target/remote"); + FileSystemUtils.deleteRecursively(remoteDirectory); + remoteDirectory.mkdirs(); + FileSystemUtils.copyRecursively(new File("target/test-classes/com"), + new File("target/remote/com")); + } List entries = new ArrayList<>(); entries.add("target/remote"); for (File jar : new File("target/dependencies").listFiles()) {