@@ -32,6 +32,7 @@ dependencies { | |||
testCompile 'com.tngtech.java:junit-dataprovider' | |||
testCompile 'junit:junit' | |||
testCompile 'org.assertj:assertj-core' | |||
testCompile 'org.awaitility:awaitility' | |||
testCompile 'org.mockito:mockito-core' | |||
testCompile 'org.slf4j:slf4j-api' | |||
@@ -0,0 +1,51 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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.ce.app; | |||
import org.sonar.ce.security.PluginCeRule; | |||
import org.sonar.process.PluginFileWriteRule; | |||
import org.sonar.process.PluginSecurityManager; | |||
import org.sonar.process.ProcessProperties; | |||
import org.sonar.process.Props; | |||
public class CeSecurityManager { | |||
private final PluginSecurityManager pluginSecurityManager; | |||
private final Props props; | |||
private boolean applied; | |||
public CeSecurityManager(PluginSecurityManager pluginSecurityManager, Props props) { | |||
this.pluginSecurityManager = pluginSecurityManager; | |||
this.props = props; | |||
} | |||
public void apply() { | |||
if (applied) { | |||
throw new IllegalStateException("can't apply twice"); | |||
} | |||
applied = true; | |||
PluginFileWriteRule writeRule = new PluginFileWriteRule( | |||
props.nonNullValueAsFile(ProcessProperties.Property.PATH_HOME.getKey()).toPath(), | |||
props.nonNullValueAsFile(ProcessProperties.Property.PATH_TEMP.getKey()).toPath()); | |||
PluginCeRule ceRule = new PluginCeRule(); | |||
pluginSecurityManager.restrictPlugins(writeRule, ceRule); | |||
} | |||
} |
@@ -29,13 +29,10 @@ import org.sonar.ce.ComputeEngine; | |||
import org.sonar.ce.ComputeEngineImpl; | |||
import org.sonar.ce.container.ComputeEngineContainerImpl; | |||
import org.sonar.ce.logging.CeProcessLogging; | |||
import org.sonar.ce.security.PluginCeRule; | |||
import org.sonar.process.MinimumViableSystem; | |||
import org.sonar.process.Monitored; | |||
import org.sonar.process.PluginFileWriteRule; | |||
import org.sonar.process.PluginSecurityManager; | |||
import org.sonar.process.ProcessEntryPoint; | |||
import org.sonar.process.ProcessProperties; | |||
import org.sonar.process.Props; | |||
import static com.google.common.base.Preconditions.checkState; | |||
@@ -52,14 +49,14 @@ public class CeServer implements Monitored { | |||
private static final String CE_MAIN_THREAD_NAME = "ce-main"; | |||
private CountDownLatch awaitStop = new CountDownLatch(1); | |||
private final CountDownLatch awaitStop = new CountDownLatch(1); | |||
private final ComputeEngine computeEngine; | |||
@Nullable | |||
private CeMainThread ceMainThread = null; | |||
@VisibleForTesting | |||
protected CeServer(ComputeEngine computeEngine, MinimumViableSystem mvs) { | |||
protected CeServer(ComputeEngine computeEngine, MinimumViableSystem mvs, CeSecurityManager securityManager) { | |||
securityManager.apply(); | |||
this.computeEngine = computeEngine; | |||
mvs | |||
.checkWritableTempDir() | |||
@@ -121,15 +118,10 @@ public class CeServer implements Monitored { | |||
Props props = entryPoint.getProps(); | |||
new CeProcessLogging().configure(props); | |||
PluginFileWriteRule writeRule = new PluginFileWriteRule( | |||
props.nonNullValueAsFile(ProcessProperties.Property.PATH_HOME.getKey()).toPath(), | |||
props.nonNullValueAsFile(ProcessProperties.Property.PATH_TEMP.getKey()).toPath()); | |||
PluginCeRule ceRule = new PluginCeRule(); | |||
PluginSecurityManager.restrictPlugins(writeRule, ceRule); | |||
CeServer server = new CeServer( | |||
new ComputeEngineImpl(props, new ComputeEngineContainerImpl()), | |||
new MinimumViableSystem()); | |||
new MinimumViableSystem(), | |||
new CeSecurityManager(new PluginSecurityManager(), props)); | |||
entryPoint.launch(server); | |||
} | |||
@@ -0,0 +1,61 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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.ce.app; | |||
import java.util.Properties; | |||
import org.junit.Test; | |||
import org.sonar.ce.security.PluginCeRule; | |||
import org.sonar.process.PluginFileWriteRule; | |||
import org.sonar.process.PluginSecurityManager; | |||
import org.sonar.process.Props; | |||
import static org.junit.Assert.assertThrows; | |||
import static org.mockito.ArgumentMatchers.any; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.verify; | |||
import static org.sonar.process.ProcessProperties.Property.PATH_HOME; | |||
import static org.sonar.process.ProcessProperties.Property.PATH_TEMP; | |||
public class CeSecurityManagerTest { | |||
private final PluginSecurityManager pluginSecurityManager = mock(PluginSecurityManager.class); | |||
@Test | |||
public void apply_calls_PluginSecurityManager() { | |||
Properties properties = new Properties(); | |||
properties.setProperty(PATH_HOME.getKey(), "home"); | |||
properties.setProperty(PATH_TEMP.getKey(), "temp"); | |||
Props props = new Props(properties); | |||
CeSecurityManager ceSecurityManager = new CeSecurityManager(pluginSecurityManager, props); | |||
ceSecurityManager.apply(); | |||
verify(pluginSecurityManager).restrictPlugins(any(PluginFileWriteRule.class), any(PluginCeRule.class)); | |||
} | |||
@Test | |||
public void fail_if_runs_twice() { | |||
Properties properties = new Properties(); | |||
properties.setProperty(PATH_HOME.getKey(), "home"); | |||
properties.setProperty(PATH_TEMP.getKey(), "temp"); | |||
Props props = new Props(properties); | |||
CeSecurityManager ceSecurityManager = new CeSecurityManager(pluginSecurityManager, props); | |||
ceSecurityManager.apply(); | |||
assertThrows(IllegalStateException.class, ceSecurityManager::apply); | |||
} | |||
} |
@@ -19,7 +19,11 @@ | |||
*/ | |||
package org.sonar.ce.app; | |||
import com.tngtech.java.junit.dataprovider.DataProvider; | |||
import com.tngtech.java.junit.dataprovider.DataProviderRunner; | |||
import com.tngtech.java.junit.dataprovider.UseDataProvider; | |||
import java.util.concurrent.CountDownLatch; | |||
import java.util.concurrent.TimeUnit; | |||
import javax.annotation.CheckForNull; | |||
import javax.annotation.Nullable; | |||
import org.junit.After; | |||
@@ -29,16 +33,21 @@ import org.junit.rules.DisableOnDebug; | |||
import org.junit.rules.ExpectedException; | |||
import org.junit.rules.TestRule; | |||
import org.junit.rules.Timeout; | |||
import org.junit.runner.RunWith; | |||
import org.mockito.Mockito; | |||
import org.sonar.ce.ComputeEngine; | |||
import org.sonar.process.MessageException; | |||
import org.sonar.process.MinimumViableSystem; | |||
import org.sonar.process.Monitored; | |||
import static com.google.common.base.Preconditions.checkState; | |||
import static java.util.concurrent.TimeUnit.MILLISECONDS; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.awaitility.Awaitility.await; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.verify; | |||
@RunWith(DataProviderRunner.class) | |||
public class CeServerTest { | |||
@Rule | |||
public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60)); | |||
@@ -48,6 +57,7 @@ public class CeServerTest { | |||
private CeServer underTest = null; | |||
private Thread waitingThread = null; | |||
private MinimumViableSystem minimumViableSystem = mock(MinimumViableSystem.class, Mockito.RETURNS_MOCKS); | |||
private CeSecurityManager ceSecurityManager = mock(CeSecurityManager.class); | |||
@After | |||
public void tearDown() throws Exception { | |||
@@ -64,12 +74,16 @@ public class CeServerTest { | |||
@Test | |||
public void constructor_does_not_start_a_new_Thread() { | |||
int activeCount = Thread.activeCount(); | |||
newCeServer(); | |||
assertThat(Thread.activeCount()).isSameAs(activeCount); | |||
} | |||
@Test | |||
public void constructor_calls_ceSecurityManager() { | |||
newCeServer(); | |||
verify(ceSecurityManager).apply(); | |||
} | |||
@Test | |||
public void awaitStop_throws_ISE_if_called_before_start() { | |||
CeServer ceServer = newCeServer(); | |||
@@ -89,6 +103,31 @@ public class CeServerTest { | |||
assertThat(Thread.activeCount()).isSameAs(activeCount + 1); | |||
} | |||
@Test | |||
public void stop_stops_Thread() { | |||
CeServer ceServer = newCeServer(); | |||
assertThat(ceThreadExists()).isFalse(); | |||
ceServer.start(); | |||
assertThat(ceThreadExists()).isTrue(); | |||
ceServer.stop(); | |||
await().atMost(5, TimeUnit.SECONDS).until(() -> !ceThreadExists()); | |||
} | |||
@Test | |||
public void stop_dontDoAnythingIfThreadDoesntExist() { | |||
CeServer ceServer = newCeServer(); | |||
assertThat(ceThreadExists()).isFalse(); | |||
ceServer.stop(); | |||
//expect no exception and thread still doesn't exist | |||
assertThat(ceThreadExists()).isFalse(); | |||
} | |||
private static boolean ceThreadExists() { | |||
return Thread.getAllStackTraces().keySet().stream().anyMatch(t -> t.getName().equals("ce-main")); | |||
} | |||
@Test | |||
public void start_throws_ISE_when_called_twice() { | |||
CeServer ceServer = newCeServer(); | |||
@@ -123,17 +162,13 @@ public class CeServerTest { | |||
// release ComputeEngine startup method | |||
computeEngine.releaseStartup(); | |||
while (ceServer.getStatus() == Monitored.Status.DOWN) { | |||
// wait for isReady to change to true, otherwise test will fail with timeout | |||
} | |||
assertThat(ceServer.getStatus()).isEqualTo(Monitored.Status.OPERATIONAL); | |||
await().atMost(5, TimeUnit.SECONDS).until(() -> ceServer.getStatus() == Monitored.Status.OPERATIONAL); | |||
} | |||
@Test | |||
public void getStatus_returns_OPERATIONAL_when_ComputeEngine_startup_throws_any_Exception_or_Error() { | |||
Throwable startupException = new Throwable("Faking failing ComputeEngine#startup()"); | |||
BlockingStartupComputeEngine computeEngine = new BlockingStartupComputeEngine(startupException); | |||
@UseDataProvider("exceptions") | |||
public void getStatus_returns_FAILED_when_ComputeEngine_startup_throws_any_Exception_or_Error(RuntimeException exception) { | |||
BlockingStartupComputeEngine computeEngine = new BlockingStartupComputeEngine(exception); | |||
CeServer ceServer = newCeServer(computeEngine); | |||
ceServer.start(); | |||
@@ -143,10 +178,12 @@ public class CeServerTest { | |||
// release ComputeEngine startup method which will throw startupException | |||
computeEngine.releaseStartup(); | |||
while (ceServer.getStatus() == Monitored.Status.DOWN) { | |||
// wait for isReady to change to not DOWN, otherwise test will fail with timeout | |||
} | |||
assertThat(ceServer.getStatus()).isEqualTo(Monitored.Status.OPERATIONAL); | |||
await().atMost(5, TimeUnit.SECONDS).until(() -> ceServer.getStatus() == Monitored.Status.FAILED); | |||
} | |||
@DataProvider | |||
public static Object[] exceptions() { | |||
return new Object[] {new MessageException("exception"), new IllegalStateException("Faking failing ComputeEngine#startup()")}; | |||
} | |||
@Test | |||
@@ -194,6 +231,15 @@ public class CeServerTest { | |||
ceServer.awaitStop(); | |||
} | |||
@Test | |||
public void staticMain_withoutAnyArguments_expectException() { | |||
String[] emptyArray = {}; | |||
expectedException.expectMessage("Only a single command-line argument is accepted (absolute path to configuration file)"); | |||
CeServer.main(emptyArray); | |||
} | |||
@Test | |||
public void stop_releases_thread_in_awaitStop_even_when_ComputeEngine_shutdown_fails() throws InterruptedException { | |||
final CeServer ceServer = newCeServer(new ComputeEngine() { | |||
@@ -223,13 +269,12 @@ public class CeServerTest { | |||
} | |||
private CeServer newCeServer() { | |||
return newCeServer(DoNothingComputeEngine.INSTANCE); | |||
return newCeServer(mock(ComputeEngine.class)); | |||
} | |||
private CeServer newCeServer(ComputeEngine computeEngine) { | |||
checkState(this.underTest == null, "Only one CeServer can be created per test method"); | |||
this.underTest = new CeServer( | |||
computeEngine, minimumViableSystem); | |||
this.underTest = new CeServer(computeEngine, minimumViableSystem, ceSecurityManager); | |||
return underTest; | |||
} | |||
@@ -243,9 +288,9 @@ public class CeServerTest { | |||
private static class BlockingStartupComputeEngine implements ComputeEngine { | |||
private final CountDownLatch latch = new CountDownLatch(1); | |||
@CheckForNull | |||
private final Throwable throwable; | |||
private final RuntimeException throwable; | |||
public BlockingStartupComputeEngine(@Nullable Throwable throwable) { | |||
public BlockingStartupComputeEngine(@Nullable RuntimeException throwable) { | |||
this.throwable = throwable; | |||
} | |||
@@ -257,11 +302,7 @@ public class CeServerTest { | |||
throw new RuntimeException("await failed", e); | |||
} | |||
if (throwable != null) { | |||
if (throwable instanceof Error) { | |||
throw (Error) throwable; | |||
} else if (throwable instanceof RuntimeException) { | |||
throw (RuntimeException) throwable; | |||
} | |||
throw throwable; | |||
} | |||
} | |||
@@ -279,23 +320,4 @@ public class CeServerTest { | |||
this.latch.countDown(); | |||
} | |||
} | |||
private enum DoNothingComputeEngine implements ComputeEngine { | |||
INSTANCE; | |||
@Override | |||
public void startup() { | |||
// do nothing | |||
} | |||
@Override | |||
public void stopProcessing() { | |||
// do nothing | |||
} | |||
@Override | |||
public void shutdown() { | |||
// do nothing | |||
} | |||
} | |||
} |
@@ -26,19 +26,19 @@ import java.security.Permissions; | |||
import java.security.Policy; | |||
import java.security.ProtectionDomain; | |||
import java.security.Security; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
public class PluginSecurityManager { | |||
private static final String CACHE_TTL_KEY = "networkaddress.cache.ttl"; | |||
private boolean alreadyRan = false; | |||
private PluginSecurityManager() { | |||
// static only | |||
} | |||
public static void restrictPlugins(PluginPolicyRule... rules) { | |||
public void restrictPlugins(PluginPolicyRule... rules) { | |||
if (alreadyRan) { | |||
throw new IllegalStateException("can't run twice"); | |||
} | |||
alreadyRan = true; | |||
SecurityManager sm = new SecurityManager(); | |||
Policy.setPolicy(new PluginPolicy(Arrays.asList(rules))); | |||
Policy.setPolicy(new PluginPolicy(List.of(rules))); | |||
System.setSecurityManager(sm); | |||
// SONAR-14870 By default, with a security manager installed, the DNS cache never times out. See InetAddressCachePolicy. | |||
if (Security.getProperty(CACHE_TTL_KEY) == null) { |
@@ -20,6 +20,7 @@ | |||
package org.sonar.process; | |||
import java.security.Permission; | |||
import java.security.Policy; | |||
import java.security.ProtectionDomain; | |||
import java.util.Arrays; | |||
import javax.management.MBeanPermission; | |||
@@ -40,6 +41,15 @@ public class PluginSecurityManagerTest { | |||
private final PluginPolicyRule rule1 = mock(PluginPolicyRule.class); | |||
private final PluginPolicyRule rule2 = mock(PluginPolicyRule.class); | |||
@Test | |||
public void constructor_dontSetAnyPolicy() { | |||
Policy policy = Policy.getPolicy(); | |||
new PluginSecurityManager(); | |||
assertThat(policy).isEqualTo(Policy.getPolicy()); | |||
} | |||
@Test | |||
public void protection_domain_can_have_no_classloader() { | |||
PluginSecurityManager.PluginPolicy policy = new PluginSecurityManager.PluginPolicy(Arrays.asList(rule1, rule2)); |
@@ -0,0 +1,49 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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.server.app; | |||
import org.sonar.process.PluginFileWriteRule; | |||
import org.sonar.process.PluginSecurityManager; | |||
import org.sonar.process.ProcessProperties; | |||
import org.sonar.process.Props; | |||
public class WebSecurityManager { | |||
private final PluginSecurityManager pluginSecurityManager; | |||
private final Props props; | |||
private boolean applied; | |||
public WebSecurityManager(PluginSecurityManager pluginSecurityManager, Props props) { | |||
this.pluginSecurityManager = pluginSecurityManager; | |||
this.props = props; | |||
} | |||
public void apply() { | |||
if (applied) { | |||
throw new IllegalStateException("can't apply twice"); | |||
} | |||
applied = true; | |||
PluginFileWriteRule writeRule = new PluginFileWriteRule( | |||
props.nonNullValueAsFile(ProcessProperties.Property.PATH_HOME.getKey()).toPath(), | |||
props.nonNullValueAsFile(ProcessProperties.Property.PATH_TEMP.getKey()).toPath()); | |||
pluginSecurityManager.restrictPlugins(writeRule); | |||
} | |||
} |
@@ -24,11 +24,9 @@ import java.io.File; | |||
import org.slf4j.LoggerFactory; | |||
import org.sonar.process.MinimumViableSystem; | |||
import org.sonar.process.Monitored; | |||
import org.sonar.process.PluginFileWriteRule; | |||
import org.sonar.process.PluginSecurityManager; | |||
import org.sonar.process.ProcessEntryPoint; | |||
import org.sonar.process.ProcessId; | |||
import org.sonar.process.ProcessProperties; | |||
import org.sonar.process.Props; | |||
import org.sonar.process.sharedmemoryfile.DefaultProcessCommands; | |||
@@ -98,12 +96,7 @@ public class WebServer implements Monitored { | |||
ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(args); | |||
Props props = entryPoint.getProps(); | |||
new WebServerProcessLogging().configure(props); | |||
PluginFileWriteRule writeRule = new PluginFileWriteRule( | |||
props.nonNullValueAsFile(ProcessProperties.Property.PATH_HOME.getKey()).toPath(), | |||
props.nonNullValueAsFile(ProcessProperties.Property.PATH_TEMP.getKey()).toPath()); | |||
PluginSecurityManager.restrictPlugins(writeRule); | |||
new WebSecurityManager(new PluginSecurityManager(), props).apply(); | |||
WebServer server = new WebServer(props); | |||
entryPoint.launch(server); |
@@ -0,0 +1,60 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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.server.app; | |||
import java.util.Properties; | |||
import org.junit.Test; | |||
import org.sonar.process.PluginFileWriteRule; | |||
import org.sonar.process.PluginSecurityManager; | |||
import org.sonar.process.Props; | |||
import static org.junit.Assert.assertThrows; | |||
import static org.mockito.ArgumentMatchers.any; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.verify; | |||
import static org.sonar.process.ProcessProperties.Property.PATH_HOME; | |||
import static org.sonar.process.ProcessProperties.Property.PATH_TEMP; | |||
public class WebSecurityManagerTest { | |||
private final PluginSecurityManager pluginSecurityManager = mock(PluginSecurityManager.class); | |||
@Test | |||
public void apply_calls_PluginSecurityManager() { | |||
Properties properties = new Properties(); | |||
properties.setProperty(PATH_HOME.getKey(), "home"); | |||
properties.setProperty(PATH_TEMP.getKey(), "temp"); | |||
Props props = new Props(properties); | |||
WebSecurityManager securityManager = new WebSecurityManager(pluginSecurityManager, props); | |||
securityManager.apply(); | |||
verify(pluginSecurityManager).restrictPlugins(any(PluginFileWriteRule.class)); | |||
} | |||
@Test | |||
public void fail_if_runs_twice() { | |||
Properties properties = new Properties(); | |||
properties.setProperty(PATH_HOME.getKey(), "home"); | |||
properties.setProperty(PATH_TEMP.getKey(), "temp"); | |||
Props props = new Props(properties); | |||
WebSecurityManager securityManager = new WebSecurityManager(pluginSecurityManager, props); | |||
securityManager.apply(); | |||
assertThrows(IllegalStateException.class, securityManager::apply); | |||
} | |||
} |
@@ -0,0 +1,37 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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.server.app; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
import org.junit.function.ThrowingRunnable; | |||
public class WebServerTest { | |||
@Test | |||
public void main_givenNoArguments() { | |||
String[] arguments = {}; | |||
ThrowingRunnable runnable = () -> WebServer.main(arguments); | |||
Assert.assertThrows("Only a single command-line argument is accepted (absolute path to configuration file)", | |||
IllegalArgumentException.class, runnable); | |||
} | |||
} |