]> source.dussan.org Git - sonar-scanner-cli.git/commitdiff
Merge sonar-home into sonar-runner
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Thu, 1 Oct 2015 12:19:04 +0000 (14:19 +0200)
committerDuarte Meneses <duarte.meneses@sonarsource.com>
Thu, 1 Oct 2015 12:45:52 +0000 (14:45 +0200)
39 files changed:
pom.xml
sonar-runner-api/pom.xml
sonar-runner-api/src/main/java/org/sonar/runner/api/Dirs.java
sonar-runner-api/src/main/java/org/sonar/runner/api/EmbeddedRunner.java
sonar-runner-api/src/main/java/org/sonar/runner/api/LoggerAdapter.java
sonar-runner-api/src/main/java/org/sonar/runner/cache/DeleteFileOnCloseInputStream.java [new file with mode: 0644]
sonar-runner-api/src/main/java/org/sonar/runner/cache/DirectoryLock.java [new file with mode: 0644]
sonar-runner-api/src/main/java/org/sonar/runner/cache/FileCache.java [new file with mode: 0644]
sonar-runner-api/src/main/java/org/sonar/runner/cache/FileCacheBuilder.java [new file with mode: 0644]
sonar-runner-api/src/main/java/org/sonar/runner/cache/FileHashes.java [new file with mode: 0644]
sonar-runner-api/src/main/java/org/sonar/runner/cache/Logger.java [new file with mode: 0644]
sonar-runner-api/src/main/java/org/sonar/runner/cache/PersistentCache.java [new file with mode: 0644]
sonar-runner-api/src/main/java/org/sonar/runner/cache/PersistentCacheBuilder.java [new file with mode: 0644]
sonar-runner-api/src/main/java/org/sonar/runner/cache/PersistentCacheInvalidation.java [new file with mode: 0644]
sonar-runner-api/src/main/java/org/sonar/runner/cache/TTLCacheInvalidation.java [new file with mode: 0644]
sonar-runner-api/src/main/java/org/sonar/runner/cache/package-info.java [new file with mode: 0644]
sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedLauncherFactory.java
sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedLauncherProxy.java
sonar-runner-api/src/main/java/org/sonar/runner/impl/JarDownloader.java
sonar-runner-api/src/main/java/org/sonar/runner/impl/Jars.java
sonar-runner-api/src/main/java/org/sonar/runner/impl/ServerConnection.java
sonar-runner-api/src/main/java/org/sonar/runner/impl/SimulatedLauncher.java
sonar-runner-api/src/main/java/org/sonar/runner/impl/TempCleaning.java
sonar-runner-api/src/test/java/org/sonar/runner/api/DirsTest.java
sonar-runner-api/src/test/java/org/sonar/runner/api/EmbeddedRunnerTest.java
sonar-runner-api/src/test/java/org/sonar/runner/cache/DirectoryLockTest.java [new file with mode: 0644]
sonar-runner-api/src/test/java/org/sonar/runner/cache/FileCacheBuilderTest.java [new file with mode: 0644]
sonar-runner-api/src/test/java/org/sonar/runner/cache/FileCacheTest.java [new file with mode: 0644]
sonar-runner-api/src/test/java/org/sonar/runner/cache/FileHashesTest.java [new file with mode: 0644]
sonar-runner-api/src/test/java/org/sonar/runner/cache/PersistentCacheBuilderTest.java [new file with mode: 0644]
sonar-runner-api/src/test/java/org/sonar/runner/cache/PersistentCacheTest.java [new file with mode: 0644]
sonar-runner-api/src/test/java/org/sonar/runner/cache/TTLCacheInvalidationTest.java [new file with mode: 0644]
sonar-runner-api/src/test/java/org/sonar/runner/impl/IsolatedLauncherFactoryTest.java
sonar-runner-api/src/test/java/org/sonar/runner/impl/IsolatedLauncherProxyTest.java
sonar-runner-api/src/test/java/org/sonar/runner/impl/JarDownloaderTest.java
sonar-runner-api/src/test/java/org/sonar/runner/impl/JarsTest.java
sonar-runner-api/src/test/java/org/sonar/runner/impl/ServerConnectionTest.java
sonar-runner-api/src/test/java/org/sonar/runner/impl/SimulatedLauncherTest.java
sonar-runner-api/src/test/java/org/sonar/runner/impl/TempCleaningTest.java

diff --git a/pom.xml b/pom.xml
index ba1bb66f73dd4e251b414aa48c622d6a433ea484..1b0a6d5128fc10c8cee5e26ba8e6b748657b69a8 100644 (file)
--- a/pom.xml
+++ b/pom.xml
         <version>1.9.5</version>
       </dependency>
       <dependency>
-        <groupId>org.codehaus.sonar</groupId>
-        <artifactId>sonar-home</artifactId>
-        <version>5.2-SNAPSHOT</version>
-      </dependency>
+        <groupId>commons-codec</groupId>
+        <artifactId>commons-codec</artifactId>
+        <version>1.10</version>
+    </dependency>
     </dependencies>
   </dependencyManagement>
 
index a1cf3c66164553d16e3baea939a8fa4f2287db43..24c2fb3717f76bda10b43ffebc70cd8b74b8f1a8 100644 (file)
       <artifactId>jsr305</artifactId>
       <scope>provided</scope>
     </dependency>
-    <dependency>
-      <groupId>org.codehaus.sonar</groupId>
-      <artifactId>sonar-home</artifactId>
-    </dependency>
     <dependency>
       <groupId>commons-io</groupId>
       <artifactId>commons-io</artifactId>
@@ -59,7 +55,7 @@
     <dependency>
     <groupId>org.assertj</groupId>
     <artifactId>assertj-core</artifactId>
-    <version>1.7.1</version>
+    <version>2.2.0</version>
     <scope>test</scope>
   </dependency>
     <dependency>
       <artifactId>commons-lang</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <!-- used to compare results -->
+      <groupId>commons-codec</groupId>
+      <artifactId>commons-codec</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>
index dc39e96173d030fad765e89f3ad038c1d83eb5ff..73c49bbc79c59d6de3229e30a7b9728d4355cab4 100644 (file)
  */
 package org.sonar.runner.api;
 
+import org.sonar.runner.cache.Logger;
+
 import java.io.File;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Properties;
 
-import org.sonar.home.cache.Logger;
 
 class Dirs {
 
index 2a22a054ed1d832e4c0ebd468c3fae1a0b4eb294..021e1296d9fe0036cb955b437154f901b7980b63 100644 (file)
@@ -19,6 +19,8 @@
  */
 package org.sonar.runner.api;
 
+import org.sonar.runner.cache.Logger;
+
 import org.sonar.runner.impl.ClassloadRules;
 
 import java.nio.charset.Charset;
@@ -33,7 +35,6 @@ import java.util.Set;
 
 import javax.annotation.Nullable;
 
-import org.sonar.home.cache.Logger;
 import org.sonar.runner.batch.IsolatedLauncher;
 import org.sonar.runner.impl.InternalProperties;
 import org.sonar.runner.impl.IsolatedLauncherFactory;
index c3b70072cc845dd4e5951a9a9e3b8ddfc1f4066e..c898251542b3bf2ad3bff95b58fe71d6e673bd27 100644 (file)
@@ -19,7 +19,8 @@
  */
 package org.sonar.runner.api;
 
-import org.sonar.home.cache.Logger;
+
+import org.sonar.runner.cache.Logger;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/cache/DeleteFileOnCloseInputStream.java b/sonar-runner-api/src/main/java/org/sonar/runner/cache/DeleteFileOnCloseInputStream.java
new file mode 100644 (file)
index 0000000..15363d1
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.runner.cache;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class DeleteFileOnCloseInputStream extends InputStream {
+  private final InputStream is;
+  private final Path p;
+
+  public DeleteFileOnCloseInputStream(InputStream stream, Path p) {
+    this.is = stream;
+    this.p = p;
+  }
+
+  @Override
+  public int read() throws IOException {
+    return is.read();
+  }
+
+  @Override
+  public int read(byte[] b) throws IOException {
+    return is.read(b);
+  }
+
+  @Override
+  public int read(byte[] b, int off, int len) throws IOException {
+    return is.read(b, off, len);
+  }
+
+  @Override
+  public long skip(long n) throws IOException {
+    return is.skip(n);
+  }
+
+  @Override
+  public synchronized void mark(int readlimit) {
+    is.mark(readlimit);
+  }
+
+  @Override
+  public synchronized void reset() throws IOException {
+    is.reset();
+  }
+
+  @Override
+  public int available() throws IOException {
+    return is.available();
+  }
+
+  @Override
+  public boolean markSupported() {
+    return is.markSupported();
+  }
+
+  @Override
+  public void close() throws IOException {
+    try {
+      super.close();
+    } finally {
+      Files.delete(p);
+    }
+  }
+}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/cache/DirectoryLock.java b/sonar-runner-api/src/main/java/org/sonar/runner/cache/DirectoryLock.java
new file mode 100644 (file)
index 0000000..e063d5e
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.runner.cache;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.RandomAccessFile;
+import java.io.StringWriter;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class DirectoryLock {
+  static final String LOCK_FILE_NAME = ".sonar_lock";
+  private final Path lockFilePath;
+  private final Logger logger;
+
+  private RandomAccessFile lockRandomAccessFile;
+  private FileChannel lockChannel;
+  private FileLock lockFile;
+
+  public DirectoryLock(Path directory, Logger logger) {
+    this.logger = logger;
+    this.lockFilePath = directory.resolve(LOCK_FILE_NAME).toAbsolutePath();
+  }
+
+  public String getFileLockName() {
+    return LOCK_FILE_NAME;
+  }
+  
+  public void lock() {
+    try {
+      lockRandomAccessFile = new RandomAccessFile(lockFilePath.toFile(), "rw");
+      lockChannel = lockRandomAccessFile.getChannel();
+      lockFile = lockChannel.lock(0, 1024, false);
+    } catch (IOException e) {
+      throw new IllegalStateException("Failed to create lock in " + lockFilePath.toString(), e);
+    }
+  }
+  
+  public boolean tryLock() {
+    try {
+      lockRandomAccessFile = new RandomAccessFile(lockFilePath.toFile(), "rw");
+      lockChannel = lockRandomAccessFile.getChannel();
+      lockFile = lockChannel.tryLock(0, 1024, false);
+
+      return lockFile != null;
+    } catch (IOException e) {
+      throw new IllegalStateException("Failed to create lock in " + lockFilePath.toString(), e);
+    }
+  }
+
+  public void unlock() {
+    if (lockFile != null) {
+      try {
+        lockFile.release();
+        lockFile = null;
+      } catch (IOException e) {
+        logger.error("Error releasing lock", e);
+      }
+    }
+    if (lockChannel != null) {
+      try {
+        lockChannel.close();
+        lockChannel = null;
+      } catch (IOException e) {
+        logger.error("Error closing file channel", e);
+      }
+    }
+    if (lockRandomAccessFile != null) {
+      try {
+        lockRandomAccessFile.close();
+        lockRandomAccessFile = null;
+      } catch (IOException e) {
+        logger.error("Error closing file", e);
+      }
+    }
+
+    try {
+      Files.delete(lockFilePath);
+    } catch (IOException e) {
+      // ignore, as an error happens if another process just started to acquire the same lock
+      StringWriter errors = new StringWriter();
+      e.printStackTrace(new PrintWriter(errors));
+      logger.debug("Couldn't delete lock file: " + lockFilePath.toString() + " " + errors.toString());
+    }
+  }
+}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/cache/FileCache.java b/sonar-runner-api/src/main/java/org/sonar/runner/cache/FileCache.java
new file mode 100644 (file)
index 0000000..904ec75
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.runner.cache;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import javax.annotation.CheckForNull;
+
+/**
+ * This class is responsible for managing Sonar batch file cache. You can put file into cache and
+ * later try to retrieve them. MD5 is used to differentiate files (name is not secure as files may come
+ * from different Sonar servers and have same name but be actually different, and same for SNAPSHOTs).
+ */
+public class FileCache {
+
+  /** Maximum loop count when creating temp directories. */
+  private static final int TEMP_DIR_ATTEMPTS = 10000;
+
+  private final File dir;
+  private final File tmpDir;
+  private final FileHashes hashes;
+  private final Logger logger;
+
+  FileCache(File dir, FileHashes fileHashes, Logger logger) {
+    this.hashes = fileHashes;
+    this.logger = logger;
+    this.dir = createDir(dir, "user cache");
+    logger.info(String.format("User cache: %s", dir.getAbsolutePath()));
+    this.tmpDir = createDir(new File(dir, "_tmp"), "temp dir");
+  }
+
+  public static FileCache create(File dir, Logger logger) {
+    return new FileCache(dir, new FileHashes(), logger);
+  }
+
+  public File getDir() {
+    return dir;
+  }
+
+  /**
+   * Look for a file in the cache by its filename and md5 checksum. If the file is not
+   * present then return null.
+   */
+  @CheckForNull
+  public File get(String filename, String hash) {
+    File cachedFile = new File(new File(dir, hash), filename);
+    if (cachedFile.exists()) {
+      return cachedFile;
+    }
+    logger.debug(String.format("No file found in the cache with name %s and hash %s", filename, hash));
+    return null;
+  }
+
+  public interface Downloader {
+    void download(String filename, File toFile) throws IOException;
+  }
+
+  public File get(String filename, String hash, Downloader downloader) {
+    // Does not fail if another process tries to create the directory at the same time.
+    File hashDir = hashDir(hash);
+    File targetFile = new File(hashDir, filename);
+    if (!targetFile.exists()) {
+      File tempFile = newTempFile();
+      download(downloader, filename, tempFile);
+      String downloadedHash = hashes.of(tempFile);
+      if (!hash.equals(downloadedHash)) {
+        throw new IllegalStateException("INVALID HASH: File " + tempFile.getAbsolutePath() + " was expected to have hash " + hash
+          + " but was downloaded with hash " + downloadedHash);
+      }
+      mkdirQuietly(hashDir);
+      renameQuietly(tempFile, targetFile);
+    }
+    return targetFile;
+  }
+
+  private void download(Downloader downloader, String filename, File tempFile) {
+    try {
+      downloader.download(filename, tempFile);
+    } catch (IOException e) {
+      throw new IllegalStateException("Fail to download " + filename + " to " + tempFile, e);
+    }
+  }
+
+  private void renameQuietly(File sourceFile, File targetFile) {
+    boolean rename = sourceFile.renameTo(targetFile);
+    // Check if the file was cached by another process during download
+    if (!rename && !targetFile.exists()) {
+      logger.warn(String.format("Unable to rename %s to %s", sourceFile.getAbsolutePath(), targetFile.getAbsolutePath()));
+      logger.warn("A copy/delete will be tempted but with no guarantee of atomicity");
+      try {
+        Files.move(sourceFile.toPath(), targetFile.toPath());
+      } catch (IOException e) {
+        throw new IllegalStateException("Fail to move " + sourceFile.getAbsolutePath() + " to " + targetFile, e);
+      }
+    }
+  }
+
+  private File hashDir(String hash) {
+    return new File(dir, hash);
+  }
+
+  private static void mkdirQuietly(File hashDir) {
+    try {
+      Files.createDirectories(hashDir.toPath());
+    } catch (IOException e) {
+      throw new IllegalStateException("Fail to create cache directory: " + hashDir, e);
+    }
+  }
+
+  private File newTempFile() {
+    try {
+      return File.createTempFile("fileCache", null, tmpDir);
+    } catch (IOException e) {
+      throw new IllegalStateException("Fail to create temp file in " + tmpDir, e);
+    }
+  }
+
+  public File createTempDir() {
+    String baseName = System.currentTimeMillis() + "-";
+
+    for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) {
+      File tempDir = new File(tmpDir, baseName + counter);
+      if (tempDir.mkdir()) {
+        return tempDir;
+      }
+    }
+    throw new IllegalStateException("Failed to create directory in " + tmpDir);
+  }
+
+  private File createDir(File dir, String debugTitle) {
+    if (!dir.isDirectory() || !dir.exists()) {
+      logger.debug("Create : " + dir.getAbsolutePath());
+      try {
+        Files.createDirectories(dir.toPath());
+      } catch (IOException e) {
+        throw new IllegalStateException("Unable to create " + debugTitle + dir.getAbsolutePath(), e);
+      }
+    }
+    return dir;
+  }
+}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/cache/FileCacheBuilder.java b/sonar-runner-api/src/main/java/org/sonar/runner/cache/FileCacheBuilder.java
new file mode 100644 (file)
index 0000000..9be8e11
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.runner.cache;
+
+import java.io.File;
+
+import javax.annotation.Nullable;
+
+public class FileCacheBuilder {
+  private final Logger logger;
+  private File userHome;
+
+  public FileCacheBuilder(Logger logger) {
+    this.logger = logger;
+  }
+
+  public FileCacheBuilder setUserHome(File d) {
+    this.userHome = d;
+    return this;
+  }
+
+  public FileCacheBuilder setUserHome(@Nullable String path) {
+    this.userHome = (path == null) ? null : new File(path);
+    return this;
+  }
+
+  public FileCache build() {
+    if (userHome == null) {
+      userHome = findHome();
+    }
+    File cacheDir = new File(userHome, "cache");
+    return FileCache.create(cacheDir, logger);
+  }
+  
+  private File findHome() {
+    String path = System.getenv("SONAR_USER_HOME");
+    if (path == null) {
+      // Default
+      path = System.getProperty("user.home") + File.separator + ".sonar";
+    }
+    return new File(path);
+  }
+}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/cache/FileHashes.java b/sonar-runner-api/src/main/java/org/sonar/runner/cache/FileHashes.java
new file mode 100644 (file)
index 0000000..4d1e632
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.runner.cache;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+
+/**
+ * Hashes used to store files in the cache directory.
+ *
+ * @since 3.5
+ */
+public class FileHashes {
+
+  private static final int STREAM_BUFFER_LENGTH = 1024;
+
+  public String of(File file) {
+    try {
+      return of(new FileInputStream(file));
+    } catch (IOException e) {
+      throw new IllegalStateException("Fail to compute hash of: " + file.getAbsolutePath(), e);
+    }
+  }
+
+  /**
+   * Computes the hash of given stream. The stream is closed by this method.
+   */
+  public String of(InputStream input) {
+    try(InputStream is = input) {
+      MessageDigest digest = MessageDigest.getInstance("MD5");
+      byte[] hash = digest(is, digest);
+      return toHex(hash);
+    } catch (Exception e) {
+      throw new IllegalStateException("Fail to compute hash", e);
+    }
+  }
+
+  private static byte[] digest(InputStream input, MessageDigest digest) throws IOException {
+    final byte[] buffer = new byte[STREAM_BUFFER_LENGTH];
+    int read = input.read(buffer, 0, STREAM_BUFFER_LENGTH);
+    while (read > -1) {
+      digest.update(buffer, 0, read);
+      read = input.read(buffer, 0, STREAM_BUFFER_LENGTH);
+    }
+    return digest.digest();
+  }
+
+  static String toHex(byte[] bytes) {
+    BigInteger bi = new BigInteger(1, bytes);
+    return String.format("%0" + (bytes.length << 1) + "x", bi);
+  }
+}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/cache/Logger.java b/sonar-runner-api/src/main/java/org/sonar/runner/cache/Logger.java
new file mode 100644 (file)
index 0000000..03dae61
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.runner.cache;
+
+public interface Logger {
+
+  void debug(String msg);
+
+  void info(String msg);
+
+  void warn(String msg);
+
+  void error(String msg);
+
+  void error(String msg, Throwable t);
+
+}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/cache/PersistentCache.java b/sonar-runner-api/src/main/java/org/sonar/runner/cache/PersistentCache.java
new file mode 100644 (file)
index 0000000..d6359c2
--- /dev/null
@@ -0,0 +1,281 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.runner.cache;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+import static java.nio.file.StandardOpenOption.CREATE;
+import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
+import static java.nio.file.StandardOpenOption.WRITE;
+
+public class PersistentCache {
+  private static final char[] hexArray = "0123456789ABCDEF".toCharArray();
+  private static final Charset ENCODING = StandardCharsets.UTF_8;
+  private static final String DIGEST_ALGO = "MD5";
+
+  private final PersistentCacheInvalidation invalidation;
+  private final Logger logger;
+  private final Path dir;
+  private DirectoryLock lock;
+
+  public PersistentCache(Path dir, PersistentCacheInvalidation invalidation, Logger logger, DirectoryLock lock) {
+    this.dir = dir;
+    this.invalidation = invalidation;
+    this.logger = logger;
+    this.lock = lock;
+
+    reconfigure();
+    logger.debug("cache: " + dir);
+  }
+
+  public synchronized void reconfigure() {
+    try {
+      Files.createDirectories(dir);
+    } catch (IOException e) {
+      throw new IllegalStateException("failed to create cache dir", e);
+    }
+  }
+
+  public Path getDirectory() {
+    return dir;
+  }
+
+  @CheckForNull
+  public synchronized String getString(@Nonnull String obj) throws IOException {
+    byte[] cached = get(obj);
+
+    if (cached == null) {
+      return null;
+    }
+
+    return new String(cached, ENCODING);
+  }
+
+  @CheckForNull
+  public synchronized InputStream getStream(@Nonnull String obj) throws IOException {
+    String key = getKey(obj);
+
+    try {
+      lock();
+      Path path = getCacheCopy(key);
+      if (path == null) {
+        return null;
+      }
+      return new DeleteFileOnCloseInputStream(new FileInputStream(path.toFile()), path);
+
+    } finally {
+      unlock();
+    }
+  }
+
+  @CheckForNull
+  public synchronized byte[] get(@Nonnull String obj) throws IOException {
+    String key = getKey(obj);
+
+    try {
+      lock();
+
+      byte[] cached = getCache(key);
+
+      if (cached != null) {
+        logger.debug("cache hit for " + obj + " -> " + key);
+        return cached;
+      }
+
+      logger.debug("cache miss for " + obj + " -> " + key);
+    } finally {
+      unlock();
+    }
+
+    return null;
+  }
+
+  public synchronized void put(@Nonnull String obj, @Nonnull InputStream stream) throws IOException {
+    String key = getKey(obj);
+    try {
+      lock();
+      putCache(key, stream);
+    } finally {
+      unlock();
+    }
+  }
+
+  public synchronized void put(@Nonnull String obj, @Nonnull byte[] value) throws IOException {
+    String key = getKey(obj);
+    try {
+      lock();
+      putCache(key, value);
+    } finally {
+      unlock();
+    }
+  }
+
+  /**
+   * Deletes all cache entries
+   */
+  public synchronized void clear() {
+    logger.info("cache: clearing");
+    try {
+      lock();
+      deleteCacheEntries(new DirectoryClearFilter());
+    } catch (IOException e) {
+      logger.error("Error clearing cache", e);
+    } finally {
+      unlock();
+    }
+  }
+
+  /**
+   * Deletes cache entries that are no longer valid according to the default expiration time period.
+   */
+  public synchronized void clean() {
+    logger.info("cache: cleaning");
+    try {
+      lock();
+      deleteCacheEntries(new DirectoryCleanFilter());
+    } catch (IOException e) {
+      logger.error("Error cleaning cache", e);
+    } finally {
+      unlock();
+    }
+  }
+
+  private void lock() throws IOException {
+    lock.lock();
+  }
+
+  private void unlock() {
+    lock.unlock();
+  }
+
+  private static String getKey(String uri) {
+    try {
+      String key = uri;
+      MessageDigest digest = MessageDigest.getInstance(DIGEST_ALGO);
+      digest.update(key.getBytes(StandardCharsets.UTF_8));
+      return byteArrayToHex(digest.digest());
+    } catch (NoSuchAlgorithmException e) {
+      throw new IllegalStateException("Couldn't create hash", e);
+    }
+  }
+
+  private void deleteCacheEntries(DirectoryStream.Filter<Path> filter) throws IOException {
+    try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, filter)) {
+      for (Path p : stream) {
+        try {
+          Files.delete(p);
+        } catch (Exception e) {
+          logger.error("Error deleting " + p, e);
+        }
+      }
+    }
+  }
+
+  private class DirectoryClearFilter implements DirectoryStream.Filter<Path> {
+    @Override
+    public boolean accept(Path entry) throws IOException {
+      return !lock.getFileLockName().equals(entry.getFileName().toString());
+    }
+  }
+
+  private class DirectoryCleanFilter implements DirectoryStream.Filter<Path> {
+    @Override
+    public boolean accept(Path entry) throws IOException {
+      if (lock.getFileLockName().equals(entry.getFileName().toString())) {
+        return false;
+      }
+
+      return invalidation.test(entry);
+    }
+  }
+
+  private void putCache(String key, byte[] value) throws IOException {
+    Path cachePath = getCacheEntryPath(key);
+    Files.write(cachePath, value, CREATE, WRITE, TRUNCATE_EXISTING);
+  }
+
+  private void putCache(String key, InputStream stream) throws IOException {
+    Path cachePath = getCacheEntryPath(key);
+    Files.copy(stream, cachePath, StandardCopyOption.REPLACE_EXISTING);
+  }
+
+  private byte[] getCache(String key) throws IOException {
+    Path cachePath = getCacheEntryPath(key);
+
+    if (!validateCacheEntry(cachePath)) {
+      return null;
+    }
+
+    return Files.readAllBytes(cachePath);
+  }
+
+  private Path getCacheCopy(String key) throws IOException {
+    Path cachePath = getCacheEntryPath(key);
+
+    if (!validateCacheEntry(cachePath)) {
+      return null;
+    }
+
+    Path temp = Files.createTempFile("sonar_cache", null);
+    Files.copy(cachePath, temp, StandardCopyOption.REPLACE_EXISTING);
+    return temp;
+  }
+
+  private boolean validateCacheEntry(Path cacheEntryPath) throws IOException {
+    if (!Files.exists(cacheEntryPath)) {
+      return false;
+    }
+
+    if (invalidation.test(cacheEntryPath)) {
+      logger.debug("cache: evicting entry");
+      Files.delete(cacheEntryPath);
+      return false;
+    }
+
+    return true;
+  }
+
+  private Path getCacheEntryPath(String key) {
+    return dir.resolve(key);
+  }
+
+  public static String byteArrayToHex(byte[] bytes) {
+    char[] hexChars = new char[bytes.length * 2];
+    for (int j = 0; j < bytes.length; j++) {
+      int v = bytes[j] & 0xFF;
+      hexChars[j * 2] = hexArray[v >>> 4];
+      hexChars[j * 2 + 1] = hexArray[v & 0x0F];
+    }
+    return new String(hexChars);
+  }
+}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/cache/PersistentCacheBuilder.java b/sonar-runner-api/src/main/java/org/sonar/runner/cache/PersistentCacheBuilder.java
new file mode 100644 (file)
index 0000000..2e34d40
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.runner.cache;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Nullable;
+
+/**
+ * Cache files will be placed in 3 areas:
+ * <pre>
+ *   &lt;sonar_home&gt;/ws_cache/&lt;server_url&gt;-&lt;version&gt;/projects/&lt;project&gt;/
+ *   &lt;sonar_home&gt;/ws_cache/&lt;server_url&gt;-&lt;version&gt;/global/
+ *   &lt;sonar_home&gt;/ws_cache/&lt;server_url&gt;-&lt;version&gt;/local/
+ * </pre>
+ */
+public class PersistentCacheBuilder {
+  private static final long DEFAULT_EXPIRE_DURATION = TimeUnit.MILLISECONDS.convert(9999L, TimeUnit.DAYS);
+  private static final String DIR_NAME = "ws_cache";
+
+  private Path cacheBasePath;
+  private Path relativePath;
+  private final Logger logger;
+
+  public PersistentCacheBuilder(Logger logger) {
+    this.logger = logger;
+  }
+
+  public PersistentCacheBuilder setAreaForProject(String serverUrl, String serverVersion, String projectKey) {
+    relativePath = Paths.get(sanitizeFilename(serverUrl))
+      .resolve(sanitizeFilename(serverVersion))
+      .resolve("projects")
+      .resolve(sanitizeFilename(projectKey));
+    return this;
+  }
+
+  public PersistentCacheBuilder setAreaForGlobal(String serverUrl) {
+    relativePath = Paths.get(sanitizeFilename(serverUrl))
+      .resolve("global");
+    return this;
+  }
+
+  public PersistentCacheBuilder setAreaForLocalProject(String serverUrl, String serverVersion) {
+    relativePath = Paths.get(sanitizeFilename(serverUrl))
+      .resolve(sanitizeFilename(serverVersion))
+      .resolve("local");
+    return this;
+  }
+
+  public PersistentCacheBuilder setSonarHome(@Nullable Path p) {
+    if (p != null) {
+      this.cacheBasePath = p.resolve(DIR_NAME);
+    }
+    return this;
+  }
+
+  public PersistentCache build() {
+    if (relativePath == null) {
+      throw new IllegalStateException("area must be set before building");
+    }
+    if (cacheBasePath == null) {
+      setSonarHome(findHome());
+    }
+    Path cachePath = cacheBasePath.resolve(relativePath);
+    DirectoryLock lock = new DirectoryLock(cacheBasePath, logger);
+    PersistentCacheInvalidation criteria = new TTLCacheInvalidation(DEFAULT_EXPIRE_DURATION);
+    return new PersistentCache(cachePath, criteria, logger, lock);
+  }
+
+  private static Path findHome() {
+    String home = System.getenv("SONAR_USER_HOME");
+
+    if (home != null) {
+      return Paths.get(home);
+    }
+
+    home = System.getProperty("user.home");
+    return Paths.get(home, ".sonar");
+  }
+
+  private static String sanitizeFilename(String name) {
+    try {
+      return URLEncoder.encode(name, StandardCharsets.UTF_8.name());
+    } catch (UnsupportedEncodingException e) {
+      throw new IllegalStateException("Couldn't sanitize filename: " + name, e);
+    }
+  }
+}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/cache/PersistentCacheInvalidation.java b/sonar-runner-api/src/main/java/org/sonar/runner/cache/PersistentCacheInvalidation.java
new file mode 100644 (file)
index 0000000..d320650
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.runner.cache;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+public interface PersistentCacheInvalidation {
+  boolean test(Path cacheEntryPath) throws IOException;
+}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/cache/TTLCacheInvalidation.java b/sonar-runner-api/src/main/java/org/sonar/runner/cache/TTLCacheInvalidation.java
new file mode 100644 (file)
index 0000000..7368256
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.runner.cache;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+
+public class TTLCacheInvalidation implements PersistentCacheInvalidation {
+  private final long durationToExpireMs;
+
+  public TTLCacheInvalidation(long durationToExpireMs) {
+    this.durationToExpireMs = durationToExpireMs;
+  }
+  
+  @Override
+  public boolean test(Path cacheEntryPath) throws IOException {
+    BasicFileAttributes attr = Files.readAttributes(cacheEntryPath, BasicFileAttributes.class);
+    long modTime = attr.lastModifiedTime().toMillis();
+
+    long age = System.currentTimeMillis() - modTime;
+
+    if (age > durationToExpireMs) {
+      return true;
+    }
+
+    return false;
+  }
+
+}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/cache/package-info.java b/sonar-runner-api/src/main/java/org/sonar/runner/cache/package-info.java
new file mode 100644 (file)
index 0000000..c1b3daa
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.runner.cache;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
index e3bf2c8d4ef2e98c3dabc4bc01dd06e9e49aebb1..be92d0208410855f5264bdf716e43912a5a72911 100644 (file)
  */
 package org.sonar.runner.impl;
 
+import org.sonar.runner.batch.IsolatedLauncher;
+import org.sonar.runner.cache.Logger;
+import org.sonar.runner.cache.PersistentCache;
+import org.sonar.runner.cache.PersistentCacheBuilder;
+
 import java.io.File;
 import java.nio.file.Paths;
 import java.security.AccessController;
@@ -26,11 +31,6 @@ import java.security.PrivilegedAction;
 import java.util.List;
 import java.util.Properties;
 
-import org.sonar.home.cache.Logger;
-import org.sonar.home.cache.PersistentCache;
-import org.sonar.home.cache.PersistentCacheBuilder;
-import org.sonar.runner.batch.IsolatedLauncher;
-
 public class IsolatedLauncherFactory {
   static final String ISOLATED_LAUNCHER_IMPL = "org.sonar.runner.batch.BatchIsolatedLauncher";
   private final TempCleaning tempCleaning;
index 629ca435371dbdd2255c8336551fca1ea3fa6e92..f0a1bff2dcacfd7d38e002d517d8678d3e098cc3 100644 (file)
  */
 package org.sonar.runner.impl;
 
+import org.sonar.runner.cache.Logger;
+
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.lang.reflect.UndeclaredThrowableException;
-import org.sonar.home.cache.Logger;
 
 public class IsolatedLauncherProxy implements InvocationHandler {
   private final Object proxied;
index 4160754ec1db858e974a334ae5099d1fa829b96d..3f372dcd8d48ec0f4c5f8a52f4c5931122c6936b 100644 (file)
  */
 package org.sonar.runner.impl;
 
+import org.sonar.runner.cache.Logger;
+
 import java.io.File;
 import java.util.List;
 import java.util.Properties;
 
-import org.sonar.home.cache.Logger;
-
 class JarDownloader {
   private final ServerConnection serverConnection;
   private final Logger logger;
index b526df9bec7da1dec2c393fcf8b842c008993044..972675bbb29fbf9f206b3429159b1265dcd1a58e 100644 (file)
  */
 package org.sonar.runner.impl;
 
+import org.sonar.runner.cache.FileCache;
+import org.sonar.runner.cache.FileCacheBuilder;
+import org.sonar.runner.cache.Logger;
+
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Properties;
 
-import org.sonar.home.cache.FileCache;
-import org.sonar.home.cache.FileCacheBuilder;
-import org.sonar.home.cache.Logger;
-
 class Jars {
   private static final String BOOTSTRAP_INDEX_PATH = "/batch_bootstrap/index";
   static final String BATCH_PATH = "/batch/";
index 8f24f1f6460fab7d7dd797ad047105ce2ae1e98b..30c3cf1bae7603cc42ceb3656e7e69b3e81586b7 100644 (file)
  */
 package org.sonar.runner.impl;
 
-import com.github.kevinsawicki.http.HttpRequest.HttpRequestException;
-
 import com.github.kevinsawicki.http.HttpRequest;
+import com.github.kevinsawicki.http.HttpRequest.HttpRequestException;
+import org.apache.commons.io.FileUtils;
+import org.sonar.runner.cache.Logger;
+import org.sonar.runner.cache.PersistentCache;
 
 import java.io.File;
 import java.io.IOException;
@@ -33,10 +35,6 @@ import java.util.Properties;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import org.apache.commons.io.FileUtils;
-import org.sonar.home.cache.Logger;
-import org.sonar.home.cache.PersistentCache;
-
 class ServerConnection {
 
   private static final String SONAR_SERVER_CAN_NOT_BE_REACHED = "SonarQube server ''{0}'' can not be reached";
index b4cc365b50395c4accc718223573448c3f3df9d7..aac6b25b88ca0f68f8bbc4671a7d99be2eb92f61 100644 (file)
  */
 package org.sonar.runner.impl;
 
-import org.sonar.home.cache.Logger;
-
-import javax.annotation.Nullable;
-
+import org.sonar.runner.batch.IsolatedLauncher;
 import org.sonar.runner.batch.IssueListener;
 import org.sonar.runner.batch.LogOutput;
+import org.sonar.runner.cache.Logger;
+
+import javax.annotation.Nullable;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -32,8 +32,6 @@ import java.io.OutputStream;
 import java.util.List;
 import java.util.Properties;
 
-import org.sonar.runner.batch.IsolatedLauncher;
-
 public class SimulatedLauncher implements IsolatedLauncher {
   private final String version;
   private final Logger logger;
index 89c07c796cce1c6335bfe585dcc59bf924661b65..ad2f70f85f92b2f4ced98674148b783ccba473cf 100644 (file)
  */
 package org.sonar.runner.impl;
 
-import java.io.File;
-import java.util.Collection;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.filefilter.AgeFileFilter;
 import org.apache.commons.io.filefilter.AndFileFilter;
 import org.apache.commons.io.filefilter.PrefixFileFilter;
-import org.sonar.home.cache.Logger;
+import org.sonar.runner.cache.Logger;
+
+import java.io.File;
+import java.util.Collection;
 
 /**
  * The file sonar-runner-batch.jar is locked by the classloader on Windows and can't be dropped at the end of the execution.
index d8a4eda95629fa9484131210b6c110891fa201b0..9cb24dccd5023eb31c35dd210c01686144c8dc3b 100644 (file)
  */
 package org.sonar.runner.api;
 
+import org.sonar.runner.cache.Logger;
+
 import java.io.File;
 import java.util.Properties;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
-import org.sonar.home.cache.Logger;
-
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 
index 5464bd85f61abbd389a0404d271531f5fd501595..e9d4535a14c883eb8337d046e98a06e98fe6e185 100644 (file)
  */
 package org.sonar.runner.api;
 
-import org.sonar.runner.impl.ClassloadRules;
-
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
 import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.ArgumentMatcher;
+import org.sonar.runner.batch.IsolatedLauncher;
+import org.sonar.runner.cache.Logger;
+import org.sonar.runner.impl.ClassloadRules;
+import org.sonar.runner.impl.IsolatedLauncherFactory;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -31,18 +38,10 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Properties;
 
-import static org.mockito.Matchers.eq;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.mockito.ArgumentMatcher;
-import org.sonar.home.cache.Logger;
-import org.sonar.runner.batch.IsolatedLauncher;
-import org.sonar.runner.impl.IsolatedLauncherFactory;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -295,7 +294,7 @@ public class EmbeddedRunnerTest {
     expectedException.expectMessage("started");
     runner.runAnalysis(new Properties());
   }
-  
+
   @Test
   public void cannot_start_twice() {
     runner.start();
diff --git a/sonar-runner-api/src/test/java/org/sonar/runner/cache/DirectoryLockTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/cache/DirectoryLockTest.java
new file mode 100644 (file)
index 0000000..0d4a8f5
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.runner.cache;
+
+import static org.mockito.Mockito.mock;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.rules.ExpectedException;
+
+import java.nio.channels.OverlappingFileLockException;
+import java.nio.file.Paths;
+
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+
+public class DirectoryLockTest {
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+  @Rule
+  public ExpectedException exception = ExpectedException.none();
+  private DirectoryLock lock;
+
+  @Before
+  public void setUp() {
+    lock = new DirectoryLock(temp.getRoot().toPath(), mock(Logger.class));
+  }
+
+  @Test
+  public void lock() {
+    assertThat(temp.getRoot().list()).isEmpty();
+    lock.lock();
+    assertThat(temp.getRoot().toPath().resolve(".sonar_lock")).exists();
+    lock.unlock();
+    assertThat(temp.getRoot().list()).isEmpty();
+  }
+
+  @Test
+  public void tryLock() {
+    assertThat(temp.getRoot().list()).isEmpty();
+    lock.tryLock();
+    assertThat(temp.getRoot().toPath().resolve(".sonar_lock")).exists();
+    lock.unlock();
+    assertThat(temp.getRoot().list()).isEmpty();
+  }
+
+  @Test(expected = OverlappingFileLockException.class)
+  public void error_2locks() {
+    assertThat(temp.getRoot().list()).isEmpty();
+    lock.lock();
+    lock.lock();
+  }
+
+  @Test
+  public void unlockWithoutLock() {
+    lock.unlock();
+  }
+
+  @Test
+  public void errorCreatingLock() {
+    lock = new DirectoryLock(Paths.get("non", "existing", "path"), mock(Logger.class));
+
+    exception.expect(IllegalStateException.class);
+    exception.expectMessage("Failed to create lock");
+    lock.lock();
+  }
+
+  @Test
+  public void errorTryLock() {
+    lock = new DirectoryLock(Paths.get("non", "existing", "path"), mock(Logger.class));
+
+    exception.expect(IllegalStateException.class);
+    exception.expectMessage("Failed to create lock");
+    lock.tryLock();
+  }
+}
diff --git a/sonar-runner-api/src/test/java/org/sonar/runner/cache/FileCacheBuilderTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/cache/FileCacheBuilderTest.java
new file mode 100644 (file)
index 0000000..d146298
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.runner.cache;
+
+import java.io.File;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class FileCacheBuilderTest {
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Test
+  public void setUserHome() throws Exception {
+    File userHome = temp.newFolder();
+    FileCache cache = new FileCacheBuilder(mock(Logger.class)).setUserHome(userHome).build();
+
+    assertThat(cache.getDir()).isDirectory().exists();
+    assertThat(cache.getDir().getName()).isEqualTo("cache");
+    assertThat(cache.getDir().getParentFile()).isEqualTo(userHome);
+  }
+
+  @Test
+  public void user_home_property_can_be_null() {
+    FileCache cache = new FileCacheBuilder(mock(Logger.class)).setUserHome((String) null).build();
+
+    // does not fail. It uses default path or env variable
+    assertThat(cache.getDir()).isDirectory().exists();
+    assertThat(cache.getDir().getName()).isEqualTo("cache");
+  }
+
+  @Test
+  public void use_default_path_or_env_variable() {
+    FileCache cache = new FileCacheBuilder(mock(Logger.class)).build();
+
+    assertThat(cache.getDir()).isDirectory().exists();
+    assertThat(cache.getDir().getName()).isEqualTo("cache");
+  }
+}
diff --git a/sonar-runner-api/src/test/java/org/sonar/runner/cache/FileCacheTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/cache/FileCacheTest.java
new file mode 100644 (file)
index 0000000..b88105d
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.runner.cache;
+
+import java.io.File;
+import java.io.IOException;
+import org.apache.commons.io.FileUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class FileCacheTest {
+  @Rule
+  public TemporaryFolder tempFolder = new TemporaryFolder();
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  @Test
+  public void not_in_cache() throws IOException {
+    FileCache cache = FileCache.create(tempFolder.newFolder(), mock(Logger.class));
+    assertThat(cache.get("sonar-foo-plugin-1.5.jar", "ABCDE")).isNull();
+  }
+
+  @Test
+  public void found_in_cache() throws IOException {
+    FileCache cache = FileCache.create(tempFolder.newFolder(), mock(Logger.class));
+
+    // populate the cache. Assume that hash is correct.
+    File cachedFile = new File(new File(cache.getDir(), "ABCDE"), "sonar-foo-plugin-1.5.jar");
+    FileUtils.write(cachedFile, "body");
+
+    assertThat(cache.get("sonar-foo-plugin-1.5.jar", "ABCDE")).isNotNull().exists().isEqualTo(cachedFile);
+  }
+
+  @Test
+  public void download_and_add_to_cache() throws IOException {
+    FileHashes hashes = mock(FileHashes.class);
+    FileCache cache = new FileCache(tempFolder.newFolder(), hashes, mock(Logger.class));
+    when(hashes.of(any(File.class))).thenReturn("ABCDE");
+
+    FileCache.Downloader downloader = new FileCache.Downloader() {
+      public void download(String filename, File toFile) throws IOException {
+        FileUtils.write(toFile, "body");
+      }
+    };
+    File cachedFile = cache.get("sonar-foo-plugin-1.5.jar", "ABCDE", downloader);
+    assertThat(cachedFile).isNotNull().exists().isFile();
+    assertThat(cachedFile.getName()).isEqualTo("sonar-foo-plugin-1.5.jar");
+    assertThat(cachedFile.getParentFile().getParentFile()).isEqualTo(cache.getDir());
+    assertThat(FileUtils.readFileToString(cachedFile)).isEqualTo("body");
+  }
+
+  @Test
+  public void download_corrupted_file() throws IOException {
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("INVALID HASH");
+
+    FileHashes hashes = mock(FileHashes.class);
+    FileCache cache = new FileCache(tempFolder.newFolder(), hashes, mock(Logger.class));
+    when(hashes.of(any(File.class))).thenReturn("VWXYZ");
+
+    FileCache.Downloader downloader = new FileCache.Downloader() {
+      public void download(String filename, File toFile) throws IOException {
+        FileUtils.write(toFile, "corrupted body");
+      }
+    };
+    cache.get("sonar-foo-plugin-1.5.jar", "ABCDE", downloader);
+  }
+
+  @Test
+  public void concurrent_download() throws IOException {
+    FileHashes hashes = mock(FileHashes.class);
+    when(hashes.of(any(File.class))).thenReturn("ABCDE");
+    final FileCache cache = new FileCache(tempFolder.newFolder(), hashes, mock(Logger.class));
+
+    FileCache.Downloader downloader = new FileCache.Downloader() {
+      public void download(String filename, File toFile) throws IOException {
+        // Emulate a concurrent download that adds file to cache before
+        File cachedFile = new File(new File(cache.getDir(), "ABCDE"), "sonar-foo-plugin-1.5.jar");
+        FileUtils.write(cachedFile, "downloaded by other");
+
+        FileUtils.write(toFile, "downloaded by me");
+      }
+    };
+
+    // do not fail
+    File cachedFile = cache.get("sonar-foo-plugin-1.5.jar", "ABCDE", downloader);
+    assertThat(cachedFile).isNotNull().exists().isFile();
+    assertThat(cachedFile.getName()).isEqualTo("sonar-foo-plugin-1.5.jar");
+    assertThat(cachedFile.getParentFile().getParentFile()).isEqualTo(cache.getDir());
+    assertThat(FileUtils.readFileToString(cachedFile)).contains("downloaded by");
+  }
+}
diff --git a/sonar-runner-api/src/test/java/org/sonar/runner/cache/FileHashesTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/cache/FileHashesTest.java
new file mode 100644 (file)
index 0000000..582e10f
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.runner.cache;
+
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.security.SecureRandom;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class FileHashesTest {
+
+  SecureRandom secureRandom = new SecureRandom();
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Test
+  public void test_md5_hash() {
+    assertThat(hash("sonar")).isEqualTo("d85e336d61f5344395c42126fac239bc");
+
+    // compare results with commons-codec
+    for (int index = 0; index < 100; index++) {
+      String random = randomString();
+      assertThat(hash(random)).as(random).isEqualTo(
+        DigestUtils.md5Hex(random).toLowerCase()
+        );
+    }
+  }
+
+  @Test
+  public void test_hash_file() throws IOException {
+    File f = temp.newFile();
+    Files.write(f.toPath(), "sonar".getBytes(StandardCharsets.UTF_8));
+    assertThat(hashFile(f)).isEqualTo("d85e336d61f5344395c42126fac239bc");
+  }
+
+  @Test
+  public void test_toHex() {
+    // lower-case
+    assertThat(FileHashes.toHex("aloa_bi_bop_a_loula".getBytes())).isEqualTo("616c6f615f62695f626f705f615f6c6f756c61");
+
+    // compare results with commons-codec
+    for (int index = 0; index < 100; index++) {
+      String random = randomString();
+      assertThat(FileHashes.toHex(random.getBytes())).as(random).isEqualTo(
+        Hex.encodeHexString(random.getBytes()).toLowerCase()
+        );
+    }
+  }
+
+  @Test
+  public void fail_if_file_does_not_exist() throws IOException {
+    File file = temp.newFile("does_not_exist");
+    FileUtils.forceDelete(file);
+
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("Fail to compute hash of: " + file.getAbsolutePath());
+
+    new FileHashes().of(file);
+  }
+
+  @Test
+  public void fail_if_stream_is_closed() throws Exception {
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("Fail to compute hash");
+
+    InputStream input = mock(InputStream.class);
+    when(input.read(any(byte[].class), anyInt(), anyInt())).thenThrow(new IllegalThreadStateException());
+    new FileHashes().of(input);
+  }
+
+  private String randomString() {
+    return new BigInteger(130, secureRandom).toString(32);
+  }
+
+  private String hash(String s) {
+    InputStream in = new ByteArrayInputStream(s.getBytes());
+    try {
+      return new FileHashes().of(in);
+    } finally {
+      IOUtils.closeQuietly(in);
+    }
+  }
+
+  private String hashFile(File f) {
+    return new FileHashes().of(f);
+  }
+}
diff --git a/sonar-runner-api/src/test/java/org/sonar/runner/cache/PersistentCacheBuilderTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/cache/PersistentCacheBuilderTest.java
new file mode 100644 (file)
index 0000000..1fa0418
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.runner.cache;
+
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.Mockito.mock;
+
+public class PersistentCacheBuilderTest {
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Test
+  public void user_home_property_can_be_null() {
+    PersistentCache cache = new PersistentCacheBuilder(mock(Logger.class)).setSonarHome(null).setAreaForGlobal("url").build();
+    assertTrue(Files.isDirectory(cache.getDirectory()));
+    assertThat(cache.getDirectory()).endsWith(Paths.get("url", "global"));
+  }
+
+  @Test
+  public void set_user_home() {
+    PersistentCache cache = new PersistentCacheBuilder(mock(Logger.class)).setSonarHome(temp.getRoot().toPath()).setAreaForGlobal("url").build();
+
+    assertThat(cache.getDirectory()).isDirectory();
+    assertThat(cache.getDirectory()).startsWith(temp.getRoot().toPath());
+    assertTrue(Files.isDirectory(cache.getDirectory()));
+  }
+
+  @Test
+  public void read_system_env() {
+    assumeTrue(System.getenv("SONAR_USER_HOME") == null);
+
+    System.setProperty("user.home", temp.getRoot().getAbsolutePath());
+
+    PersistentCache cache = new PersistentCacheBuilder(mock(Logger.class)).setAreaForGlobal("url").build();
+    assertTrue(Files.isDirectory(cache.getDirectory()));
+    assertThat(cache.getDirectory()).startsWith(temp.getRoot().toPath());
+  }
+
+  @Test
+  public void directories() {
+    System.setProperty("user.home", temp.getRoot().getAbsolutePath());
+
+    PersistentCache cache = new PersistentCacheBuilder(mock(Logger.class)).setAreaForProject("url", "0", "proj").build();
+    assertThat(cache.getDirectory()).endsWith(Paths.get(".sonar", "ws_cache", "url", "0", "projects", "proj"));
+
+    cache = new PersistentCacheBuilder(mock(Logger.class)).setAreaForLocalProject("url", "0").build();
+    assertThat(cache.getDirectory()).endsWith(Paths.get(".sonar", "ws_cache", "url", "0", "local"));
+
+    cache = new PersistentCacheBuilder(mock(Logger.class)).setAreaForGlobal("url").build();
+    assertThat(cache.getDirectory()).endsWith(Paths.get(".sonar", "ws_cache", "url", "global"));
+  }
+}
diff --git a/sonar-runner-api/src/test/java/org/sonar/runner/cache/PersistentCacheTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/cache/PersistentCacheTest.java
new file mode 100644 (file)
index 0000000..911b863
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.runner.cache;
+
+import org.apache.commons.io.IOUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static org.mockito.Matchers.any;
+
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.verify;
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class PersistentCacheTest {
+  private final static String URI = "key1";
+  private final static String VALUE = "cache content";
+  private PersistentCache cache = null;
+  private DirectoryLock lock = null;
+  private PersistentCacheInvalidation invalidation = null;
+
+  @Rule
+  public TemporaryFolder tmp = new TemporaryFolder();
+
+  @Before
+  public void setUp() throws IOException {
+    invalidation = mock(PersistentCacheInvalidation.class);
+    when(invalidation.test(any(Path.class))).thenReturn(false);
+    lock = mock(DirectoryLock.class);
+    when(lock.getFileLockName()).thenReturn("lock");
+    cache = new PersistentCache(tmp.getRoot().toPath(), invalidation, mock(Logger.class), lock);
+  }
+
+  @Test
+  public void testCacheMiss() throws Exception {
+    assertCacheHit(false);
+  }
+
+  @Test
+  public void testClean() throws Exception {
+    Path lockFile = cache.getDirectory().resolve("lock");
+    // puts entry
+    cache.put(URI, VALUE.getBytes(StandardCharsets.UTF_8));
+    Files.write(lockFile, "test".getBytes(StandardCharsets.UTF_8));
+    assertCacheHit(true);
+    when(invalidation.test(any(Path.class))).thenReturn(true);
+    cache.clean();
+    when(invalidation.test(any(Path.class))).thenReturn(false);
+    assertCacheHit(false);
+    // lock file should not get deleted
+    assertThat(new String(Files.readAllBytes(lockFile), StandardCharsets.UTF_8)).isEqualTo("test");
+  }
+
+  @Test
+  public void testStream() throws IOException {
+    cache.put("id", "test".getBytes());
+    InputStream stream = cache.getStream("id");
+    assertThat(IOUtils.toString(stream)).isEqualTo("test");
+
+    assertThat(cache.getStream("non existing")).isNull();
+  }
+
+  @Test
+  public void testClear() throws Exception {
+    Path lockFile = cache.getDirectory().resolve("lock");
+    cache.put(URI, VALUE.getBytes(StandardCharsets.UTF_8));
+    Files.write(lockFile, "test".getBytes(StandardCharsets.UTF_8));
+    assertCacheHit(true);
+    cache.clear();
+    assertCacheHit(false);
+    // lock file should not get deleted
+    assertThat(new String(Files.readAllBytes(lockFile), StandardCharsets.UTF_8)).isEqualTo("test");
+  }
+
+  @Test
+  public void testCacheHit() throws Exception {
+    cache.put(URI, VALUE.getBytes(StandardCharsets.UTF_8));
+    assertCacheHit(true);
+  }
+
+  @Test
+  public void testReconfigure() throws Exception {
+    assertCacheHit(false);
+    cache.put(URI, VALUE.getBytes(StandardCharsets.UTF_8));
+    assertCacheHit(true);
+
+    File root = tmp.getRoot();
+    FileUtils.deleteQuietly(root);
+
+    // should re-create cache directory and start using the cache
+    cache.reconfigure();
+    assertThat(root).exists();
+
+    assertCacheHit(false);
+    cache.put(URI, VALUE.getBytes(StandardCharsets.UTF_8));
+    assertCacheHit(true);
+  }
+
+  @Test
+  public void testExpiration() throws Exception {
+    when(invalidation.test(any(Path.class))).thenReturn(true);
+    cache.put(URI, VALUE.getBytes(StandardCharsets.UTF_8));
+    assertCacheHit(false);
+  }
+
+  private void assertCacheHit(boolean hit) throws Exception {
+    assertCacheHit(cache, hit);
+  }
+
+  private void assertCacheHit(PersistentCache pCache, boolean hit) throws Exception {
+    String expected = hit ? VALUE : null;
+    assertThat(pCache.getString(URI)).isEqualTo(expected);
+    verify(lock, atLeast(1)).unlock();
+  }
+
+}
diff --git a/sonar-runner-api/src/test/java/org/sonar/runner/cache/TTLCacheInvalidationTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/cache/TTLCacheInvalidationTest.java
new file mode 100644 (file)
index 0000000..06cf037
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.runner.cache;
+
+import org.junit.Before;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+
+public class TTLCacheInvalidationTest {
+  private Path testFile;
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Before
+  public void setUp() throws IOException {
+    testFile = temp.newFile().toPath();
+  }
+
+  @Test
+  public void testExpired() throws IOException {
+    TTLCacheInvalidation invalidation = new TTLCacheInvalidation(-100);
+    assertThat(invalidation.test(testFile)).isEqualTo(true);
+  }
+
+  @Test
+  public void testValid() throws IOException {
+    TTLCacheInvalidation invalidation = new TTLCacheInvalidation(100_000);
+    assertThat(invalidation.test(testFile)).isEqualTo(false);
+  }
+}
index d29873ed7f58ffb33a059c2de72553919371bdbd..fa71aceee35fb0ea1d0908481e25df272beaf7e9 100644 (file)
  */
 package org.sonar.runner.impl;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.runner.batch.IsolatedLauncher;
 import org.sonar.runner.batch.IssueListener;
+import org.sonar.runner.batch.LogOutput;
+import org.sonar.runner.cache.Logger;
 
 import java.util.HashSet;
 import java.util.List;
 import java.util.Properties;
 
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.home.cache.Logger;
-import org.sonar.runner.batch.IsolatedLauncher;
-import org.sonar.runner.batch.LogOutput;
 import static org.fest.assertions.Fail.fail;
 import static org.mockito.Mockito.mock;
 
index 9d47dad6f5e607ff5640c48d3958f519bb301aa7..e3553e48d355e13ee14ee9a802e70f6d1af3320c 100644 (file)
  */
 package org.sonar.runner.impl;
 
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.concurrent.Callable;
 import org.junit.Before;
 import org.junit.Test;
-import org.sonar.home.cache.Logger;
 import org.sonar.runner.batch.BatchIsolatedLauncher;
+import org.sonar.runner.cache.Logger;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.concurrent.Callable;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
index 284fb149b2d47480fd0aaf37bb8a66c1ec1e7462..6c00d88669cbde257b55b63fb83f0af2aea3117a 100644 (file)
  */
 package org.sonar.runner.impl;
 
+import org.junit.Test;
+import org.sonar.runner.cache.Logger;
+
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Properties;
-import org.junit.Test;
-import org.sonar.home.cache.Logger;
 
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Mockito.doReturn;
index 222c693c6fddf001d0456070044884f550f09f9c..762473c6289eecd9f78b2f2ce3de58e2e841c4c7 100644 (file)
  */
 package org.sonar.runner.impl;
 
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.runner.cache.FileCache;
+import org.sonar.runner.cache.Logger;
+
 import java.io.File;
 import java.io.IOException;
 import java.util.List;
 import java.util.Properties;
 
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.home.cache.FileCache;
-import org.sonar.home.cache.Logger;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.fest.assertions.Fail.fail;
 import static org.mockito.Matchers.any;
index fe6069fa2d5e3758e5ea47ed91bc8305725ad5a0..46d8ab75f95d0012bdf161226c7524ca1c829a2d 100644 (file)
 package org.sonar.runner.impl;
 
 import com.github.kevinsawicki.http.HttpRequest;
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.runner.cache.Logger;
+import org.sonar.runner.cache.PersistentCache;
+import org.sonar.runner.cache.PersistentCacheBuilder;
 
 import java.io.File;
 import java.io.IOException;
@@ -27,23 +35,15 @@ import java.net.ConnectException;
 import java.net.SocketTimeoutException;
 import java.util.Properties;
 
-import static org.mockito.Matchers.startsWith;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.junit.Assert.*;
-import org.apache.commons.io.FileUtils;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.home.cache.Logger;
-import org.sonar.home.cache.PersistentCache;
-import org.sonar.home.cache.PersistentCacheBuilder;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.fest.assertions.Fail.fail;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.startsWith;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 
 public class ServerConnectionTest {
 
index c2a5cc54ab982f363383f4ce164f0204db70837f..ae4101b079810cd1aac74da06d71491c38f5a968 100644 (file)
  */
 package org.sonar.runner.impl;
 
-import org.junit.Rule;
-import org.junit.rules.TemporaryFolder;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
-import org.sonar.home.cache.Logger;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.runner.cache.Logger;
 
 import java.io.File;
 import java.io.FileInputStream;
index 1a05e476f77b4985df6434b1f6612e7a34e4e489..d952eca469cd1f910fe4f8401d4f589ce7e29ed0 100644 (file)
  */
 package org.sonar.runner.impl;
 
-import java.io.File;
 import org.apache.commons.io.FileUtils;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
-import org.sonar.home.cache.Logger;
+import org.sonar.runner.cache.Logger;
+
+import java.io.File;
 
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Mockito.mock;