diff options
author | Simon Brandhof <simon.brandhof@gmail.com> | 2013-10-14 01:00:12 +0200 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@gmail.com> | 2013-10-14 01:00:12 +0200 |
commit | 275da1c743917341256322a09a3172a2ccf04ef3 (patch) | |
tree | 42f62b18140966f7da89847f6033ce8691700285 | |
parent | 5b4b4172c45aa794d7b40c8e5b9fde6d3a001492 (diff) | |
download | sonarqube-275da1c743917341256322a09a3172a2ccf04ef3.tar.gz sonarqube-275da1c743917341256322a09a3172a2ccf04ef3.zip |
SONAR-4741 HTTPS mode
11 files changed, 357 insertions, 88 deletions
diff --git a/sonar-application/pom.xml b/sonar-application/pom.xml index a55c24d7c99..5a63c20575e 100644 --- a/sonar-application/pom.xml +++ b/sonar-application/pom.xml @@ -223,6 +223,12 @@ <artifactId>http-request</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <scope>test</scope> + </dependency> + </dependencies> <build> diff --git a/sonar-application/src/main/assembly/conf/sonar.properties b/sonar-application/src/main/assembly/conf/sonar.properties index 3b4fc79cd0e..1ef1d031316 100644 --- a/sonar-application/src/main/assembly/conf/sonar.properties +++ b/sonar-application/src/main/assembly/conf/sonar.properties @@ -11,34 +11,6 @@ #-------------------------------------------------------------------------------------------------- -# WEB SERVER - -# Binding address -#sonar.web.host=0.0.0.0 - -# TCP port for incoming HTTP connections -#sonar.web.port=9000 - -# Web context must start with slash (/) -#sonar.web.context=/ - -# The maximum number of connections that the server will accept and process at any given time. -# When this number has been reached, the server will not accept any more connections until -# the number of connections falls below this value. The operating system may still accept connections -# based on the sonar.web.connections.acceptCount property. The default value is 50. -#sonar.web.connections.maxThreads=50 - -# The minimum number of threads always kept running. If not specified, the default of 5 is used. -#sonar.web.connections.minThreads=5 - -# The maximum queue length for incoming connection requests when all possible request processing -# threads are in use. Any requests received when the queue is full will be refused. -# The default value is 25. -#sonar.web.connections.acceptCount=25 - - - -#-------------------------------------------------------------------------------------------------- # DATABASE # # IMPORTANT: the embedded H2 database is used by default. It is recommended for tests only. @@ -112,6 +84,70 @@ sonar.jdbc.timeBetweenEvictionRunsMillis=30000 #-------------------------------------------------------------------------------------------------- +# WEB SERVER + +# Binding address +#sonar.web.host=0.0.0.0 + +# Web context. When set, it must start with forward slash (for example /sonarqube). +# The default value is root context (empty value). +#sonar.web.context= + +# TCP port for incoming HTTP connections +#sonar.web.http.enable=true +#sonar.web.port=9000 + +# TCP port for incoming HTTPS connections. Disabled by default. +#sonar.web.https.enable=false +#sonar.web.https.port=9443 + +# HTTPS - the alias used to for the server certificate in the keystore. +# If not specified the first key read in the keystore is used. +#sonar.web.https.keyAlias= + +# HTTPS - the password used to access the server certificate from the +# specified keystore file. The default value is "changeit". +#sonar.web.https.keyPass=changeit + +# HTTPS - the pathname of the keystore file where is stored the server certificate. +# By default, the pathname is the file ".keystore" in the user home. +# If keystoreType doesn't need a file use empty value. +#sonar.web.https.keystoreFile= + +# HTTPS - the password used to access the specified keystore file. The default +# value is the value of sonar.web.https.keyPass. +#sonar.web.https.keystorePass= + +# HTTPS - the type of keystore file to be used for the server certificate. +# The default value is JKS (Java KeyStore). +#sonar.web.https.keystoreType=JKS + +# HTTPS - the name of the keystore provider to be used for the server certificate. +# If not specified, the list of registered providers is traversed in preference order +# and the first provider that supports the keystore type is used (see sonar.web.https.keystoreType). +#sonar.web.https.keystoreProvider= + +# The maximum number of connections that the server will accept and process at any given time. +# When this number has been reached, the server will not accept any more connections until +# the number of connections falls below this value. The operating system may still accept connections +# based on the sonar.web.connections.acceptCount property. The default value is 50 for each +# enabled connector. +#sonar.web.http.maxThreads=50 +#sonar.web.https.maxThreads=50 + +# The minimum number of threads always kept running. If not specified, the default of 5 is used. +#sonar.web.http.minThreads=5 +#sonar.web.https.minThreads=5 + +# The maximum queue length for incoming connection requests when all possible request processing +# threads are in use. Any requests received when the queue is full will be refused. +# The default value is 25 for each enabled connector. +#sonar.web.http.acceptCount=25 +#sonar.web.https.acceptCount=25 + + + +#-------------------------------------------------------------------------------------------------- # UPDATE CENTER # The Update Center requires an internet connection to request http://update.sonarsource.org diff --git a/sonar-application/src/main/java/org/sonar/application/Connectors.java b/sonar-application/src/main/java/org/sonar/application/Connectors.java index 9dac5526d6e..77609d1820d 100644 --- a/sonar-application/src/main/java/org/sonar/application/Connectors.java +++ b/sonar-application/src/main/java/org/sonar/application/Connectors.java @@ -21,42 +21,99 @@ package org.sonar.application; import org.apache.catalina.connector.Connector; import org.apache.catalina.startup.Tomcat; +import org.slf4j.LoggerFactory; -class Connectors { +import javax.annotation.Nullable; - static final String PROPERTY_SHUTDOWN_TOKEN = "sonar.web.shutdown.token"; - static final String PROPERTY_SHUTDOWN_PORT = "sonar.web.shutdown.port"; - static final String PROPERTY_MIN_THREADS = "sonar.web.connections.minThreads"; - static final String PROPERTY_MAX_THREADS = "sonar.web.connections.maxThreads"; - static final String PROPERTY_ACCEPT_COUNT = "sonar.web.connections.acceptCount"; +class Connectors { static void configure(Tomcat tomcat, Props props) { tomcat.getServer().setAddress(props.of("sonar.web.host", "0.0.0.0")); configureShutdown(tomcat, props); + configureConnectors(tomcat, props); + } - Connector connector = new Connector("HTTP/1.1"); - connector.setPort(props.intOf("sonar.web.port", 9000)); - connector.setURIEncoding("UTF-8"); - configurePool(props, connector); - configureCompression(connector); - tomcat.setConnector(connector); - tomcat.getService().addConnector(connector); + private static void configureConnectors(Tomcat tomcat, Props props) { + Connector http = newHttpConnector(props); + Connector https = newHttpsConnector(props); + + if (http == null) { + if (https == null) { + throw new IllegalStateException("Both HTTP and HTTPS connectors are disabled"); + } + // Enable only HTTPS + tomcat.setConnector(https); + tomcat.getService().addConnector(https); + } else { + // Both HTTP and HTTPS are enabled + tomcat.setConnector(http); + tomcat.getService().addConnector(http); + if (https != null) { + tomcat.getService().addConnector(https); + } + } } private static void configureShutdown(Tomcat tomcat, Props props) { - String shutdownToken = props.of(PROPERTY_SHUTDOWN_TOKEN); - Integer shutdownPort = props.intOf(PROPERTY_SHUTDOWN_PORT); + String shutdownToken = props.of("sonar.web.shutdown.token"); + Integer shutdownPort = props.intOf("sonar.web.shutdown.port"); if (shutdownToken != null && !"".equals(shutdownToken) && shutdownPort != null) { tomcat.getServer().setPort(shutdownPort); tomcat.getServer().setShutdown(shutdownToken); + info("Shutdown command is enabled on port " + shutdownPort); + } + } + + @Nullable + private static Connector newHttpConnector(Props props) { + Connector connector = null; + if (props.booleanOf("sonar.web.http.enable", true)) { + connector = newConnector(props, "http"); + // Not named "sonar.web.http.port" to keep backward-compatibility + int port = props.intOf("sonar.web.port", 9000); + connector.setPort(port); + info("HTTP connector is enabled on port " + port); + } + return connector; + } + + @Nullable + private static Connector newHttpsConnector(Props props) { + Connector connector = null; + if (props.booleanOf("sonar.web.https.enable")) { + connector = newConnector(props, "https"); + int port = props.intOf("sonar.web.https.port", 9443); + connector.setPort(port); + connector.setSecure(true); + connector.setScheme("https"); + setConnectorAttribute(connector, "keyAlias", props.of("sonar.web.https.keyAlias")); + String keyPassword = props.of("sonar.web.https.keyPass", "changeit"); + setConnectorAttribute(connector, "keyPass", keyPassword); + setConnectorAttribute(connector, "keystorePass", props.of("sonar.web.https.keystorePass", keyPassword)); + setConnectorAttribute(connector, "keystoreFile", props.of("sonar.web.https.keystoreFile")); + setConnectorAttribute(connector, "keystoreType", props.of("sonar.web.https.keystoreType", "JKS")); + setConnectorAttribute(connector, "keystoreProvider", props.of("sonar.web.https.keystoreProvider")); + setConnectorAttribute(connector, "clientAuth", false); + setConnectorAttribute(connector, "sslProtocol", "TLS"); + setConnectorAttribute(connector, "SSLEnabled", true); + info("HTTPS connector is enabled on port " + port); } + return connector; + } + + private static Connector newConnector(Props props, String scheme) { + Connector connector = new Connector("HTTP/1.1"); + connector.setURIEncoding("UTF-8"); + configurePool(props, connector, scheme); + configureCompression(connector); + return connector; } - private static void configurePool(Props props, Connector connector) { + private static void configurePool(Props props, Connector connector, String scheme) { connector.setProperty("acceptorThreadCount", String.valueOf(2)); - connector.setProperty("minSpareThreads", String.valueOf(props.intOf(PROPERTY_MIN_THREADS, 5))); - connector.setProperty("maxThreads", String.valueOf(props.intOf(PROPERTY_MAX_THREADS, 50))); - connector.setProperty("acceptCount", String.valueOf(props.intOf(PROPERTY_ACCEPT_COUNT, 25))); + connector.setProperty("minSpareThreads", String.valueOf(props.intOf("sonar.web." + scheme + ".minThreads", 5))); + connector.setProperty("maxThreads", String.valueOf(props.intOf("sonar.web." + scheme + ".maxThreads", 50))); + connector.setProperty("acceptCount", String.valueOf(props.intOf("sonar.web." + scheme + ".acceptCount", 25))); } private static void configureCompression(Connector connector) { @@ -64,4 +121,14 @@ class Connectors { connector.setProperty("compressionMinSize", "1024"); connector.setProperty("compressableMimeType", "text/html,text/xml,text/plain,text/css,application/json,application/javascript"); } + + private static void setConnectorAttribute(Connector c, String key, @Nullable Object value) { + if (value != null) { + c.setAttribute(key, value); + } + } + + private static void info(String message) { + LoggerFactory.getLogger(Connectors.class).info(message); + } } diff --git a/sonar-application/src/main/java/org/sonar/application/EmbeddedTomcat.java b/sonar-application/src/main/java/org/sonar/application/EmbeddedTomcat.java index 4838e3d2202..b92783982d3 100644 --- a/sonar-application/src/main/java/org/sonar/application/EmbeddedTomcat.java +++ b/sonar-application/src/main/java/org/sonar/application/EmbeddedTomcat.java @@ -19,7 +19,10 @@ */ package org.sonar.application; +import org.apache.catalina.LifecycleEvent; import org.apache.catalina.LifecycleException; +import org.apache.catalina.LifecycleListener; +import org.apache.catalina.connector.Connector; import org.apache.catalina.startup.Tomcat; import org.apache.commons.io.FileUtils; @@ -67,7 +70,6 @@ class EmbeddedTomcat { Logging.configure(tomcat, env); Connectors.configure(tomcat, props); Webapp.configure(tomcat, env, props); - tomcat.start(); addShutdownHook(); tomcat.getServer().await(); @@ -117,6 +119,10 @@ class EmbeddedTomcat { } int port() { - return tomcat.getService().findConnectors()[0].getLocalPort(); + Connector[] connectors = tomcat.getService().findConnectors(); + if (connectors.length > 0) { + return connectors[0].getLocalPort(); + } + return -1; } } diff --git a/sonar-application/src/main/java/org/sonar/application/Logging.java b/sonar-application/src/main/java/org/sonar/application/Logging.java index 29d8d097698..ae5a5a4d6b3 100644 --- a/sonar-application/src/main/java/org/sonar/application/Logging.java +++ b/sonar-application/src/main/java/org/sonar/application/Logging.java @@ -20,7 +20,11 @@ package org.sonar.application; import ch.qos.logback.access.tomcat.LogbackValve; +import org.apache.catalina.LifecycleEvent; +import org.apache.catalina.LifecycleListener; import org.apache.catalina.startup.Tomcat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.slf4j.bridge.SLF4JBridgeHandler; import java.io.File; @@ -38,6 +42,8 @@ class Logging { static void configure(Tomcat tomcat, Env env) { tomcat.setSilent(false); + tomcat.getService().addLifecycleListener(new StartupLogger(LoggerFactory.getLogger(Logging.class))); + LogbackValve valve = new LogbackValve(); valve.setQuiet(true); File confFile = env.file(CONF_PATH); @@ -47,4 +53,20 @@ class Logging { valve.setFilename(confFile.getAbsolutePath()); tomcat.getHost().getPipeline().addValve(valve); } + + static class StartupLogger implements LifecycleListener { + private Logger logger; + + StartupLogger(Logger logger) { + this.logger = logger; + } + + @Override + public void lifecycleEvent(LifecycleEvent event) { + if ("after_start".equals(event.getType())) { + logger.info("Web server is up"); + } + } + } + } diff --git a/sonar-application/src/main/java/org/sonar/application/Props.java b/sonar-application/src/main/java/org/sonar/application/Props.java index f720553c18a..4e0bb6c3d20 100644 --- a/sonar-application/src/main/java/org/sonar/application/Props.java +++ b/sonar-application/src/main/java/org/sonar/application/Props.java @@ -29,7 +29,7 @@ import java.io.IOException; import java.util.Properties; /** - * TODO support env substitution + * TODO support env substitution and encryption */ class Props { private final Properties props; @@ -52,6 +52,11 @@ class Props { return s != null && Boolean.parseBoolean(s); } + boolean booleanOf(String key, boolean defaultValue) { + String s = of(key); + return s != null ? Boolean.parseBoolean(s) : defaultValue; + } + Integer intOf(String key) { String s = of(key); if (s != null && !"".equals(s)) { diff --git a/sonar-application/src/main/java/org/sonar/application/Webapp.java b/sonar-application/src/main/java/org/sonar/application/Webapp.java index aa85ea7d1b5..89f84c3603a 100644 --- a/sonar-application/src/main/java/org/sonar/application/Webapp.java +++ b/sonar-application/src/main/java/org/sonar/application/Webapp.java @@ -28,7 +28,7 @@ class Webapp { private static final String RAILS_ENV = "rails.env"; static void configure(Tomcat tomcat, Env env, Props props) { - String ctx = props.of("sonar.web.context", "/"); + String ctx = getContext(props); try { Context context = tomcat.addWebapp(ctx, env.file("web").getAbsolutePath()); context.setConfigFile(env.file("web/META-INF/context.xml").toURI().toURL()); @@ -40,6 +40,14 @@ class Webapp { } } + static String getContext(Props props) { + String context = props.of("sonar.web.context", ""); + if (!"".equals(context) && !context.startsWith("/")) { + throw new IllegalStateException("Value of sonar.web.context must start with a forward slash: " + context); + } + return context; + } + static void configureRailsMode(Props props, Context context) { if (props.booleanOf("sonar.web.dev")) { context.addParameter(RAILS_ENV, "development"); diff --git a/sonar-application/src/test/java/org/sonar/application/ConnectorsTest.java b/sonar-application/src/test/java/org/sonar/application/ConnectorsTest.java index b40b79b32f0..c1594b2c46a 100644 --- a/sonar-application/src/test/java/org/sonar/application/ConnectorsTest.java +++ b/sonar-application/src/test/java/org/sonar/application/ConnectorsTest.java @@ -19,98 +19,167 @@ */ package org.sonar.application; +import com.google.common.collect.ImmutableMap; import org.apache.catalina.connector.Connector; import org.apache.catalina.startup.Tomcat; import org.junit.Test; import org.mockito.ArgumentMatcher; import org.mockito.Mockito; +import java.util.Map; import java.util.Properties; +import static org.fest.assertions.Assertions.assertThat; +import static org.fest.assertions.Fail.fail; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.argThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.*; public class ConnectorsTest { + + Tomcat tomcat = mock(Tomcat.class, Mockito.RETURNS_DEEP_STUBS); + + //---- connectors + @Test - public void enable_shutdown_port() throws Exception { + public void configure_thread_pool() throws Exception { Properties p = new Properties(); - p.setProperty(Connectors.PROPERTY_SHUTDOWN_PORT, "9010"); - p.setProperty(Connectors.PROPERTY_SHUTDOWN_TOKEN, "SHUTDOWN"); + p.setProperty("sonar.web.http.minThreads", "2"); + p.setProperty("sonar.web.http.maxThreads", "30"); + p.setProperty("sonar.web.http.acceptCount", "20"); Props props = new Props(p); - Tomcat tomcat = mock(Tomcat.class, Mockito.RETURNS_DEEP_STUBS); Connectors.configure(tomcat, props); - verify(tomcat.getServer()).setPort(9010); - verify(tomcat.getServer()).setShutdown("SHUTDOWN"); + verify(tomcat).setConnector(argThat(new PropertiesMatcher( + ImmutableMap.<String, Object>of("minSpareThreads", 2, "maxThreads", 30, "acceptCount", 20) + ))); } @Test - public void disable_shutdown_port_by_default() throws Exception { + public void configure_default_thread_pool() throws Exception { Props props = new Props(new Properties()); - Tomcat tomcat = mock(Tomcat.class, Mockito.RETURNS_DEEP_STUBS); Connectors.configure(tomcat, props); - verify(tomcat.getServer(), never()).setPort(anyInt()); - verify(tomcat.getServer(), never()).setShutdown(anyString()); + verify(tomcat).setConnector(argThat(new PropertiesMatcher( + ImmutableMap.<String, Object>of("minSpareThreads", 5, "maxThreads", 50, "acceptCount", 25) + ))); } @Test - public void disable_shutdown_port_if_missing_token() throws Exception { + public void fail_if_both_http_and_https_are_disabled() { Properties p = new Properties(); - // only the port, but not the token - p.setProperty(Connectors.PROPERTY_SHUTDOWN_PORT, "9010"); + p.setProperty("sonar.web.http.enable", "false"); + p.setProperty("sonar.web.https.enable", "false"); Props props = new Props(p); - Tomcat tomcat = mock(Tomcat.class, Mockito.RETURNS_DEEP_STUBS); - Connectors.configure(tomcat, props); - - verify(tomcat.getServer(), never()).setPort(anyInt()); - verify(tomcat.getServer(), never()).setShutdown(anyString()); + try { + Connectors.configure(tomcat, props); + fail(); + } catch (IllegalStateException e) { + assertThat(e.getMessage()).isEqualTo("Both HTTP and HTTPS connectors are disabled"); + } } @Test - public void configure_thread_pool() throws Exception { + public void only_https_is_enabled() { Properties p = new Properties(); - p.setProperty(Connectors.PROPERTY_MIN_THREADS, "2"); - p.setProperty(Connectors.PROPERTY_MAX_THREADS, "30"); - p.setProperty(Connectors.PROPERTY_ACCEPT_COUNT, "20"); + p.setProperty("sonar.web.http.enable", "false"); + p.setProperty("sonar.web.https.enable", "true"); Props props = new Props(p); - Tomcat tomcat = mock(Tomcat.class, Mockito.RETURNS_DEEP_STUBS); Connectors.configure(tomcat, props); verify(tomcat).setConnector(argThat(new ArgumentMatcher<Connector>() { @Override public boolean matches(Object o) { Connector c = (Connector)o; - return (Integer)c.getProperty("minSpareThreads") == 2 && - (Integer) c.getProperty("maxThreads") == 30 && - (Integer) c.getProperty("acceptCount") == 20; + return c.getScheme().equals("https") && c.getPort()==9443; } })); } @Test - public void configure_default_thread_pool() throws Exception { - Props props = new Props(new Properties()); + public void both_http_and_https_are_enabled() { + Properties p = new Properties(); + p.setProperty("sonar.web.http.enable", "true"); + p.setProperty("sonar.web.https.enable", "true"); + Props props = new Props(p); - Tomcat tomcat = mock(Tomcat.class, Mockito.RETURNS_DEEP_STUBS); Connectors.configure(tomcat, props); - verify(tomcat).setConnector(argThat(new ArgumentMatcher<Connector>() { + verify(tomcat.getService()).addConnector(argThat(new ArgumentMatcher<Connector>() { @Override public boolean matches(Object o) { Connector c = (Connector)o; - return (Integer)c.getProperty("minSpareThreads") == 5 && - (Integer) c.getProperty("maxThreads") == 50 && - (Integer) c.getProperty("acceptCount") == 25; + return c.getScheme().equals("http") && c.getPort()==9000; } })); + verify(tomcat.getService()).addConnector(argThat(new ArgumentMatcher<Connector>() { + @Override + public boolean matches(Object o) { + Connector c = (Connector)o; + return c.getScheme().equals("https") && c.getPort()==9443; + } + })); + } + + + //---- shutdown port + + @Test + public void enable_shutdown_port() throws Exception { + Properties p = new Properties(); + p.setProperty("sonar.web.shutdown.port", "9010"); + p.setProperty("sonar.web.shutdown.token", "SHUTDOWN"); + Props props = new Props(p); + + Connectors.configure(tomcat, props); + + verify(tomcat.getServer()).setPort(9010); + verify(tomcat.getServer()).setShutdown("SHUTDOWN"); + } + + @Test + public void disable_shutdown_port_by_default() throws Exception { + Props props = new Props(new Properties()); + + Connectors.configure(tomcat, props); + + verify(tomcat.getServer(), never()).setPort(anyInt()); + verify(tomcat.getServer(), never()).setShutdown(anyString()); + } + + @Test + public void disable_shutdown_port_if_missing_token() throws Exception { + Properties p = new Properties(); + // only the port, but not the token + p.setProperty("sonar.web.shutdown.port", "9010"); + Props props = new Props(p); + + Connectors.configure(tomcat, props); + + verify(tomcat.getServer(), never()).setPort(anyInt()); + verify(tomcat.getServer(), never()).setShutdown(anyString()); + } + + private static class PropertiesMatcher extends ArgumentMatcher<Connector> { + private final Map<String, Object> expected; + + PropertiesMatcher(Map<String, Object> expected) { + this.expected = expected; + } + + public boolean matches(Object o) { + Connector c = (Connector) o; + for (Map.Entry<String, Object> entry : expected.entrySet()) { + if (!entry.getValue().equals(c.getProperty(entry.getKey()))) { + return false; + } + } + return true; + } } } diff --git a/sonar-application/src/test/java/org/sonar/application/LoggingTest.java b/sonar-application/src/test/java/org/sonar/application/LoggingTest.java index ca5f6ef3c51..39808394b88 100644 --- a/sonar-application/src/test/java/org/sonar/application/LoggingTest.java +++ b/sonar-application/src/test/java/org/sonar/application/LoggingTest.java @@ -20,11 +20,14 @@ package org.sonar.application; import ch.qos.logback.access.tomcat.LogbackValve; +import org.apache.catalina.Lifecycle; +import org.apache.catalina.LifecycleEvent; import org.apache.catalina.Valve; import org.apache.catalina.startup.Tomcat; import org.junit.Test; import org.mockito.ArgumentMatcher; import org.mockito.Mockito; +import org.slf4j.Logger; import java.io.File; @@ -65,4 +68,18 @@ public class LoggingTest { assertThat(e).hasMessage("File is missing: " + confFile.getAbsolutePath()); } } + + @Test + public void log_when_started() { + Logger logger = mock(Logger.class); + Logging.StartupLogger listener = new Logging.StartupLogger(logger); + + LifecycleEvent event = new LifecycleEvent(mock(Lifecycle.class), "before_init", null); + listener.lifecycleEvent(event); + verifyZeroInteractions(logger); + + event = new LifecycleEvent(mock(Lifecycle.class), "after_start", null); + listener.lifecycleEvent(event); + verify(logger).info("Web server is up"); + } } diff --git a/sonar-application/src/test/java/org/sonar/application/PropsTest.java b/sonar-application/src/test/java/org/sonar/application/PropsTest.java index f6c1f2ece01..20ca51e5161 100644 --- a/sonar-application/src/test/java/org/sonar/application/PropsTest.java +++ b/sonar-application/src/test/java/org/sonar/application/PropsTest.java @@ -85,6 +85,19 @@ public class PropsTest { } @Test + public void booleanOf_default_value() throws Exception { + Properties p = new Properties(); + p.setProperty("foo", "true"); + p.setProperty("bar", "false"); + Props props = new Props(p); + + assertThat(props.booleanOf("unset", false)).isFalse(); + assertThat(props.booleanOf("unset", true)).isTrue(); + assertThat(props.booleanOf("foo", false)).isTrue(); + assertThat(props.booleanOf("bar", true)).isFalse(); + } + + @Test public void load_file_and_system_properties() throws Exception { Env env = mock(Env.class); File propsFile = new File(getClass().getResource("/org/sonar/application/PropsTest/sonar.properties").toURI()); diff --git a/sonar-application/src/test/java/org/sonar/application/WebappTest.java b/sonar-application/src/test/java/org/sonar/application/WebappTest.java index 0e0dd904a1c..aa1ba82eee8 100644 --- a/sonar-application/src/test/java/org/sonar/application/WebappTest.java +++ b/sonar-application/src/test/java/org/sonar/application/WebappTest.java @@ -26,6 +26,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import java.io.File; +import java.util.Properties; import static org.fest.assertions.Assertions.assertThat; import static org.fest.assertions.Fail.fail; @@ -46,7 +47,7 @@ public class WebappTest { when(tomcat.addContext("", webDir.getAbsolutePath())).thenThrow(new NullPointerException()); try { - Webapp.configure(tomcat, env, mock(Props.class)); + Webapp.configure(tomcat, env, new Props(new Properties())); fail(); } catch (IllegalStateException e) { assertThat(e).hasMessage("Fail to configure webapp"); @@ -76,4 +77,23 @@ public class WebappTest { verify(context).addParameter("jruby.max.runtimes", "1"); verify(context).addParameter("rails.env", "production"); } + + @Test + public void context_must_start_with_slash() throws Exception { + Properties p = new Properties(); + p.setProperty("sonar.web.context", "foo"); + + try { + Webapp.getContext(new Props(p)); + fail(); + } catch (IllegalStateException e) { + assertThat(e.getMessage()).isEqualTo("Value of sonar.web.context must start with a forward slash: foo"); + } + } + + @Test + public void default_context_is_root() throws Exception { + String context = Webapp.getContext(new Props(new Properties())); + assertThat(context).isEqualTo(""); + } } |