From 7370c19e9acfefb2b9ac1fb3aeea7fd73f5a5fc3 Mon Sep 17 00:00:00 2001 From: Duarte Meneses Date: Thu, 20 Aug 2015 16:58:48 +0200 Subject: [PATCH] SONAR-6721 Prevent running concurrent batch processes on the same physical project --- .../src/test/java/batch/IssuesModeTest.java | 15 ++- .../analysis/AnalysisTempFolderProvider.java | 57 ++++++---- .../bootstrap/GlobalTempFolderProvider.java | 48 +++++++-- .../bootstrap/LifecycleProviderAdapter.java | 85 --------------- .../scan/MutableProjectReactorProvider.java | 19 +++- .../org/sonar/batch/scan/ProjectLock.java | 101 ++++++++++++++++++ .../batch/scan/ProjectScanContainer.java | 5 +- .../AnalysisTempFolderProviderTest.java | 48 ++++----- .../GlobalTempFolderProviderTest.java | 21 +++- .../LifecycleProviderAdapterTest.java | 79 -------------- .../issues/IssuesIssuesModeMediumTest.java | 4 +- .../org/sonar/batch/scan/ProjectLockTest.java | 83 ++++++++++++++ .../java/org/sonar/api/CoreProperties.java | 2 +- 13 files changed, 330 insertions(+), 237 deletions(-) delete mode 100644 sonar-batch/src/main/java/org/sonar/batch/bootstrap/LifecycleProviderAdapter.java create mode 100644 sonar-batch/src/main/java/org/sonar/batch/scan/ProjectLock.java delete mode 100644 sonar-batch/src/test/java/org/sonar/batch/bootstrap/LifecycleProviderAdapterTest.java create mode 100644 sonar-batch/src/test/java/org/sonar/batch/scan/ProjectLockTest.java diff --git a/it/it-tests/src/test/java/batch/IssuesModeTest.java b/it/it-tests/src/test/java/batch/IssuesModeTest.java index 0e8ce198440..e4c0f3306a1 100644 --- a/it/it-tests/src/test/java/batch/IssuesModeTest.java +++ b/it/it-tests/src/test/java/batch/IssuesModeTest.java @@ -295,7 +295,6 @@ public class IssuesModeTest { fail("Issue not found"); } - // SONAR-4602 @Test public void concurrent_issue_mode_on_existing_project() throws Exception { restoreProfile("one-issue-per-line.xml"); @@ -312,7 +311,7 @@ public class IssuesModeTest { // Install sonar-runner in advance to avoid concurrent unzip issues FileSystem fileSystem = orchestrator.getConfiguration().fileSystem(); new SonarRunnerInstaller(fileSystem).install(Version.create(SonarRunner.DEFAULT_RUNNER_VERSION), fileSystem.workspace()); - final int nThreads = 5; + final int nThreads = 3; ExecutorService executorService = Executors.newFixedThreadPool(nThreads); List> tasks = new ArrayList<>(); for (int i = 0; i < nThreads; i++) { @@ -325,8 +324,20 @@ public class IssuesModeTest { }); } + boolean expectedError = false; for (Future result : executorService.invokeAll(tasks)) { + try { result.get(); + } catch(ExecutionException e) { + if(e.getCause() instanceof BuildFailureException) { + BuildFailureException bfe = (BuildFailureException) e.getCause(); + assertThat(bfe.getResult().getLogs()).contains("Another SonarQube analysis is already in progress for this project"); + expectedError = true; + } + } + } + if(!expectedError) { + fail("At least one of the threads should have failed"); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/analysis/AnalysisTempFolderProvider.java b/sonar-batch/src/main/java/org/sonar/batch/analysis/AnalysisTempFolderProvider.java index 639a932fb36..63078cdb648 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/analysis/AnalysisTempFolderProvider.java +++ b/sonar-batch/src/main/java/org/sonar/batch/analysis/AnalysisTempFolderProvider.java @@ -19,49 +19,62 @@ */ package org.sonar.batch.analysis; -import org.sonar.batch.bootstrap.LifecycleProviderAdapter; - +import org.sonar.api.batch.bootstrap.ProjectReactor; +import org.picocontainer.PicoContainer; +import org.picocontainer.ComponentLifecycle; +import org.picocontainer.injectors.ProviderAdapter; import org.sonar.api.utils.TempFolder; -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.Files; import java.nio.file.Path; -import java.nio.file.Paths; -public class AnalysisTempFolderProvider extends LifecycleProviderAdapter { +public class AnalysisTempFolderProvider extends ProviderAdapter implements ComponentLifecycle { static final String TMP_NAME = ".sonartmp"; private DefaultTempFolder projectTempFolder; - public TempFolder provide(AnalysisProperties props) { + public TempFolder provide(ProjectReactor projectReactor) { if (projectTempFolder == null) { - String workingDirPath = StringUtils.defaultIfBlank(props.property(CoreProperties.WORKING_DIRECTORY), CoreProperties.WORKING_DIRECTORY_DEFAULT_VALUE); - Path workingDir = Paths.get(workingDirPath).normalize(); - - if (!workingDir.isAbsolute()) { - Path base = getBasePath(props); - workingDir = base.resolve(workingDir); - } - - Path tempDir = workingDir.resolve(TMP_NAME); + Path workingDir = projectReactor.getRoot().getWorkDir().toPath(); + Path tempDir = workingDir.normalize().resolve(TMP_NAME); try { + Files.deleteIfExists(tempDir); Files.createDirectories(tempDir); } catch (IOException e) { throw new IllegalStateException("Unable to create root temp directory " + tempDir, e); } + projectTempFolder = new DefaultTempFolder(tempDir.toFile(), true); - this.instance = projectTempFolder; } return projectTempFolder; } - private static Path getBasePath(AnalysisProperties props) { - String baseDir = props.property("sonar.projectBaseDir"); - if (baseDir == null) { - throw new IllegalStateException("sonar.projectBaseDir needs to be specified"); + @Override + public void start(PicoContainer container) { + started = true; + } + + private boolean started = false; + + @Override + public void stop(PicoContainer container) { + if (projectTempFolder != null) { + projectTempFolder.stop(); } - return Paths.get(baseDir); + } + + @Override + public void dispose(PicoContainer container) { + } + + @Override + public boolean componentHasLifecycle() { + return true; + } + + @Override + public boolean isStarted() { + return started; } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalTempFolderProvider.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalTempFolderProvider.java index e86114efb2e..9ccec68c9bd 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalTempFolderProvider.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalTempFolderProvider.java @@ -19,6 +19,10 @@ */ package org.sonar.batch.bootstrap; +import org.picocontainer.ComponentLifecycle; + +import org.picocontainer.PicoContainer; +import org.picocontainer.injectors.ProviderAdapter; import org.sonar.api.utils.System2; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; @@ -36,10 +40,9 @@ import java.nio.file.Paths; import java.nio.file.attribute.BasicFileAttributes; import java.util.concurrent.TimeUnit; -public class GlobalTempFolderProvider extends LifecycleProviderAdapter { +public class GlobalTempFolderProvider extends ProviderAdapter implements ComponentLifecycle { private static final Logger LOG = Loggers.get(GlobalTempFolderProvider.class); private static final long CLEAN_MAX_AGE = TimeUnit.DAYS.toMillis(21); - static final String TMP_NAME_PREFIX = ".sonartmp_"; private System2 system; @@ -57,10 +60,10 @@ public class GlobalTempFolderProvider extends LifecycleProviderAdapter { if (tempFolder == null) { String workingPathName = StringUtils.defaultIfBlank(bootstrapProps.property(CoreProperties.GLOBAL_WORKING_DIRECTORY), CoreProperties.GLOBAL_WORKING_DIRECTORY_DEFAULT_VALUE); - Path workingPath = Paths.get(workingPathName).normalize(); + Path workingPath = Paths.get(workingPathName); if (!workingPath.isAbsolute()) { - Path home = findHome(bootstrapProps); + Path home = findSonarHome(bootstrapProps); workingPath = home.resolve(workingPath).normalize(); } @@ -71,7 +74,6 @@ public class GlobalTempFolderProvider extends LifecycleProviderAdapter { } Path tempDir = createTempFolder(workingPath); tempFolder = new DefaultTempFolder(tempDir.toFile(), true); - this.instance = tempFolder; } return tempFolder; } @@ -90,20 +92,20 @@ public class GlobalTempFolderProvider extends LifecycleProviderAdapter { } } - private Path findHome(GlobalProperties props) { + private Path findSonarHome(GlobalProperties props) { String home = props.property("sonar.userHome"); if (home != null) { - return Paths.get(home); + return Paths.get(home).toAbsolutePath(); } home = system.envVariable("SONAR_USER_HOME"); if (home != null) { - return Paths.get(home); + return Paths.get(home).toAbsolutePath(); } home = system.property("user.home"); - return Paths.get(home, ".sonar"); + return Paths.get(home, ".sonar").toAbsolutePath(); } private static void cleanTempFolders(Path path) throws IOException { @@ -143,4 +145,32 @@ public class GlobalTempFolderProvider extends LifecycleProviderAdapter { return creationTime < threshold; } } + + @Override + public void start(PicoContainer container) { + started = true; + } + + private boolean started = false; + + @Override + public void stop(PicoContainer container) { + if (tempFolder != null) { + tempFolder.stop(); + } + } + + @Override + public void dispose(PicoContainer container) { + } + + @Override + public boolean componentHasLifecycle() { + return true; + } + + @Override + public boolean isStarted() { + return started; + } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/LifecycleProviderAdapter.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/LifecycleProviderAdapter.java deleted file mode 100644 index 009aadad61d..00000000000 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/LifecycleProviderAdapter.java +++ /dev/null @@ -1,85 +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.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 { - 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; - -} diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/MutableProjectReactorProvider.java b/sonar-batch/src/main/java/org/sonar/batch/scan/MutableProjectReactorProvider.java index b9a8a1cf460..a9b29578702 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/MutableProjectReactorProvider.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/MutableProjectReactorProvider.java @@ -19,15 +19,18 @@ */ package org.sonar.batch.scan; +import org.apache.commons.io.FileUtils; import org.sonar.batch.analysis.AnalysisProperties; - import org.picocontainer.injectors.ProviderAdapter; import org.sonar.api.batch.bootstrap.ProjectBootstrapper; import org.sonar.api.batch.bootstrap.ProjectReactor; -import org.sonar.api.utils.SonarException; import javax.annotation.Nullable; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + public class MutableProjectReactorProvider extends ProviderAdapter { private final ProjectBootstrapper projectBootstrapper; @@ -49,9 +52,19 @@ public class MutableProjectReactorProvider extends ProviderAdapter { reactor = projectBootstrapper.bootstrap(); } if (reactor == null) { - throw new SonarException(projectBootstrapper + " has returned null as ProjectReactor"); + throw new IllegalStateException(projectBootstrapper + " has returned null as ProjectReactor"); } + cleanDirectory(reactor.getRoot().getWorkDir()); } return reactor; } + + private void cleanDirectory(File dir) { + try { + FileUtils.deleteDirectory(dir); + Files.createDirectories(dir.toPath()); + } catch (IOException e) { + throw new IllegalStateException("Failed to recreate working directory: " + dir.getAbsolutePath(), e); + } + } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectLock.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectLock.java new file mode 100644 index 00000000000..a373763a361 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectLock.java @@ -0,0 +1,101 @@ +/* + * 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.scan; + +import org.picocontainer.Startable; +import org.sonar.api.batch.bootstrap.ProjectReactor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.OverlappingFileLockException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class ProjectLock implements Startable { + private static final Logger LOG = LoggerFactory.getLogger(ProjectLock.class); + static final String LOCK_FILE_NAME = ".sonar_lock"; + private final Path lockFilePath; + + private RandomAccessFile lockRandomAccessFile; + private FileChannel lockChannel; + private FileLock lockFile; + + public ProjectLock(ProjectReactor projectReactor) { + Path directory = projectReactor.getRoot().getBaseDir().toPath(); + this.lockFilePath = directory.resolve(LOCK_FILE_NAME).toAbsolutePath(); + } + + public void tryLock() { + try { + lockRandomAccessFile = new RandomAccessFile(lockFilePath.toFile(), "rw"); + lockChannel = lockRandomAccessFile.getChannel(); + lockFile = lockChannel.tryLock(0, 1024, false); + + if (lockFile == null) { + failAlreadyInProgress(); + } + } catch (OverlappingFileLockException e) { + failAlreadyInProgress(); + } catch (IOException e) { + throw new IllegalStateException("Failed to create project lock in " + lockFilePath.toString(), e); + } + } + + private static void failAlreadyInProgress() { + throw new IllegalStateException("Another SonarQube analysis is already in progress for this project"); + } + + public void stop() { + if (lockFile != null) { + try { + Files.delete(lockFilePath); + lockFile.release(); + lockFile = null; + } catch (IOException e) { + LOG.error("Error releasing lock", e); + } + } + if (lockChannel != null) { + try { + lockChannel.close(); + lockChannel = null; + } catch (IOException e) { + LOG.error("Error closing file channel", e); + } + } + if (lockRandomAccessFile != null) { + try { + lockRandomAccessFile.close(); + lockRandomAccessFile = null; + } catch (IOException e) { + LOG.error("Error closing file", e); + } + } + } + + @Override + public void start() { + } + +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java index 869a64b4c93..e31f14a104e 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java @@ -20,7 +20,6 @@ package org.sonar.batch.scan; import org.sonar.batch.analysis.DefaultAnalysisMode; - import org.sonar.batch.analysis.AnalysisWSLoaderProvider; import org.sonar.batch.analysis.AnalysisTempFolderProvider; import org.sonar.batch.analysis.AnalysisProperties; @@ -51,7 +50,6 @@ import org.sonar.batch.bootstrap.ExtensionMatcher; import org.sonar.batch.bootstrap.ExtensionUtils; import org.sonar.batch.bootstrap.MetricProvider; import org.sonar.batch.bootstrapper.EnvironmentInformation; -import org.sonar.batch.deprecated.components.DefaultResourceCreationLock; import org.sonar.batch.duplication.DuplicationCache; import org.sonar.batch.events.EventBus; import org.sonar.batch.index.BatchComponentCache; @@ -107,6 +105,7 @@ public class ProjectScanContainer extends ComponentContainer { add(component); } addBatchComponents(); + getComponentByType(ProjectLock.class).tryLock(); addBatchExtensions(); Settings settings = getComponentByType(Settings.class); if (settings != null && settings.getBoolean(CoreProperties.PROFILING_LOG_PROPERTY)) { @@ -135,6 +134,7 @@ public class ProjectScanContainer extends ComponentContainer { new MutableProjectReactorProvider(getComponentByType(ProjectBootstrapper.class)), new ImmutableProjectReactorProvider(), ProjectBuildersExecutor.class, + ProjectLock.class, EventBus.class, PhasesTimeProfiler.class, ResourceTypes.class, @@ -143,7 +143,6 @@ public class ProjectScanContainer extends ComponentContainer { ProjectReactorValidator.class, new ProjectRepositoriesProvider(), new AnalysisWSLoaderProvider(), - DefaultResourceCreationLock.class, CodeColorizers.class, MetricProvider.class, ProjectConfigurator.class, diff --git a/sonar-batch/src/test/java/org/sonar/batch/analysis/AnalysisTempFolderProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/analysis/AnalysisTempFolderProviderTest.java index 601f90d876c..3a81a7304d6 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/analysis/AnalysisTempFolderProviderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/analysis/AnalysisTempFolderProviderTest.java @@ -19,20 +19,21 @@ */ package org.sonar.batch.analysis; -import org.sonar.batch.analysis.AnalysisTempFolderProvider; +import org.sonar.api.batch.bootstrap.ProjectDefinition; -import org.sonar.batch.analysis.AnalysisProperties; +import org.sonar.api.batch.bootstrap.ProjectReactor; +import org.junit.Before; +import org.sonar.batch.analysis.AnalysisTempFolderProvider; import org.sonar.api.utils.TempFolder; -import org.apache.commons.io.FileUtils; -import com.google.common.collect.ImmutableMap; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import org.sonar.api.CoreProperties; import java.io.File; import java.io.IOException; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.mock; import static org.assertj.core.api.Assertions.assertThat; public class AnalysisTempFolderProviderTest { @@ -40,33 +41,26 @@ public class AnalysisTempFolderProviderTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); - private AnalysisTempFolderProvider tempFolderProvider = new AnalysisTempFolderProvider(); - - @Test - public void createTempFolderWithProps() throws Exception { - File workingDir = temp.newFolder(); - File tmpDir = new File(workingDir, AnalysisTempFolderProvider.TMP_NAME); + private AnalysisTempFolderProvider tempFolderProvider; + private ProjectReactor projectReactor; - TempFolder tempFolder = tempFolderProvider.provide(new AnalysisProperties(ImmutableMap.of(CoreProperties.WORKING_DIRECTORY, workingDir.getAbsolutePath()), "")); - tempFolder.newDir(); - tempFolder.newFile(); - assertThat(tmpDir).exists(); - assertThat(tmpDir.list()).hasSize(2); + @Before + public void setUp() { + tempFolderProvider = new AnalysisTempFolderProvider(); + projectReactor = mock(ProjectReactor.class); + ProjectDefinition projectDefinition = mock(ProjectDefinition.class); + when(projectReactor.getRoot()).thenReturn(projectDefinition); + when(projectDefinition.getWorkDir()).thenReturn(temp.getRoot()); } @Test public void createTempFolder() throws IOException { - File workingDir = temp.newFolder(); - File defaultDir = new File(new File(workingDir, CoreProperties.WORKING_DIRECTORY_DEFAULT_VALUE), AnalysisTempFolderProvider.TMP_NAME); + File defaultDir = new File(temp.getRoot(), AnalysisTempFolderProvider.TMP_NAME); - try { - TempFolder tempFolder = tempFolderProvider.provide(new AnalysisProperties(ImmutableMap.of("sonar.projectBaseDir", workingDir.getAbsolutePath()), "")); - tempFolder.newDir(); - tempFolder.newFile(); - assertThat(defaultDir).exists(); - assertThat(defaultDir.list()).hasSize(2); - } finally { - FileUtils.deleteDirectory(defaultDir); - } + TempFolder tempFolder = tempFolderProvider.provide(projectReactor); + tempFolder.newDir(); + tempFolder.newFile(); + assertThat(defaultDir).exists(); + assertThat(defaultDir.list()).hasSize(2); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/GlobalTempFolderProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/GlobalTempFolderProviderTest.java index aeb18d14d99..6620f59fb98 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/GlobalTempFolderProviderTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/GlobalTempFolderProviderTest.java @@ -55,7 +55,7 @@ public class GlobalTempFolderProviderTest { tempFolder.newFile(); assertThat(getCreatedTempDir(workingDir)).exists(); assertThat(getCreatedTempDir(workingDir).list()).hasSize(2); - + FileUtils.deleteQuietly(workingDir); } @@ -73,7 +73,7 @@ public class GlobalTempFolderProviderTest { tempFolderProvider.provide(new GlobalProperties(ImmutableMap.of(CoreProperties.GLOBAL_WORKING_DIRECTORY, workingDir.getAbsolutePath()))); // this also checks that all other temps were deleted assertThat(getCreatedTempDir(workingDir)).exists(); - + FileUtils.deleteQuietly(workingDir); } @@ -88,7 +88,7 @@ public class GlobalTempFolderProviderTest { tempFolder.newFile(); assertThat(getCreatedTempDir(workingDir)).exists(); assertThat(getCreatedTempDir(workingDir).list()).hasSize(2); - + FileUtils.deleteQuietly(sonarHome); } @@ -97,7 +97,7 @@ public class GlobalTempFolderProviderTest { System2 system = mock(System2.class); tempFolderProvider = new GlobalTempFolderProvider(system); File userHome = temp.newFolder(); - + when(system.envVariable("SONAR_USER_HOME")).thenReturn(null); when(system.property("user.home")).thenReturn(userHome.getAbsolutePath().toString()); @@ -115,6 +115,19 @@ public class GlobalTempFolderProviderTest { } } + @Test + public void dotWorkingDir() throws IOException { + File sonarHome = temp.getRoot(); + String globalWorkDir = "."; + GlobalProperties globalProperties = new GlobalProperties(ImmutableMap.of("sonar.userHome", sonarHome.getAbsolutePath(), + CoreProperties.GLOBAL_WORKING_DIRECTORY, globalWorkDir)); + + TempFolder tempFolder = tempFolderProvider.provide(globalProperties); + File newFile = tempFolder.newFile(); + assertThat(newFile.getParentFile().getParentFile().getAbsolutePath()).isEqualTo(sonarHome.getAbsolutePath()); + assertThat(newFile.getParentFile().getName()).startsWith(".sonartmp_"); + } + private File getCreatedTempDir(File workingDir) { assertThat(workingDir).isDirectory(); assertThat(workingDir.listFiles()).hasSize(1); diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/LifecycleProviderAdapterTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/LifecycleProviderAdapterTest.java deleted file mode 100644 index 5c3bb85a109..00000000000 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/LifecycleProviderAdapterTest.java +++ /dev/null @@ -1,79 +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.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; - } - } -} diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesIssuesModeMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesIssuesModeMediumTest.java index bb44b2390d3..e21eea304c4 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesIssuesModeMediumTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesIssuesModeMediumTest.java @@ -86,8 +86,8 @@ public class IssuesIssuesModeMediumTest { .property(CoreProperties.ANALYSIS_MODE, CoreProperties.ANALYSIS_MODE_ISSUES) .start(); - assertThat(result2.trackedIssues()).hasSize(28); - assertThat(issueListener.issueList).hasSize(28); + assertThat(result2.trackedIssues()).hasSize(14); + assertThat(issueListener.issueList).hasSize(14); } private class IssueRecorder implements IssueListener { diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/ProjectLockTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/ProjectLockTest.java new file mode 100644 index 00000000000..b3eb55252e8 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/ProjectLockTest.java @@ -0,0 +1,83 @@ +/* + * 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.scan; + +import org.junit.rules.ExpectedException; + +import org.sonar.api.batch.bootstrap.ProjectDefinition; +import org.sonar.api.batch.bootstrap.ProjectReactor; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.Test; +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; + +public class ProjectLockTest { + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + @Rule + public ExpectedException exception = ExpectedException.none(); + private ProjectLock lock; + + @Before + public void setUp() { + ProjectReactor projectReactor = mock(ProjectReactor.class); + ProjectDefinition projectDefinition = mock(ProjectDefinition.class); + when(projectReactor.getRoot()).thenReturn(projectDefinition); + when(projectDefinition.getBaseDir()).thenReturn(tempFolder.getRoot()); + + lock = new ProjectLock(projectReactor); + } + + @Test + public void tryLock() { + Path lockFilePath = tempFolder.getRoot().toPath().resolve(ProjectLock.LOCK_FILE_NAME); + lock.tryLock(); + assertThat(Files.exists(lockFilePath)).isTrue(); + assertThat(Files.isRegularFile(lockFilePath)).isTrue(); + + lock.stop(); + assertThat(Files.exists(lockFilePath)).isFalse(); + } + + @Test + public void tryLockConcurrently() { + exception.expect(IllegalStateException.class); + exception.expectMessage("Another SonarQube analysis is already in progress for this project"); + lock.tryLock(); + lock.tryLock(); + } + + @Test + public void tryLockTwice() { + lock.tryLock(); + lock.stop(); + lock.tryLock(); + lock.stop(); + } + +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java b/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java index c023f8a0782..51b49b34089 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java @@ -483,7 +483,7 @@ public interface CoreProperties { * @since 5.2 */ String GLOBAL_WORKING_DIRECTORY = "sonar.globalWorking.directory"; - String GLOBAL_WORKING_DIRECTORY_DEFAULT_VALUE = "."; + String GLOBAL_WORKING_DIRECTORY_DEFAULT_VALUE = ""; /** * @since 4.2 -- 2.39.5