From cfe7a59ac8d45c4d1d7def3923c67c5b48f70161 Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Thu, 23 Feb 2017 11:40:30 +0100 Subject: [PATCH] SONAR-7937 extract JavaCommandFactory from App and fix missing coverage on this part --- .../main/java/org/sonar/application/App.java | 98 +---- .../sonar/application/JavaCommandFactory.java | 32 ++ .../application/JavaCommandFactoryImpl.java | 114 +++++ .../java/org/sonar/application/AppTest.java | 98 ++--- .../JavaCommandFactoryImplTest.java | 407 ++++++++++++++++++ 5 files changed, 592 insertions(+), 157 deletions(-) create mode 100644 sonar-application/src/main/java/org/sonar/application/JavaCommandFactory.java create mode 100644 sonar-application/src/main/java/org/sonar/application/JavaCommandFactoryImpl.java create mode 100644 sonar-application/src/test/java/org/sonar/application/JavaCommandFactoryImplTest.java diff --git a/sonar-application/src/main/java/org/sonar/application/App.java b/sonar-application/src/main/java/org/sonar/application/App.java index 7271d593ad8..d6c3b843a85 100644 --- a/sonar-application/src/main/java/org/sonar/application/App.java +++ b/sonar-application/src/main/java/org/sonar/application/App.java @@ -27,7 +27,6 @@ import org.apache.commons.io.FilenameUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.process.Lifecycle; -import org.sonar.process.ProcessId; import org.sonar.process.ProcessProperties; import org.sonar.process.Props; import org.sonar.process.Stoppable; @@ -36,29 +35,13 @@ import org.sonar.process.monitor.Monitor; import static org.sonar.process.Lifecycle.State; import static org.sonar.process.ProcessId.APP; -import static org.sonar.process.ProcessProperties.HTTPS_PROXY_HOST; -import static org.sonar.process.ProcessProperties.HTTPS_PROXY_PORT; -import static org.sonar.process.ProcessProperties.HTTP_PROXY_HOST; -import static org.sonar.process.ProcessProperties.HTTP_PROXY_PORT; /** * Entry-point of process that starts and monitors ElasticSearch, the Web Server and the Compute Engine. */ public class App implements Stoppable { - /** - * Properties about proxy that must be set as system properties - */ - private static final String[] PROXY_PROPERTY_KEYS = new String[] { - HTTP_PROXY_HOST, - HTTP_PROXY_PORT, - "http.nonProxyHosts", - HTTPS_PROXY_HOST, - HTTPS_PROXY_PORT, - "http.auth.ntlm.domain", - "socksProxyHost", - "socksProxyPort"}; - + private final JavaCommandFactory javaCommandFactory; private final Monitor monitor; public App(AppFileSystem appFileSystem, boolean watchForHardStop) { @@ -68,10 +51,12 @@ public class App implements Stoppable { .setWatchForHardStop(watchForHardStop) .setWaitForOperational() .addListener(new AppLifecycleListener()) - .build()); + .build(), + new JavaCommandFactoryImpl()); } - App(Monitor monitor) { + App(Monitor monitor, JavaCommandFactory javaCommandFactory) { + this.javaCommandFactory = javaCommandFactory; this.monitor = monitor; } @@ -80,19 +65,19 @@ public class App implements Stoppable { monitor.awaitTermination(); } - private static List createCommands(Props props) { + private List createCommands(Props props) { File homeDir = props.nonNullValueAsFile(ProcessProperties.PATH_HOME); List commands = new ArrayList<>(3); if (isProcessEnabled(props, ProcessProperties.CLUSTER_SEARCH_DISABLED)) { - commands.add(createESCommand(props, homeDir)); + commands.add(javaCommandFactory.createESCommand(props, homeDir)); } if (isProcessEnabled(props, ProcessProperties.CLUSTER_WEB_DISABLED)) { - commands.add(createWebServerCommand(props, homeDir)); + commands.add(javaCommandFactory.createWebCommand(props, homeDir)); } if (isProcessEnabled(props, ProcessProperties.CLUSTER_CE_DISABLED)) { - commands.add(createCeServerCommand(props, homeDir)); + commands.add(javaCommandFactory.createCeCommand(props, homeDir)); } return commands; @@ -103,71 +88,6 @@ public class App implements Stoppable { !props.valueAsBoolean(disabledPropertyKey); } - private static JavaCommand createESCommand(Props props, File homeDir) { - return newJavaCommand(ProcessId.ELASTICSEARCH, props, homeDir) - .addJavaOptions("-Djava.awt.headless=true") - .addJavaOptions(props.nonNullValue(ProcessProperties.SEARCH_JAVA_OPTS)) - .addJavaOptions(props.nonNullValue(ProcessProperties.SEARCH_JAVA_ADDITIONAL_OPTS)) - .setClassName("org.sonar.search.SearchServer") - .addClasspath("./lib/common/*") - .addClasspath("./lib/search/*"); - } - - private static JavaCommand createWebServerCommand(Props props, File homeDir) { - JavaCommand command = newJavaCommand(ProcessId.WEB_SERVER, props, homeDir) - .addJavaOptions(ProcessProperties.WEB_ENFORCED_JVM_ARGS) - .addJavaOptions(props.nonNullValue(ProcessProperties.WEB_JAVA_OPTS)) - .addJavaOptions(props.nonNullValue(ProcessProperties.WEB_JAVA_ADDITIONAL_OPTS)) - // required for logback tomcat valve - .setEnvVariable(ProcessProperties.PATH_LOGS, props.nonNullValue(ProcessProperties.PATH_LOGS)) - .setClassName("org.sonar.server.app.WebServer") - .addClasspath("./lib/common/*") - .addClasspath("./lib/server/*"); - String driverPath = props.value(ProcessProperties.JDBC_DRIVER_PATH); - if (driverPath != null) { - command.addClasspath(driverPath); - } - return command; - } - - private static JavaCommand createCeServerCommand(Props props, File homeDir) { - JavaCommand command = newJavaCommand(ProcessId.COMPUTE_ENGINE, props, homeDir) - .addJavaOptions(ProcessProperties.CE_ENFORCED_JVM_ARGS) - .addJavaOptions(props.nonNullValue(ProcessProperties.CE_JAVA_OPTS)) - .addJavaOptions(props.nonNullValue(ProcessProperties.CE_JAVA_ADDITIONAL_OPTS)) - .setClassName("org.sonar.ce.app.CeServer") - .addClasspath("./lib/common/*") - .addClasspath("./lib/server/*") - .addClasspath("./lib/ce/*"); - String driverPath = props.value(ProcessProperties.JDBC_DRIVER_PATH); - if (driverPath != null) { - command.addClasspath(driverPath); - } - return command; - } - - private static JavaCommand newJavaCommand(ProcessId id, Props props, File homeDir) { - JavaCommand command = new JavaCommand(id) - .setWorkDir(homeDir) - .setArguments(props.rawProperties()); - - for (String key : PROXY_PROPERTY_KEYS) { - if (props.contains(key)) { - command.addJavaOption("-D" + key + "=" + props.value(key)); - } - } - // defaults of HTTPS are the same than HTTP defaults - setSystemPropertyToDefaultIfNotSet(command, props, HTTPS_PROXY_HOST, HTTP_PROXY_HOST); - setSystemPropertyToDefaultIfNotSet(command, props, HTTPS_PROXY_PORT, HTTP_PROXY_PORT); - return command; - } - - private static void setSystemPropertyToDefaultIfNotSet(JavaCommand command, Props props, String httpsProperty, String httpProperty) { - if (!props.contains(httpsProperty) && props.contains(httpProperty)) { - command.addJavaOption("-D" + httpsProperty + "=" + props.value(httpProperty)); - } - } - static String starPath(File homeDir, String relativePath) { File dir = new File(homeDir, relativePath); return FilenameUtils.concat(dir.getAbsolutePath(), "*"); diff --git a/sonar-application/src/main/java/org/sonar/application/JavaCommandFactory.java b/sonar-application/src/main/java/org/sonar/application/JavaCommandFactory.java new file mode 100644 index 00000000000..0071a3619d8 --- /dev/null +++ b/sonar-application/src/main/java/org/sonar/application/JavaCommandFactory.java @@ -0,0 +1,32 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.application; + +import java.io.File; +import org.sonar.process.Props; +import org.sonar.process.monitor.JavaCommand; + +public interface JavaCommandFactory { + JavaCommand createESCommand(Props props, File homeDir); + + JavaCommand createWebCommand(Props props, File homeDir); + + JavaCommand createCeCommand(Props props, File homeDir); +} diff --git a/sonar-application/src/main/java/org/sonar/application/JavaCommandFactoryImpl.java b/sonar-application/src/main/java/org/sonar/application/JavaCommandFactoryImpl.java new file mode 100644 index 00000000000..4f9ebd80ed8 --- /dev/null +++ b/sonar-application/src/main/java/org/sonar/application/JavaCommandFactoryImpl.java @@ -0,0 +1,114 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.application; + +import java.io.File; +import org.sonar.process.ProcessId; +import org.sonar.process.ProcessProperties; +import org.sonar.process.Props; +import org.sonar.process.monitor.JavaCommand; + +import static org.sonar.process.ProcessProperties.HTTPS_PROXY_HOST; +import static org.sonar.process.ProcessProperties.HTTPS_PROXY_PORT; +import static org.sonar.process.ProcessProperties.HTTP_PROXY_HOST; +import static org.sonar.process.ProcessProperties.HTTP_PROXY_PORT; + +public class JavaCommandFactoryImpl implements JavaCommandFactory { + /** + * Properties about proxy that must be set as system properties + */ + private static final String[] PROXY_PROPERTY_KEYS = new String[] { + HTTP_PROXY_HOST, + HTTP_PROXY_PORT, + "http.nonProxyHosts", + HTTPS_PROXY_HOST, + HTTPS_PROXY_PORT, + "http.auth.ntlm.domain", + "socksProxyHost", + "socksProxyPort"}; + + @Override + public JavaCommand createESCommand(Props props, File workDir) { + return newJavaCommand(ProcessId.ELASTICSEARCH, props, workDir) + .addJavaOptions("-Djava.awt.headless=true") + .addJavaOptions(props.nonNullValue(ProcessProperties.SEARCH_JAVA_OPTS)) + .addJavaOptions(props.nonNullValue(ProcessProperties.SEARCH_JAVA_ADDITIONAL_OPTS)) + .setClassName("org.sonar.search.SearchServer") + .addClasspath("./lib/common/*") + .addClasspath("./lib/search/*"); + } + + @Override + public JavaCommand createWebCommand(Props props, File workDir) { + JavaCommand command = newJavaCommand(ProcessId.WEB_SERVER, props, workDir) + .addJavaOptions(ProcessProperties.WEB_ENFORCED_JVM_ARGS) + .addJavaOptions(props.nonNullValue(ProcessProperties.WEB_JAVA_OPTS)) + .addJavaOptions(props.nonNullValue(ProcessProperties.WEB_JAVA_ADDITIONAL_OPTS)) + // required for logback tomcat valve + .setEnvVariable(ProcessProperties.PATH_LOGS, props.nonNullValue(ProcessProperties.PATH_LOGS)) + .setClassName("org.sonar.server.app.WebServer") + .addClasspath("./lib/common/*") + .addClasspath("./lib/server/*"); + String driverPath = props.value(ProcessProperties.JDBC_DRIVER_PATH); + if (driverPath != null) { + command.addClasspath(driverPath); + } + return command; + } + + @Override + public JavaCommand createCeCommand(Props props, File workDir) { + JavaCommand command = newJavaCommand(ProcessId.COMPUTE_ENGINE, props, workDir) + .addJavaOptions(ProcessProperties.CE_ENFORCED_JVM_ARGS) + .addJavaOptions(props.nonNullValue(ProcessProperties.CE_JAVA_OPTS)) + .addJavaOptions(props.nonNullValue(ProcessProperties.CE_JAVA_ADDITIONAL_OPTS)) + .setClassName("org.sonar.ce.app.CeServer") + .addClasspath("./lib/common/*") + .addClasspath("./lib/server/*") + .addClasspath("./lib/ce/*"); + String driverPath = props.value(ProcessProperties.JDBC_DRIVER_PATH); + if (driverPath != null) { + command.addClasspath(driverPath); + } + return command; + } + + private static JavaCommand newJavaCommand(ProcessId id, Props props, File workDir) { + JavaCommand command = new JavaCommand(id) + .setWorkDir(workDir) + .setArguments(props.rawProperties()); + + for (String key : PROXY_PROPERTY_KEYS) { + if (props.contains(key)) { + command.addJavaOption("-D" + key + "=" + props.value(key)); + } + } + // defaults of HTTPS are the same than HTTP defaults + setSystemPropertyToDefaultIfNotSet(command, props, HTTPS_PROXY_HOST, HTTP_PROXY_HOST); + setSystemPropertyToDefaultIfNotSet(command, props, HTTPS_PROXY_PORT, HTTP_PROXY_PORT); + return command; + } + + private static void setSystemPropertyToDefaultIfNotSet(JavaCommand command, Props props, String httpsProperty, String httpProperty) { + if (!props.contains(httpsProperty) && props.contains(httpProperty)) { + command.addJavaOption("-D" + httpsProperty + "=" + props.value(httpProperty)); + } + } +} diff --git a/sonar-application/src/test/java/org/sonar/application/AppTest.java b/sonar-application/src/test/java/org/sonar/application/AppTest.java index e335cbd9c9d..70bec0bb1d1 100644 --- a/sonar-application/src/test/java/org/sonar/application/AppTest.java +++ b/sonar-application/src/test/java/org/sonar/application/AppTest.java @@ -29,7 +29,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.mockito.ArgumentCaptor; -import org.sonar.process.ProcessId; import org.sonar.process.ProcessProperties; import org.sonar.process.Props; import org.sonar.process.monitor.JavaCommand; @@ -44,6 +43,27 @@ public class AppTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); + private final JavaCommand esCommand = mock(JavaCommand.class); + private final JavaCommand webCommand = mock(JavaCommand.class); + private final JavaCommand ceCommand = mock(JavaCommand.class); + private final JavaCommandFactory javaCommandFactory = new JavaCommandFactory() { + + @Override + public JavaCommand createESCommand(Props props, File homeDir) { + return AppTest.this.esCommand; + } + + @Override + public JavaCommand createWebCommand(Props props, File homeDir) { + return AppTest.this.webCommand; + } + + @Override + public JavaCommand createCeCommand(Props props, File homeDir) { + return AppTest.this.ceCommand; + } + }; + @Test public void starPath() throws IOException { File homeDir = temp.newFolder(); @@ -57,11 +77,13 @@ public class AppTest { public void start_all_processes_if_cluster_mode_is_disabled() throws Exception { Props props = initDefaultProps(); Monitor monitor = mock(Monitor.class); - App app = new App(monitor); + App app = new App(monitor, javaCommandFactory); app.start(props); + ArgumentCaptor>> argument = newJavaCommandArgumentCaptor(); verify(monitor).start(argument.capture()); - assertThat(argument.getValue().get()).extracting("processId").containsExactly(ProcessId.ELASTICSEARCH, ProcessId.WEB_SERVER, ProcessId.COMPUTE_ENGINE); + assertThat(argument.getValue().get()) + .containsExactly(esCommand, webCommand, ceCommand); app.stopAsync(); verify(monitor).stop(); @@ -76,7 +98,7 @@ public class AppTest { List commands = start(props); - assertThat(commands).extracting("processId").containsOnly(ProcessId.WEB_SERVER); + assertThat(commands).containsOnly(webCommand); } @Test @@ -88,7 +110,7 @@ public class AppTest { List commands = start(props); - assertThat(commands).extracting("processId").containsOnly(ProcessId.COMPUTE_ENGINE); + assertThat(commands).contains(ceCommand); } @Test @@ -100,68 +122,7 @@ public class AppTest { List commands = start(props); - assertThat(commands).extracting("processId").containsOnly(ProcessId.ELASTICSEARCH); - } - - @Test - public void add_custom_jdbc_driver_to_tomcat_classpath() throws Exception { - Props props = initDefaultProps(); - props.set("sonar.jdbc.driverPath", "oracle/ojdbc6.jar"); - - List commands = start(props); - - assertThat(commands.get(1).getClasspath()).contains("oracle/ojdbc6.jar"); - } - - @Test - public void configure_http_and_https_proxies_on_all_processes() throws Exception { - Props props = initDefaultProps(); - // These properties can be defined in conf/sonar.properties. - // They must be propagated to JVM. - props.set("http.proxyHost", "1.2.3.4"); - props.set("http.proxyPort", "80"); - props.set("https.proxyHost", "5.6.7.8"); - props.set("https.proxyPort", "443"); - - List commands = start(props); - assertThat(commands).isNotEmpty(); - - for (JavaCommand command : commands) { - assertThat(command.getJavaOptions()).contains("-Dhttp.proxyHost=1.2.3.4"); - assertThat(command.getJavaOptions()).contains("-Dhttp.proxyPort=80"); - assertThat(command.getJavaOptions()).contains("-Dhttps.proxyHost=5.6.7.8"); - assertThat(command.getJavaOptions()).contains("-Dhttps.proxyPort=443"); - } - } - - @Test - public void https_proxy_defaults_are_http_proxy_properties() throws Exception { - Props props = initDefaultProps(); - props.set("http.proxyHost", "1.2.3.4"); - props.set("http.proxyPort", "80"); - - List commands = start(props); - assertThat(commands).isNotEmpty(); - - for (JavaCommand command : commands) { - assertThat(command.getJavaOptions()).contains("-Dhttp.proxyHost=1.2.3.4"); - assertThat(command.getJavaOptions()).contains("-Dhttp.proxyPort=80"); - assertThat(command.getJavaOptions()).contains("-Dhttps.proxyHost=1.2.3.4"); - assertThat(command.getJavaOptions()).contains("-Dhttps.proxyPort=80"); - } - } - - @Test - public void no_http_proxy_settings_by_default() throws Exception { - List commands = start(initDefaultProps()); - - assertThat(commands).isNotEmpty(); - for (JavaCommand command : commands) { - assertThat(command.getJavaOptions()).doesNotContain("http.proxyHost"); - assertThat(command.getJavaOptions()).doesNotContain("https.proxyHost"); - assertThat(command.getJavaOptions()).doesNotContain("http.proxyPort"); - assertThat(command.getJavaOptions()).doesNotContain("https.proxyPort"); - } + assertThat(commands).containsOnly(esCommand); } private Props initDefaultProps() throws IOException { @@ -175,7 +136,7 @@ public class AppTest { private List start(Props props) throws Exception { Monitor monitor = mock(Monitor.class); - App app = new App(monitor); + App app = new App(monitor, javaCommandFactory); app.start(props); ArgumentCaptor>> argument = newJavaCommandArgumentCaptor(); verify(monitor).start(argument.capture()); @@ -186,4 +147,5 @@ public class AppTest { Class>> listClass = (Class>>) (Class) List.class; return ArgumentCaptor.forClass(listClass); } + } diff --git a/sonar-application/src/test/java/org/sonar/application/JavaCommandFactoryImplTest.java b/sonar-application/src/test/java/org/sonar/application/JavaCommandFactoryImplTest.java new file mode 100644 index 00000000000..fc3bb457846 --- /dev/null +++ b/sonar-application/src/test/java/org/sonar/application/JavaCommandFactoryImplTest.java @@ -0,0 +1,407 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.application; + +import java.io.File; +import java.util.Map; +import java.util.Properties; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.process.ProcessId; +import org.sonar.process.Props; +import org.sonar.process.monitor.JavaCommand; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +public class JavaCommandFactoryImplTest { + private static final String SEARCH_JAVA_OPTS = "sonar.search.javaOpts"; + private static final String SEARCH_JAVA_ADDITIONAL_OPTS = "sonar.search.javaAdditionalOpts"; + private static final String WEB_JAVA_OPTS = "sonar.web.javaOpts"; + private static final String WEB_JAVA_ADDITIONAL_OPTS = "sonar.web.javaAdditionalOpts"; + private static final String CE_JAVA_OPTS = "sonar.ce.javaOpts"; + private static final String CE_JAVA_ADDITIONAL_OPTS = "sonar.ce.javaAdditionalOpts"; + private static final String PATH_LOGS = "sonar.path.logs"; + private static final String JDBC_DRIVER_PATH = "sonar.jdbc.driverPath"; + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private File homeDir; + private JavaCommandFactoryImpl underTest = new JavaCommandFactoryImpl(); + + @Before + public void setUp() throws Exception { + homeDir = temp.newFolder(); + } + + @Test + public void createEsCommand_fails_if_search_javaOpts_property_is_not_set() { + expectMissingPropertyIAE(SEARCH_JAVA_OPTS); + + underTest.createESCommand(newProps(), homeDir); + } + + @Test + public void createEsCommand_fails_if_search_javaAdditionalOpts_property_is_not_set() { + expectMissingPropertyIAE(SEARCH_JAVA_ADDITIONAL_OPTS); + + underTest.createESCommand(newProps(SEARCH_JAVA_OPTS, "foo"), homeDir); + } + + @Test + public void createEsCommand_sets_SearchServer_for_className() { + JavaCommand javaCommand = underTest.createESCommand(newPropsWithRequiredProperties(), homeDir); + + assertThat(javaCommand.getClassName()).isEqualTo("org.sonar.search.SearchServer"); + } + + @Test + public void createESCommand_puts_common_and_search_lib_directories_in_classpath() { + JavaCommand javaCommand = underTest.createESCommand(newPropsWithRequiredProperties(), homeDir); + + assertThat(javaCommand.getClasspath()).containsOnly("./lib/common/*", "./lib/search/*"); + } + + @Test + public void createESCommand_adds_headless_java_option() { + JavaCommand javaCommand = underTest.createESCommand(newPropsWithRequiredProperties(), homeDir); + + assertThat(javaCommand.getJavaOptions()).contains("-Djava.awt.headless=true"); + } + + @Test + public void createESCommand_adds_search_javaOpts_and_javaAdditionalOpts_java_options() { + JavaCommand javaCommand = underTest.createESCommand(newPropsWithRequiredProperties(), homeDir); + + assertThat(javaCommand.getJavaOptions()).contains(mockValueFor(SEARCH_JAVA_OPTS), mockValueFor(SEARCH_JAVA_ADDITIONAL_OPTS)); + } + + @Test + public void createESCommand_sets_ES_processId() { + JavaCommand javaCommand = underTest.createESCommand(newPropsWithRequiredProperties(), homeDir); + + assertThat(javaCommand.getProcessId()).isSameAs(ProcessId.ELASTICSEARCH); + } + + @Test + public void createESCommand_sets_workdir_to_argument() { + JavaCommand javaCommand = underTest.createESCommand(newPropsWithRequiredProperties(), homeDir); + + assertThat(javaCommand.getWorkDir()).isSameAs(homeDir); + } + + @Test + public void createESCommand_add_options_for_http_and_https_proxies_from_props() throws Exception { + addOptionsForHttpAndHttpsProxiesFromProps((props, fileDir) -> underTest.createESCommand(props, fileDir)); + } + + @Test + public void createESCommand_use_http_properties_from_props_as_defaults_for_https_properties() throws Exception { + userHttpPropertiesFromPropsAsDefaultForHttpsProperties((props, file) -> underTest.createESCommand(props, file)); + } + + @Test + public void createEsCommand_add_no_proxy_option_if_no_proxy_property_in_props() throws Exception { + noProxyOptionIfNoProxyPropertyInProps((props, file) -> underTest.createESCommand(props, file)); + } + + @Test + public void createEsCommand_passes_rawProperties_of_Props_argument_as_argument_of_javaCommand() { + passesRawPropertiesOfPropsAsArgumentsOfJavaCommand((props, fileDir) -> underTest.createESCommand(props, fileDir)); + } + + @Test + public void createWebCommand_fails_if_web_javaOpts_property_is_not_set() { + expectMissingPropertyIAE(WEB_JAVA_OPTS); + + underTest.createWebCommand(newProps(), homeDir); + } + + @Test + public void createWebCommand_fails_if_web_javaAdditionalOpts_property_is_not_set() { + expectMissingPropertyIAE(WEB_JAVA_ADDITIONAL_OPTS); + + underTest.createWebCommand(newProps(WEB_JAVA_OPTS, "foo"), homeDir); + } + + @Test + public void createWebCommand_fails_if_log_dir_path_property_is_not_set() { + expectMissingPropertyIAE(PATH_LOGS); + + underTest.createWebCommand(newProps(WEB_JAVA_OPTS, "foo", WEB_JAVA_ADDITIONAL_OPTS, "bar"), homeDir); + } + + @Test + public void createWebCommand_sets_SearchServer_for_className() { + JavaCommand javaCommand = underTest.createWebCommand(newPropsWithRequiredProperties(), homeDir); + + assertThat(javaCommand.getClassName()).isEqualTo("org.sonar.server.app.WebServer"); + } + + @Test + public void createWebCommand_puts_common_and_server_lib_directories_in_classpath() { + JavaCommand javaCommand = underTest.createWebCommand(newPropsWithRequiredProperties(), homeDir); + + assertThat(javaCommand.getClasspath()).containsOnly("./lib/common/*", "./lib/server/*"); + } + + @Test + public void createWebCommand_adds_jdbc_driver_to_classpath_if_property_is_set_in_props() { + JavaCommand javaCommand = underTest.createWebCommand(newPropsWithRequiredProperties(JDBC_DRIVER_PATH, "foo"), homeDir); + + assertThat(javaCommand.getClasspath()).contains("foo"); + } + + @Test + public void createWebCommand_set_env_variable_for_path_to_log_dir() { + JavaCommand javaCommand = underTest.createWebCommand(newPropsWithRequiredProperties(), homeDir); + + assertThat(javaCommand.getEnvVariables()).contains(entry("sonar.path.logs", mockValueFor(PATH_LOGS))); + } + + @Test + public void createWebCommand_adds_search_javaOpts_and_javaAdditionalOpts_java_options() { + JavaCommand javaCommand = underTest.createWebCommand(newPropsWithRequiredProperties(), homeDir); + + assertThat(javaCommand.getJavaOptions()).contains(mockValueFor(WEB_JAVA_OPTS), mockValueFor(WEB_JAVA_ADDITIONAL_OPTS)); + } + + @Test + public void createWebCommand_sets_headless_and_encoding_java_options() { + JavaCommand javaCommand = underTest.createWebCommand(newPropsWithRequiredProperties(), homeDir); + + assertThat(javaCommand.getJavaOptions()).contains("-Djava.awt.headless=true", "-Dfile.encoding=UTF-8"); + } + + @Test + public void createWebCommand_sets_WEB_SERVER_processId() { + JavaCommand javaCommand = underTest.createWebCommand(newPropsWithRequiredProperties(), homeDir); + + assertThat(javaCommand.getProcessId()).isSameAs(ProcessId.WEB_SERVER); + } + + @Test + public void createWebCommand_sets_workdir_to_argument() { + JavaCommand javaCommand = underTest.createWebCommand(newPropsWithRequiredProperties(), homeDir); + + assertThat(javaCommand.getWorkDir()).isSameAs(homeDir); + } + + @Test + public void createWebCommand_add_options_fore_http_and_https_proxies_from_props() throws Exception { + addOptionsForHttpAndHttpsProxiesFromProps((props, fileDir) -> underTest.createWebCommand(props, fileDir)); + } + + @Test + public void createWebCommand_use_http_properties_from_props_as_defaults_for_https_properties() throws Exception { + userHttpPropertiesFromPropsAsDefaultForHttpsProperties((props, file) -> underTest.createWebCommand(props, file)); + } + + @Test + public void createWebCommand_add_no_proxy_option_if_no_proxy_property_in_props() throws Exception { + noProxyOptionIfNoProxyPropertyInProps((props, file) -> underTest.createWebCommand(props, file)); + } + + @Test + public void createWebCommand_passes_rawProperties_of_Props_argument_as_argument_of_javaCommand() { + passesRawPropertiesOfPropsAsArgumentsOfJavaCommand((props, fileDir) -> underTest.createWebCommand(props, fileDir)); + } + + @Test + public void createCeCommand_fails_if_web_javaOpts_property_is_not_set() { + expectMissingPropertyIAE(CE_JAVA_OPTS); + + underTest.createCeCommand(newProps(), homeDir); + } + + @Test + public void createCeCommand_fails_if_web_javaAdditionalOpts_property_is_not_set() { + expectMissingPropertyIAE(CE_JAVA_ADDITIONAL_OPTS); + + underTest.createCeCommand(newProps(CE_JAVA_OPTS, "foo"), homeDir); + } + + @Test + public void createCeCommand_sets_SearchServer_for_className() { + JavaCommand javaCommand = underTest.createCeCommand(newPropsWithRequiredProperties(), homeDir); + + assertThat(javaCommand.getClassName()).isEqualTo("org.sonar.ce.app.CeServer"); + } + + @Test + public void createCeCommand_puts_common_server_and_ce_lib_directories_in_classpath() { + JavaCommand javaCommand = underTest.createCeCommand(newPropsWithRequiredProperties(), homeDir); + + assertThat(javaCommand.getClasspath()).containsOnly("./lib/common/*", "./lib/server/*", "./lib/ce/*"); + } + + @Test + public void createCeCommand_adds_jdbc_driver_to_classpath_if_property_is_set_in_props() { + JavaCommand javaCommand = underTest.createCeCommand(newPropsWithRequiredProperties(JDBC_DRIVER_PATH, "foo"), homeDir); + + assertThat(javaCommand.getClasspath()).contains("foo"); + } + + @Test + public void createCeCommand_sets_headless_and_encoding_java_options() { + JavaCommand javaCommand = underTest.createCeCommand(newPropsWithRequiredProperties(), homeDir); + + assertThat(javaCommand.getJavaOptions()).contains("-Djava.awt.headless=true", "-Dfile.encoding=UTF-8"); + } + + @Test + public void createCeCommand_adds_search_javaOpts_and_javaAdditionalOpts_java_options() { + JavaCommand javaCommand = underTest.createCeCommand(newPropsWithRequiredProperties(), homeDir); + + assertThat(javaCommand.getJavaOptions()).contains(mockValueFor(CE_JAVA_OPTS), mockValueFor(CE_JAVA_ADDITIONAL_OPTS)); + } + + @Test + public void createCeCommand_sets_workdir_to_argument() { + JavaCommand javaCommand = underTest.createCeCommand(newPropsWithRequiredProperties(), homeDir); + + assertThat(javaCommand.getWorkDir()).isSameAs(homeDir); + } + + @Test + public void createCeCommand_sets_COMPUTE_ENGINE_processId() { + JavaCommand javaCommand = underTest.createCeCommand(newPropsWithRequiredProperties(), homeDir); + + assertThat(javaCommand.getProcessId()).isSameAs(ProcessId.COMPUTE_ENGINE); + } + + @Test + public void createCeCommand_add_options_for_http_and_https_proxies_from_props() throws Exception { + addOptionsForHttpAndHttpsProxiesFromProps((props, fileDir) -> underTest.createCeCommand(props, fileDir)); + } + + @Test + public void createCeCommand_use_http_properties_from_props_as_defaults_for_https_properties() throws Exception { + userHttpPropertiesFromPropsAsDefaultForHttpsProperties((props, file) -> underTest.createCeCommand(props, file)); + } + + @Test + public void createCeCommand_passes_rawProperties_of_Props_argument_as_argument_of_javaCommand() { + passesRawPropertiesOfPropsAsArgumentsOfJavaCommand((props, fileDir) -> underTest.createCeCommand(props, fileDir)); + } + + private void addOptionsForHttpAndHttpsProxiesFromProps(BiFunction callCreateMethod) { + Props props = newPropsWithRequiredProperties(); + + // These properties can be defined in conf/sonar.properties. + // They must be propagated to JVM. + props.set("http.proxyHost", "1.2.3.4"); + props.set("http.proxyPort", "80"); + props.set("https.proxyHost", "5.6.7.8"); + props.set("https.proxyPort", "443"); + + JavaCommand command = callCreateMethod.apply(props, homeDir); + assertThat(command.getJavaOptions()).contains("-Dhttp.proxyHost=1.2.3.4"); + assertThat(command.getJavaOptions()).contains("-Dhttp.proxyPort=80"); + assertThat(command.getJavaOptions()).contains("-Dhttps.proxyHost=5.6.7.8"); + assertThat(command.getJavaOptions()).contains("-Dhttps.proxyPort=443"); + } + + @Test + public void createCeCommand_add_no_proxy_option_if_no_proxy_property_in_props() throws Exception { + noProxyOptionIfNoProxyPropertyInProps((props, file) -> underTest.createCeCommand(props, file)); + } + + private void userHttpPropertiesFromPropsAsDefaultForHttpsProperties(BiFunction callCreateMethod) { + Props props = newPropsWithRequiredProperties(); + props.set("http.proxyHost", "1.2.3.4"); + props.set("http.proxyPort", "80"); + + JavaCommand command = callCreateMethod.apply(props, homeDir); + assertThat(command.getJavaOptions()).contains("-Dhttp.proxyHost=1.2.3.4"); + assertThat(command.getJavaOptions()).contains("-Dhttp.proxyPort=80"); + assertThat(command.getJavaOptions()).contains("-Dhttps.proxyHost=1.2.3.4"); + assertThat(command.getJavaOptions()).contains("-Dhttps.proxyPort=80"); + } + + private void passesRawPropertiesOfPropsAsArgumentsOfJavaCommand(BiFunction callCreateMethod) { + Props props = newPropsWithRequiredProperties("cryptedProperty", "{AES}AAAAA"); + JavaCommand javaCommand = callCreateMethod.apply(props, homeDir); + + Map rawProperties = (Map) ((Map) props.rawProperties()); + assertThat(javaCommand.getArguments()).containsAllEntriesOf(rawProperties); + } + + private void noProxyOptionIfNoProxyPropertyInProps(BiFunction callCreateMethod) { + JavaCommand command = callCreateMethod.apply(newPropsWithRequiredProperties(), homeDir); + + assertThat(command.getJavaOptions()).doesNotContain("http.proxyHost"); + assertThat(command.getJavaOptions()).doesNotContain("https.proxyHost"); + assertThat(command.getJavaOptions()).doesNotContain("http.proxyPort"); + assertThat(command.getJavaOptions()).doesNotContain("https.proxyPort"); + } + + private void expectMissingPropertyIAE(String property) { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Missing property: " + property); + } + + private Props newPropsWithRequiredProperties(String... properties) { + return newProps( + (props) -> addProperties(props, properties), + SEARCH_JAVA_OPTS, mockValueFor(SEARCH_JAVA_OPTS), + SEARCH_JAVA_ADDITIONAL_OPTS, mockValueFor(SEARCH_JAVA_ADDITIONAL_OPTS), + WEB_JAVA_OPTS, mockValueFor(WEB_JAVA_OPTS), + WEB_JAVA_ADDITIONAL_OPTS, mockValueFor(WEB_JAVA_ADDITIONAL_OPTS), + PATH_LOGS, mockValueFor(PATH_LOGS), + CE_JAVA_OPTS, mockValueFor(CE_JAVA_OPTS), + CE_JAVA_ADDITIONAL_OPTS, mockValueFor(CE_JAVA_ADDITIONAL_OPTS)); + } + + private static String mockValueFor(String str) { + return str + "_value"; + } + + private Props newProps(String... properties) { + return newProps((props) -> { + }, properties); + } + + private Props newProps(Consumer extraConf, String... properties) { + Properties props = new Properties(); + addProperties(props, properties); + extraConf.accept(props); + return new Props(props); + } + + private void addProperties(Properties props, String[] properties) { + if (properties.length % 2 != 0) { + throw new IllegalArgumentException("Properties must all have key and value"); + } + for (int i = 0; i < properties.length; i++) { + props.setProperty(properties[i], properties[i + 1]); + i++; + } + } + +} -- 2.39.5