/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jooby.test;
import com.google.inject.Binder;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigValueFactory;
import org.jooby.Env;
import org.jooby.Jooby;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.MultipleFailureException;
import org.junit.runners.model.Statement;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* JUnit4 block runner for Jooby. Internal use only.
*
* @author edgar
*/
public class JoobyRunner extends BlockJUnit4ClassRunner {
private Jooby app;
private int port;
private int securePort;
private Class<?> server;
public JoobyRunner(final Class<?> klass) throws InitializationError {
super(klass);
prepare(klass, null);
}
public JoobyRunner(final Class<?> klass, final Class<?> server) throws InitializationError {
super(klass);
prepare(klass, server);
}
@Override
protected String getName() {
if (server != null) {
return "[" + server.getSimpleName().toLowerCase() + "]";
}
return super.getName();
}
@Override
protected String testName(final FrameworkMethod method) {
if (server != null) {
return method.getName() + getName();
}
return super.testName(method);
}
private void prepare(final Class<?> klass, final Class<?> server) throws InitializationError {
try {
this.port = port("coverage.port", 9999);
this.securePort = port("coverage.securePort", 9943);
this.server = server;
Class<?> appClass = klass;
if (!Jooby.class.isAssignableFrom(appClass)) {
throw new InitializationError("Invalid jooby app: " + appClass);
}
int processors = Math.max(1, Runtime.getRuntime().availableProcessors());
// required by Jetty (processors * 2, 1(http), 1(https), 1(request)
int maxThreads = processors * 2 + 3;
Config config = ConfigFactory.empty("test-config")
.withValue("server.join", ConfigValueFactory.fromAnyRef(false))
.withValue("server.http.IdleTimeout", ConfigValueFactory.fromAnyRef("5m"))
.withValue("server.threads.Min", ConfigValueFactory.fromAnyRef(1))
.withValue("server.threads.Max", ConfigValueFactory.fromAnyRef(maxThreads))
.withValue("application.port", ConfigValueFactory.fromAnyRef(port))
.withValue("undertow.ioThreads", ConfigValueFactory.fromAnyRef(2))
.withValue("undertow.workerThreads", ConfigValueFactory.fromAnyRef(4))
.withValue("netty.threads.Parent", ConfigValueFactory.fromAnyRef(2));
if (server != null) {
config = config.withFallback(ConfigFactory.empty()
.withValue("server.module", ConfigValueFactory.fromAnyRef(server.getName())));
}
app = (Jooby) appClass.newInstance();
if (app instanceof ServerFeature) {
int appport = ((ServerFeature) app).port;
if (appport > 0) {
config = config.withValue("application.port", ConfigValueFactory.fromAnyRef(appport));
this.port = appport;
}
int sappport = ((ServerFeature) app).securePort;
if (sappport > 0) {
config = config.withValue("application.securePort",
ConfigValueFactory.fromAnyRef(sappport));
this.securePort = sappport;
}
}
Config testConfig = config;
app.use(new Jooby.Module() {
@Override
public void configure(final Env mode, final Config config, final Binder binder) {
}
@Override
public Config config() {
return testConfig;
}
});
} catch (Exception ex) {
throw new InitializationError(Arrays.asList(ex));
}
}
@Override
protected Statement withBeforeClasses(final Statement statement) {
Statement next = super.withBeforeClasses(statement);
return new Statement() {
@Override
public void evaluate() throws Throwable {
app.start();
next.evaluate();
}
};
}
@Override
protected Object createTest() throws Exception {
Object test = super.createTest();
Class<? extends Object> c = test.getClass();
set(test, c, "port", port);
set(test, c, "securePort", securePort);
return test;
}
@SuppressWarnings("rawtypes")
private void set(final Object test, final Class clazz, final String field, final Object value)
throws Exception {
try {
Field f = clazz.getDeclaredField(field);
f.setAccessible(true);
f.set(test, value);
} catch (NoSuchFieldException ex) {
Class superclass = clazz.getSuperclass();
if (superclass != Object.class) {
set(test, superclass, field, value);
}
}
}
@Override
protected Statement withAfterClasses(final Statement statement) {
Statement next = super.withAfterClasses(statement);
return new Statement() {
@Override
public void evaluate() throws Throwable {
List<Throwable> errors = new ArrayList<Throwable>();
try {
next.evaluate();
} catch (Throwable e) {
errors.add(e);
}
try {
app.stop();
} catch (Exception ex) {
errors.add(ex);
}
if (errors.isEmpty()) {
return;
}
if (errors.size() == 1) {
throw errors.get(0);
}
throw new MultipleFailureException(errors);
}
};
}
private int port(String property, Integer defaultPort) throws IOException {
String port = System.getProperty(property, defaultPort.toString());
if (port.equalsIgnoreCase("random")) {
try (ServerSocket socket = new ServerSocket(0)) {
return socket.getLocalPort();
}
} else {
return Integer.parseInt(port);
}
}
}