This reverts commit 0bf3c7e2b4
.
tags/2.5-rc1
@@ -0,0 +1,103 @@ | |||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | |||
<modelVersion>4.0.0</modelVersion> | |||
<parent> | |||
<groupId>org.codehaus.sonar-plugins</groupId> | |||
<artifactId>sonar-runner</artifactId> | |||
<version>2.1-SNAPSHOT</version> | |||
</parent> | |||
<artifactId>sonar-runner-api</artifactId> | |||
<name>Sonar Runner - API</name> | |||
<dependencies> | |||
<!-- Dependencies will be shaded --> | |||
<dependency> | |||
<groupId>commons-io</groupId> | |||
<artifactId>commons-io</artifactId> | |||
<version>2.2</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>commons-codec</groupId> | |||
<artifactId>commons-codec</artifactId> | |||
<version>1.4</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>commons-lang</groupId> | |||
<artifactId>commons-lang</artifactId> | |||
<version>2.4</version> | |||
</dependency> | |||
<!-- Unit tests --> | |||
<dependency> | |||
<groupId>junit</groupId> | |||
<artifactId>junit</artifactId> | |||
<version>4.10</version> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.easytesting</groupId> | |||
<artifactId>fest-assert</artifactId> | |||
<version>1.4</version> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.mockito</groupId> | |||
<artifactId>mockito-all</artifactId> | |||
<version>1.9.5</version> | |||
<scope>test</scope> | |||
</dependency> | |||
</dependencies> | |||
<build> | |||
<resources> | |||
<resource> | |||
<directory>src/main/resources</directory> | |||
<filtering>true</filtering> | |||
</resource> | |||
</resources> | |||
<plugins> | |||
<plugin> | |||
<groupId>org.apache.maven.plugins</groupId> | |||
<artifactId>maven-jar-plugin</artifactId> | |||
<configuration> | |||
<archive> | |||
<manifest> | |||
<addClasspath>true</addClasspath> | |||
</manifest> | |||
</archive> | |||
</configuration> | |||
</plugin> | |||
<plugin> | |||
<groupId>org.apache.maven.plugins</groupId> | |||
<artifactId>maven-shade-plugin</artifactId> | |||
<executions> | |||
<execution> | |||
<phase>package</phase> | |||
<goals> | |||
<goal>shade</goal> | |||
</goals> | |||
<configuration> | |||
<createDependencyReducedPom>true</createDependencyReducedPom> | |||
<minimizeJar>true</minimizeJar> | |||
<relocations> | |||
<relocation> | |||
<pattern>org.apache.commons.io</pattern> | |||
<shadedPattern>org.sonar.runner.commonsio</shadedPattern> | |||
</relocation> | |||
<relocation> | |||
<pattern>org.apache.commons.codec</pattern> | |||
<shadedPattern>org.sonar.runner.commonscodec</shadedPattern> | |||
</relocation> | |||
<relocation> | |||
<pattern>org.apache.commons.lang</pattern> | |||
<shadedPattern>org.sonar.runner.commonslang</shadedPattern> | |||
</relocation> | |||
</relocations> | |||
</configuration> | |||
</execution> | |||
</executions> | |||
</plugin> | |||
</plugins> | |||
</build> | |||
</project> |
@@ -0,0 +1,124 @@ | |||
/* | |||
* Sonar Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* dev@sonar.codehaus.org | |||
* | |||
* 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; | |||
import java.io.IOException; | |||
import java.net.URL; | |||
import java.net.URLClassLoader; | |||
import java.util.Enumeration; | |||
/** | |||
* Special {@link URLClassLoader} to execute Sonar, which restricts loading from parent. | |||
*/ | |||
class BootstrapClassLoader extends URLClassLoader { | |||
private final String[] unmaskedPackages; | |||
BootstrapClassLoader(ClassLoader parent, String... unmaskedPackages) { | |||
super(new URL[0], parent); | |||
this.unmaskedPackages = unmaskedPackages; | |||
} | |||
/** | |||
* {@inheritDoc} Visibility of a method has been relaxed to public. | |||
*/ | |||
@Override | |||
public void addURL(URL url) { | |||
super.addURL(url); | |||
} | |||
/** | |||
* {@inheritDoc} Visibility of a method has been relaxed to public. | |||
*/ | |||
@Override | |||
public Class<?> findClass(String name) throws ClassNotFoundException { | |||
return super.findClass(name); | |||
} | |||
/** | |||
* @return true, if class can be loaded from parent ClassLoader | |||
*/ | |||
boolean canLoadFromParent(String name) { | |||
if (name.startsWith("org.sonar.runner.") && !name.startsWith("org.sonar.runner.internal.batch.")) { | |||
return true; | |||
} | |||
for (String pkg : unmaskedPackages) { | |||
if (name.startsWith(pkg + ".")) { | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
/** | |||
* Same behavior as in {@link URLClassLoader#loadClass(String, boolean)}, except loading from parent. | |||
*/ | |||
@Override | |||
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { | |||
// First, check if the class has already been loaded | |||
Class<?> c = findLoadedClass(name); | |||
if (c == null) { | |||
try { | |||
// Load from parent | |||
if (getParent() != null && canLoadFromParent(name)) { | |||
c = getParent().loadClass(name); | |||
} else { | |||
// Load from system | |||
// I don't know for other vendors, but for Oracle JVM : | |||
// - ClassLoader.getSystemClassLoader() is sun.misc.Launcher$AppClassLoader. It contains app classpath. | |||
// - ClassLoader.getSystemClassLoader().getParent() is sun.misc.Launcher$ExtClassLoader. It contains core JVM | |||
ClassLoader systemClassLoader = getSystemClassLoader(); | |||
if (systemClassLoader.getParent() != null) { | |||
systemClassLoader = systemClassLoader.getParent(); | |||
} | |||
c = systemClassLoader.loadClass(name); | |||
} | |||
} catch (ClassNotFoundException e) { | |||
// If still not found, then invoke findClass in order | |||
// to find the class. | |||
c = findClass(name); | |||
} | |||
} | |||
if (resolve) { | |||
resolveClass(c); | |||
} | |||
return c; | |||
} | |||
/** | |||
* Unlike {@link URLClassLoader#getResource(String)} don't return resource from parent. | |||
* See http://jira.codehaus.org/browse/SONAR-2276 | |||
*/ | |||
@Override | |||
public URL getResource(String name) { | |||
return findResource(name); | |||
} | |||
/** | |||
* Unlike {@link URLClassLoader#getResources(String)} don't return resources from parent. | |||
* See http://jira.codehaus.org/browse/SONAR-2276 | |||
*/ | |||
@Override | |||
public Enumeration<URL> getResources(String name) throws IOException { | |||
return findResources(name); | |||
} | |||
} |
@@ -0,0 +1,264 @@ | |||
/* | |||
* Sonar Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* dev@sonar.codehaus.org | |||
* | |||
* 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; | |||
import org.apache.commons.io.FileUtils; | |||
import org.apache.commons.io.IOUtils; | |||
import java.io.File; | |||
import java.io.FileOutputStream; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.InputStreamReader; | |||
import java.io.Reader; | |||
import java.net.ConnectException; | |||
import java.net.HttpURLConnection; | |||
import java.net.MalformedURLException; | |||
import java.net.URL; | |||
import java.net.UnknownHostException; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import java.util.regex.Matcher; | |||
import java.util.regex.Pattern; | |||
/** | |||
* Bootstrapper used to download everything from the server and create the correct classloader required to execute a Sonar analysis in isolation. | |||
*/ | |||
class Bootstrapper { | |||
static final String VERSION_PATH = "/api/server/version"; | |||
static final String BATCH_PATH = "/batch/"; | |||
static final String BOOTSTRAP_INDEX_PATH = "/batch_bootstrap/index"; | |||
static final int CONNECT_TIMEOUT_MILLISECONDS = 30000; | |||
static final int READ_TIMEOUT_MILLISECONDS = 60000; | |||
private static final Pattern CHARSET_PATTERN = Pattern.compile("(?i)\\bcharset=\\s*\"?([^\\s;\"]*)"); | |||
private static final String[] UNSUPPORTED_VERSIONS_FOR_CACHE = {"1", "2", "3.0", "3.1", "3.2", "3.3", "3.4"}; | |||
private File bootDir; | |||
private String serverUrl; | |||
private String productToken; | |||
private String serverVersion; | |||
private SonarCache cache; | |||
/** | |||
* @param productToken part of User-Agent request-header field - see http://tools.ietf.org/html/rfc1945#section-10.15 | |||
*/ | |||
Bootstrapper(String productToken, String serverUrl, File workDir, SonarCache cache) { | |||
this.productToken = productToken; | |||
this.cache = cache; | |||
bootDir = new File(workDir, "batch"); | |||
bootDir.mkdirs(); | |||
if (serverUrl.endsWith("/")) { | |||
this.serverUrl = serverUrl.substring(0, serverUrl.length() - 1); | |||
} else { | |||
this.serverUrl = serverUrl; | |||
} | |||
} | |||
/** | |||
* @return server url | |||
*/ | |||
String getServerUrl() { | |||
return serverUrl; | |||
} | |||
/** | |||
* @return server version | |||
*/ | |||
String getServerVersion() { | |||
if (serverVersion == null) { | |||
try { | |||
serverVersion = remoteContent(VERSION_PATH); | |||
} catch (ConnectException e) { | |||
Logs.error("Sonar server '" + serverUrl + "' can not be reached"); | |||
throw new RunnerException("Fail to request server version", e); | |||
} catch (UnknownHostException e) { | |||
Logs.error("Sonar server '" + serverUrl + "' can not be reached"); | |||
throw new RunnerException("Fail to request server version", e); | |||
} catch (IOException e) { | |||
throw new RunnerException("Fail to request server version", e); | |||
} | |||
} | |||
return serverVersion; | |||
} | |||
/** | |||
* Download batch files from server and creates {@link BootstrapClassLoader}. | |||
* To use this method version of Sonar should be at least 2.6. | |||
* | |||
* @param urls additional URLs for loading classes and resources | |||
* @param parent parent ClassLoader | |||
* @param unmaskedPackages only classes and resources from those packages would be available for loading from parent | |||
*/ | |||
BootstrapClassLoader createClassLoader(URL[] urls, ClassLoader parent, String... unmaskedPackages) { | |||
BootstrapClassLoader classLoader = new BootstrapClassLoader(parent, unmaskedPackages); | |||
List<File> files = downloadBatchFiles(); | |||
for (URL url : urls) { | |||
classLoader.addURL(url); | |||
} | |||
for (File file : files) { | |||
try { | |||
classLoader.addURL(file.toURI().toURL()); | |||
} catch (MalformedURLException e) { | |||
throw new IllegalStateException("Fail to create classloader", e); | |||
} | |||
} | |||
return classLoader; | |||
} | |||
private void remoteContentToFile(String path, File toFile) { | |||
InputStream input = null; | |||
FileOutputStream output = null; | |||
String fullUrl = serverUrl + path; | |||
if (Logs.isDebugEnabled()) { | |||
Logs.debug("Downloading " + fullUrl + " to " + toFile.getAbsolutePath()); | |||
} | |||
// Don't log for old versions without cache to not pollute logs | |||
else if (!isUnsupportedVersionForCache(getServerVersion())) { | |||
Logs.info("Downloading " + path.substring(path.lastIndexOf("/") + 1)); | |||
} | |||
try { | |||
HttpURLConnection connection = newHttpConnection(new URL(fullUrl)); | |||
output = new FileOutputStream(toFile, false); | |||
input = connection.getInputStream(); | |||
IOUtils.copyLarge(input, output); | |||
} catch (IOException e) { | |||
IOUtils.closeQuietly(output); | |||
FileUtils.deleteQuietly(toFile); | |||
throw new IllegalStateException("Fail to download the file: " + fullUrl, e); | |||
} finally { | |||
IOUtils.closeQuietly(input); | |||
IOUtils.closeQuietly(output); | |||
} | |||
} | |||
String remoteContent(String path) throws IOException { | |||
String fullUrl = serverUrl + path; | |||
HttpURLConnection conn = newHttpConnection(new URL(fullUrl)); | |||
String charset = getCharsetFromContentType(conn.getContentType()); | |||
if (charset == null || "".equals(charset)) { | |||
charset = "UTF-8"; | |||
} | |||
Reader reader = new InputStreamReader(conn.getInputStream(), charset); | |||
try { | |||
int statusCode = conn.getResponseCode(); | |||
if (statusCode != HttpURLConnection.HTTP_OK) { | |||
throw new IOException("Status returned by url : '" + fullUrl + "' is invalid : " + statusCode); | |||
} | |||
return IOUtils.toString(reader); | |||
} finally { | |||
IOUtils.closeQuietly(reader); | |||
conn.disconnect(); | |||
} | |||
} | |||
/** | |||
* By convention, the product tokens are listed in order of their significance for identifying the application. | |||
*/ | |||
String getUserAgent() { | |||
return "sonar-bootstrapper/" + Version.getVersion() + " " + productToken; | |||
} | |||
HttpURLConnection newHttpConnection(URL url) throws IOException { | |||
HttpURLConnection connection = (HttpURLConnection) url.openConnection(); | |||
connection.setConnectTimeout(CONNECT_TIMEOUT_MILLISECONDS); | |||
connection.setReadTimeout(READ_TIMEOUT_MILLISECONDS); | |||
connection.setInstanceFollowRedirects(true); | |||
connection.setRequestMethod("GET"); | |||
connection.setRequestProperty("User-Agent", getUserAgent()); | |||
return connection; | |||
} | |||
private List<File> downloadBatchFiles() { | |||
try { | |||
List<File> files = new ArrayList<File>(); | |||
if (isUnsupportedVersionForCache(getServerVersion())) { | |||
getBootstrapFilesFromOldURL(files); | |||
} | |||
else { | |||
getBootstrapFiles(files); | |||
} | |||
return files; | |||
} catch (Exception e) { | |||
throw new IllegalStateException("Fail to download libraries from server", e); | |||
} | |||
} | |||
private void getBootstrapFilesFromOldURL(List<File> files) throws IOException { | |||
String libs = remoteContent(BATCH_PATH); | |||
for (String lib : libs.split(",")) { | |||
File file = new File(bootDir, lib); | |||
remoteContentToFile(BATCH_PATH + lib, file); | |||
files.add(file); | |||
} | |||
} | |||
private void getBootstrapFiles(List<File> files) throws IOException { | |||
String libs = remoteContent(BOOTSTRAP_INDEX_PATH); | |||
String[] lines = libs.split("[\r\n]+"); | |||
for (String line : lines) { | |||
line = line.trim(); | |||
if ("".equals(line)) { | |||
continue; | |||
} | |||
String[] libAndMd5 = line.split("\\|"); | |||
String libName = libAndMd5[0]; | |||
String remoteMd5 = libAndMd5.length > 0 ? libAndMd5[1] : null; | |||
File libInCache = null; | |||
if (remoteMd5 != null && !"".equals(remoteMd5)) { | |||
libInCache = cache.getFileFromCache(libName, remoteMd5); | |||
} | |||
if (libInCache == null) { | |||
File tmpLocation = cache.getTemporaryFile(); | |||
remoteContentToFile(BATCH_PATH + libName, tmpLocation); | |||
String md5 = cache.cacheFile(tmpLocation, libName); | |||
libInCache = cache.getFileFromCache(libName, md5); | |||
if (!md5.equals(remoteMd5)) { | |||
throw new RunnerException("INVALID CHECKSUM: File " + libInCache.getAbsolutePath() + " was expected to have checksum " + remoteMd5 | |||
+ " but was downloaded with checksum " + md5); | |||
} | |||
} | |||
files.add(libInCache); | |||
} | |||
} | |||
static boolean isUnsupportedVersionForCache(String version) { | |||
return VersionUtils.isUnsupportedVersion(version, UNSUPPORTED_VERSIONS_FOR_CACHE); | |||
} | |||
/** | |||
* Parse out a charset from a content type header. | |||
* | |||
* @param contentType e.g. "text/html; charset=EUC-JP" | |||
* @return "EUC-JP", or null if not found. Charset is trimmed and uppercased. | |||
*/ | |||
static String getCharsetFromContentType(String contentType) { | |||
if (contentType == null) { | |||
return null; | |||
} | |||
Matcher m = CHARSET_PATTERN.matcher(contentType); | |||
if (m.find()) { | |||
return m.group(1).trim().toUpperCase(); | |||
} | |||
return null; | |||
} | |||
} |
@@ -0,0 +1,60 @@ | |||
/* | |||
* Sonar Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* dev@sonar.codehaus.org | |||
* | |||
* 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; | |||
final class Logs { | |||
private Logs() { | |||
} | |||
private static boolean debugEnabled = false; | |||
public static void setDebugEnabled(boolean debugEnabled) { | |||
Logs.debugEnabled = debugEnabled; | |||
} | |||
public static boolean isDebugEnabled() { | |||
return debugEnabled; | |||
} | |||
static void debug(String message) { | |||
if (isDebugEnabled()) { | |||
System.out.println("DEBUG: " + message); | |||
} | |||
} | |||
static void info(String message) { | |||
System.out.println("INFO: " + message); | |||
} | |||
static void warn(String message) { | |||
System.out.println("WARN: " + message); | |||
} | |||
static void error(String message) { | |||
System.err.println("ERROR: " + message); | |||
} | |||
static void error(String message, Throwable t) { | |||
System.err.println("ERROR: " + message); | |||
if (t != null) { | |||
t.printStackTrace(System.err); | |||
} | |||
} | |||
} |
@@ -0,0 +1,325 @@ | |||
/* | |||
* Sonar Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* dev@sonar.codehaus.org | |||
* | |||
* 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; | |||
import org.apache.commons.io.IOUtils; | |||
import java.io.File; | |||
import java.io.FileInputStream; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.util.Locale; | |||
import java.util.Properties; | |||
/** | |||
* Arguments : | |||
* <ul> | |||
* <li>runner.home: optional path to runner home (root directory with sub-directories bin, lib and conf)</li> | |||
* <li>runner.settings: optional path to runner global settings, usually ${runner.home}/conf/sonar-runner.properties. | |||
* This property is used only if ${runner.home} is not defined</li> | |||
* <li>project.home: path to project root directory. If not set, then it's supposed to be the directory where the runner is executed</li> | |||
* <li>project.settings: optional path to project settings. Default value is ${project.home}/sonar-project.properties.</li> | |||
* </ul> | |||
* | |||
* @since 1.0 | |||
*/ | |||
public final class Main { | |||
private static final String RUNNER_HOME = "runner.home"; | |||
private static final String RUNNER_SETTINGS = "runner.settings"; | |||
private static final String PROJECT_HOME = "project.home"; | |||
private static final String PROJECT_SETTINGS = "project.settings"; | |||
// TODO Remove this after everything is updated to support tasks | |||
private static final String TASK_COMMAND = "sonar.task"; | |||
boolean debugMode = false; | |||
boolean displayVersionOnly = false; | |||
boolean displayStackTrace = false; | |||
String command; | |||
Properties globalProperties; | |||
Properties projectProperties; | |||
/** | |||
* Entry point of the program. | |||
*/ | |||
public static void main(String[] args) { | |||
new Main().execute(args); | |||
} | |||
Main() { | |||
} | |||
private void execute(String[] args) { | |||
Properties argsProperties = parseArguments(args); | |||
System.out.println("Runner version: " + Version.getVersion()); | |||
System.out.println("Java version: " + System.getProperty("java.version", "<unknown>") | |||
+ ", vendor: " + System.getProperty("java.vendor", "<unknown>")); | |||
System.out | |||
.println("OS name: \"" + System.getProperty("os.name") + "\", version: \"" + System.getProperty("os.version") + "\", arch: \"" + System.getProperty("os.arch") + "\""); | |||
if (!displayVersionOnly) { | |||
int result = execute(argsProperties); | |||
System.exit(result); | |||
} | |||
} | |||
private int execute(Properties argsProperties) { | |||
if (displayStackTrace) { | |||
Logs.info("Error stacktraces are turned on."); | |||
} | |||
Stats stats = new Stats().start(); | |||
try { | |||
loadProperties(argsProperties); | |||
Runner runner = Runner.create(command, globalProperties, projectProperties); | |||
Logs.info("Default locale: \"" + Locale.getDefault() + "\", source code encoding: \"" + runner.getSourceCodeEncoding() + "\"" | |||
+ (runner.isEncodingPlatformDependant() ? " (analysis is platform dependent)" : "")); | |||
Logs.debug("Other system properties:"); | |||
Logs.debug(" - sun.arch.data.model: \"" + System.getProperty("sun.arch.data.model") + "\""); | |||
Logs.info("Server: " + runner.getSonarServerURL()); | |||
try { | |||
Logs.info("Work directory: " + runner.getWorkDir().getCanonicalPath()); | |||
Logs.info("Cache directory: " + runner.getCache().getCacheLocation()); | |||
} catch (IOException e) { | |||
throw new RunnerException("Unable to resolve directory", e); | |||
} | |||
runner.execute(); | |||
} catch (Exception e) { | |||
displayExecutionResult(stats, "FAILURE"); | |||
showError("Error during Sonar runner execution", e, displayStackTrace); | |||
return 1; | |||
} | |||
displayExecutionResult(stats, "SUCCESS"); | |||
return 0; | |||
} | |||
private void displayExecutionResult(Stats stats, String resultMsg) { | |||
Logs.info("------------------------------------------------------------------------"); | |||
Logs.info("EXECUTION " + resultMsg); | |||
Logs.info("------------------------------------------------------------------------"); | |||
stats.stop(); | |||
Logs.info("------------------------------------------------------------------------"); | |||
} | |||
public void showError(String message, Throwable e, boolean showStackTrace) { | |||
if (showStackTrace) { | |||
Logs.error(message, e); | |||
if (!debugMode) { | |||
Logs.error(""); | |||
suggestDebugMode(); | |||
} | |||
} | |||
else { | |||
Logs.error(message); | |||
if (e != null) { | |||
Logs.error(e.getMessage()); | |||
String previousMsg = ""; | |||
for (Throwable cause = e.getCause(); cause != null | |||
&& cause.getMessage() != null | |||
&& !cause.getMessage().equals(previousMsg); cause = cause.getCause()) { | |||
Logs.error("Caused by: " + cause.getMessage()); | |||
previousMsg = cause.getMessage(); | |||
} | |||
} | |||
Logs.error(""); | |||
Logs.error("To see the full stack trace of the errors, re-run Sonar Runner with the -e switch."); | |||
if (!debugMode) { | |||
suggestDebugMode(); | |||
} | |||
} | |||
} | |||
private void suggestDebugMode() { | |||
Logs.error("Re-run Sonar Runner using the -X switch to enable full debug logging."); | |||
} | |||
void loadProperties(Properties argsProperties) { | |||
globalProperties = loadGlobalProperties(argsProperties); | |||
projectProperties = loadProjectProperties(argsProperties); | |||
} | |||
Properties loadGlobalProperties(Properties argsProperties) { | |||
Properties commandLineProps = new Properties(); | |||
commandLineProps.putAll(System.getProperties()); | |||
commandLineProps.putAll(argsProperties); | |||
Properties result = new Properties(); | |||
result.putAll(loadRunnerConfiguration(commandLineProps)); | |||
result.putAll(commandLineProps); | |||
return result; | |||
} | |||
Properties loadProjectProperties(Properties argsProperties) { | |||
Properties commandLineProps = new Properties(); | |||
commandLineProps.putAll(System.getProperties()); | |||
commandLineProps.putAll(argsProperties); | |||
Properties result = new Properties(); | |||
result.putAll(loadProjectConfiguration(commandLineProps)); | |||
result.putAll(commandLineProps); | |||
if (result.containsKey(PROJECT_HOME)) { | |||
// the real property of the Sonar Runner is "sonar.projectDir" | |||
String baseDir = result.getProperty(PROJECT_HOME); | |||
result.remove(PROJECT_HOME); | |||
result.put(Runner.PROPERTY_SONAR_PROJECT_BASEDIR, baseDir); | |||
} | |||
return result; | |||
} | |||
Properties loadRunnerConfiguration(Properties props) { | |||
File settingsFile = locatePropertiesFile(props, RUNNER_HOME, "conf/sonar-runner.properties", RUNNER_SETTINGS); | |||
if (settingsFile != null && settingsFile.isFile() && settingsFile.exists()) { | |||
Logs.info("Runner configuration file: " + settingsFile.getAbsolutePath()); | |||
return toProperties(settingsFile); | |||
} | |||
Logs.info("Runner configuration file: NONE"); | |||
return new Properties(); | |||
} | |||
private Properties loadProjectConfiguration(Properties props) { | |||
File settingsFile = locatePropertiesFile(props, PROJECT_HOME, "sonar-project.properties", PROJECT_SETTINGS); | |||
if (settingsFile != null && settingsFile.isFile() && settingsFile.exists()) { | |||
Logs.info("Project configuration file: " + settingsFile.getAbsolutePath()); | |||
return toProperties(settingsFile); | |||
} | |||
Logs.info("Project configuration file: NONE"); | |||
return new Properties(); | |||
} | |||
private File locatePropertiesFile(Properties props, String homeKey, String relativePathFromHome, String settingsKey) { | |||
File settingsFile = null; | |||
String runnerHome = props.getProperty(homeKey); | |||
if (runnerHome != null && !"".equals(runnerHome)) { | |||
settingsFile = new File(runnerHome, relativePathFromHome); | |||
} | |||
if (settingsFile == null || !settingsFile.exists()) { | |||
String settingsPath = props.getProperty(settingsKey); | |||
if (settingsPath != null && !"".equals(settingsPath)) { | |||
settingsFile = new File(settingsPath); | |||
} | |||
} | |||
return settingsFile; | |||
} | |||
private Properties toProperties(File file) { | |||
InputStream in = null; | |||
Properties properties = new Properties(); | |||
try { | |||
in = new FileInputStream(file); | |||
properties.load(in); | |||
return properties; | |||
} catch (Exception e) { | |||
throw new IllegalStateException("Fail to load file: " + file.getAbsolutePath(), e); | |||
} finally { | |||
IOUtils.closeQuietly(in); | |||
} | |||
} | |||
Properties parseArguments(String[] args) { | |||
int i = 0; | |||
if (args.length > 0 && !args[0].startsWith("-")) { | |||
command = args[0]; | |||
i++; | |||
} | |||
else { | |||
command = null; | |||
} | |||
Properties props = new Properties(); | |||
for (; i < args.length; i++) { | |||
String arg = args[i]; | |||
if ("-h".equals(arg) || "--help".equals(arg)) { | |||
printUsage(); | |||
} | |||
else if ("-v".equals(arg) || "--version".equals(arg)) { | |||
displayVersionOnly = true; | |||
} | |||
else if ("-e".equals(arg) || "--errors".equals(arg)) { | |||
displayStackTrace = true; | |||
} | |||
else if ("-X".equals(arg) || "--debug".equals(arg)) { | |||
props.setProperty(Runner.PROPERTY_VERBOSE, "true"); | |||
displayStackTrace = true; | |||
debugMode = true; | |||
Logs.setDebugEnabled(true); | |||
} | |||
else if ("-D".equals(arg) || "--define".equals(arg)) { | |||
i++; | |||
if (i >= args.length) { | |||
printError("Missing argument for option --define"); | |||
} | |||
arg = args[i]; | |||
appendPropertyTo(arg, props); | |||
} | |||
else if (arg.startsWith("-D")) { | |||
arg = arg.substring(2); | |||
appendPropertyTo(arg, props); | |||
} | |||
else { | |||
printError("Unrecognized option: " + arg); | |||
} | |||
} | |||
return props; | |||
} | |||
private void appendPropertyTo(String arg, Properties props) { | |||
final String key, value; | |||
int j = arg.indexOf('='); | |||
if (j == -1) { | |||
key = arg; | |||
value = "true"; | |||
} else { | |||
key = arg.substring(0, j); | |||
value = arg.substring(j + 1); | |||
} | |||
if (TASK_COMMAND.equals(key)) { | |||
command = value; | |||
} | |||
else { | |||
props.setProperty(key, value); | |||
} | |||
} | |||
private void printError(String message) { | |||
Logs.error(message); | |||
printUsage(); | |||
} | |||
private void printUsage() { | |||
Logs.info(""); | |||
Logs.info("usage: sonar-runner [command] [options]"); | |||
Logs.info(""); | |||
Logs.info("Command:"); | |||
Logs.info(" analyse-project Run Sonar analysis task on the current project (default)"); | |||
Logs.info(" list-tasks Display all tasks available"); | |||
Logs.info("Options:"); | |||
Logs.info(" -D,--define <arg> Define property"); | |||
Logs.info(" -e,--errors Produce execution error messages"); | |||
Logs.info(" -h,--help Display help information"); | |||
Logs.info(" -v,--version Display version information"); | |||
Logs.info(" -X,--debug Produce execution debug output"); | |||
System.exit(0); | |||
} | |||
} |
@@ -0,0 +1,397 @@ | |||
/* | |||
* Sonar Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* dev@sonar.codehaus.org | |||
* | |||
* 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; | |||
import org.apache.commons.io.FileUtils; | |||
import org.apache.commons.lang.StringUtils; | |||
import java.io.File; | |||
import java.lang.reflect.Constructor; | |||
import java.lang.reflect.InvocationTargetException; | |||
import java.lang.reflect.Method; | |||
import java.net.MalformedURLException; | |||
import java.net.URL; | |||
import java.nio.charset.Charset; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import java.util.Properties; | |||
/** | |||
* <p> | |||
* Sonar Runner class that can be used to launch Sonar analyses. | |||
* </p> | |||
* <p> | |||
* Configuration is all done through properties: | |||
* </p> | |||
* <ul> | |||
* <li>"sonar.projectDir": the base directory of the project to analyse (this can also be passed via the {@link #create(Properties, File)} constructor)</li> | |||
* <li>"sonar.working.directory": the working directory, which is "${sonar.projectDir}/.sonar" by default.</li> | |||
* <li>"sonar.verbose": if set to "true", more information is displayed in the log</li> | |||
* <li>"sonar.environment.information.key" and "sonar.environment.information.version": can be used to overwrite environment information (can also be | |||
* set via {@link #setEnvironmentInformation(String, String)} method)</li> | |||
* <li>... plus all the other Sonar and Sonar plugins properties.</li> | |||
* </ul> | |||
* | |||
* @since 1.1 | |||
*/ | |||
public final class Runner { | |||
/** | |||
* Old property used to activate debug level for logging. | |||
* | |||
* @deprecated Replaced by sonar.verbose since 1.2 | |||
*/ | |||
@Deprecated | |||
public static final String PROPERTY_OLD_DEBUG_MODE = "runner.debug"; | |||
/** | |||
* Property used to increase logging information. | |||
* | |||
* @since 1.2 | |||
*/ | |||
public static final String PROPERTY_VERBOSE = "sonar.verbose"; | |||
/** | |||
* Property used to specify the working directory for the runner. May be a relative or absolute path. | |||
* | |||
* @since 1.4 | |||
*/ | |||
public static final String PROPERTY_WORK_DIRECTORY = "sonar.working.directory"; | |||
/** | |||
* Default value of the working directory. | |||
*/ | |||
public static final String DEF_VALUE_WORK_DIRECTORY = ".sonar"; | |||
/** | |||
* Property used to specify the base directory of the project to analyse. | |||
* | |||
* @since 1.5 | |||
*/ | |||
public static final String PROPERTY_SONAR_PROJECT_BASEDIR = "sonar.projectBaseDir"; | |||
/** | |||
* Property used to specify the name of the tool that will run a Sonar analysis. | |||
* | |||
* @since 1.5 | |||
*/ | |||
public static final String PROPERTY_ENVIRONMENT_INFORMATION_KEY = "sonar.environment.information.key"; | |||
/** | |||
* Property used to specify the version of the tool that will run a Sonar analysis. | |||
* | |||
* @since 1.5 | |||
*/ | |||
public static final String PROPERTY_ENVIRONMENT_INFORMATION_VERSION = "sonar.environment.information.version"; | |||
/** | |||
* Property used to define cache location (default to ~/.sonar/cache). | |||
* | |||
* @since 2.1 | |||
*/ | |||
String ENV_SONAR_USER_HOME = "SONAR_USER_HOME"; | |||
String PROPERTY_SONAR_USER_HOME = "sonar.userHome"; | |||
/** | |||
* Array of prefixes of versions of Sonar without support of this runner. | |||
*/ | |||
private static final String[] UNSUPPORTED_VERSIONS = {"1", "2.0", "2.1", "2.2", "2.3", "2.4", "2.5", "2.6", "2.7", "2.8", "2.9", "2.10"}; | |||
private static final String[] UNSUPPORTED_VERSIONS_FOR_TASKS = {"1", "2", "3.0", "3.1", "3.2", "3.3", "3.4"}; | |||
private static final String PROPERTY_SOURCE_ENCODING = "sonar.sourceEncoding"; | |||
private String command; | |||
private File projectDir; | |||
private File sonarUserHomeDir; | |||
private File workDir; | |||
private String[] unmaskedPackages; | |||
private List<Object> containerExtensions = new ArrayList<Object>(); | |||
private Properties globalProperties; | |||
private Properties projectProperties; | |||
private boolean isEncodingPlatformDependant; | |||
private SonarCache cache; | |||
private Runner(String command, Properties globalProperties, Properties projectProperties) { | |||
this.command = command; | |||
this.globalProperties = globalProperties; | |||
this.projectProperties = projectProperties; | |||
this.unmaskedPackages = new String[0]; | |||
// set the default values for the Sonar Runner - they can be overriden with #setEnvironmentInformation | |||
this.globalProperties.put(PROPERTY_ENVIRONMENT_INFORMATION_KEY, "Runner"); | |||
this.globalProperties.put(PROPERTY_ENVIRONMENT_INFORMATION_VERSION, Version.getVersion()); | |||
// sets the encoding if not forced | |||
if (!globalProperties.containsKey(PROPERTY_SOURCE_ENCODING) && !projectProperties.containsKey(PROPERTY_SOURCE_ENCODING)) { | |||
isEncodingPlatformDependant = true; | |||
globalProperties.setProperty(PROPERTY_SOURCE_ENCODING, Charset.defaultCharset().name()); | |||
} | |||
// and init the directories | |||
initDirs(); | |||
// init the cache | |||
// Try to get Sonar user home from property | |||
cache = SonarCache.create(getSonarUserHomeDir()).build(); | |||
} | |||
/** | |||
* Creates a Runner based only on the given properties. | |||
* @deprecated Use {@link Runner#create(String, Properties, Properties)} | |||
*/ | |||
@Deprecated | |||
public static Runner create(Properties props) { | |||
return create(null, new Properties(), props); | |||
} | |||
/** | |||
* Creates a Runner based only on the given properties. | |||
*/ | |||
public static Runner create(String command, Properties globalProperties, Properties projectProperties) { | |||
return new Runner(command, globalProperties, projectProperties); | |||
} | |||
/** | |||
* Creates a Runner based only on the properties and with the given base directory. | |||
* @deprecated Use {@link Runner#create(String, Properties, Properties, File)} | |||
*/ | |||
@Deprecated | |||
public static Runner create(Properties props, File basedir) { | |||
return create(null, new Properties(), props, basedir); | |||
} | |||
/** | |||
* Creates a Runner based only on the properties and with the given base directory. | |||
*/ | |||
public static Runner create(String command, Properties globalProperties, Properties projectProperties, File basedir) { | |||
projectProperties.put(PROPERTY_SONAR_PROJECT_BASEDIR, basedir.getAbsolutePath()); | |||
return new Runner(command, globalProperties, projectProperties); | |||
} | |||
/** | |||
* Runs a Sonar analysis. | |||
*/ | |||
public void execute() { | |||
Bootstrapper bootstrapper = new Bootstrapper("SonarRunner/" + Version.getVersion(), getSonarServerURL(), getWorkDir(), getCache()); | |||
checkSonarVersion(bootstrapper); | |||
delegateExecution(createClassLoader(bootstrapper)); | |||
} | |||
public String getSonarServerURL() { | |||
return projectProperties.getProperty("sonar.host.url", globalProperties.getProperty("sonar.host.url", "http://localhost:9000")); | |||
} | |||
public SonarCache getCache() { | |||
return cache; | |||
} | |||
private void initDirs() { | |||
projectDir = initProjectDir(); | |||
// project home exists: add its absolute path as "sonar.projectBaseDir" property | |||
projectProperties.put(PROPERTY_SONAR_PROJECT_BASEDIR, projectDir.getAbsolutePath()); | |||
workDir = initWorkDir(); | |||
sonarUserHomeDir = initSonarUserHomeDir(); | |||
} | |||
private File initSonarUserHomeDir() { | |||
String sonarUserHome = globalProperties.getProperty(PROPERTY_SONAR_USER_HOME); | |||
if (StringUtils.isBlank(sonarUserHome)) { | |||
// Try to get Sonar user home from environment variable | |||
sonarUserHome = System.getenv(ENV_SONAR_USER_HOME); | |||
} | |||
if (StringUtils.isBlank(sonarUserHome)) { | |||
// Default Sonar user home | |||
sonarUserHome = System.getProperty("user.home") + File.separator + ".sonar"; | |||
} | |||
return new File(sonarUserHome); | |||
} | |||
private File initProjectDir() { | |||
String path = projectProperties.getProperty(PROPERTY_SONAR_PROJECT_BASEDIR, "."); | |||
File dir = new File(path); | |||
if (!dir.isDirectory()) { | |||
throw new RunnerException("Project home must be an existing directory: " + path); | |||
} | |||
return dir; | |||
} | |||
private File initWorkDir() { | |||
File newWorkDir; | |||
String customWorkDir = projectProperties.getProperty(PROPERTY_WORK_DIRECTORY, globalProperties.getProperty(PROPERTY_WORK_DIRECTORY)); | |||
if (customWorkDir == null || "".equals(customWorkDir.trim())) { | |||
newWorkDir = new File(getProjectDir(), DEF_VALUE_WORK_DIRECTORY); | |||
} | |||
else { | |||
newWorkDir = defineCustomizedWorkDir(new File(customWorkDir)); | |||
} | |||
FileUtils.deleteQuietly(newWorkDir); | |||
return newWorkDir; | |||
} | |||
private File defineCustomizedWorkDir(File customWorkDir) { | |||
if (customWorkDir.isAbsolute()) { | |||
return customWorkDir; | |||
} | |||
return new File(getProjectDir(), customWorkDir.getPath()); | |||
} | |||
/** | |||
* @return the project base directory | |||
*/ | |||
public File getProjectDir() { | |||
return projectDir; | |||
} | |||
/** | |||
* @return work directory, default is ".sonar" in project directory | |||
*/ | |||
public File getWorkDir() { | |||
return workDir; | |||
} | |||
/** | |||
* @return sonar user home directory | |||
*/ | |||
public File getSonarUserHomeDir() { | |||
return sonarUserHomeDir; | |||
} | |||
/** | |||
* @return the source code encoding that will be used by Sonar | |||
*/ | |||
public String getSourceCodeEncoding() { | |||
return projectProperties.getProperty(PROPERTY_SOURCE_ENCODING, globalProperties.getProperty(PROPERTY_SOURCE_ENCODING)); | |||
} | |||
/** | |||
* @return true if the property "sonar.sourceEncoding" hasn't been forced | |||
*/ | |||
public boolean isEncodingPlatformDependant() { | |||
return isEncodingPlatformDependant; | |||
} | |||
public String getCommand() { | |||
return command; | |||
} | |||
/** | |||
* @return global properties, project properties and command-line properties | |||
*/ | |||
public Properties getProperties() { | |||
Properties props = new Properties(); | |||
props.putAll(globalProperties); | |||
props.putAll(projectProperties); | |||
return props; | |||
} | |||
protected void checkSonarVersion(Bootstrapper bootstrapper) { | |||
String serverVersion = bootstrapper.getServerVersion(); | |||
if (isUnsupportedVersion(serverVersion)) { | |||
throw new RunnerException("Sonar " + serverVersion | |||
+ " is not supported. Please upgrade Sonar to version 2.11 or more."); | |||
} | |||
if (command != null && isUnsupportedVersionForTasks(serverVersion)) { | |||
throw new RunnerException("Sonar " + serverVersion | |||
+ " doesn't support tasks. Please upgrade Sonar to version 3.5 or more."); | |||
} | |||
} | |||
private BootstrapClassLoader createClassLoader(Bootstrapper bootstrapper) { | |||
URL url = getJarPath(); | |||
return bootstrapper.createClassLoader( | |||
// Add JAR with Sonar Runner - it's a Jar which contains this class | |||
new URL[] {url}, | |||
getClass().getClassLoader(), | |||
unmaskedPackages); | |||
} | |||
/** | |||
* For unknown reasons <code>getClass().getProtectionDomain().getCodeSource().getLocation()</code> doesn't work under Ant 1.7.0. | |||
* So this is a workaround. | |||
* | |||
* @return Jar which contains this class | |||
*/ | |||
public static URL getJarPath() { | |||
String pathToClass = "/" + Runner.class.getName().replace('.', '/') + ".class"; | |||
URL url = Runner.class.getResource(pathToClass); | |||
if (url != null) { | |||
String path = url.toString(); | |||
String uri = null; | |||
if (path.startsWith("jar:file:")) { | |||
int bang = path.indexOf('!'); | |||
uri = path.substring(4, bang); | |||
} else if (path.startsWith("file:")) { | |||
int tail = path.indexOf(pathToClass); | |||
uri = path.substring(0, tail); | |||
} | |||
if (uri != null) { | |||
try { | |||
return new URL(uri); | |||
} catch (MalformedURLException e) { | |||
} | |||
} | |||
} | |||
return null; | |||
} | |||
static boolean isUnsupportedVersion(String version) { | |||
return VersionUtils.isUnsupportedVersion(version, UNSUPPORTED_VERSIONS); | |||
} | |||
static boolean isUnsupportedVersionForTasks(String version) { | |||
return VersionUtils.isUnsupportedVersion(version, UNSUPPORTED_VERSIONS_FOR_TASKS); | |||
} | |||
private void delegateExecution(BootstrapClassLoader sonarClassLoader) { | |||
ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader(); | |||
try { | |||
Thread.currentThread().setContextClassLoader(sonarClassLoader); | |||
Class<?> launcherClass = sonarClassLoader.findClass("org.sonar.runner.internal.batch.Launcher"); | |||
Constructor<?> constructor = launcherClass.getConstructor(String.class, Properties.class, Properties.class, List.class); | |||
Object launcher = constructor.newInstance(getCommand(), globalProperties, projectProperties, containerExtensions); | |||
Method method = launcherClass.getMethod("execute"); | |||
method.invoke(launcher); | |||
} catch (InvocationTargetException e) { | |||
// Unwrap original exception | |||
throw new RunnerException("Unable to execute Sonar", e.getTargetException()); | |||
} catch (Exception e) { | |||
// Catch all other exceptions, which relates to reflection | |||
throw new RunnerException("Unable to execute Sonar", e); | |||
} finally { | |||
Thread.currentThread().setContextClassLoader(oldContextClassLoader); | |||
} | |||
} | |||
/** | |||
* Allows to overwrite the environment information when Sonar Runner is embedded in a specific tool (for instance, with the Sonar Ant Task). | |||
* | |||
* @param key the key of the tool that embeds Sonar Runner | |||
* @param version the version of this tool | |||
*/ | |||
public void setEnvironmentInformation(String key, String version) { | |||
this.globalProperties.put(PROPERTY_ENVIRONMENT_INFORMATION_KEY, key); | |||
this.globalProperties.put(PROPERTY_ENVIRONMENT_INFORMATION_VERSION, version); | |||
} | |||
public void setUnmaskedPackages(String... unmaskedPackages) { | |||
this.unmaskedPackages = unmaskedPackages; | |||
} | |||
public void addContainerExtension(Object extension) { | |||
containerExtensions.add(extension); | |||
} | |||
} |
@@ -0,0 +1,52 @@ | |||
/* | |||
* Sonar Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* dev@sonar.codehaus.org | |||
* | |||
* 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; | |||
/** | |||
* Exception thrown by the Sonar Runner when something bad happens. | |||
* | |||
* @since 1.2 | |||
*/ | |||
public class RunnerException extends RuntimeException { | |||
private static final long serialVersionUID = 4810407777585753030L; | |||
/** | |||
* See {@link RuntimeException} | |||
*/ | |||
public RunnerException(String message) { | |||
super(message); | |||
} | |||
/** | |||
* See {@link RuntimeException} | |||
*/ | |||
public RunnerException(Throwable cause) { | |||
super(cause); | |||
} | |||
/** | |||
* See {@link RuntimeException} | |||
*/ | |||
public RunnerException(String message, Throwable cause) { | |||
super(message, cause); | |||
} | |||
} |
@@ -0,0 +1,202 @@ | |||
/* | |||
* Sonar Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* dev@sonar.codehaus.org | |||
* | |||
* 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; | |||
import org.apache.commons.codec.digest.DigestUtils; | |||
import org.apache.commons.io.FileUtils; | |||
import org.apache.commons.io.IOUtils; | |||
import java.io.File; | |||
import java.io.FileInputStream; | |||
import java.io.IOException; | |||
/** | |||
* 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). | |||
* Default location of cache is | |||
* @author Julien HENRY | |||
* | |||
*/ | |||
public class SonarCache { | |||
private static final int TEMP_FILE_ATTEMPTS = 10000; | |||
private File cacheLocation; | |||
/** | |||
* Temporary directory where files should be stored before be inserted in the cache. | |||
* Having a temporary close to the final location (read on same FS) will assure | |||
* the move will be atomic. | |||
*/ | |||
private File tmpDir; | |||
private SonarCache(File cacheLocation) { | |||
this.cacheLocation = cacheLocation; | |||
tmpDir = new File(cacheLocation, "tmp"); | |||
if (!cacheLocation.exists()) { | |||
Logs.debug("Creating cache directory: " + cacheLocation.getAbsolutePath()); | |||
try { | |||
FileUtils.forceMkdir(cacheLocation); | |||
} catch (IOException e) { | |||
throw new RuntimeException("Unable to create cache directory " + cacheLocation.getAbsolutePath(), e); | |||
} | |||
} | |||
} | |||
public static class Builder { | |||
private File sonarUserHomeLocation; | |||
private File cacheLocation; | |||
public Builder(File sonarUserHomeLocation) { | |||
this.sonarUserHomeLocation = sonarUserHomeLocation; | |||
} | |||
public Builder setCacheLocation(File cacheLocation) { | |||
this.cacheLocation = cacheLocation; | |||
return this; | |||
} | |||
public SonarCache build() { | |||
if (cacheLocation == null) { | |||
return new SonarCache(new File(sonarUserHomeLocation, "cache")); | |||
} | |||
else { | |||
return new SonarCache(cacheLocation); | |||
} | |||
} | |||
} | |||
public static Builder create(File sonarUserHomeLocation) { | |||
if (sonarUserHomeLocation == null) { | |||
throw new RunnerException("Sonar user home directory should not be null"); | |||
} | |||
return new Builder(sonarUserHomeLocation); | |||
} | |||
/** | |||
* Move the given file inside the cache. Return the MD5 of the cached file. | |||
* @param sourceFile | |||
* @throws IOException | |||
*/ | |||
public String cacheFile(File sourceFile, String filename) throws IOException { | |||
Logs.debug("Trying to cache file " + sourceFile.getAbsolutePath() + " with filename " + filename); | |||
File tmpFileName = null; | |||
try { | |||
if (!sourceFile.getParentFile().equals(getTmpDir())) { | |||
// Provided file is not close to the cache so we will move it first in a temporary file (could be non atomic) | |||
tmpFileName = getTemporaryFile(); | |||
FileUtils.moveFile(sourceFile, tmpFileName); | |||
} | |||
else { | |||
tmpFileName = sourceFile; | |||
} | |||
// Now compute the md5 to find the final destination | |||
String md5; | |||
FileInputStream fis = null; | |||
try { | |||
fis = new FileInputStream(tmpFileName); | |||
md5 = DigestUtils.md5Hex(fis); | |||
} finally { | |||
IOUtils.closeQuietly(fis); | |||
} | |||
File finalDir = new File(cacheLocation, md5); | |||
File finalFileName = new File(finalDir, filename); | |||
// Try to create final destination folder | |||
FileUtils.forceMkdir(finalDir); | |||
// Now try to move the file from temporary folder to final location | |||
boolean rename = tmpFileName.renameTo(finalFileName); | |||
if (!rename) { | |||
// Check if the file was already in cache | |||
if (!finalFileName.exists()) { | |||
Logs.warn("Unable to rename " + tmpFileName.getAbsolutePath() + " to " + finalFileName.getAbsolutePath()); | |||
Logs.warn("A copy/delete will be tempted but with no garantee of atomicity"); | |||
FileUtils.moveFile(tmpFileName, finalFileName); | |||
} | |||
} | |||
Logs.debug("File cached at " + finalFileName.getAbsolutePath()); | |||
return md5; | |||
} finally { | |||
FileUtils.deleteQuietly(tmpFileName); | |||
} | |||
} | |||
/** | |||
* Look for a file in the cache by its filename and md5 checksum. If the file is not | |||
* present then return null. | |||
*/ | |||
public File getFileFromCache(String filename, String md5) { | |||
File location = new File(new File(cacheLocation, md5), filename); | |||
Logs.debug("Looking for " + location.getAbsolutePath()); | |||
if (location.exists()) { | |||
return location; | |||
} | |||
Logs.debug("No file found in the cache with name " + filename + " and checksum " + md5); | |||
return null; | |||
} | |||
/** | |||
* Return a temporary file that caller can use to store file content before | |||
* asking for caching it with {@link #cacheFile(File)}. | |||
* This is to avoid extra copy. | |||
* @return | |||
* @throws IOException | |||
*/ | |||
public File getTemporaryFile() throws IOException { | |||
return createTempFile(getTmpDir()); | |||
} | |||
/** | |||
* Create a temporary file in the given directory. | |||
* @param baseDir | |||
* @return | |||
* @throws IOException | |||
*/ | |||
private static File createTempFile(File baseDir) throws IOException { | |||
String baseName = System.currentTimeMillis() + "-"; | |||
for (int counter = 0; counter < TEMP_FILE_ATTEMPTS; counter++) { | |||
File tempFile = new File(baseDir, baseName + counter); | |||
if (tempFile.createNewFile()) { | |||
return tempFile; | |||
} | |||
} | |||
throw new IOException("Failed to create temporary file in " + baseDir.getAbsolutePath() + " within " | |||
+ TEMP_FILE_ATTEMPTS + " attempts (tried " | |||
+ baseName + "0 to " + baseName + (TEMP_FILE_ATTEMPTS - 1) + ')'); | |||
} | |||
public File getTmpDir() { | |||
if (!tmpDir.exists()) { | |||
Logs.debug("Creating temporary cache directory: " + tmpDir.getAbsolutePath()); | |||
try { | |||
FileUtils.forceMkdir(tmpDir); | |||
} catch (IOException e) { | |||
throw new RuntimeException("Unable to create temporary cache directory " + tmpDir.getAbsolutePath(), e); | |||
} | |||
} | |||
return tmpDir; | |||
} | |||
public File getCacheLocation() { | |||
return cacheLocation; | |||
} | |||
} |
@@ -0,0 +1,60 @@ | |||
/* | |||
* Sonar Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* dev@sonar.codehaus.org | |||
* | |||
* 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; | |||
class Stats { | |||
private long startTime; | |||
Stats() { | |||
} | |||
Stats start() { | |||
startTime = System.currentTimeMillis(); | |||
return this; | |||
} | |||
Stats stop() { | |||
long stopTime = System.currentTimeMillis() - startTime; | |||
Logs.info("Total time: " + formatTime(stopTime)); | |||
System.gc(); | |||
Runtime r = Runtime.getRuntime(); | |||
long mb = 1024L * 1024; | |||
Logs.info("Final Memory: " + (r.totalMemory() - r.freeMemory()) / mb + "M/" + r.totalMemory() / mb + "M"); | |||
return this; | |||
} | |||
static String formatTime(long time) { | |||
long h = time / (60 * 60 * 1000); | |||
long m = (time - h * 60 * 60 * 1000) / (60 * 1000); | |||
long s = (time - h * 60 * 60 * 1000 - m * 60 * 1000) / 1000; | |||
long ms = time % 1000; | |||
final String format; | |||
if (h > 0) { | |||
format = "%1$d:%2$02d:%3$02d.%4$03ds"; | |||
} else if (m > 0) { | |||
format = "%2$d:%3$02d.%4$03ds"; | |||
} else { | |||
format = "%3$d.%4$03ds"; | |||
} | |||
return String.format(format, h, m, s, ms); | |||
} | |||
} |
@@ -0,0 +1,54 @@ | |||
/* | |||
* Sonar Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* dev@sonar.codehaus.org | |||
* | |||
* 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; | |||
import org.apache.commons.io.IOUtils; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.util.Properties; | |||
public enum Version { | |||
INSTANCE; | |||
private static final String PROPERTIES_PATH = "/org/sonar/runner/version.txt"; | |||
private String version; | |||
public static String getVersion() { | |||
return INSTANCE.version; | |||
} | |||
private Version() { | |||
InputStream input = getClass().getResourceAsStream(PROPERTIES_PATH); | |||
try { | |||
Properties properties = new Properties(); | |||
properties.load(input); | |||
this.version = properties.getProperty("version"); | |||
} catch (IOException e) { | |||
// Can not load the version | |||
this.version = ""; | |||
} finally { | |||
IOUtils.closeQuietly(input); | |||
} | |||
} | |||
} |
@@ -0,0 +1,45 @@ | |||
/* | |||
* Sonar Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* dev@sonar.codehaus.org | |||
* | |||
* 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; | |||
/** | |||
* Internal class used only by the Runner. | |||
* This class should not be used by Sonar Runner consumers. | |||
*/ | |||
final class VersionUtils { | |||
private VersionUtils() { | |||
// only static methods | |||
} | |||
static boolean isUnsupportedVersion(String version, String[] unsuportedVersions) { | |||
for (String unsupportedVersion : unsuportedVersions) { | |||
if (isVersion(version, unsupportedVersion)) { | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
private static boolean isVersion(String version, String prefix) { | |||
return version.startsWith(prefix + ".") || version.equals(prefix); | |||
} | |||
} |
@@ -0,0 +1,23 @@ | |||
/* | |||
* Sonar Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* dev@sonar.codehaus.org | |||
* | |||
* 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 | |||
*/ | |||
/** | |||
* API package of the Sonar Runner. | |||
*/ | |||
package org.sonar.runner; |
@@ -0,0 +1 @@ | |||
version=${project.version} |
@@ -0,0 +1,62 @@ | |||
/* | |||
* Sonar Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* dev@sonar.codehaus.org | |||
* | |||
* 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; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
public class BootstrapClassLoaderTest { | |||
@Rule | |||
public ExpectedException thrown = ExpectedException.none(); | |||
@Test | |||
public void should_restrict_loading_from_parent() throws Exception { | |||
BootstrapClassLoader classLoader = new BootstrapClassLoader(getClass().getClassLoader(), "org.apache.ant"); | |||
assertThat(classLoader.canLoadFromParent("org.sonar.runner.internal.batch.Launcher")).isFalse(); | |||
assertThat(classLoader.canLoadFromParent("org.sonar.runner.Runner")).isTrue(); | |||
assertThat(classLoader.canLoadFromParent("org.objectweb.asm.ClassVisitor")).isFalse(); | |||
assertThat(classLoader.canLoadFromParent("org.apache.ant.project.Project")).isTrue(); | |||
} | |||
@Test | |||
public void should_use_isolated_system_classloader_when_parent_is_excluded() throws ClassNotFoundException { | |||
thrown.expect(ClassNotFoundException.class); | |||
thrown.expectMessage("org.junit.Test"); | |||
ClassLoader parent = getClass().getClassLoader(); | |||
BootstrapClassLoader classLoader = new BootstrapClassLoader(parent); | |||
// JUnit is available in the parent classloader (classpath used to execute this test) but not in the core JVM | |||
assertThat(classLoader.loadClass("java.lang.String", false)).isNotNull(); | |||
classLoader.loadClass("org.junit.Test", false); | |||
} | |||
@Test | |||
public void should_find_in_parent_when_matches_unmasked_packages() throws ClassNotFoundException { | |||
ClassLoader parent = getClass().getClassLoader(); | |||
BootstrapClassLoader classLoader = new BootstrapClassLoader(parent, "org.junit"); | |||
// JUnit is available in the parent classloader (classpath used to execute this test) but not in the core JVM | |||
assertThat(classLoader.loadClass("org.junit.Test", false)).isNotNull(); | |||
} | |||
} |
@@ -0,0 +1,178 @@ | |||
/* | |||
* Sonar Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* dev@sonar.codehaus.org | |||
* | |||
* 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; | |||
import org.apache.commons.io.IOUtils; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.TemporaryFolder; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.net.HttpURLConnection; | |||
import java.net.MalformedURLException; | |||
import java.net.URL; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.when; | |||
public class BootstrapperTest { | |||
@Rule | |||
public TemporaryFolder tempFolder = new TemporaryFolder(); | |||
@Test | |||
public void shouldRemoveLastUrlSlash() { | |||
Bootstrapper bootstrapper = new Bootstrapper("", "http://test/", new File("target/tmp"), null); | |||
assertThat(bootstrapper.getServerUrl()).isEqualTo("http://test"); | |||
} | |||
@Test(expected = Exception.class) | |||
public void shouldFailIfCanNotConnectServer() { | |||
Bootstrapper bootstrapper = new Bootstrapper("", "http://unknown.foo", new File("target/tmp"), null); | |||
bootstrapper.getServerVersion(); | |||
} | |||
@Test | |||
public void shouldReturnUserAgent() { | |||
Bootstrapper bootstrapper = new Bootstrapper("test/0.1", "http://unknown.foo", new File("target/tmp"), null); | |||
String userAgent = bootstrapper.getUserAgent(); | |||
assertThat(userAgent.length()).isGreaterThan(0); | |||
assertThat(userAgent).startsWith("sonar-bootstrapper/"); | |||
assertThat(userAgent).endsWith(" test/0.1"); | |||
} | |||
@Test | |||
public void shouldReturnValidVersion() { | |||
Bootstrapper bootstrapper = new Bootstrapper("", "http://test", new File("target/tmp"), null) { | |||
@Override | |||
String remoteContent(String path) throws IOException { | |||
return "2.6"; | |||
} | |||
}; | |||
assertThat(bootstrapper.getServerVersion()).isEqualTo("2.6"); | |||
} | |||
@Test | |||
public void shouldParseEncodingFromContentType() { | |||
assertThat(Bootstrapper.getCharsetFromContentType("text/html; charset=EUC-JP")).isEqualTo("EUC-JP"); | |||
assertThat(Bootstrapper.getCharsetFromContentType("text/html")).isNull(); | |||
} | |||
@Test | |||
public void shouldCheckVersionForCache() { | |||
assertThat(Bootstrapper.isUnsupportedVersionForCache("1.0")).isTrue(); | |||
assertThat(Bootstrapper.isUnsupportedVersionForCache("2.0")).isTrue(); | |||
assertThat(Bootstrapper.isUnsupportedVersionForCache("2.1")).isTrue(); | |||
assertThat(Bootstrapper.isUnsupportedVersionForCache("2.2")).isTrue(); | |||
assertThat(Bootstrapper.isUnsupportedVersionForCache("2.3")).isTrue(); | |||
assertThat(Bootstrapper.isUnsupportedVersionForCache("2.4")).isTrue(); | |||
assertThat(Bootstrapper.isUnsupportedVersionForCache("2.4.1")).isTrue(); | |||
assertThat(Bootstrapper.isUnsupportedVersionForCache("2.5")).isTrue(); | |||
assertThat(Bootstrapper.isUnsupportedVersionForCache("2.11")).isTrue(); | |||
assertThat(Bootstrapper.isUnsupportedVersionForCache("3.0")).isTrue(); | |||
assertThat(Bootstrapper.isUnsupportedVersionForCache("3.1")).isTrue(); | |||
assertThat(Bootstrapper.isUnsupportedVersionForCache("3.2")).isTrue(); | |||
assertThat(Bootstrapper.isUnsupportedVersionForCache("3.3")).isTrue(); | |||
assertThat(Bootstrapper.isUnsupportedVersionForCache("3.4")).isTrue(); | |||
assertThat(Bootstrapper.isUnsupportedVersionForCache("3.5")).isFalse(); | |||
} | |||
@Test | |||
public void shouldCacheWhenNecessary() throws Exception { | |||
File sonarUserHome = tempFolder.newFolder(); | |||
SonarCache cache = SonarCache.create(sonarUserHome).build(); | |||
final MockedConnectionFactory connections = new MockedConnectionFactory("http://test"); | |||
connections.register("/api/server/version", "3.5"); | |||
connections.register("/batch_bootstrap/index", "foo.jar|922afef30ca31573d7131347d01b76c4\nbar.jar|69155f65900fbabbf21e28abb33dd06a"); | |||
connections.register("/batch/foo.jar", "fakecontent1"); | |||
connections.register("/batch/bar.jar", "fakecontent2"); | |||
Bootstrapper bootstrapper = new Bootstrapper("", "http://test", new File("target/tmp"), cache) { | |||
@Override | |||
HttpURLConnection newHttpConnection(URL url) throws IOException { | |||
return connections.get(url); | |||
} | |||
}; | |||
bootstrapper.createClassLoader(new URL[] {}, this.getClass().getClassLoader()); | |||
assertThat(new File(new File(cache.getCacheLocation(), "922afef30ca31573d7131347d01b76c4"), "foo.jar")).exists(); | |||
assertThat(new File(new File(cache.getCacheLocation(), "69155f65900fbabbf21e28abb33dd06a"), "bar.jar")).exists(); | |||
// Should not download during the second execution | |||
final MockedConnectionFactory connections2 = new MockedConnectionFactory("http://test"); | |||
connections2.register("/api/server/version", "3.5"); | |||
connections2.register("/batch_bootstrap/index", "foo.jar|922afef30ca31573d7131347d01b76c4\nbar.jar|69155f65900fbabbf21e28abb33dd06a"); | |||
Bootstrapper bootstrapper2 = new Bootstrapper("", "http://test", new File("target/tmp"), cache) { | |||
@Override | |||
HttpURLConnection newHttpConnection(URL url) throws IOException { | |||
return connections2.get(url); | |||
} | |||
}; | |||
bootstrapper2.createClassLoader(new URL[] {}, this.getClass().getClassLoader()); | |||
} | |||
@Test | |||
public void shouldDownloadFromOldURL() throws Exception { | |||
File sonarUserHome = tempFolder.newFolder(); | |||
final MockedConnectionFactory connections = new MockedConnectionFactory("http://test"); | |||
connections.register("/api/server/version", "3.4"); | |||
connections.register("/batch/", "foo.jar,bar.jar"); | |||
connections.register("/batch/foo.jar", "fakecontent1"); | |||
connections.register("/batch/bar.jar", "fakecontent2"); | |||
Bootstrapper bootstrapper = new Bootstrapper("", "http://test", new File("target/tmp"), SonarCache.create(sonarUserHome).build()) { | |||
@Override | |||
HttpURLConnection newHttpConnection(URL url) throws IOException { | |||
return connections.get(url); | |||
} | |||
}; | |||
bootstrapper.createClassLoader(new URL[] {}, this.getClass().getClassLoader()); | |||
verify(connections.get("/batch/foo.jar")).getInputStream(); | |||
verify(connections.get("/batch/bar.jar")).getInputStream(); | |||
} | |||
private class MockedConnectionFactory { | |||
private Map<URL, HttpURLConnection> mockedConnections = new HashMap<URL, HttpURLConnection>(); | |||
private String serverUrl; | |||
public MockedConnectionFactory(String serverUrl) { | |||
this.serverUrl = serverUrl; | |||
} | |||
public void register(String path, String content) throws Exception { | |||
HttpURLConnection mockConnection = mock(HttpURLConnection.class); | |||
when(mockConnection.getInputStream()).thenReturn(IOUtils.toInputStream(content)); | |||
when(mockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK); | |||
mockedConnections.put(new URL(serverUrl + path), mockConnection); | |||
} | |||
public HttpURLConnection get(URL url) { | |||
return mockedConnections.get(url); | |||
} | |||
public HttpURLConnection get(String path) throws MalformedURLException { | |||
return mockedConnections.get(new URL(serverUrl + path)); | |||
} | |||
} | |||
} |
@@ -0,0 +1,83 @@ | |||
/* | |||
* Sonar Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* dev@sonar.codehaus.org | |||
* | |||
* 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; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.PrintStream; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
public class LogsTest { | |||
private PrintStream oldSysout; | |||
private PrintStream oldSyserr; | |||
private ByteArrayOutputStream baosOut; | |||
private ByteArrayOutputStream baosErr; | |||
@Before | |||
public void prepare() { | |||
oldSysout = System.out; | |||
oldSyserr = System.err; | |||
baosOut = new ByteArrayOutputStream(); | |||
System.setOut(new PrintStream(baosOut)); | |||
baosErr = new ByteArrayOutputStream(); | |||
System.setErr(new PrintStream(baosErr)); | |||
} | |||
@After | |||
public void restore() { | |||
System.setOut(oldSysout); | |||
System.setErr(oldSyserr); | |||
} | |||
@Test | |||
public void shouldLogInfo() { | |||
Logs.info("info"); | |||
assertThat(baosOut.toString()).contains("INFO: info"); | |||
assertThat(baosErr.toString()).isEmpty(); | |||
} | |||
@Test | |||
public void shouldLogError() { | |||
Logs.error("error"); | |||
assertThat(baosOut.toString()).isEmpty(); | |||
assertThat(baosErr.toString()).contains("ERROR: error"); | |||
} | |||
@Test | |||
public void shouldLogErrorWithoutThrowable() { | |||
Logs.error("error", null); | |||
assertThat(baosOut.toString()).isEmpty(); | |||
assertThat(baosErr.toString()).contains("ERROR: error"); | |||
} | |||
@Test | |||
public void shouldLogErrorWithThrowable() { | |||
Logs.error("error", new RuntimeException()); | |||
assertThat(baosOut.toString()).isEmpty(); | |||
assertThat(baosErr.toString()).contains("ERROR: error").contains("RuntimeException"); | |||
} | |||
} |
@@ -0,0 +1,131 @@ | |||
/* | |||
* Sonar Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* dev@sonar.codehaus.org | |||
* | |||
* 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; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import java.io.File; | |||
import java.util.Properties; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
public class MainTest { | |||
private Main main; | |||
@Before | |||
public void prepare() { | |||
main = new Main(); | |||
} | |||
@Test | |||
public void shouldParseEmptyArguments() { | |||
Properties props = main.parseArguments(new String[] {}); | |||
assertThat(props).isEmpty(); | |||
} | |||
@Test | |||
public void shouldParseArguments() { | |||
Properties props = main.parseArguments(new String[] {"-D", "foo=bar", "--define", "hello=world", "-Dboolean"}); | |||
assertThat(props).hasSize(3); | |||
assertThat(props.getProperty("foo")).isEqualTo("bar"); | |||
assertThat(props.getProperty("hello")).isEqualTo("world"); | |||
assertThat(props.getProperty("boolean")).isEqualTo("true"); | |||
} | |||
@Test | |||
public void shouldParseCommand() { | |||
Properties props = main.parseArguments(new String[] {"cmd", "-D", "foo=bar", "--define", "hello=world", "-Dboolean"}); | |||
assertThat(main.command).isEqualTo("cmd"); | |||
assertThat(props).hasSize(3); | |||
} | |||
@Test | |||
public void shouldEnableDebugMode() { | |||
Properties props = main.parseArguments(new String[] {"-X"}); | |||
assertThat(main.debugMode).isTrue(); | |||
assertThat(main.displayStackTrace).isTrue(); | |||
assertThat(props.getProperty(Runner.PROPERTY_VERBOSE)).isEqualTo("true"); | |||
} | |||
@Test | |||
public void shouldEnableError() { | |||
Properties props = main.parseArguments(new String[] {"-e"}); | |||
assertThat(main.debugMode).isFalse(); | |||
assertThat(main.displayStackTrace).isTrue(); | |||
assertThat(props.getProperty(Runner.PROPERTY_VERBOSE)).isNull(); | |||
} | |||
@Test | |||
public void shouldDisableDebugModeAndStackByDefault() { | |||
Properties props = main.parseArguments(new String[] {}); | |||
assertThat(main.debugMode).isFalse(); | |||
assertThat(main.displayStackTrace).isFalse(); | |||
assertThat(props.getProperty(Runner.PROPERTY_VERBOSE)).isNull(); | |||
} | |||
@Test | |||
public void shouldLoadRunnerSettingsByHome() throws Exception { | |||
File home = new File(getClass().getResource("/org/sonar/runner/MainTest/shouldLoadRunnerSettingsByHome/").toURI()); | |||
Properties args = new Properties(); | |||
args.setProperty("runner.home", home.getCanonicalPath()); | |||
Properties props = new Main().loadRunnerConfiguration(args); | |||
assertThat(props.getProperty("sonar.host.url")).isEqualTo("http://moon/sonar"); | |||
} | |||
@Test | |||
public void shouldNotFailIfNoHome() throws Exception { | |||
Properties args = new Properties(); | |||
Properties props = new Main().loadRunnerConfiguration(args); | |||
assertThat(props).isEmpty(); | |||
} | |||
@Test | |||
public void shouldLoadRunnerSettingsByDirectPath() throws Exception { | |||
File settings = new File(getClass().getResource("/org/sonar/runner/MainTest/shouldLoadRunnerSettingsByDirectPath/other-conf.properties").toURI()); | |||
Properties args = new Properties(); | |||
args.setProperty("runner.settings", settings.getCanonicalPath()); | |||
Properties props = new Main().loadRunnerConfiguration(args); | |||
assertThat(props.getProperty("sonar.host.url")).isEqualTo("http://other/sonar"); | |||
} | |||
@Test | |||
public void shouldLoadCompleteConfiguration() throws Exception { | |||
File runnerHome = new File(getClass().getResource("/org/sonar/runner/MainTest/shouldLoadCompleteConfiguration/runner").toURI()); | |||
File projectHome = new File(getClass().getResource("/org/sonar/runner/MainTest/shouldLoadCompleteConfiguration/project").toURI()); | |||
Main main = new Main(); | |||
Properties args = main.parseArguments(new String[] { | |||
"-D", "runner.home=" + runnerHome.getCanonicalPath(), | |||
"-D", "project.home=" + projectHome.getCanonicalPath() | |||
}); | |||
main.loadProperties(args); | |||
assertThat(main.projectProperties.getProperty("project.prop")).isEqualTo("foo"); | |||
assertThat(main.projectProperties.getProperty("overridden.prop")).isEqualTo("project scope"); | |||
assertThat(main.globalProperties.getProperty("global.prop")).isEqualTo("jdbc:mysql:localhost/sonar"); | |||
} | |||
} |
@@ -0,0 +1,227 @@ | |||
/* | |||
* Sonar Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* dev@sonar.codehaus.org | |||
* | |||
* 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; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.junit.rules.TemporaryFolder; | |||
import java.io.File; | |||
import java.nio.charset.Charset; | |||
import java.util.Properties; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
public class RunnerTest { | |||
@Rule | |||
public TemporaryFolder tempFolder = new TemporaryFolder(); | |||
@Rule | |||
public ExpectedException thrown = ExpectedException.none(); | |||
@Test | |||
public void shouldHaveDefaultEncodingIfNotForce() { | |||
Runner runner = Runner.create(new Properties()); | |||
assertThat(runner.getSourceCodeEncoding()).isEqualTo(Charset.defaultCharset().name()); | |||
assertThat(runner.isEncodingPlatformDependant()).isTrue(); | |||
} | |||
@Test | |||
public void shouldKeepEncodingIfSpecified() { | |||
Properties props = new Properties(); | |||
// Yeah, windows charset! | |||
props.setProperty("sonar.sourceEncoding", "cp1252"); | |||
Runner runner = Runner.create(props); | |||
assertThat(runner.getSourceCodeEncoding()).isEqualTo("cp1252"); | |||
assertThat(runner.isEncodingPlatformDependant()).isFalse(); | |||
} | |||
@Test | |||
public void shouldHaveDefaultEnvironmentInformationValues() { | |||
Runner runner = Runner.create(new Properties()); | |||
assertThat(runner.getProperties().getProperty(Runner.PROPERTY_ENVIRONMENT_INFORMATION_KEY)).isEqualTo("Runner"); | |||
assertThat(runner.getProperties().getProperty(Runner.PROPERTY_ENVIRONMENT_INFORMATION_VERSION)).contains("."); | |||
assertThat(runner.getProperties().getProperty(Runner.PROPERTY_ENVIRONMENT_INFORMATION_VERSION)).doesNotContain("$"); | |||
} | |||
@Test | |||
public void shouldOverwriteDefaultEnvironmentInformationValues() { | |||
Runner runner = Runner.create(new Properties()); | |||
runner.setEnvironmentInformation("Ant", "1.2.3"); | |||
assertThat(runner.getProperties().getProperty(Runner.PROPERTY_ENVIRONMENT_INFORMATION_KEY)).isEqualTo("Ant"); | |||
assertThat(runner.getProperties().getProperty(Runner.PROPERTY_ENVIRONMENT_INFORMATION_VERSION)).isEqualTo("1.2.3"); | |||
} | |||
@Test | |||
public void shouldCheckVersion() { | |||
assertThat(Runner.isUnsupportedVersion("1.0")).isTrue(); | |||
assertThat(Runner.isUnsupportedVersion("2.0")).isTrue(); | |||
assertThat(Runner.isUnsupportedVersion("2.1")).isTrue(); | |||
assertThat(Runner.isUnsupportedVersion("2.2")).isTrue(); | |||
assertThat(Runner.isUnsupportedVersion("2.3")).isTrue(); | |||
assertThat(Runner.isUnsupportedVersion("2.4")).isTrue(); | |||
assertThat(Runner.isUnsupportedVersion("2.4.1")).isTrue(); | |||
assertThat(Runner.isUnsupportedVersion("2.5")).isTrue(); | |||
assertThat(Runner.isUnsupportedVersion("2.11")).isFalse(); | |||
assertThat(Runner.isUnsupportedVersion("3.0")).isFalse(); | |||
assertThat(Runner.isUnsupportedVersion("3.1")).isFalse(); | |||
assertThat(Runner.isUnsupportedVersion("3.2")).isFalse(); | |||
assertThat(Runner.isUnsupportedVersion("3.3")).isFalse(); | |||
assertThat(Runner.isUnsupportedVersion("3.4")).isFalse(); | |||
assertThat(Runner.isUnsupportedVersion("3.5")).isFalse(); | |||
} | |||
@Test | |||
public void shouldCheckVersionForTasks() { | |||
assertThat(Runner.isUnsupportedVersionForTasks("1.0")).isTrue(); | |||
assertThat(Runner.isUnsupportedVersionForTasks("2.0")).isTrue(); | |||
assertThat(Runner.isUnsupportedVersionForTasks("2.1")).isTrue(); | |||
assertThat(Runner.isUnsupportedVersionForTasks("2.2")).isTrue(); | |||
assertThat(Runner.isUnsupportedVersionForTasks("2.3")).isTrue(); | |||
assertThat(Runner.isUnsupportedVersionForTasks("2.4")).isTrue(); | |||
assertThat(Runner.isUnsupportedVersionForTasks("2.4.1")).isTrue(); | |||
assertThat(Runner.isUnsupportedVersionForTasks("2.5")).isTrue(); | |||
assertThat(Runner.isUnsupportedVersionForTasks("2.11")).isTrue(); | |||
assertThat(Runner.isUnsupportedVersionForTasks("3.0")).isTrue(); | |||
assertThat(Runner.isUnsupportedVersionForTasks("3.1")).isTrue(); | |||
assertThat(Runner.isUnsupportedVersionForTasks("3.2")).isTrue(); | |||
assertThat(Runner.isUnsupportedVersionForTasks("3.3")).isTrue(); | |||
assertThat(Runner.isUnsupportedVersionForTasks("3.4")).isTrue(); | |||
assertThat(Runner.isUnsupportedVersionForTasks("3.5")).isFalse(); | |||
} | |||
@Test | |||
public void shouldGetServerUrl() { | |||
Properties properties = new Properties(); | |||
Runner runner = Runner.create(properties); | |||
assertThat(runner.getSonarServerURL()).isEqualTo("http://localhost:9000"); | |||
properties.setProperty("sonar.host.url", "foo"); | |||
assertThat(runner.getSonarServerURL()).isEqualTo("foo"); | |||
} | |||
@Test | |||
public void shouldInitDirs() throws Exception { | |||
Properties props = new Properties(); | |||
File home = tempFolder.newFolder("shouldInitDirs").getCanonicalFile(); | |||
props.setProperty(Runner.PROPERTY_SONAR_PROJECT_BASEDIR, home.getCanonicalPath()); | |||
Runner runner = Runner.create(props); | |||
assertThat(runner.getProperties().get(Runner.PROPERTY_SONAR_PROJECT_BASEDIR)).isEqualTo(home.getCanonicalPath()); | |||
assertThat(runner.getProjectDir().getCanonicalFile()).isEqualTo(home); | |||
assertThat(runner.getWorkDir().getCanonicalFile()).isEqualTo(new File(home, ".sonar")); | |||
} | |||
@Test | |||
public void shouldInitProjectDirWithCurrentDir() throws Exception { | |||
Runner runner = Runner.create(new Properties()); | |||
assertThat(runner.getProjectDir().isDirectory()).isTrue(); | |||
assertThat(runner.getProjectDir().exists()).isTrue(); | |||
} | |||
@Test | |||
public void shouldSetValidBaseDirOnConstructor() { | |||
File baseDir = tempFolder.newFolder("shouldInitDirs"); | |||
Runner runner = Runner.create(new Properties(), baseDir); | |||
assertThat(runner.getProjectDir()).isEqualTo(baseDir); | |||
} | |||
@Test | |||
public void shouldFailIfBaseDirDoesNotExist() { | |||
File fakeBasedir = new File("fake"); | |||
thrown.expect(RunnerException.class); | |||
thrown.expectMessage("Project home must be an existing directory: " + fakeBasedir.getAbsolutePath()); | |||
Runner.create(new Properties(), fakeBasedir); | |||
} | |||
@Test | |||
public void shouldSpecifyWorkingDirectory() { | |||
Properties properties = new Properties(); | |||
Runner runner = Runner.create(properties); | |||
assertThat(runner.getWorkDir()).isEqualTo(new File(".", ".sonar")); | |||
// empty string | |||
properties.setProperty(Runner.PROPERTY_WORK_DIRECTORY, " "); | |||
runner = Runner.create(properties); | |||
assertThat(runner.getWorkDir()).isEqualTo(new File(".", ".sonar").getAbsoluteFile()); | |||
// real relative path | |||
properties.setProperty(Runner.PROPERTY_WORK_DIRECTORY, "temp-dir"); | |||
runner = Runner.create(properties); | |||
assertThat(runner.getWorkDir()).isEqualTo(new File(".", "temp-dir").getAbsoluteFile()); | |||
// real absolute path | |||
properties.setProperty(Runner.PROPERTY_WORK_DIRECTORY, new File("target", "temp-dir2").getAbsolutePath()); | |||
runner = Runner.create(properties); | |||
assertThat(runner.getWorkDir()).isEqualTo(new File("target", "temp-dir2").getAbsoluteFile()); | |||
} | |||
@Test | |||
public void shouldDeleteWorkingDirectory() { | |||
Properties properties = new Properties(); | |||
File workDir = new File("target", "temp-dir-should-be-deleted"); | |||
workDir.mkdirs(); | |||
assertThat(workDir.exists()).isTrue(); | |||
// real absolute path | |||
properties.setProperty(Runner.PROPERTY_WORK_DIRECTORY, workDir.getAbsolutePath()); | |||
Runner.create(properties); | |||
assertThat(workDir.exists()).isFalse(); | |||
} | |||
@Test | |||
public void shouldCheckSonarVersion() { | |||
Properties properties = new Properties(); | |||
Runner runner = Runner.create(properties); | |||
Bootstrapper bootstrapper = mock(Bootstrapper.class); | |||
// nothing happens, OK | |||
when(bootstrapper.getServerVersion()).thenReturn("3.0"); | |||
runner.checkSonarVersion(bootstrapper); | |||
// but fails with older versions | |||
when(bootstrapper.getServerVersion()).thenReturn("2.1"); | |||
thrown.expect(RunnerException.class); | |||
thrown.expectMessage("Sonar 2.1 is not supported. Please upgrade Sonar to version 2.11 or more."); | |||
runner.checkSonarVersion(bootstrapper); | |||
} | |||
@Test | |||
public void shouldCheckSonarVersionForTasks() { | |||
Properties properties = new Properties(); | |||
Runner runner = Runner.create("foo-cmd", properties, properties); | |||
Bootstrapper bootstrapper = mock(Bootstrapper.class); | |||
// nothing happens, OK | |||
when(bootstrapper.getServerVersion()).thenReturn("3.5"); | |||
runner.checkSonarVersion(bootstrapper); | |||
// but fails with older versions | |||
when(bootstrapper.getServerVersion()).thenReturn("3.4"); | |||
thrown.expect(RunnerException.class); | |||
thrown.expectMessage("Sonar 3.4 doesn't support tasks. Please upgrade Sonar to version 3.5 or more."); | |||
runner.checkSonarVersion(bootstrapper); | |||
} | |||
} |
@@ -0,0 +1,42 @@ | |||
/* | |||
* Sonar Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* dev@sonar.codehaus.org | |||
* | |||
* 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; | |||
import org.junit.Test; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
public class StatsTest { | |||
@Test | |||
public void shouldPrintStats() { | |||
new Stats().start().stop(); | |||
//TODO mock Logs | |||
} | |||
@Test | |||
public void shouldFormatTime() { | |||
assertThat(Stats.formatTime(1 * 60 * 60 * 1000 + 2 * 60 * 1000 + 3 * 1000 + 400)).isEqualTo("1:02:03.400s"); | |||
assertThat(Stats.formatTime(2 * 60 * 1000 + 3 * 1000 + 400)).isEqualTo("2:03.400s"); | |||
assertThat(Stats.formatTime(3 * 1000 + 400)).isEqualTo("3.400s"); | |||
assertThat(Stats.formatTime(400)).isEqualTo("0.400s"); | |||
} | |||
} |
@@ -0,0 +1,36 @@ | |||
/* | |||
* Sonar Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* dev@sonar.codehaus.org | |||
* | |||
* 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; | |||
import org.junit.Test; | |||
import org.sonar.runner.Version; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
public class VersionTest { | |||
@Test | |||
public void shouldLoadVersion() { | |||
String version = Version.getVersion(); | |||
assertThat(version).contains("."); | |||
assertThat(version).doesNotContain("$"); | |||
} | |||
} |
@@ -0,0 +1,4 @@ | |||
project.prop=foo | |||
# overridden property | |||
overridden.prop=project scope |
@@ -0,0 +1,2 @@ | |||
overridden.prop=runner scope | |||
global.prop=jdbc:mysql:localhost/sonar |
@@ -0,0 +1 @@ | |||
sonar.host.url=http://other/sonar |
@@ -0,0 +1,2 @@ | |||
sonar.host.url=http://moon/sonar | |||
sonar.jdbc.url=jdbc:mysql:localhost/sonar |
@@ -0,0 +1 @@ | |||
fake |
@@ -0,0 +1,50 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" | |||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd"> | |||
<id>bin</id> | |||
<formats> | |||
<format>zip</format> | |||
</formats> | |||
<includeBaseDirectory>true</includeBaseDirectory> | |||
<moduleSets> | |||
<moduleSet> | |||
<!-- Enable access to all projects in the current multimodule build! --> | |||
<useAllReactorProjects>true</useAllReactorProjects> | |||
<includes> | |||
<include>org.codehaus.sonar-plugins:sonar-runner-impl</include> | |||
</includes> | |||
<binaries> | |||
<outputDirectory>lib</outputDirectory> | |||
<unpack>false</unpack> | |||
</binaries> | |||
</moduleSet> | |||
</moduleSets> | |||
<fileSets> | |||
<fileSet> | |||
<directory>src/main/assembly/bin</directory> | |||
<outputDirectory>bin</outputDirectory> | |||
<includes> | |||
<include>sonar-runner.bat</include> | |||
</includes> | |||
<lineEnding>dos</lineEnding> | |||
<filtered>true</filtered> | |||
</fileSet> | |||
<fileSet> | |||
<directory>src/main/assembly/bin</directory> | |||
<outputDirectory>bin</outputDirectory> | |||
<includes> | |||
<include>sonar-runner</include> | |||
</includes> | |||
<lineEnding>unix</lineEnding> | |||
<fileMode>0755</fileMode> | |||
<filtered>true</filtered> | |||
</fileSet> | |||
<fileSet> | |||
<directory>src/main/assembly/conf</directory> | |||
<outputDirectory>conf</outputDirectory> | |||
<fileMode>0644</fileMode> | |||
<lineEnding>dos</lineEnding> | |||
</fileSet> | |||
</fileSets> | |||
</assembly> |
@@ -0,0 +1,48 @@ | |||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | |||
<modelVersion>4.0.0</modelVersion> | |||
<parent> | |||
<groupId>org.codehaus.sonar-plugins</groupId> | |||
<artifactId>sonar-runner</artifactId> | |||
<version>2.1-SNAPSHOT</version> | |||
</parent> | |||
<artifactId>sonar-runner-dist</artifactId> | |||
<version>2.1-SNAPSHOT</version> | |||
<packaging>pom</packaging> | |||
<name>Sonar Runner - Distribution</name> | |||
<dependencies> | |||
<dependency> | |||
<groupId>${project.groupId}</groupId> | |||
<artifactId>sonar-runner-impl</artifactId> | |||
<version>${project.version}</version> | |||
</dependency> | |||
</dependencies> | |||
<build> | |||
<plugins> | |||
<plugin> | |||
<groupId>org.apache.maven.plugins</groupId> | |||
<artifactId>maven-assembly-plugin</artifactId> | |||
<executions> | |||
<execution> | |||
<phase>package</phase> | |||
<goals> | |||
<goal>single</goal> | |||
</goals> | |||
<configuration> | |||
<finalName>sonar-runner-${project.version}</finalName> | |||
<appendAssemblyId>false</appendAssemblyId> | |||
<escapeString>\</escapeString> | |||
<descriptors> | |||
<descriptor>${project.basedir}/assembly.xml</descriptor> | |||
</descriptors> | |||
</configuration> | |||
</execution> | |||
</executions> | |||
</plugin> | |||
</plugins> | |||
</build> | |||
</project> | |||
@@ -0,0 +1,101 @@ | |||
#!/bin/sh | |||
# | |||
# Sonar Runner Startup Script for Unix | |||
# | |||
# Optional ENV vars: | |||
# SONAR_RUNNER_HOME - location of runner's installed home dir | |||
# SONAR_RUNNER_OPTS - parameters passed to the Java VM when running Sonar | |||
# The following notice only apply to real_path function copied from | |||
# https://sites.google.com/site/jdisnard/realpath | |||
# Copyright 2010 Jon Disnard. All rights reserved. | |||
# | |||
# Redistribution and use in source and binary forms, with or without modification, are | |||
# permitted provided that the following conditions are met: | |||
# | |||
# 1. Redistributions of source code must retain the above copyright notice, this list of | |||
# conditions and the following disclaimer. | |||
# | |||
# 2. Redistributions in binary form must reproduce the above copyright notice, this list | |||
# of conditions and the following disclaimer in the documentation and/or other materials | |||
# provided with the distribution. | |||
# | |||
# THIS SOFTWARE IS PROVIDED BY Jon Disnard ``AS IS'' AND ANY EXPRESS OR IMPLIED | |||
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND | |||
# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR | |||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | |||
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |||
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | |||
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
# | |||
# The views and conclusions contained in the software and documentation are those of the | |||
# authors and should not be interpreted as representing official policies, either expressed | |||
# or implied, of Jon Disnard. | |||
real_path () { | |||
OIFS=$IFS | |||
IFS='/' | |||
for I in $1 | |||
do | |||
# Resolve relative path punctuation. | |||
if [ "$I" = "." ] || [ -z "$I" ] | |||
then continue | |||
elif [ "$I" = ".." ] | |||
then FOO="${FOO%%/${FOO##*/}}" | |||
continue | |||
else FOO="${FOO}/${I}" | |||
fi | |||
# Dereference symbolic links. | |||
if [ -h "$FOO" ] && [ -x "/bin/ls" ] | |||
then IFS=$OIFS | |||
set `/bin/ls -l "$FOO"` | |||
while shift ; | |||
do | |||
if [ "$1" = "->" ] | |||
then FOO=$2 | |||
shift $# | |||
break | |||
fi | |||
done | |||
fi | |||
done | |||
IFS=$OIFS | |||
echo "$FOO" | |||
} | |||
if [ -z "$SONAR_RUNNER_HOME" ] ; then | |||
PRG="$0" | |||
# resolve symlinks | |||
PRG=`real_path "$PRG"` | |||
SONAR_RUNNER_HOME=`dirname "$PRG"`/.. | |||
# make it fully qualified | |||
SONAR_RUNNER_HOME=`cd "$SONAR_RUNNER_HOME" && pwd` | |||
fi | |||
# check that the SONAR_RUNNER_HOME has been correctly set | |||
if [ ! -e "$SONAR_RUNNER_HOME/lib/sonar-runner-impl-${project.version}.jar" ] ; then | |||
echo '$SONAR_RUNNER_HOME' does not point to a valid installation directory: $SONAR_RUNNER_HOME | |||
exit 1 | |||
fi | |||
JAVA_CMD="`which java`" | |||
JAVA_CLASSPATH="${SONAR_RUNNER_HOME}"/lib/sonar-runner-impl-${project.version}.jar | |||
PROJECT_HOME=`pwd` | |||
#echo "Info: Using sonar-runner at $SONAR_RUNNER_HOME" | |||
#echo "Info: Using java at $JAVA_CMD" | |||
#echo "Info: Using classpath $JAVA_CLASSPATH" | |||
#echo "Info: Using project $PROJECT_HOME" | |||
exec "$JAVA_CMD" \ | |||
$SONAR_RUNNER_OPTS \ | |||
-classpath $JAVA_CLASSPATH \ | |||
"-Drunner.home=\${SONAR_RUNNER_HOME}" \ | |||
"-Dproject.home=\${PROJECT_HOME}" \ | |||
org.sonar.runner.Main "$@" | |||
@@ -0,0 +1,97 @@ | |||
@REM Sonar Runner Startup Script for Windows | |||
@REM | |||
@REM Required ENV vars: | |||
@REM JAVA_HOME - location of a JDK home dir | |||
@REM | |||
@REM Optional ENV vars: | |||
@REM SONAR_RUNNER_HOME - location of runner's installed home dir | |||
@REM SONAR_RUNNER_OPTS - parameters passed to the Java VM when running Sonar | |||
@echo off | |||
set ERROR_CODE=0 | |||
@REM set local scope for the variables with windows NT shell | |||
@setlocal | |||
@REM ==== START VALIDATION ==== | |||
@REM *** JAVA EXEC VALIDATION *** | |||
if not "%JAVA_HOME%" == "" goto foundJavaHome | |||
for %%i in (java.exe) do set JAVA_EXEC=%%~$PATH:i | |||
if not "%JAVA_EXEC%" == "" ( | |||
set JAVA_EXEC="%JAVA_EXEC%" | |||
goto OkJava | |||
) | |||
if not "%JAVA_EXEC%" == "" goto OkJava | |||
echo. | |||
echo ERROR: JAVA_HOME not found in your environment, and no Java | |||
echo executable present in the PATH. | |||
echo Please set the JAVA_HOME variable in your environment to match the | |||
echo location of your Java installation, or add "java.exe" to the PATH | |||
echo. | |||
goto error | |||
:foundJavaHome | |||
if EXIST "%JAVA_HOME%\bin\java.exe" goto foundJavaExeFromJavaHome | |||
echo. | |||
echo ERROR: JAVA_HOME exists but does not point to a valid Java home | |||
echo folder. No "\bin\java.exe" file can be found there. | |||
echo. | |||
goto error | |||
:foundJavaExeFromJavaHome | |||
set JAVA_EXEC="%JAVA_HOME%\bin\java.exe" | |||
@REM *** SONAR RUNNER HOME VALIDATION *** | |||
:OkJava | |||
if NOT "%SONAR_RUNNER_HOME%"=="" goto cleanSonarRunnerHome | |||
set SONAR_RUNNER_HOME=%~dp0.. | |||
goto run | |||
:cleanSonarRunnerHome | |||
@REM If the property has a trailing backslash, remove it | |||
if %SONAR_RUNNER_HOME:~-1%==\ set SONAR_RUNNER_HOME=%SONAR_RUNNER_HOME:~0,-1% | |||
@REM Check if the provided SONAR_RUNNER_HOME is a valid install dir | |||
IF EXIST "%SONAR_RUNNER_HOME%\lib\sonar-runner-impl-${project.version}.jar" goto run | |||
echo. | |||
echo ERROR: SONAR_RUNNER_HOME exists but does not point to a valid install | |||
echo directory: %SONAR_RUNNER_HOME% | |||
echo. | |||
goto error | |||
@REM ==== START RUN ==== | |||
:run | |||
echo %SONAR_RUNNER_HOME% | |||
set PROJECT_HOME=%CD% | |||
%JAVA_EXEC% %SONAR_RUNNER_OPTS% -classpath "%SONAR_RUNNER_HOME%\lib\sonar-runner-impl-${project.version}.jar" "-Drunner.home=%SONAR_RUNNER_HOME%" "-Dproject.home=%PROJECT_HOME%" org.sonar.runner.Main %* | |||
if ERRORLEVEL 1 goto error | |||
goto end | |||
:error | |||
set ERROR_CODE=1 | |||
@REM ==== END EXECUTION ==== | |||
:end | |||
@REM set local scope for the variables with windows NT shell | |||
@endlocal & set ERROR_CODE=%ERROR_CODE% | |||
@REM see http://code-bear.com/bearlog/2007/06/01/getting-the-exit-code-from-a-batch-file-that-is-run-from-a-python-program/ | |||
goto exit | |||
:returncode | |||
exit /B %1 | |||
:exit | |||
call :returncode %ERROR_CODE% |
@@ -0,0 +1,28 @@ | |||
#Configure here general information about the environment, such as Sonar DB details for example | |||
#No information about specific project should appear here | |||
#----- Default Sonar server | |||
#sonar.host.url=http://localhost:9000 | |||
#----- PostgreSQL | |||
#sonar.jdbc.url=jdbc:postgresql://localhost/sonar | |||
#sonar.jdbc.driver=org.postgresql.Driver | |||
#----- MySQL | |||
#sonar.jdbc.url=jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8 | |||
#sonar.jdbc.driver=com.mysql.jdbc.Driver | |||
#----- Oracle | |||
#sonar.jdbc.url=jdbc:oracle:thin:@localhost/XE | |||
#sonar.jdbc.driver=oracle.jdbc.driver.OracleDriver | |||
#----- Global database settings | |||
#sonar.jdbc.username=sonar | |||
#sonar.jdbc.password=sonar | |||
#----- Default source code encoding | |||
#sonar.sourceEncoding=UTF-8 | |||
#----- Security (when 'sonar.forceAuthentication' is set to 'true') | |||
#sonar.login=admin | |||
#sonar.password=admin |
@@ -0,0 +1,10 @@ | |||
sonar.projectKey=com.foo.project | |||
sonar.projectName=Foo Project | |||
sonar.projectVersion=1.0-SNAPSHOT | |||
sonar.projectDescription=Description of Foo Project | |||
# Those are the deprecated properties | |||
sources=sources | |||
tests=tests | |||
binaries=target/classes | |||
libraries=libs/*.txt |
@@ -0,0 +1,6 @@ | |||
sonar.projectKey=com.foo.project | |||
sonar.projectName=Foo Project | |||
sonar.projectVersion=1.0-SNAPSHOT | |||
sonar.projectDescription=Description of Foo Project | |||
sonar.sources=unexisting-source-dir |
@@ -0,0 +1 @@ | |||
Fake |
@@ -0,0 +1,4 @@ | |||
prop= foo, bar, \ | |||
toto,\ | |||
\ | |||
tutu, |
@@ -12,19 +12,17 @@ | |||
<name>Sonar Runner</name> | |||
<modules> | |||
<module>sonar-runner-api</module> | |||
<module>sonar-runner-impl</module> | |||
<module>sonar-runner-dist</module> | |||
<module>api</module> | |||
<module>impl</module> | |||
<module>distribution</module> | |||
</modules> | |||
<url>http://docs.codehaus.org/display/SONAR/Sonar+Standalone+Runner</url> | |||
<inceptionYear>2011</inceptionYear> | |||
<organization> | |||
<name>SonarSource</name> | |||
<url>http://www.sonarsource.com</url> | |||
</organization> | |||
<licenses> | |||
<license> | |||
<name>GNU LGPL 3</name> | |||
@@ -61,12 +59,10 @@ | |||
<developerConnection>scm:git:git@github.com:SonarCommunity/sonar-runner.git</developerConnection> | |||
<url>https://github.com/SonarCommunity/sonar-runner</url> | |||
</scm> | |||
<issueManagement> | |||
<system>JIRA</system> | |||
<url>http://jira.codehaus.org/browse/SONARPLUGINS/component/14640</url> | |||
</issueManagement> | |||
<ciManagement> | |||
</ciManagement> | |||