From 0bdc699f5bfee42615d1c57b1fd2bc7a58a02188 Mon Sep 17 00:00:00 2001 From: Duarte Meneses Date: Thu, 1 Oct 2015 14:19:04 +0200 Subject: [PATCH] Merge sonar-home into sonar-runner --- pom.xml | 8 +- sonar-runner-api/pom.xml | 12 +- .../main/java/org/sonar/runner/api/Dirs.java | 3 +- .../org/sonar/runner/api/EmbeddedRunner.java | 3 +- .../org/sonar/runner/api/LoggerAdapter.java | 3 +- .../cache/DeleteFileOnCloseInputStream.java | 84 ++++++ .../org/sonar/runner/cache/DirectoryLock.java | 106 +++++++ .../org/sonar/runner/cache/FileCache.java | 159 ++++++++++ .../sonar/runner/cache/FileCacheBuilder.java | 60 ++++ .../org/sonar/runner/cache/FileHashes.java | 73 +++++ .../java/org/sonar/runner/cache/Logger.java | 34 +++ .../sonar/runner/cache/PersistentCache.java | 281 ++++++++++++++++++ .../runner/cache/PersistentCacheBuilder.java | 110 +++++++ .../cache/PersistentCacheInvalidation.java | 27 ++ .../runner/cache/TTLCacheInvalidation.java | 48 +++ .../org/sonar/runner/cache/package-info.java | 25 ++ .../runner/impl/IsolatedLauncherFactory.java | 10 +- .../runner/impl/IsolatedLauncherProxy.java | 3 +- .../org/sonar/runner/impl/JarDownloader.java | 4 +- .../main/java/org/sonar/runner/impl/Jars.java | 8 +- .../sonar/runner/impl/ServerConnection.java | 10 +- .../sonar/runner/impl/SimulatedLauncher.java | 10 +- .../org/sonar/runner/impl/TempCleaning.java | 7 +- .../java/org/sonar/runner/api/DirsTest.java | 5 +- .../sonar/runner/api/EmbeddedRunnerTest.java | 23 +- .../sonar/runner/cache/DirectoryLockTest.java | 94 ++++++ .../runner/cache/FileCacheBuilderTest.java | 60 ++++ .../org/sonar/runner/cache/FileCacheTest.java | 117 ++++++++ .../sonar/runner/cache/FileHashesTest.java | 127 ++++++++ .../cache/PersistentCacheBuilderTest.java | 78 +++++ .../runner/cache/PersistentCacheTest.java | 145 +++++++++ .../cache/TTLCacheInvalidationTest.java | 55 ++++ .../impl/IsolatedLauncherFactoryTest.java | 10 +- .../impl/IsolatedLauncherProxyTest.java | 9 +- .../sonar/runner/impl/JarDownloaderTest.java | 5 +- .../java/org/sonar/runner/impl/JarsTest.java | 11 +- .../runner/impl/ServerConnectionTest.java | 28 +- .../runner/impl/SimulatedLauncherTest.java | 6 +- .../sonar/runner/impl/TempCleaningTest.java | 5 +- 39 files changed, 1778 insertions(+), 88 deletions(-) create mode 100644 sonar-runner-api/src/main/java/org/sonar/runner/cache/DeleteFileOnCloseInputStream.java create mode 100644 sonar-runner-api/src/main/java/org/sonar/runner/cache/DirectoryLock.java create mode 100644 sonar-runner-api/src/main/java/org/sonar/runner/cache/FileCache.java create mode 100644 sonar-runner-api/src/main/java/org/sonar/runner/cache/FileCacheBuilder.java create mode 100644 sonar-runner-api/src/main/java/org/sonar/runner/cache/FileHashes.java create mode 100644 sonar-runner-api/src/main/java/org/sonar/runner/cache/Logger.java create mode 100644 sonar-runner-api/src/main/java/org/sonar/runner/cache/PersistentCache.java create mode 100644 sonar-runner-api/src/main/java/org/sonar/runner/cache/PersistentCacheBuilder.java create mode 100644 sonar-runner-api/src/main/java/org/sonar/runner/cache/PersistentCacheInvalidation.java create mode 100644 sonar-runner-api/src/main/java/org/sonar/runner/cache/TTLCacheInvalidation.java create mode 100644 sonar-runner-api/src/main/java/org/sonar/runner/cache/package-info.java create mode 100644 sonar-runner-api/src/test/java/org/sonar/runner/cache/DirectoryLockTest.java create mode 100644 sonar-runner-api/src/test/java/org/sonar/runner/cache/FileCacheBuilderTest.java create mode 100644 sonar-runner-api/src/test/java/org/sonar/runner/cache/FileCacheTest.java create mode 100644 sonar-runner-api/src/test/java/org/sonar/runner/cache/FileHashesTest.java create mode 100644 sonar-runner-api/src/test/java/org/sonar/runner/cache/PersistentCacheBuilderTest.java create mode 100644 sonar-runner-api/src/test/java/org/sonar/runner/cache/PersistentCacheTest.java create mode 100644 sonar-runner-api/src/test/java/org/sonar/runner/cache/TTLCacheInvalidationTest.java diff --git a/pom.xml b/pom.xml index ba1bb66..1b0a6d5 100644 --- a/pom.xml +++ b/pom.xml @@ -125,10 +125,10 @@ 1.9.5 - org.codehaus.sonar - sonar-home - 5.2-SNAPSHOT - + commons-codec + commons-codec + 1.10 + diff --git a/sonar-runner-api/pom.xml b/sonar-runner-api/pom.xml index a1cf3c6..24c2fb3 100644 --- a/sonar-runner-api/pom.xml +++ b/sonar-runner-api/pom.xml @@ -20,10 +20,6 @@ jsr305 provided - - org.codehaus.sonar - sonar-home - commons-io commons-io @@ -59,7 +55,7 @@ org.assertj assertj-core - 1.7.1 + 2.2.0 test @@ -73,6 +69,12 @@ commons-lang test + + + commons-codec + commons-codec + test + diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/Dirs.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/Dirs.java index dc39e96..73c49bb 100644 --- a/sonar-runner-api/src/main/java/org/sonar/runner/api/Dirs.java +++ b/sonar-runner-api/src/main/java/org/sonar/runner/api/Dirs.java @@ -19,13 +19,14 @@ */ 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 { diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/EmbeddedRunner.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/EmbeddedRunner.java index 2a22a05..021e129 100644 --- a/sonar-runner-api/src/main/java/org/sonar/runner/api/EmbeddedRunner.java +++ b/sonar-runner-api/src/main/java/org/sonar/runner/api/EmbeddedRunner.java @@ -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; diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/LoggerAdapter.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/LoggerAdapter.java index c3b7007..c898251 100644 --- a/sonar-runner-api/src/main/java/org/sonar/runner/api/LoggerAdapter.java +++ b/sonar-runner-api/src/main/java/org/sonar/runner/api/LoggerAdapter.java @@ -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 index 0000000..15363d1 --- /dev/null +++ b/sonar-runner-api/src/main/java/org/sonar/runner/cache/DeleteFileOnCloseInputStream.java @@ -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 index 0000000..e063d5e --- /dev/null +++ b/sonar-runner-api/src/main/java/org/sonar/runner/cache/DirectoryLock.java @@ -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 index 0000000..904ec75 --- /dev/null +++ b/sonar-runner-api/src/main/java/org/sonar/runner/cache/FileCache.java @@ -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 index 0000000..9be8e11 --- /dev/null +++ b/sonar-runner-api/src/main/java/org/sonar/runner/cache/FileCacheBuilder.java @@ -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 index 0000000..4d1e632 --- /dev/null +++ b/sonar-runner-api/src/main/java/org/sonar/runner/cache/FileHashes.java @@ -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 index 0000000..03dae61 --- /dev/null +++ b/sonar-runner-api/src/main/java/org/sonar/runner/cache/Logger.java @@ -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 index 0000000..d6359c2 --- /dev/null +++ b/sonar-runner-api/src/main/java/org/sonar/runner/cache/PersistentCache.java @@ -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 filter) throws IOException { + try (DirectoryStream 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 { + @Override + public boolean accept(Path entry) throws IOException { + return !lock.getFileLockName().equals(entry.getFileName().toString()); + } + } + + private class DirectoryCleanFilter implements DirectoryStream.Filter { + @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 index 0000000..2e34d40 --- /dev/null +++ b/sonar-runner-api/src/main/java/org/sonar/runner/cache/PersistentCacheBuilder.java @@ -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: + *
+ *   <sonar_home>/ws_cache/<server_url>-<version>/projects/<project>/
+ *   <sonar_home>/ws_cache/<server_url>-<version>/global/
+ *   <sonar_home>/ws_cache/<server_url>-<version>/local/
+ * 
+ */ +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 index 0000000..d320650 --- /dev/null +++ b/sonar-runner-api/src/main/java/org/sonar/runner/cache/PersistentCacheInvalidation.java @@ -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 index 0000000..7368256 --- /dev/null +++ b/sonar-runner-api/src/main/java/org/sonar/runner/cache/TTLCacheInvalidation.java @@ -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 index 0000000..c1b3daa --- /dev/null +++ b/sonar-runner-api/src/main/java/org/sonar/runner/cache/package-info.java @@ -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; + diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedLauncherFactory.java b/sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedLauncherFactory.java index e3bf2c8..be92d02 100644 --- a/sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedLauncherFactory.java +++ b/sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedLauncherFactory.java @@ -19,6 +19,11 @@ */ 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; diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedLauncherProxy.java b/sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedLauncherProxy.java index 629ca43..f0a1bff 100644 --- a/sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedLauncherProxy.java +++ b/sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedLauncherProxy.java @@ -19,12 +19,13 @@ */ 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; diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/impl/JarDownloader.java b/sonar-runner-api/src/main/java/org/sonar/runner/impl/JarDownloader.java index 4160754..3f372dc 100644 --- a/sonar-runner-api/src/main/java/org/sonar/runner/impl/JarDownloader.java +++ b/sonar-runner-api/src/main/java/org/sonar/runner/impl/JarDownloader.java @@ -19,12 +19,12 @@ */ 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; diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/impl/Jars.java b/sonar-runner-api/src/main/java/org/sonar/runner/impl/Jars.java index b526df9..972675b 100644 --- a/sonar-runner-api/src/main/java/org/sonar/runner/impl/Jars.java +++ b/sonar-runner-api/src/main/java/org/sonar/runner/impl/Jars.java @@ -19,16 +19,16 @@ */ 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/"; diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/impl/ServerConnection.java b/sonar-runner-api/src/main/java/org/sonar/runner/impl/ServerConnection.java index 8f24f1f..30c3cf1 100644 --- a/sonar-runner-api/src/main/java/org/sonar/runner/impl/ServerConnection.java +++ b/sonar-runner-api/src/main/java/org/sonar/runner/impl/ServerConnection.java @@ -19,9 +19,11 @@ */ 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"; diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/impl/SimulatedLauncher.java b/sonar-runner-api/src/main/java/org/sonar/runner/impl/SimulatedLauncher.java index b4cc365..aac6b25 100644 --- a/sonar-runner-api/src/main/java/org/sonar/runner/impl/SimulatedLauncher.java +++ b/sonar-runner-api/src/main/java/org/sonar/runner/impl/SimulatedLauncher.java @@ -19,12 +19,12 @@ */ 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; diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/impl/TempCleaning.java b/sonar-runner-api/src/main/java/org/sonar/runner/impl/TempCleaning.java index 89c07c7..ad2f70f 100644 --- a/sonar-runner-api/src/main/java/org/sonar/runner/impl/TempCleaning.java +++ b/sonar-runner-api/src/main/java/org/sonar/runner/impl/TempCleaning.java @@ -19,13 +19,14 @@ */ 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. diff --git a/sonar-runner-api/src/test/java/org/sonar/runner/api/DirsTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/api/DirsTest.java index d8a4eda..9cb24dc 100644 --- a/sonar-runner-api/src/test/java/org/sonar/runner/api/DirsTest.java +++ b/sonar-runner-api/src/test/java/org/sonar/runner/api/DirsTest.java @@ -19,13 +19,14 @@ */ 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; diff --git a/sonar-runner-api/src/test/java/org/sonar/runner/api/EmbeddedRunnerTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/api/EmbeddedRunnerTest.java index 5464bd8..e9d4535 100644 --- a/sonar-runner-api/src/test/java/org/sonar/runner/api/EmbeddedRunnerTest.java +++ b/sonar-runner-api/src/test/java/org/sonar/runner/api/EmbeddedRunnerTest.java @@ -19,9 +19,16 @@ */ 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 index 0000000..0d4a8f5 --- /dev/null +++ b/sonar-runner-api/src/test/java/org/sonar/runner/cache/DirectoryLockTest.java @@ -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 index 0000000..d146298 --- /dev/null +++ b/sonar-runner-api/src/test/java/org/sonar/runner/cache/FileCacheBuilderTest.java @@ -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 index 0000000..b88105d --- /dev/null +++ b/sonar-runner-api/src/test/java/org/sonar/runner/cache/FileCacheTest.java @@ -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 index 0000000..582e10f --- /dev/null +++ b/sonar-runner-api/src/test/java/org/sonar/runner/cache/FileHashesTest.java @@ -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 index 0000000..1fa0418 --- /dev/null +++ b/sonar-runner-api/src/test/java/org/sonar/runner/cache/PersistentCacheBuilderTest.java @@ -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 index 0000000..911b863 --- /dev/null +++ b/sonar-runner-api/src/test/java/org/sonar/runner/cache/PersistentCacheTest.java @@ -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 index 0000000..06cf037 --- /dev/null +++ b/sonar-runner-api/src/test/java/org/sonar/runner/cache/TTLCacheInvalidationTest.java @@ -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); + } +} diff --git a/sonar-runner-api/src/test/java/org/sonar/runner/impl/IsolatedLauncherFactoryTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/impl/IsolatedLauncherFactoryTest.java index d29873e..fa71ace 100644 --- a/sonar-runner-api/src/test/java/org/sonar/runner/impl/IsolatedLauncherFactoryTest.java +++ b/sonar-runner-api/src/test/java/org/sonar/runner/impl/IsolatedLauncherFactoryTest.java @@ -19,17 +19,17 @@ */ 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; diff --git a/sonar-runner-api/src/test/java/org/sonar/runner/impl/IsolatedLauncherProxyTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/impl/IsolatedLauncherProxyTest.java index 9d47dad..e3553e4 100644 --- a/sonar-runner-api/src/test/java/org/sonar/runner/impl/IsolatedLauncherProxyTest.java +++ b/sonar-runner-api/src/test/java/org/sonar/runner/impl/IsolatedLauncherProxyTest.java @@ -19,13 +19,14 @@ */ 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; diff --git a/sonar-runner-api/src/test/java/org/sonar/runner/impl/JarDownloaderTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/impl/JarDownloaderTest.java index 284fb14..6c00d88 100644 --- a/sonar-runner-api/src/test/java/org/sonar/runner/impl/JarDownloaderTest.java +++ b/sonar-runner-api/src/test/java/org/sonar/runner/impl/JarDownloaderTest.java @@ -19,12 +19,13 @@ */ 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; diff --git a/sonar-runner-api/src/test/java/org/sonar/runner/impl/JarsTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/impl/JarsTest.java index 222c693..762473c 100644 --- a/sonar-runner-api/src/test/java/org/sonar/runner/impl/JarsTest.java +++ b/sonar-runner-api/src/test/java/org/sonar/runner/impl/JarsTest.java @@ -19,16 +19,17 @@ */ 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; diff --git a/sonar-runner-api/src/test/java/org/sonar/runner/impl/ServerConnectionTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/impl/ServerConnectionTest.java index fe6069f..46d8ab7 100644 --- a/sonar-runner-api/src/test/java/org/sonar/runner/impl/ServerConnectionTest.java +++ b/sonar-runner-api/src/test/java/org/sonar/runner/impl/ServerConnectionTest.java @@ -20,6 +20,14 @@ 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 { diff --git a/sonar-runner-api/src/test/java/org/sonar/runner/impl/SimulatedLauncherTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/impl/SimulatedLauncherTest.java index c2a5cc5..ae4101b 100644 --- a/sonar-runner-api/src/test/java/org/sonar/runner/impl/SimulatedLauncherTest.java +++ b/sonar-runner-api/src/test/java/org/sonar/runner/impl/SimulatedLauncherTest.java @@ -19,11 +19,11 @@ */ 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; diff --git a/sonar-runner-api/src/test/java/org/sonar/runner/impl/TempCleaningTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/impl/TempCleaningTest.java index 1a05e47..d952eca 100644 --- a/sonar-runner-api/src/test/java/org/sonar/runner/impl/TempCleaningTest.java +++ b/sonar-runner-api/src/test/java/org/sonar/runner/impl/TempCleaningTest.java @@ -19,12 +19,13 @@ */ 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; -- 2.39.5