@@ -1091,9 +1091,9 @@ | |||
<version>2.3.1</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<groupId>org.sonarsource</groupId> | |||
<artifactId>sonar-persistit</artifactId> | |||
<version>3.3.2-SNAPSHOT</version> | |||
<version>3.3.2</version> | |||
<exclusions> | |||
<exclusion> | |||
<groupId>commons-logging</groupId> |
@@ -25,7 +25,7 @@ | |||
<scope>provided</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<groupId>org.sonarsource</groupId> | |||
<artifactId>sonar-persistit</artifactId> | |||
</dependency> | |||
<dependency> |
@@ -29,7 +29,6 @@ import org.sonar.api.Plugin; | |||
import org.sonar.api.utils.Durations; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.api.utils.UriReader; | |||
import org.sonar.api.utils.internal.TempFolderCleaner; | |||
import org.sonar.batch.components.PastSnapshotFinder; | |||
import org.sonar.batch.deprecated.components.PastSnapshotFinderByDate; | |||
import org.sonar.batch.deprecated.components.PastSnapshotFinderByDays; | |||
@@ -67,13 +66,14 @@ import org.sonar.jpa.session.JpaDatabaseSession; | |||
public class GlobalContainer extends ComponentContainer { | |||
private final Map<String, String> bootstrapProperties; | |||
private PersistentCacheProvider persistentCacheProvider; | |||
private GlobalContainer(Map<String, String> bootstrapProperties) { | |||
super(); | |||
this.bootstrapProperties = bootstrapProperties; | |||
} | |||
public static GlobalContainer create(Map<String, String> bootstrapProperties, List extensions) { | |||
public static GlobalContainer create(Map<String, String> bootstrapProperties, List<?> extensions) { | |||
GlobalContainer container = new GlobalContainer(bootstrapProperties); | |||
container.add(extensions); | |||
return container; | |||
@@ -92,6 +92,8 @@ public class GlobalContainer extends ComponentContainer { | |||
} | |||
private void addBootstrapComponents() { | |||
persistentCacheProvider = new PersistentCacheProvider(); | |||
add( | |||
// plugins | |||
BatchPluginRepository.class, | |||
@@ -107,11 +109,10 @@ public class GlobalContainer extends ComponentContainer { | |||
Logback.class, | |||
DefaultServer.class, | |||
new TempFolderProvider(), | |||
TempFolderCleaner.class, | |||
DefaultHttpDownloader.class, | |||
UriReader.class, | |||
new FileCacheProvider(), | |||
new PersistentCacheProvider(), | |||
persistentCacheProvider, | |||
System2.INSTANCE, | |||
DefaultI18n.class, | |||
Durations.class, | |||
@@ -124,7 +125,7 @@ public class GlobalContainer extends ComponentContainer { | |||
addIfMissing(DefaultServerLineHashesLoader.class, ServerLineHashesLoader.class); | |||
} | |||
public void addIfMissing(Object object, Class objectType) { | |||
public void addIfMissing(Object object, Class<?> objectType) { | |||
if (getComponentByType(objectType) == null) { | |||
add(object); | |||
} | |||
@@ -166,7 +167,7 @@ public class GlobalContainer extends ComponentContainer { | |||
public void executeAnalysis(Map<String, String> analysisProperties, Object... components) { | |||
AnalysisProperties props = new AnalysisProperties(analysisProperties, this.getComponentByType(BootstrapProperties.class).property(CoreProperties.ENCRYPTION_SECRET_KEY_PATH)); | |||
persistentCacheProvider.reconfigure(props); | |||
new ProjectScanContainer(this, props, components).execute(); | |||
} | |||
} |
@@ -0,0 +1,85 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.batch.bootstrap; | |||
import org.picocontainer.lifecycle.ReflectionLifecycleStrategy; | |||
import org.picocontainer.monitors.NullComponentMonitor; | |||
import org.picocontainer.LifecycleStrategy; | |||
import org.picocontainer.PicoContainer; | |||
import org.picocontainer.ComponentLifecycle; | |||
import org.picocontainer.injectors.ProviderAdapter; | |||
import org.picocontainer.Startable; | |||
public abstract class LifecycleProviderAdapter extends ProviderAdapter implements Startable, ComponentLifecycle<Object> { | |||
private LifecycleStrategy lifecycleStrategy; | |||
protected Object instance; | |||
public LifecycleProviderAdapter() { | |||
this(new ReflectionLifecycleStrategy(new NullComponentMonitor())); | |||
} | |||
public LifecycleProviderAdapter(LifecycleStrategy lifecycleStrategy) { | |||
this.lifecycleStrategy = lifecycleStrategy; | |||
} | |||
@Override | |||
public final void start() { | |||
if (instance != null) { | |||
lifecycleStrategy.start(instance); | |||
} | |||
} | |||
@Override | |||
public final void stop() { | |||
if (instance != null) { | |||
lifecycleStrategy.stop(instance); | |||
} | |||
} | |||
@Override | |||
public void start(PicoContainer container) { | |||
start(); | |||
started = true; | |||
} | |||
@Override | |||
public void stop(PicoContainer container) { | |||
stop(); | |||
started = false; | |||
} | |||
@Override | |||
public void dispose(PicoContainer container) { | |||
} | |||
@Override | |||
public boolean componentHasLifecycle() { | |||
return true; | |||
} | |||
@Override | |||
public boolean isStarted() { | |||
return started; | |||
} | |||
private boolean started = false; | |||
} |
@@ -20,27 +20,23 @@ | |||
package org.sonar.batch.bootstrap; | |||
import org.sonar.home.log.Slf4jLog; | |||
import org.sonar.home.cache.PersistentCacheBuilder; | |||
import org.picocontainer.injectors.ProviderAdapter; | |||
import java.nio.file.Paths; | |||
import java.util.Map; | |||
import org.sonar.home.cache.PersistentCache; | |||
public class PersistentCacheProvider extends ProviderAdapter { | |||
private PersistentCache cache; | |||
public PersistentCache provide(BootstrapProperties props) { | |||
public PersistentCache provide(UserProperties props) { | |||
if (cache == null) { | |||
PersistentCacheBuilder builder = new PersistentCacheBuilder(); | |||
builder.setLog(new Slf4jLog(PersistentCache.class)); | |||
String enableCache = props.property("sonar.enableHttpCache"); | |||
if (!"true".equals(enableCache)) { | |||
builder.forceUpdate(true); | |||
} | |||
builder.forceUpdate(isForceUpdate(props.properties())); | |||
String home = props.property("sonar.userHome"); | |||
if (home != null) { | |||
@@ -51,4 +47,15 @@ public class PersistentCacheProvider extends ProviderAdapter { | |||
} | |||
return cache; | |||
} | |||
public void reconfigure(UserProperties props) { | |||
if (cache != null) { | |||
cache.reconfigure(isForceUpdate(props.properties())); | |||
} | |||
} | |||
private static boolean isForceUpdate(Map<String, String> props) { | |||
String enableCache = props.get("sonar.enableHttpCache"); | |||
return !"true".equals(enableCache); | |||
} | |||
} |
@@ -19,34 +19,33 @@ | |||
*/ | |||
package org.sonar.batch.bootstrap; | |||
import org.sonar.api.utils.ProjectTempFolder; | |||
import org.apache.commons.io.FileUtils; | |||
import org.sonar.api.utils.TempFolder; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.picocontainer.injectors.ProviderAdapter; | |||
import org.sonar.api.CoreProperties; | |||
import org.sonar.api.utils.internal.DefaultTempFolder; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.nio.file.Files; | |||
import java.nio.file.Path; | |||
import java.nio.file.Paths; | |||
public class ProjectTempFolderProvider extends ProviderAdapter { | |||
public class ProjectTempFolderProvider extends LifecycleProviderAdapter { | |||
static final String TMP_NAME = ".sonartmp"; | |||
private ProjectTempFolder projectTempFolder; | |||
private DefaultTempFolder projectTempFolder; | |||
public ProjectTempFolder provide(BootstrapProperties bootstrapProps) { | |||
public TempFolder provide(BootstrapProperties bootstrapProps) { | |||
if (projectTempFolder == null) { | |||
String workingDirPath = StringUtils.defaultIfBlank(bootstrapProps.property(CoreProperties.WORKING_DIRECTORY), CoreProperties.WORKING_DIRECTORY_DEFAULT_VALUE); | |||
File workingDir = new File(workingDirPath).getAbsoluteFile(); | |||
File tempDir = new File(workingDir, TMP_NAME); | |||
Path workingDir = Paths.get(workingDirPath); | |||
Path tempDir = workingDir.resolve(TMP_NAME).normalize(); | |||
try { | |||
FileUtils.forceMkdir(tempDir); | |||
Files.createDirectories(tempDir); | |||
} catch (IOException e) { | |||
throw new IllegalStateException("Unable to create root temp directory " + tempDir, e); | |||
} | |||
projectTempFolder = new DefaultTempFolder(tempDir, true); | |||
projectTempFolder = new DefaultTempFolder(tempDir.toFile(), true); | |||
this.instance = projectTempFolder; | |||
} | |||
return projectTempFolder; | |||
} | |||
} |
@@ -19,20 +19,28 @@ | |||
*/ | |||
package org.sonar.batch.bootstrap; | |||
import org.sonar.api.utils.log.Logger; | |||
import org.sonar.api.utils.log.Loggers; | |||
import org.apache.commons.io.FileUtils; | |||
import org.sonar.api.utils.TempFolder; | |||
import org.picocontainer.injectors.ProviderAdapter; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.sonar.api.CoreProperties; | |||
import org.sonar.api.utils.internal.DefaultTempFolder; | |||
import java.io.IOException; | |||
import java.nio.file.DirectoryStream; | |||
import java.nio.file.Files; | |||
import java.nio.file.Path; | |||
import java.nio.file.Paths; | |||
import java.nio.file.attribute.BasicFileAttributes; | |||
import java.util.concurrent.TimeUnit; | |||
public class TempFolderProvider extends ProviderAdapter { | |||
static final String TMP_NAME = ".sonartmp"; | |||
private TempFolder tempFolder; | |||
public class TempFolderProvider extends LifecycleProviderAdapter { | |||
private static final Logger LOG = Loggers.get(TempFolderProvider.class); | |||
private static final long CLEAN_MAX_AGE = TimeUnit.DAYS.toMillis(21); | |||
static final String TMP_NAME_PREFIX = ".sonartmp_"; | |||
private DefaultTempFolder tempFolder; | |||
public TempFolder provide(BootstrapProperties bootstrapProps) { | |||
if (tempFolder == null) { | |||
@@ -45,13 +53,20 @@ public class TempFolderProvider extends ProviderAdapter { | |||
workingPath = home.resolve(workingPath).normalize(); | |||
} | |||
Path tempDir = workingPath.resolve(TMP_NAME); | |||
try { | |||
cleanTempFolders(workingPath); | |||
} catch (IOException e) { | |||
LOG.warn("failed to clean global working directory: " + e.getMessage()); | |||
} | |||
Path tempDir = workingPath.resolve(TMP_NAME_PREFIX + System.currentTimeMillis()); | |||
try { | |||
Files.createDirectories(tempDir); | |||
} catch (IOException e) { | |||
throw new IllegalStateException("Unable to create root temp directory " + tempDir, e); | |||
} | |||
tempFolder = new DefaultTempFolder(tempDir.toFile(), true); | |||
this.instance = tempFolder; | |||
} | |||
return tempFolder; | |||
} | |||
@@ -72,4 +87,41 @@ public class TempFolderProvider extends ProviderAdapter { | |||
return Paths.get(home, ".sonar"); | |||
} | |||
private static void cleanTempFolders(Path path) throws IOException { | |||
if (Files.exists(path)) { | |||
try (DirectoryStream<Path> stream = Files.newDirectoryStream(path, new CleanFilter())) { | |||
for (Path p : stream) { | |||
FileUtils.deleteQuietly(p.toFile()); | |||
} | |||
} | |||
} | |||
} | |||
private static class CleanFilter implements DirectoryStream.Filter<Path> { | |||
@Override | |||
public boolean accept(Path e) throws IOException { | |||
if (!Files.isDirectory(e)) { | |||
return false; | |||
} | |||
if (!e.getFileName().toString().startsWith(TMP_NAME_PREFIX)) { | |||
return false; | |||
} | |||
long threshold = System.currentTimeMillis() - CLEAN_MAX_AGE; | |||
// we could also check the timestamp in the name, instead | |||
BasicFileAttributes attrs; | |||
try { | |||
attrs = Files.readAttributes(e, BasicFileAttributes.class); | |||
} catch (IOException ioe) { | |||
LOG.warn("couldn't read file attributes for " + e + " : " + ioe.getMessage()); | |||
return false; | |||
} | |||
long creationTime = attrs.creationTime().toMillis(); | |||
return creationTime < threshold; | |||
} | |||
} | |||
} |
@@ -38,11 +38,11 @@ public class LogCallbackAppender extends UnsynchronizedAppenderBase<ILoggingEven | |||
@Override | |||
protected void append(ILoggingEvent event) { | |||
target.log(event.getFormattedMessage(), translate(event.getLevel())); | |||
target.log(event.getMessage(), translate(event.getLevel())); | |||
} | |||
private LogListener.Level translate(Level level) { | |||
switch(level.toInt()) { | |||
private static LogListener.Level translate(Level level) { | |||
switch (level.toInt()) { | |||
case Level.ERROR_INT: | |||
return LogListener.Level.ERROR; | |||
case Level.WARN_INT: |
@@ -37,17 +37,21 @@ import org.sonar.api.batch.BatchSide; | |||
@BatchSide | |||
public class Caches implements Startable { | |||
private final Map<String, Exchange> caches = Maps.newHashMap(); | |||
private final Map<String, Exchange> cacheMap = Maps.newHashMap(); | |||
private Persistit persistit; | |||
private Volume volume; | |||
public Caches(CachesManager caches) { | |||
persistit = caches.persistit(); | |||
start(); | |||
doStart(); | |||
} | |||
@Override | |||
public void start() { | |||
// done in constructor | |||
} | |||
private void doStart() { | |||
try { | |||
persistit.flush(); | |||
volume = persistit.createTemporaryVolume(); | |||
@@ -63,12 +67,12 @@ public class Caches implements Startable { | |||
public <V> Cache<V> createCache(String cacheName) { | |||
Preconditions.checkState(volume != null && volume.isOpened(), "Caches are not initialized"); | |||
Preconditions.checkState(!caches.containsKey(cacheName), "Cache is already created: " + cacheName); | |||
Preconditions.checkState(!cacheMap.containsKey(cacheName), "Cache is already created: " + cacheName); | |||
try { | |||
Exchange exchange = persistit.getExchange(volume, cacheName, true); | |||
exchange.setMaximumValueSize(Value.MAXIMUM_SIZE); | |||
Cache<V> cache = new Cache<>(cacheName, exchange); | |||
caches.put(cacheName, exchange); | |||
cacheMap.put(cacheName, exchange); | |||
return cache; | |||
} catch (Exception e) { | |||
throw new IllegalStateException("Fail to create cache: " + cacheName, e); | |||
@@ -77,11 +81,11 @@ public class Caches implements Startable { | |||
@Override | |||
public void stop() { | |||
for (Entry<String, Exchange> e : caches.entrySet()) { | |||
for (Entry<String, Exchange> e : cacheMap.entrySet()) { | |||
persistit.releaseExchange(e.getValue()); | |||
} | |||
caches.clear(); | |||
cacheMap.clear(); | |||
if (volume != null) { | |||
try { |
@@ -19,6 +19,8 @@ | |||
*/ | |||
package org.sonar.batch.mediumtest; | |||
import org.sonar.home.log.LogListener; | |||
import com.google.common.base.Function; | |||
import com.google.common.io.Files; | |||
import org.sonar.api.CoreProperties; | |||
@@ -78,10 +80,16 @@ public class BatchMediumTester { | |||
private final FakeServerIssuesLoader serverIssues = new FakeServerIssuesLoader(); | |||
private final FakeServerLineHashesLoader serverLineHashes = new FakeServerLineHashesLoader(); | |||
private final Map<String, String> bootstrapProperties = new HashMap<>(); | |||
private LogListener logListener = null; | |||
public BatchMediumTester build() { | |||
return new BatchMediumTester(this); | |||
} | |||
public BatchMediumTesterBuilder setLogListener(LogListener listener) { | |||
this.logListener = listener; | |||
return this; | |||
} | |||
public BatchMediumTesterBuilder registerPlugin(String pluginKey, File location) { | |||
pluginInstaller.add(pluginKey, location); | |||
@@ -167,6 +175,7 @@ public class BatchMediumTester { | |||
builder.serverLineHashes, | |||
new DefaultDebtModel()) | |||
.setBootstrapProperties(builder.bootstrapProperties) | |||
.setLogListener(builder.logListener) | |||
.build(); | |||
} | |||
@@ -21,8 +21,6 @@ package org.sonar.batch.scan; | |||
import org.sonar.batch.bootstrap.ProjectTempFolderProvider; | |||
import org.sonar.api.utils.internal.TempFolderCleaner; | |||
import org.sonar.batch.bootstrap.TempFolderProvider; | |||
import com.google.common.annotations.VisibleForTesting; | |||
import org.sonar.api.CoreProperties; | |||
import org.sonar.api.batch.InstantiationStrategy; | |||
@@ -155,9 +153,9 @@ public class ProjectScanContainer extends ComponentContainer { | |||
Caches.class, | |||
BatchComponentCache.class, | |||
//temp | |||
// temp | |||
new ProjectTempFolderProvider(), | |||
// file system | |||
InputPathCache.class, | |||
PathResolver.class, |
@@ -0,0 +1,79 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.batch.bootstrap; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import org.junit.Before; | |||
import org.picocontainer.Startable; | |||
import org.junit.Test; | |||
public class LifecycleProviderAdapterTest { | |||
private DummyProvider provider; | |||
@Before | |||
public void setUp() { | |||
provider = new DummyProvider(); | |||
provider.provide(); | |||
} | |||
@Test | |||
public void testStart() { | |||
// ComponentLifecycle's start gets called on the provider | |||
provider.start(null); | |||
assertThat(provider.inst.started).isEqualTo(true); | |||
assertThat(provider.isStarted()).isEqualTo(true); | |||
assertThat(provider.inst.stopped).isEqualTo(false); | |||
} | |||
@Test | |||
public void testSop() { | |||
// ComponentLifecycle's stop gets called on the provider | |||
provider.stop(null); | |||
assertThat(provider.inst.stopped).isEqualTo(true); | |||
assertThat(provider.isStarted()).isEqualTo(false); | |||
assertThat(provider.inst.started).isEqualTo(false); | |||
} | |||
public class DummyProvided implements Startable { | |||
boolean started = false; | |||
boolean stopped = false; | |||
@Override | |||
public void start() { | |||
started = true; | |||
} | |||
@Override | |||
public void stop() { | |||
stopped = true; | |||
} | |||
} | |||
public class DummyProvider extends LifecycleProviderAdapter { | |||
DummyProvided inst; | |||
public DummyProvided provide() { | |||
inst = new DummyProvided(); | |||
super.instance = inst; | |||
return inst; | |||
} | |||
} | |||
} |
@@ -19,24 +19,20 @@ | |||
*/ | |||
package org.sonar.batch.bootstrap; | |||
import org.mockito.Mock; | |||
import org.mockito.MockitoAnnotations; | |||
import org.junit.Before; | |||
import static org.mockito.Mockito.when; | |||
import java.util.Collections; | |||
import org.junit.Before; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import org.junit.Test; | |||
public class PersistentCacheProviderTest { | |||
private PersistentCacheProvider provider = null; | |||
@Mock | |||
private BootstrapProperties props = null; | |||
@Before | |||
public void prepare() { | |||
MockitoAnnotations.initMocks(this); | |||
props = new BootstrapProperties(Collections.<String, String>emptyMap()); | |||
provider = new PersistentCacheProvider(); | |||
} | |||
@@ -55,7 +51,7 @@ public class PersistentCacheProviderTest { | |||
// normally force update (cache disabled) | |||
assertThat(provider.provide(props).isForceUpdate()).isTrue(); | |||
when(props.property("sonar.enableHttpCache")).thenReturn("true"); | |||
props.properties().put("sonar.enableHttpCache", "true"); | |||
provider = new PersistentCacheProvider(); | |||
assertThat(provider.provide(props).isForceUpdate()).isFalse(); | |||
} |
@@ -19,9 +19,9 @@ | |||
*/ | |||
package org.sonar.batch.bootstrap; | |||
import org.apache.commons.io.FileUtils; | |||
import org.sonar.api.utils.TempFolder; | |||
import org.sonar.api.utils.ProjectTempFolder; | |||
import org.apache.commons.io.FileUtils; | |||
import com.google.common.collect.ImmutableMap; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
@@ -46,7 +46,7 @@ public class ProjectTempFolderProviderTest { | |||
File workingDir = temp.newFolder(); | |||
File tmpDir = new File(workingDir, ProjectTempFolderProvider.TMP_NAME); | |||
ProjectTempFolder tempFolder = tempFolderProvider.provide(new BootstrapProperties(ImmutableMap.of(CoreProperties.WORKING_DIRECTORY, workingDir.getAbsolutePath()))); | |||
TempFolder tempFolder = tempFolderProvider.provide(new BootstrapProperties(ImmutableMap.of(CoreProperties.WORKING_DIRECTORY, workingDir.getAbsolutePath()))); | |||
tempFolder.newDir(); | |||
tempFolder.newFile(); | |||
assertThat(tmpDir).exists(); | |||
@@ -58,7 +58,7 @@ public class ProjectTempFolderProviderTest { | |||
File defaultDir = new File(CoreProperties.WORKING_DIRECTORY_DEFAULT_VALUE, ProjectTempFolderProvider.TMP_NAME); | |||
try { | |||
ProjectTempFolder tempFolder = tempFolderProvider.provide(new BootstrapProperties(Collections.<String, String>emptyMap())); | |||
TempFolder tempFolder = tempFolderProvider.provide(new BootstrapProperties(Collections.<String, String>emptyMap())); | |||
tempFolder.newDir(); | |||
tempFolder.newFile(); | |||
assertThat(defaultDir).exists(); |
@@ -25,7 +25,12 @@ import com.google.common.collect.ImmutableMap; | |||
import org.sonar.api.CoreProperties; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.nio.file.Files; | |||
import java.nio.file.attribute.BasicFileAttributeView; | |||
import java.nio.file.attribute.FileTime; | |||
import java.util.Collections; | |||
import java.util.concurrent.TimeUnit; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import org.junit.Rule; | |||
@@ -40,42 +45,72 @@ public class TempFolderProviderTest { | |||
@Test | |||
public void createTempFolderProps() throws Exception { | |||
File workingDir = temp.newFolder(); | |||
File workingDir = temp.getRoot(); | |||
TempFolder tempFolder = tempFolderProvider.provide(new BootstrapProperties(ImmutableMap.of(CoreProperties.GLOBAL_WORKING_DIRECTORY, workingDir.getAbsolutePath()))); | |||
tempFolder.newDir(); | |||
tempFolder.newFile(); | |||
assertThat(new File(workingDir, TempFolderProvider.TMP_NAME)).exists(); | |||
assertThat(new File(workingDir, ".sonartmp").list()).hasSize(2); | |||
assertThat(getCreatedTempDir(workingDir)).exists(); | |||
assertThat(getCreatedTempDir(workingDir).list()).hasSize(2); | |||
} | |||
@Test | |||
public void cleanUpOld() throws IOException { | |||
long creationTime = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(100); | |||
File workingDir = temp.getRoot(); | |||
for (int i = 0; i < 3; i++) { | |||
File tmp = new File(workingDir, ".sonartmp_" + i); | |||
tmp.mkdirs(); | |||
setFileCreationDate(tmp, creationTime); | |||
} | |||
tempFolderProvider.provide(new BootstrapProperties(ImmutableMap.of(CoreProperties.GLOBAL_WORKING_DIRECTORY, workingDir.getAbsolutePath()))); | |||
// this also checks that all other temps were deleted | |||
assertThat(getCreatedTempDir(workingDir)).exists(); | |||
} | |||
@Test | |||
public void createTempFolderSonarHome() throws Exception { | |||
// with sonar home, it will be in {sonar.home}/.sonartmp | |||
File sonarHome = temp.newFolder(); | |||
File tmpDir = new File(new File(sonarHome, CoreProperties.GLOBAL_WORKING_DIRECTORY_DEFAULT_VALUE), TempFolderProvider.TMP_NAME); | |||
File sonarHome = temp.getRoot(); | |||
File workingDir = new File(sonarHome, CoreProperties.GLOBAL_WORKING_DIRECTORY_DEFAULT_VALUE); | |||
TempFolder tempFolder = tempFolderProvider.provide(new BootstrapProperties(ImmutableMap.of("sonar.userHome", sonarHome.getAbsolutePath()))); | |||
tempFolder.newDir(); | |||
tempFolder.newFile(); | |||
assertThat(tmpDir).exists(); | |||
assertThat(tmpDir.list()).hasSize(2); | |||
assertThat(getCreatedTempDir(workingDir)).exists(); | |||
assertThat(getCreatedTempDir(workingDir).list()).hasSize(2); | |||
} | |||
@Test | |||
public void createTempFolderDefault() throws Exception { | |||
File userHome = temp.getRoot(); | |||
System.setProperty("user.home", userHome.getAbsolutePath()); | |||
// if nothing is defined, it will be in {user.home}/.sonar/.sonartmp | |||
File defaultSonarHome = new File(System.getProperty("user.home"), ".sonar"); | |||
File tmpDir = new File(new File(defaultSonarHome, CoreProperties.GLOBAL_WORKING_DIRECTORY_DEFAULT_VALUE), TempFolderProvider.TMP_NAME); | |||
File workingDir = new File(defaultSonarHome, CoreProperties.GLOBAL_WORKING_DIRECTORY_DEFAULT_VALUE).getAbsoluteFile(); | |||
try { | |||
TempFolder tempFolder = tempFolderProvider.provide(new BootstrapProperties(Collections.<String, String>emptyMap())); | |||
tempFolder.newDir(); | |||
tempFolder.newFile(); | |||
assertThat(tmpDir).exists(); | |||
assertThat(tmpDir.list()).hasSize(2); | |||
assertThat(getCreatedTempDir(workingDir)).exists(); | |||
assertThat(getCreatedTempDir(workingDir).list()).hasSize(2); | |||
} finally { | |||
FileUtils.deleteDirectory(tmpDir); | |||
FileUtils.deleteDirectory(getCreatedTempDir(workingDir)); | |||
} | |||
} | |||
private File getCreatedTempDir(File workingDir) { | |||
assertThat(workingDir.listFiles()).hasSize(1); | |||
return workingDir.listFiles()[0]; | |||
} | |||
private void setFileCreationDate(File f, long time) throws IOException { | |||
BasicFileAttributeView attributes = Files.getFileAttributeView(f.toPath(), BasicFileAttributeView.class); | |||
FileTime creationTime = FileTime.fromMillis(time); | |||
attributes.setTimes(creationTime, creationTime, creationTime); | |||
} | |||
} |
@@ -0,0 +1,66 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.batch.bootstrapper; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.verifyNoMoreInteractions; | |||
import ch.qos.logback.classic.Level; | |||
import ch.qos.logback.classic.spi.ILoggingEvent; | |||
import org.junit.Test; | |||
import org.sonar.home.log.LogListener; | |||
import org.junit.Before; | |||
public class LogCallbackAppenderTest { | |||
private LogListener listener; | |||
private LogCallbackAppender appender; | |||
private ILoggingEvent event; | |||
@Before | |||
public void setUp() { | |||
listener = mock(LogListener.class); | |||
appender = new LogCallbackAppender(listener); | |||
event = mock(ILoggingEvent.class); | |||
when(event.getMessage()).thenReturn("test"); | |||
when(event.getLevel()).thenReturn(Level.INFO); | |||
} | |||
@Test | |||
public void testAppendLog() { | |||
appender.append(event); | |||
verify(event).getMessage(); | |||
verify(event).getLevel(); | |||
verify(listener).log("test", LogListener.Level.INFO); | |||
verifyNoMoreInteractions(event, listener); | |||
} | |||
@Test | |||
public void testChangeTarget() { | |||
listener = mock(LogListener.class); | |||
appender.setTarget(listener); | |||
testAppendLog(); | |||
} | |||
} |
@@ -1,102 +0,0 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.batch.bootstrapper; | |||
import ch.qos.logback.core.encoder.EchoEncoder; | |||
import ch.qos.logback.classic.spi.ILoggingEvent; | |||
import org.mockito.Matchers; | |||
import ch.qos.logback.core.encoder.Encoder; | |||
import ch.qos.logback.core.Context; | |||
import org.junit.Test; | |||
import org.junit.Before; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.IOException; | |||
import java.io.OutputStream; | |||
import java.io.PrintStream; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Mockito.when; | |||
import static org.mockito.Mockito.verifyNoMoreInteractions; | |||
import static org.mockito.Mockito.times; | |||
import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.mock; | |||
public class PrintStreamTest { | |||
private static final String TEST_STR = "foo"; | |||
private ByteArrayOutputStream os; | |||
private PrintStream stream; | |||
private PrintStreamAppender<ILoggingEvent> appender; | |||
private Context context = mock(Context.class); | |||
private Encoder<ILoggingEvent> encoder = mock(Encoder.class); | |||
private ILoggingEvent event = mock(ILoggingEvent.class); | |||
@Before | |||
public void setUp() { | |||
os = new ByteArrayOutputStream(); | |||
stream = new PrintStream(os); | |||
appender = new PrintStreamAppender<ILoggingEvent>(stream); | |||
when(event.getMessage()).thenReturn(TEST_STR); | |||
when(event.toString()).thenReturn(TEST_STR); | |||
} | |||
@Test | |||
public void testNullStream() { | |||
appender.setContext(mock(Context.class)); | |||
appender.setEncoder(encoder); | |||
appender.setTarget(null); | |||
appender.start(); | |||
appender.doAppend(event); | |||
verifyNoMoreInteractions(encoder); | |||
} | |||
@Test | |||
public void testEncoder() throws IOException { | |||
appender.setContext(mock(Context.class)); | |||
appender.setEncoder(encoder); | |||
appender.start(); | |||
appender.doAppend(event); | |||
verify(encoder, times(1)).init(Matchers.notNull(OutputStream.class)); | |||
verify(encoder, times(1)).doEncode(event); | |||
} | |||
@Test | |||
public void testWrite() { | |||
encoder = new EchoEncoder<>(); | |||
encoder.setContext(context); | |||
encoder.start(); | |||
appender.setContext(mock(Context.class)); | |||
appender.setEncoder(encoder); | |||
appender.setTarget(stream); | |||
appender.start(); | |||
appender.doAppend(event); | |||
assertThat(os.toString()).isEqualTo(TEST_STR + System.lineSeparator()); | |||
} | |||
} |
@@ -65,8 +65,6 @@ public class CachesTest extends AbstractCachesTest { | |||
public void leak_test() throws PersistitException { | |||
caches.stop(); | |||
System.out.println(cachesManager.tempDir()); | |||
int len = 1 * 1024 * 1024; | |||
StringBuilder sb = new StringBuilder(len); | |||
for (int i = 0; i < len; i++) { |
@@ -0,0 +1,187 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.batch.mediumtest.log; | |||
import org.junit.BeforeClass; | |||
import org.junit.AfterClass; | |||
import org.sonar.home.log.LogListener; | |||
import org.sonar.home.log.LogListener.Level; | |||
import org.apache.commons.io.FileUtils; | |||
import org.junit.Test; | |||
import com.google.common.collect.ImmutableMap; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
import org.sonar.batch.mediumtest.BatchMediumTester; | |||
import org.sonar.xoo.XooPlugin; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.io.PrintStream; | |||
import java.util.LinkedList; | |||
import java.util.List; | |||
import java.util.regex.Matcher; | |||
import java.util.regex.Pattern; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import org.junit.Rule; | |||
import org.junit.rules.ExpectedException; | |||
import org.junit.rules.TemporaryFolder; | |||
public class LogListenerTest { | |||
@Rule | |||
public TemporaryFolder temp = new TemporaryFolder(); | |||
@Rule | |||
public ExpectedException thrown = ExpectedException.none(); | |||
private Pattern simpleTimePattern = Pattern.compile("\\d{2}:\\d{2}:\\d{2}"); | |||
private List<LogEvent> logOutput; | |||
private ByteArrayOutputStream stdOutTarget = new ByteArrayOutputStream(); | |||
private ByteArrayOutputStream stdErrTarget = new ByteArrayOutputStream(); | |||
private static PrintStream savedStdOut; | |||
private static PrintStream savedStdErr; | |||
public BatchMediumTester tester = BatchMediumTester.builder() | |||
.registerPlugin("xoo", new XooPlugin()) | |||
.addDefaultQProfile("xoo", "Sonar Way") | |||
.setLogListener(new SimpleLogListener()) | |||
.build(); | |||
private File baseDir; | |||
private ImmutableMap.Builder<String, String> builder; | |||
@BeforeClass | |||
public static void backupStdStreams() { | |||
savedStdOut = System.out; | |||
savedStdErr = System.err; | |||
} | |||
@AfterClass | |||
public static void resumeStdStreams() { | |||
if (savedStdOut != null) { | |||
System.setOut(savedStdOut); | |||
} | |||
if (savedStdErr != null) { | |||
System.setErr(savedStdErr); | |||
} | |||
} | |||
@Before | |||
public void prepare() throws IOException { | |||
System.setOut(new PrintStream(stdOutTarget)); | |||
System.setErr(new PrintStream(stdErrTarget)); | |||
logOutput = new LinkedList<>(); | |||
tester.start(); | |||
baseDir = temp.newFolder(); | |||
builder = ImmutableMap.<String, String>builder() | |||
.put("sonar.task", "scan") | |||
.put("sonar.projectBaseDir", baseDir.getAbsolutePath()) | |||
.put("sonar.projectKey", "com.foo.project") | |||
.put("sonar.projectName", "Foo Project") | |||
.put("sonar.projectVersion", "1.0-SNAPSHOT") | |||
.put("sonar.projectDescription", "Description of Foo Project"); | |||
} | |||
private void assertNoStdOutput() { | |||
assertThat(stdOutTarget.toByteArray()).isEmpty(); | |||
assertThat(stdErrTarget.toByteArray()).isEmpty(); | |||
} | |||
/** | |||
* Check that log message is not formatted, i.e. has no log level and timestamp. | |||
*/ | |||
private void assertMsgClean(String msg) { | |||
for (Level l : Level.values()) { | |||
assertThat(msg).doesNotContain(l.toString()); | |||
} | |||
Matcher matcher = simpleTimePattern.matcher(msg); | |||
assertThat(matcher.find()).isFalse(); | |||
} | |||
@After | |||
public void stop() { | |||
tester.stop(); | |||
} | |||
@Test | |||
public void testNoStdLog() throws IOException { | |||
File srcDir = new File(baseDir, "src"); | |||
srcDir.mkdir(); | |||
File xooFile = new File(srcDir, "sample.xoo"); | |||
FileUtils.write(xooFile, "Sample xoo\ncontent"); | |||
tester.newTask() | |||
.properties(builder | |||
.put("sonar.sources", "src") | |||
.build()) | |||
.start(); | |||
assertNoStdOutput(); | |||
assertThat(logOutput).isNotEmpty(); | |||
for (LogEvent e : logOutput) { | |||
savedStdOut.println("[captured]" + e.level + " " + e.msg); | |||
} | |||
} | |||
@Test | |||
public void testNoFormattedMsgs() throws IOException { | |||
File srcDir = new File(baseDir, "src"); | |||
srcDir.mkdir(); | |||
File xooFile = new File(srcDir, "sample.xoo"); | |||
FileUtils.write(xooFile, "Sample xoo\ncontent"); | |||
tester.newTask() | |||
.properties(builder | |||
.put("sonar.sources", "src") | |||
.build()) | |||
.start(); | |||
assertNoStdOutput(); | |||
for (LogEvent e : logOutput) { | |||
assertMsgClean(e.msg); | |||
savedStdOut.println("[captured]" + e.level + " " + e.msg); | |||
} | |||
} | |||
private class SimpleLogListener implements LogListener { | |||
@Override | |||
public void log(String msg, Level level) { | |||
logOutput.add(new LogEvent(msg, level)); | |||
} | |||
} | |||
private static class LogEvent { | |||
String msg; | |||
Level level; | |||
LogEvent(String msg, Level level) { | |||
this.msg = msg; | |||
this.level = level; | |||
} | |||
} | |||
} |
@@ -52,15 +52,19 @@ public class PersistentCache { | |||
// eviction strategy is to expire entries after modification once a time duration has elapsed | |||
private final long defaultDurationToExpireMs; | |||
private final Log log; | |||
private final boolean forceUpdate; | |||
private boolean forceUpdate; | |||
public PersistentCache(Path baseDir, long defaultDurationToExpireMs, Log log, boolean forceUpdate) { | |||
this.baseDir = baseDir; | |||
this.defaultDurationToExpireMs = defaultDurationToExpireMs; | |||
this.log = log; | |||
this.forceUpdate = forceUpdate; | |||
reconfigure(forceUpdate); | |||
log.info("cache: " + baseDir + ", default expiration time (ms): " + defaultDurationToExpireMs); | |||
} | |||
public void reconfigure(boolean forceUpdate) { | |||
this.forceUpdate = forceUpdate; | |||
if (forceUpdate) { | |||
log.debug("cache: forcing update"); |
@@ -30,17 +30,19 @@ import java.nio.file.Paths; | |||
import java.util.concurrent.TimeUnit; | |||
public class PersistentCacheBuilder { | |||
private static final long DEFAULT_EXPIRE_DURATION = TimeUnit.MILLISECONDS.convert(1L, TimeUnit.DAYS); | |||
private static final String DIR_NAME = "ws_cache"; | |||
private boolean forceUpdate = false; | |||
private Path cachePath = null; | |||
private Log log = new StandardLog(); | |||
private String name = "ws_cache"; | |||
public PersistentCache build() { | |||
if (cachePath == null) { | |||
setSonarHome(findHome()); | |||
} | |||
return new PersistentCache(cachePath, TimeUnit.MILLISECONDS.convert(1L, TimeUnit.DAYS), log, forceUpdate); | |||
return new PersistentCache(cachePath, DEFAULT_EXPIRE_DURATION, log, forceUpdate); | |||
} | |||
public PersistentCacheBuilder setLog(Log log) { | |||
@@ -50,7 +52,7 @@ public class PersistentCacheBuilder { | |||
public PersistentCacheBuilder setSonarHome(@Nullable Path p) { | |||
if (p != null) { | |||
this.cachePath = p.resolve(name); | |||
this.cachePath = p.resolve(DIR_NAME); | |||
} | |||
return this; | |||
} |
@@ -19,12 +19,13 @@ | |||
*/ | |||
package org.sonar.home.cache; | |||
import org.apache.commons.io.FileUtils; | |||
import org.sonar.home.log.Slf4jLog; | |||
import org.junit.Rule; | |||
import org.junit.rules.TemporaryFolder; | |||
import java.io.File; | |||
import java.nio.file.Path; | |||
import java.util.concurrent.Callable; | |||
import static org.mockito.Mockito.when; | |||
@@ -95,9 +96,26 @@ public class PersistentCacheTest { | |||
assertCacheHit(true); | |||
} | |||
@Test | |||
public void testReconfigure() throws Exception { | |||
cache = new PersistentCache(tmp.getRoot().toPath(), Long.MAX_VALUE, log, true); | |||
assertCacheHit(false); | |||
assertCacheHit(false); | |||
File root = tmp.getRoot(); | |||
FileUtils.deleteDirectory(root); | |||
// should re-create cache directory and start using the cache | |||
cache.reconfigure(false); | |||
assertThat(root).exists(); | |||
assertCacheHit(false); | |||
assertCacheHit(true); | |||
} | |||
@Test | |||
public void testExpiration() throws Exception { | |||
//negative time to make sure it is expired on the second call | |||
// negative time to make sure it is expired on the second call | |||
cache = new PersistentCache(tmp.getRoot().toPath(), -100, log, false); | |||
assertCacheHit(false); | |||
assertCacheHit(false); | |||
@@ -118,7 +136,7 @@ public class PersistentCacheTest { | |||
return VALUE; | |||
} | |||
} | |||
/** | |||
* WSCache should be transparent regarding exceptions: if an exception is thrown by the value loader, it should pass through | |||
* the cache to the original caller using the cache. |
@@ -19,7 +19,6 @@ | |||
*/ | |||
package org.sonar.api.utils.internal; | |||
import org.sonar.api.utils.ProjectTempFolder; | |||
import org.apache.commons.io.FileUtils; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.sonar.api.utils.TempFolder; | |||
@@ -30,21 +29,21 @@ import java.io.File; | |||
import java.io.IOException; | |||
import java.text.MessageFormat; | |||
public class DefaultTempFolder implements TempFolder, ProjectTempFolder { | |||
public class DefaultTempFolder implements TempFolder { | |||
/** Maximum loop count when creating temp directories. */ | |||
private static final int TEMP_DIR_ATTEMPTS = 10000; | |||
private final File tempDir; | |||
private final boolean cleanUp; | |||
private final boolean deleteOnExit; | |||
public DefaultTempFolder(File tempDir) { | |||
this(tempDir, false); | |||
} | |||
public DefaultTempFolder(File tempDir, boolean cleanUp) { | |||
public DefaultTempFolder(File tempDir, boolean deleteOnExit) { | |||
this.tempDir = tempDir; | |||
this.cleanUp = cleanUp; | |||
this.deleteOnExit = deleteOnExit; | |||
} | |||
@Override | |||
@@ -114,7 +113,7 @@ public class DefaultTempFolder implements TempFolder, ProjectTempFolder { | |||
} | |||
public void stop() { | |||
if(cleanUp) { | |||
if (deleteOnExit) { | |||
clean(); | |||
} | |||
} |
@@ -19,8 +19,6 @@ | |||
*/ | |||
package org.sonar.api.utils.internal; | |||
import org.sonar.api.utils.ProjectTempFolder; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.junit.rules.ExternalResource; | |||
import org.junit.rules.TemporaryFolder; | |||
@@ -53,7 +51,7 @@ import java.io.IOException; | |||
* | |||
* @since 5.1 | |||
*/ | |||
public class JUnitTempFolder extends ExternalResource implements TempFolder, ProjectTempFolder { | |||
public class JUnitTempFolder extends ExternalResource implements TempFolder { | |||
private final TemporaryFolder junit = new TemporaryFolder(); | |||
@@ -19,11 +19,9 @@ | |||
*/ | |||
package org.sonar.api.utils.internal; | |||
import org.sonar.api.batch.BatchSide; | |||
import org.sonar.api.server.ServerSide; | |||
import org.sonar.api.utils.TempFolder; | |||
@BatchSide | |||
@ServerSide | |||
public class TempFolderCleaner { | |||