This reverts commit 0bf3c7e2b4
.
tags/2.5-rc1
<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> |
/* | |||||
* 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); | |||||
} | |||||
} |
/* | |||||
* 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; | |||||
} | |||||
} |
/* | |||||
* 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); | |||||
} | |||||
} | |||||
} |
/* | |||||
* 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); | |||||
} | |||||
} |
/* | |||||
* 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); | |||||
} | |||||
} |
/* | |||||
* 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); | |||||
} | |||||
} |
/* | |||||
* 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; | |||||
} | |||||
} |
/* | |||||
* 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); | |||||
} | |||||
} |
/* | |||||
* 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); | |||||
} | |||||
} | |||||
} |
/* | |||||
* 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); | |||||
} | |||||
} |
/* | |||||
* 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; |
version=${project.version} |
/* | |||||
* 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(); | |||||
} | |||||
} |
/* | |||||
* 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)); | |||||
} | |||||
} | |||||
} |
/* | |||||
* 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"); | |||||
} | |||||
} |
/* | |||||
* 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"); | |||||
} | |||||
} |
/* | |||||
* 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); | |||||
} | |||||
} |
/* | |||||
* 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"); | |||||
} | |||||
} |
/* | |||||
* 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("$"); | |||||
} | |||||
} |
project.prop=foo | |||||
# overridden property | |||||
overridden.prop=project scope |
overridden.prop=runner scope | |||||
global.prop=jdbc:mysql:localhost/sonar |
sonar.host.url=http://other/sonar |
sonar.host.url=http://moon/sonar | |||||
sonar.jdbc.url=jdbc:mysql:localhost/sonar |
fake |
<?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> |
<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> | |||||
#!/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 "$@" | |||||
@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% |
#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 |
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 |
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 |
Fake |
prop= foo, bar, \ | |||||
toto,\ | |||||
\ | |||||
tutu, |
<name>Sonar Runner</name> | <name>Sonar Runner</name> | ||||
<modules> | <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> | </modules> | ||||
<url>http://docs.codehaus.org/display/SONAR/Sonar+Standalone+Runner</url> | <url>http://docs.codehaus.org/display/SONAR/Sonar+Standalone+Runner</url> | ||||
<inceptionYear>2011</inceptionYear> | <inceptionYear>2011</inceptionYear> | ||||
<organization> | <organization> | ||||
<name>SonarSource</name> | <name>SonarSource</name> | ||||
<url>http://www.sonarsource.com</url> | <url>http://www.sonarsource.com</url> | ||||
</organization> | </organization> | ||||
<licenses> | <licenses> | ||||
<license> | <license> | ||||
<name>GNU LGPL 3</name> | <name>GNU LGPL 3</name> | ||||
<developerConnection>scm:git:git@github.com:SonarCommunity/sonar-runner.git</developerConnection> | <developerConnection>scm:git:git@github.com:SonarCommunity/sonar-runner.git</developerConnection> | ||||
<url>https://github.com/SonarCommunity/sonar-runner</url> | <url>https://github.com/SonarCommunity/sonar-runner</url> | ||||
</scm> | </scm> | ||||
<issueManagement> | <issueManagement> | ||||
<system>JIRA</system> | <system>JIRA</system> | ||||
<url>http://jira.codehaus.org/browse/SONARPLUGINS/component/14640</url> | <url>http://jira.codehaus.org/browse/SONARPLUGINS/component/14640</url> | ||||
</issueManagement> | </issueManagement> | ||||
<ciManagement> | <ciManagement> | ||||
</ciManagement> | </ciManagement> | ||||