Browse Source

Improve test coverage

tags/9.0.0.45539
Duarte Meneses 3 years ago
parent
commit
f9d6d236fe

+ 1
- 0
server/sonar-ce/build.gradle View File

@@ -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'

+ 51
- 0
server/sonar-ce/src/main/java/org/sonar/ce/app/CeSecurityManager.java View File

@@ -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);
}
}

+ 5
- 13
server/sonar-ce/src/main/java/org/sonar/ce/app/CeServer.java View File

@@ -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);
}


+ 61
- 0
server/sonar-ce/src/test/java/org/sonar/ce/app/CeSecurityManagerTest.java View File

@@ -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);
}
}

+ 65
- 43
server/sonar-ce/src/test/java/org/sonar/ce/app/CeServerTest.java View File

@@ -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
}
}
}

+ 7
- 7
server/sonar-process/src/main/java/org/sonar/process/PluginSecurityManager.java View File

@@ -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) {

+ 10
- 0
server/sonar-process/src/test/java/org/sonar/process/PluginSecurityManagerTest.java View File

@@ -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));

+ 49
- 0
server/sonar-webserver/src/main/java/org/sonar/server/app/WebSecurityManager.java View File

@@ -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);
}
}

+ 1
- 8
server/sonar-webserver/src/main/java/org/sonar/server/app/WebServer.java View File

@@ -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);

+ 60
- 0
server/sonar-webserver/src/test/java/org/sonar/server/app/WebSecurityManagerTest.java View File

@@ -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);
}
}

+ 37
- 0
server/sonar-webserver/src/test/java/org/sonar/server/app/WebServerTest.java View File

@@ -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);
}
}

Loading…
Cancel
Save