]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6721 Prevent running concurrent batch processes on the same physical project
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Thu, 20 Aug 2015 14:58:48 +0000 (16:58 +0200)
committerDuarte Meneses <duarte.meneses@sonarsource.com>
Fri, 21 Aug 2015 11:55:09 +0000 (13:55 +0200)
13 files changed:
it/it-tests/src/test/java/batch/IssuesModeTest.java
sonar-batch/src/main/java/org/sonar/batch/analysis/AnalysisTempFolderProvider.java
sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalTempFolderProvider.java
sonar-batch/src/main/java/org/sonar/batch/bootstrap/LifecycleProviderAdapter.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/scan/MutableProjectReactorProvider.java
sonar-batch/src/main/java/org/sonar/batch/scan/ProjectLock.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
sonar-batch/src/test/java/org/sonar/batch/analysis/AnalysisTempFolderProviderTest.java
sonar-batch/src/test/java/org/sonar/batch/bootstrap/GlobalTempFolderProviderTest.java
sonar-batch/src/test/java/org/sonar/batch/bootstrap/LifecycleProviderAdapterTest.java [deleted file]
sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesIssuesModeMediumTest.java
sonar-batch/src/test/java/org/sonar/batch/scan/ProjectLockTest.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java

index 0e8ce19844074cbc147e2961629521bc9340eb71..e4c0f3306a1d785e79a24907a824ad8d44e671ff 100644 (file)
@@ -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<Callable<BuildResult>> tasks = new ArrayList<>();
     for (int i = 0; i < nThreads; i++) {
@@ -325,8 +324,20 @@ public class IssuesModeTest {
       });
     }
 
+    boolean expectedError = false;
     for (Future<BuildResult> 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");
     }
   }
 
index 639a932fb363f5455fba663339f448470fc55a2b..63078cdb64825a6b251e79fc7f1a1105d6924430 100644 (file)
  */
 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<TempFolder> {
   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;
   }
 }
index e86114efb2e7dc6d3f57c93557a0c3a0640fd77f..9ccec68c9bd3fb264f2a3d6ffe3584ba5b0e81d4 100644 (file)
  */
 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<TempFolder> {
   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 (file)
index 009aada..0000000
+++ /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<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;
-
-}
index b9a8a1cf460bc92086c3a99166b5ff310443079f..a9b2957870208039fc05172becdc7922676b2e17 100644 (file)
  */
 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 (file)
index 0000000..a373763
--- /dev/null
@@ -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() {
+  }
+
+}
index 869a64b4c93f9f42956988b27d693115e5d4fa53..e31f14a104e1e0579ba9c41109489838b24e1384 100644 (file)
@@ -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,
index 601f90d876c978f14de6dfbb217d8acc66e5193a..3a81a7304d62f1ecdfd490499de1095d72f4eab7 100644 (file)
  */
 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);
   }
 }
index aeb18d14d99de67c05199b2a8bc23904f264540b..6620f59fb98f54bac34adbc20176cf4afc311a62 100644 (file)
@@ -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 (file)
index 5c3bb85..0000000
+++ /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;
-    }
-  }
-}
index bb44b2390d335f0ad463ed76d7a9d0ea60c6afae..e21eea304c4cd130b9cb0ede9f43f2ec7ae1bb95 100644 (file)
@@ -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 (file)
index 0000000..b3eb552
--- /dev/null
@@ -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();
+  }
+
+}
index c023f8a07827fe6728b7bbd50a045dd81e68418b..51b49b34089b12217a3f913497ff4ca67c20da98 100644 (file)
@@ -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