Skip to content

Commit

Permalink
Merge pull request #11912 from geoand/#11891
Browse files Browse the repository at this point in the history
Make native tests work with random port
  • Loading branch information
stuartwdouglas authored Sep 7, 2020
2 parents d5f972c + 94f72bc commit d979a91
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ quarkus.security.users.file.enabled=true
quarkus.security.users.file.users=test-users.properties
quarkus.security.users.file.roles=test-roles.properties
quarkus.security.users.file.plain-text=true
%test.quarkus.http.test-port=0
quarkus.http.test-port=0
quarkus.http.port=0

# we add this because we also use @TestSecurity which means that basic auth is disabled by default because @TestSecurity
# contributes TestHttpAuthenticationMechanism
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.ServiceLoader;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
import org.wildfly.common.lock.Locks;

import io.quarkus.runtime.configuration.ConfigUtils;
import io.quarkus.runtime.configuration.QuarkusConfigFactory;
Expand Down Expand Up @@ -97,6 +102,8 @@ public void start() throws IOException {
args.add(path);
args.add("-Dquarkus.http.port=" + port);
args.add("-Dquarkus.http.ssl-port=" + httpsPort);
// this won't be correct when using the random port but it's really only used by us for the rest client tests
// in the main module, since those tests hit the application itself
args.add("-Dtest.url=" + TestHTTPResourceManager.getUri());
args.add("-Dquarkus.log.file.path=" + PropertyTestUtil.getLogFileLocation());
if (profile != null) {
Expand All @@ -109,10 +116,37 @@ public void start() throws IOException {
System.out.println("Executing " + args);

quarkusProcess = Runtime.getRuntime().exec(args.toArray(new String[args.size()]));
new Thread(new ProcessReader(quarkusProcess.getInputStream())).start();

PortCapturingProcessReader portCapturingProcessReader = null;
if (port == 0) {
// when the port is 0, then the application starts on a random port and the only way for us to figure it out
// is to capture the output
portCapturingProcessReader = new PortCapturingProcessReader(quarkusProcess.getInputStream());
}
new Thread(portCapturingProcessReader != null ? portCapturingProcessReader
: new ProcessReader(quarkusProcess.getInputStream())).start();
new Thread(new ProcessReader(quarkusProcess.getErrorStream())).start();

waitForQuarkus();
if (portCapturingProcessReader != null) {
try {
portCapturingProcessReader.awaitForPort();
} catch (InterruptedException ignored) {

}
if (portCapturingProcessReader.port == null) {
quarkusProcess.destroy();
throw new RuntimeException("Unable to determine actual running port as dynamic port was used");
}

waitForQuarkus(portCapturingProcessReader.port);

System.setProperty("quarkus.http.port", portCapturingProcessReader.port.toString()); //set the port as a system property in order to have it applied to Config
System.setProperty("quarkus.http.test-port", portCapturingProcessReader.port.toString()); // needed for RestAssuredManager
installAndGetSomeConfig(); // reinitialize the configuration to make sure the actual port is used
System.setProperty("test.url", TestHTTPResourceManager.getUri());
} else {
waitForQuarkus(port);
}
}

private static String guessPath(Class<?> testClass) {
Expand Down Expand Up @@ -198,7 +232,7 @@ private static void logGuessedPath(String guessedPath) {
System.err.println("======================================================================================");
}

private void waitForQuarkus() {
private void waitForQuarkus(int port) {
long bailout = System.currentTimeMillis() + imageWaitTime * 1000;

while (System.currentTimeMillis() < bailout) {
Expand All @@ -220,14 +254,15 @@ private void waitForQuarkus() {
}
}

quarkusProcess.destroy();
throw new RuntimeException("Unable to start native image in " + imageWaitTime + "s");
}

public void addSystemProperties(Map<String, String> systemProps) {
this.systemProps.putAll(systemProps);
}

private static final class ProcessReader implements Runnable {
private static class ProcessReader implements Runnable {

private final InputStream inputStream;

Expand All @@ -237,14 +272,92 @@ private ProcessReader(InputStream inputStream) {

@Override
public void run() {
handleStart();
byte[] b = new byte[100];
int i;
try {
while ((i = inputStream.read(b)) > 0) {
System.out.print(new String(b, 0, i, StandardCharsets.UTF_8));
String str = new String(b, 0, i, StandardCharsets.UTF_8);
System.out.print(str);
handleString(str);
}
} catch (IOException e) {
//ignore
handleError(e);
}
}

protected void handleStart() {

}

protected void handleString(String str) {

}

protected void handleError(IOException e) {

}
}

private static final class PortCapturingProcessReader extends ProcessReader {
private Integer port;

private boolean portDetermined = false;
private StringBuilder sb = new StringBuilder();
private final Lock lock = Locks.reentrantLock();
private final Condition portDeterminedCondition = lock.newCondition();
private final Pattern portRegex = Pattern.compile("Listening on:\\s+https?://.*:(\\d+)");

private PortCapturingProcessReader(InputStream inputStream) {
super(inputStream);
}

@Override
protected void handleStart() {
lock.lock();
}

@Override
protected void handleString(String str) {
if (portDetermined) { // we are done with determining the port
return;
}
sb.append(str);
String currentOutput = sb.toString();
Matcher regexMatcher = portRegex.matcher(currentOutput);
if (!regexMatcher.find()) { // haven't read enough data yet
if (currentOutput.contains("Exception")) {
portDetermined(null);
}
return;
}
portDetermined(Integer.valueOf(regexMatcher.group(1)));
}

private void portDetermined(Integer portValue) {
this.port = portValue;
try {
portDetermined = true;
sb = null;
portDeterminedCondition.signal();
} finally {
lock.unlock();
}
}

@Override
protected void handleError(IOException e) {
portDetermined(null);
}

public void awaitForPort() throws InterruptedException {
lock.lock();
try {
while (!portDetermined) {
portDeterminedCondition.await();
}
} finally {
lock.unlock();
}
}
}
Expand Down

0 comments on commit d979a91

Please sign in to comment.