diff --git a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java index e011615d9..4d33ccc00 100644 --- a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java +++ b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java @@ -26,6 +26,8 @@ import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; +import org.json.JSONArray; +import org.json.JSONException; import org.json.JSONObject; import com.fasterxml.jackson.databind.ObjectMapper; @@ -292,10 +294,33 @@ public class SpringBootApp { if (environment != null) { JSONObject env = new JSONObject(environment); if (env != null) { - JSONObject portsObject = env.getJSONObject("server.ports"); + JSONObject portsObject = env.optJSONObject("server.ports"); if (portsObject != null) { - String portValue = portsObject.get("local.server.port").toString(); - return portValue; + String portValue = portsObject.optString("local.server.port"); + if (portValue!=null) { + return portValue; + } + } + //Not found as direct property value... in Boot 2.0 we must look inside the 'propertySources'. + //Similar... but structure is more complex. + JSONArray propertySources = env.optJSONArray("propertySources"); + if (propertySources!=null) { + for (Object _source : propertySources) { + if (_source instanceof JSONObject) { + JSONObject source = (JSONObject) _source; + String sourceName = source.optString("name"); + if ("server.ports".equals(sourceName)) { + JSONObject props = source.optJSONObject("properties"); + JSONObject valueObject = props.optJSONObject("local.server.port"); + if (valueObject!=null) { + String portValue = valueObject.optString("value"); + if (portValue!=null) { + return portValue; + } + } + } + } + } } } } @@ -317,7 +342,6 @@ public class SpringBootApp { } catch (InstanceNotFoundException e) { } - return null; } diff --git a/headless-services/commons/commons-boot-app-cli/src/test/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootAppTest.java b/headless-services/commons/commons-boot-app-cli/src/test/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootAppTest.java index 13d3e5c41..9271a07bb 100644 --- a/headless-services/commons/commons-boot-app-cli/src/test/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootAppTest.java +++ b/headless-services/commons/commons-boot-app-cli/src/test/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootAppTest.java @@ -11,42 +11,59 @@ package org.springframework.ide.vscode.commons.boot.app.cli; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.File; import java.net.URL; +import java.time.Duration; import java.util.Map; import java.util.Optional; +import org.json.JSONObject; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.springframework.ide.vscode.commons.util.AsyncProcess; import org.springframework.ide.vscode.commons.util.ExternalCommand; +import org.springframework.ide.vscode.commons.util.StringUtil; +import org.springframework.ide.vscode.commons.util.test.ACondition; public class SpringBootAppTest { - private static final String appName = "actuator-client-test-subject"; +// private static final String appName = "actuator-client-15-test-subject"; // Boot 1.5 test app + private static final String appName = "actuator-client-20-test-subject"; //Boot 2.0 test app + + private static final Duration TIMEOUT = Duration.ofSeconds(10); private static AsyncProcess testAppRunner; + private static SpringBootApp testApp; @BeforeClass public static void setupClass() throws Exception { testAppRunner = startTestApplication(SpringBootAppTest.class.getResource("/"+appName+"-0.0.1-SNAPSHOT.jar")); - - //TODO: add some wait here until the boot app is 'ready'. Otherwise some of our test will just fail trying to read stuff that's not there yet. + testApp = getAppContaining(appName); + assertNotNull(testApp); } private static AsyncProcess startTestApplication(URL jarUrl) throws Exception { File jarFile = new File(jarUrl.toURI()); - return new AsyncProcess(new File("."), new ExternalCommand( - "java", - "-Dserver.port=0", //let spring boot pick randomized free port - "-jar", - jarFile.getAbsolutePath() - )); + return new AsyncProcess( + new File("."), + new ExternalCommand( + "java", + "-Dserver.port=0", //let spring boot pick randomized free port + "-jar", + jarFile.getAbsolutePath() + ), + false + ); } + private static SpringBootApp getAppContaining(String nameFragment) throws Exception { + return SpringBootApp.getAllRunningJavaApps().values().stream().filter(app -> app.getProcessName().contains(nameFragment)).findAny().get(); + } + @AfterClass public static void tearDownClass() throws Exception { testAppRunner.kill(); @@ -65,5 +82,64 @@ public class SpringBootAppTest { Optional myProcess = allApps.values().stream().filter(app -> app.getProcessName().contains(appName)).findAny(); assertTrue(myProcess.isPresent()); } + + @Test + public void getPort() throws Exception { + ACondition.waitFor(TIMEOUT, () -> { + int port = Integer.parseInt(testApp.getPort()); + assertTrue(port > 0); + System.out.println("port = "+port); + }); + } + + @Test + public void getHost() throws Exception { + ACondition.waitFor(TIMEOUT, () -> { + String host = testApp.getHost(); + assertTrue(StringUtil.hasText(host)); + System.out.println("host = "+host); + }); + } + + @Test + public void getEnvironment() throws Exception { + ACondition.waitFor(TIMEOUT, () -> { + String env = testApp.getEnvironment(); + assertNonEmptyJsonObject(env); + System.out.println("env = "+env); + }); + } + + @Test + public void getBeans() throws Exception { + ACondition.waitFor(TIMEOUT, () -> { + String beans = testApp.getBeans(); + assertNonEmptyJsonObject(beans); + System.out.println("beans = "+beans); + }); + } + + @Test + public void getRequestMappings() throws Exception { + ACondition.waitFor(TIMEOUT, () -> { + String result = testApp.getRequestMappings(); + assertNonEmptyJsonObject(result); + System.out.println("requestMappings = "+result); + }); + } + + @Test + public void getAutoConfigReport() throws Exception { + ACondition.waitFor(TIMEOUT, () -> { + String result = testApp.getAutoConfigReport(); + assertNonEmptyJsonObject(result); + System.out.println("autoconfreport = "+result); + }); + } + + private void assertNonEmptyJsonObject(String jsonData) { + JSONObject parsed = new JSONObject(jsonData); + assertFalse(parsed.keySet().isEmpty()); + } } diff --git a/headless-services/commons/commons-boot-app-cli/src/test/resources/actuator-client-15-test-subject-0.0.1-SNAPSHOT.jar b/headless-services/commons/commons-boot-app-cli/src/test/resources/actuator-client-15-test-subject-0.0.1-SNAPSHOT.jar new file mode 100644 index 000000000..7c0edb30f Binary files /dev/null and b/headless-services/commons/commons-boot-app-cli/src/test/resources/actuator-client-15-test-subject-0.0.1-SNAPSHOT.jar differ diff --git a/headless-services/commons/commons-boot-app-cli/src/test/resources/actuator-client-test-subject-0.0.1-SNAPSHOT.jar b/headless-services/commons/commons-boot-app-cli/src/test/resources/actuator-client-20-test-subject-0.0.1-SNAPSHOT.jar similarity index 100% rename from headless-services/commons/commons-boot-app-cli/src/test/resources/actuator-client-test-subject-0.0.1-SNAPSHOT.jar rename to headless-services/commons/commons-boot-app-cli/src/test/resources/actuator-client-20-test-subject-0.0.1-SNAPSHOT.jar diff --git a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/test/ACondition.java b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/test/ACondition.java new file mode 100644 index 000000000..034447d17 --- /dev/null +++ b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/test/ACondition.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2017 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.util.test; + +import java.time.Duration; + +import org.springframework.ide.vscode.commons.util.ExceptionUtil; + +/** + * Utility for async condition checking, mostly intended for testing code. + *

+ * Uses a polling loop to repeatedly evaluate some code that represents a condition + * of some kind. The test passes as soon as the condition passes, or fails if + * the condition does not pass within a given time limit. + */ +public class ACondition { + + public interface Asserter { + void doAsserts() throws Exception; + } + + public static void waitFor(Duration timeout, Asserter asserter) throws Exception { + long startTime = System.currentTimeMillis(); + long timeout_millis = timeout.toMillis(); + Throwable lastException = null; + do { + try { + asserter.doAsserts(); + return; + } catch (Throwable e) { + lastException = e; + if (System.currentTimeMillis()-startTime < timeout_millis) { + Thread.sleep(300); + } + } + } while (System.currentTimeMillis()-startTime < timeout_millis); + throw ExceptionUtil.exception(lastException); + } + +}