Start adding some tests for SpringBootApp discovery and actuator client
This commit is contained in:
@@ -22,5 +22,10 @@
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
|
||||
@@ -32,6 +32,12 @@
|
||||
<scope>system</scope>
|
||||
<systemPath>${java.home}/../lib/tools.jar</systemPath>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<artifactId>commons-util</artifactId>
|
||||
<groupId>org.springframework.ide.vscode</groupId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -35,7 +35,6 @@ import com.sun.tools.attach.VirtualMachineDescriptor;
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
@SuppressWarnings("restriction")
|
||||
public class SpringBootApp {
|
||||
|
||||
private VirtualMachine vm;
|
||||
@@ -321,4 +320,9 @@ public class SpringBootApp {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SpringBootApp [" +vmd.id() + ", "+vmd.displayName()+"]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*******************************************************************************
|
||||
* 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.boot.app.cli;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
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;
|
||||
|
||||
public class SpringBootAppTest {
|
||||
|
||||
private static final String appName = "actuator-client-test-subject";
|
||||
private static AsyncProcess testAppRunner;
|
||||
|
||||
@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.
|
||||
}
|
||||
|
||||
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()
|
||||
));
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDownClass() throws Exception {
|
||||
testAppRunner.kill();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAllJavaApps() throws Exception {
|
||||
Map<String, SpringBootApp> allApps = SpringBootApp.getAllRunningJavaApps();
|
||||
Optional<SpringBootApp> myProcess = allApps.values().stream().filter(app -> app.getProcessName().contains(appName)).findAny();
|
||||
assertTrue(myProcess.isPresent());
|
||||
}
|
||||
|
||||
@Ignore //Failing... not sure how to fix.
|
||||
@Test public void getAllBootApps() throws Exception {
|
||||
Map<String, SpringBootApp> allApps = SpringBootApp.getAllRunningSpringApps();
|
||||
Optional<SpringBootApp> myProcess = allApps.values().stream().filter(app -> app.getProcessName().contains(appName)).findAny();
|
||||
assertTrue(myProcess.isPresent());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2012, 2016 Pivotal Software, 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 Software, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
|
||||
/**
|
||||
* Convenient wrapper around a {@link Process}. Simplifies the asynchronous execution of external
|
||||
* commands by handling reading from out and error streams and either buffering the result output
|
||||
* for later retrieval, or forwarding the to output to designated streams.
|
||||
* <p>
|
||||
* This provides similar apis than {@link ExternalProcess}. But has been adapted so that creation
|
||||
* of the instance starts the process asynchronously. Additional apis are also provided to
|
||||
* wait for the process and/or terminate it forcibly when no longer needed.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class AsyncProcess {
|
||||
|
||||
//TODO: much of this code copied from ExternalProcess. It should be relatively easy
|
||||
// to make 'ExternalProcess inherit from this one and just add some additional 'waitFor' call
|
||||
// in its 'init' method.
|
||||
|
||||
/**
|
||||
* A thread that keeps reading input from a Stream until the end is reached
|
||||
* or there's some error reading the Stream.
|
||||
*/
|
||||
public static class StreamGobler extends Thread {
|
||||
|
||||
private final OutputStream echo;
|
||||
private InputStream toRead; //Stream to read. This is nulled after all input has been consumed.
|
||||
|
||||
/**
|
||||
* Creates a StreamGobler that reads input from an input stream
|
||||
* and buffers up all input it has read for later retrieval via
|
||||
* the getOut() method.
|
||||
*/
|
||||
public StreamGobler(InputStream toRead) {
|
||||
this(toRead, new ByteArrayOutputStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a StreamGobler that reads input from an input stream
|
||||
* and writes it out to an outputstream.
|
||||
*/
|
||||
public StreamGobler(InputStream toRead, OutputStream forwardTo) {
|
||||
this.toRead = toRead;
|
||||
this.echo = forwardTo;
|
||||
start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
byte[] buf = new byte[256];
|
||||
while (toRead!=null) {
|
||||
try {
|
||||
int i = toRead.read(buf);
|
||||
if (i==-1) {
|
||||
//EOF
|
||||
toRead = null; //Done!
|
||||
} else {
|
||||
append(buf, i);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
toRead = null;
|
||||
ByteArrayOutputStream errMsg = new ByteArrayOutputStream();
|
||||
e.printStackTrace(new PrintStream(errMsg));
|
||||
append(errMsg.toByteArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void append(byte[] buf) {
|
||||
append(buf, buf.length);
|
||||
}
|
||||
|
||||
private void append(byte[] buf, int len) {
|
||||
if (echo!=null) {
|
||||
try {
|
||||
echo.write(buf, 0, len);
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getContents() throws InterruptedException {
|
||||
try {
|
||||
this.join();
|
||||
if (echo instanceof ByteArrayOutputStream) {
|
||||
return ((ByteArrayOutputStream)echo).toString();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} finally {
|
||||
toRead = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Process process;
|
||||
private StreamGobler err; // Standard error is to be read from here
|
||||
private StreamGobler out; // Standard out is to be read from here
|
||||
private int exitValue = -9999;
|
||||
private ExternalCommand cmd;
|
||||
|
||||
/**
|
||||
* Creates an external process and waits for it to terminate. The output and error streams
|
||||
* will be read and forwarded to System.out and System.err
|
||||
*/
|
||||
public AsyncProcess(File workingDir, ExternalCommand cmd) throws IOException {
|
||||
this(workingDir, cmd, false);
|
||||
}
|
||||
|
||||
private void init(File workingDir, ExternalCommand cmd,
|
||||
OutputStream outStream, OutputStream errStream
|
||||
) throws IOException {
|
||||
this.cmd = cmd;
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(cmd.getProgramAndArgs());
|
||||
processBuilder.directory(workingDir);
|
||||
cmd.configure(processBuilder);
|
||||
process = processBuilder.start();
|
||||
err = new StreamGobler(process.getErrorStream(), errStream);
|
||||
out = new StreamGobler(process.getInputStream(), outStream);
|
||||
}
|
||||
|
||||
public AsyncProcess(File workingDir, ExternalCommand cmd, boolean captureStreams) throws IOException {
|
||||
if (captureStreams) {
|
||||
init(workingDir, cmd, new ByteArrayOutputStream(), new ByteArrayOutputStream());
|
||||
} else {
|
||||
init(workingDir, cmd, System.out, System.err);
|
||||
}
|
||||
}
|
||||
|
||||
public String getOut() throws InterruptedException {
|
||||
return out.getContents();
|
||||
}
|
||||
|
||||
public String getErr() throws InterruptedException {
|
||||
return err.getContents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the process to exit. If a timeout is specified and the process
|
||||
* does not terminate within that time, the process is destroyed forcibly.
|
||||
* <p>
|
||||
* If timeout is not specified (i.e. null) then this waits for as long as necessary
|
||||
* (which is potentially forever if the process does not terminate).
|
||||
*/
|
||||
public int waitFor(Duration timeout) throws InterruptedException, TimeoutException {
|
||||
try {
|
||||
if (timeout==null) {
|
||||
exitValue = process.waitFor();
|
||||
} else {
|
||||
if (process.waitFor(timeout.toMillis(), TimeUnit.MILLISECONDS)) {
|
||||
exitValue = process.exitValue();
|
||||
} else {
|
||||
process.destroy();
|
||||
exitValue = 999; //Set some non-0 value as that is what some callers might use to determine 'failure' occurred.
|
||||
throw new TimeoutException("Command timed out: "+this);
|
||||
}
|
||||
}
|
||||
return exitValue;
|
||||
} finally {
|
||||
try {
|
||||
//Try destroying unresponsive processes no matter what.
|
||||
process.destroyForcibly();
|
||||
} catch (Exception e) {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer result = new StringBuffer();
|
||||
try {
|
||||
process.exitValue();
|
||||
result.append(">>>> ExternalProcess: ");
|
||||
result.append(cmd+"\n");
|
||||
result.append("exitValue = "+exitValue+"\n");
|
||||
String strOut = getOut();
|
||||
if (strOut!=null) {
|
||||
result.append("------- System.out -------\n");
|
||||
result.append(strOut);
|
||||
}
|
||||
String strErr = getErr();
|
||||
if (strErr!=null) {
|
||||
result.append("------- System.err -------\n");
|
||||
result.append(strErr);
|
||||
}
|
||||
result.append("<<<< ExternalProcess");
|
||||
return result.toString();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return result.toString();
|
||||
} catch (IllegalThreadStateException e) {
|
||||
return "ExternalProcess(RUNNING, "+cmd+")";
|
||||
}
|
||||
}
|
||||
|
||||
public int getExitValue() {
|
||||
return exitValue;
|
||||
}
|
||||
|
||||
public void kill() {
|
||||
try {
|
||||
waitFor(Duration.ofSeconds(0));
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user