From 80971d59f5c0ce60ae53715fa525dd5d5a81e9c8 Mon Sep 17 00:00:00 2001 From: Fabrice Bellingard Date: Wed, 5 Sep 2012 10:26:04 +0000 Subject: [PATCH] SONARPLUGINS-2217 Move Sonar Batch Bootstrapper classes from Sonar to Sonar Runner --- pom.xml | 6 - src/main/assembly/bin/sonar-runner | 2 +- src/main/assembly/bin/sonar-runner.bat | 2 +- src/main/java/org/sonar/runner/Launcher.java | 28 +-- src/main/java/org/sonar/runner/Main.java | 4 +- src/main/java/org/sonar/runner/Runner.java | 28 ++- .../bootstrapper/BootstrapClassLoader.java | 121 +++++++++++++ .../bootstrapper/BootstrapException.java | 36 ++++ .../runner/bootstrapper/Bootstrapper.java | 167 ++++++++++++++++++ .../bootstrapper/BootstrapperIOUtils.java | 98 ++++++++++ .../bootstrapper/BootstrapperVersion.java | 52 ++++++ .../runner/bootstrapper/package-info.java | 23 +++ .../java/org/sonar/runner/LauncherTest.java | 19 ++ .../java/org/sonar/runner/RunnerTest.java | 21 +-- .../BootstrapClassLoaderTest.java | 61 +++++++ .../runner/bootstrapper/BootstrapperTest.java | 65 +++++++ .../bootstrapper/BootstrapperVersionTest.java | 36 ++++ 17 files changed, 715 insertions(+), 54 deletions(-) create mode 100644 src/main/java/org/sonar/runner/bootstrapper/BootstrapClassLoader.java create mode 100644 src/main/java/org/sonar/runner/bootstrapper/BootstrapException.java create mode 100644 src/main/java/org/sonar/runner/bootstrapper/Bootstrapper.java create mode 100644 src/main/java/org/sonar/runner/bootstrapper/BootstrapperIOUtils.java create mode 100644 src/main/java/org/sonar/runner/bootstrapper/BootstrapperVersion.java create mode 100644 src/main/java/org/sonar/runner/bootstrapper/package-info.java create mode 100644 src/test/java/org/sonar/runner/bootstrapper/BootstrapClassLoaderTest.java create mode 100644 src/test/java/org/sonar/runner/bootstrapper/BootstrapperTest.java create mode 100644 src/test/java/org/sonar/runner/bootstrapper/BootstrapperVersionTest.java diff --git a/pom.xml b/pom.xml index e6a9adb..f274e25 100644 --- a/pom.xml +++ b/pom.xml @@ -60,12 +60,6 @@ - - - org.codehaus.sonar - sonar-batch-bootstrapper - ${sonar.buildVersion} - ch.qos.logback diff --git a/src/main/assembly/bin/sonar-runner b/src/main/assembly/bin/sonar-runner index a9c4a75..0aaf46b 100755 --- a/src/main/assembly/bin/sonar-runner +++ b/src/main/assembly/bin/sonar-runner @@ -22,7 +22,7 @@ else fi JAVA_CMD="`which java`" -JAVA_CLASSPATH="${SONAR_RUNNER_HOME}"/lib/sonar-runner.jar:"${SONAR_RUNNER_HOME}"/lib/sonar-batch-bootstrapper.jar +JAVA_CLASSPATH="${SONAR_RUNNER_HOME}"/lib/sonar-runner.jar PROJECT_HOME=`pwd` #echo "Info: Using sonar-runner at $SONAR_RUNNER_HOME" diff --git a/src/main/assembly/bin/sonar-runner.bat b/src/main/assembly/bin/sonar-runner.bat index 4896fdd..fd97e3e 100644 --- a/src/main/assembly/bin/sonar-runner.bat +++ b/src/main/assembly/bin/sonar-runner.bat @@ -70,7 +70,7 @@ echo %SONAR_RUNNER_HOME% set PROJECT_HOME=%CD% -%JAVA_EXEC% %SONAR_RUNNER_OPTS% -classpath "%SONAR_RUNNER_HOME%\lib\sonar-runner.jar";"%SONAR_RUNNER_HOME%\lib\sonar-batch-bootstrapper.jar" "-Drunner.home=%SONAR_RUNNER_HOME%" "-Dproject.home=%PROJECT_HOME%" org.sonar.runner.Main %* +%JAVA_EXEC% %SONAR_RUNNER_OPTS% -classpath "%SONAR_RUNNER_HOME%\lib\sonar-runner.jar" "-Drunner.home=%SONAR_RUNNER_HOME%" "-Dproject.home=%PROJECT_HOME%" org.sonar.runner.Main %* diff --git a/src/main/java/org/sonar/runner/Launcher.java b/src/main/java/org/sonar/runner/Launcher.java index 614662d..2549300 100644 --- a/src/main/java/org/sonar/runner/Launcher.java +++ b/src/main/java/org/sonar/runner/Launcher.java @@ -23,6 +23,7 @@ package org.sonar.runner; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.joran.JoranConfigurator; import ch.qos.logback.core.joran.spi.JoranException; +import com.google.common.annotations.VisibleForTesting; import org.apache.commons.configuration.CompositeConfiguration; import org.apache.commons.configuration.Configuration; import org.apache.commons.configuration.EnvironmentConfiguration; @@ -37,29 +38,28 @@ import org.sonar.batch.Batch; import org.sonar.batch.bootstrapper.EnvironmentInformation; import org.sonar.runner.model.SonarProjectBuilder; +import java.io.File; import java.io.InputStream; +import java.util.Properties; /** * Contrary to {@link org.sonar.runner.Runner}, this class is executed within the classloader * provided by the server. It contains the installed plugins and the same version of sonar-batch as the server. - * - * TODO : instead of depending on Runner, that is not in the same classloader, we could simply use Properties : - * sonar.runner.version, sonar.runner.debug, sonar.runner.projectDir, sonar.runner.workDir and all the - * other properties. */ public class Launcher { - private Runner runner; + private Properties propertiesFromRunner; - public Launcher(Runner runner) { - this.runner = runner; + public Launcher(Properties properties) { + this.propertiesFromRunner = properties; } /** * This method invoked from {@link Main}. Do not rename it. */ public void execute() { - ProjectDefinition project = SonarProjectBuilder.create(runner.getProjectDir(), runner.getProperties()).generateProjectDefinition(); + File baseDir = new File(propertiesFromRunner.getProperty(Runner.PROPERTY_PROJECT_DIR)); + ProjectDefinition project = SonarProjectBuilder.create(baseDir, propertiesFromRunner).generateProjectDefinition(); Configuration initialConfiguration = getInitialConfiguration(project); initLogging(initialConfiguration); executeBatch(project, initialConfiguration); @@ -67,7 +67,8 @@ public class Launcher { private void executeBatch(ProjectDefinition project, Configuration initialConfiguration) { ProjectReactor reactor = new ProjectReactor(project); - Batch batch = Batch.create(reactor, initialConfiguration, new EnvironmentInformation("Runner", runner.getRunnerVersion())); + String runnerVersion = propertiesFromRunner.getProperty(Runner.PROPERTY_RUNNER_VERSION); + Batch batch = Batch.create(reactor, initialConfiguration, new EnvironmentInformation("Runner", runnerVersion)); batch.execute(); } @@ -77,7 +78,7 @@ public class Launcher { jc.setContext(context); context.reset(); InputStream input = Batch.class.getResourceAsStream("/org/sonar/batch/logback.xml"); - System.setProperty("ROOT_LOGGER_LEVEL", runner.isDebug() ? "DEBUG" : "INFO"); + System.setProperty("ROOT_LOGGER_LEVEL", isDebug() ? "DEBUG" : "INFO"); context.putProperty("SQL_LOGGER_LEVEL", getSqlLevel(initialConfiguration));// since 2.14. Ignored on previous versions. context.putProperty("SQL_RESULTS_LOGGER_LEVEL", getSqlResultsLevel(initialConfiguration));// since 2.14. Ignored on previous versions. try { @@ -91,11 +92,18 @@ public class Launcher { } } + @VisibleForTesting + protected boolean isDebug() { + return Boolean.parseBoolean(propertiesFromRunner.getProperty(Runner.PROPERTY_VERBOSE, propertiesFromRunner.getProperty(Runner.PROPERTY_OLD_DEBUG_MODE, "false"))); + } + + @VisibleForTesting protected static String getSqlLevel(Configuration config) { boolean showSql = config.getBoolean("sonar.showSql", false); return showSql ? "DEBUG" : "WARN"; } + @VisibleForTesting protected static String getSqlResultsLevel(Configuration config) { boolean showSql = config.getBoolean("sonar.showSqlResults", false); return showSql ? "DEBUG" : "WARN"; diff --git a/src/main/java/org/sonar/runner/Main.java b/src/main/java/org/sonar/runner/Main.java index aedfc8a..d133b5f 100644 --- a/src/main/java/org/sonar/runner/Main.java +++ b/src/main/java/org/sonar/runner/Main.java @@ -20,8 +20,8 @@ package org.sonar.runner; -import org.sonar.batch.bootstrapper.BootstrapException; -import org.sonar.batch.bootstrapper.BootstrapperIOUtils; +import org.sonar.runner.bootstrapper.BootstrapException; +import org.sonar.runner.bootstrapper.BootstrapperIOUtils; import java.io.File; import java.io.FileInputStream; diff --git a/src/main/java/org/sonar/runner/Runner.java b/src/main/java/org/sonar/runner/Runner.java index 4224689..130bc8c 100644 --- a/src/main/java/org/sonar/runner/Runner.java +++ b/src/main/java/org/sonar/runner/Runner.java @@ -19,10 +19,10 @@ */ package org.sonar.runner; -import org.sonar.batch.bootstrapper.BootstrapClassLoader; -import org.sonar.batch.bootstrapper.BootstrapException; -import org.sonar.batch.bootstrapper.Bootstrapper; -import org.sonar.batch.bootstrapper.BootstrapperIOUtils; +import org.sonar.runner.bootstrapper.BootstrapClassLoader; +import org.sonar.runner.bootstrapper.BootstrapException; +import org.sonar.runner.bootstrapper.Bootstrapper; +import org.sonar.runner.bootstrapper.BootstrapperIOUtils; import java.io.File; import java.io.IOException; @@ -37,11 +37,15 @@ import java.util.Properties; * @since 1.1 */ public final class Runner { + + public static final String PROPERTY_PROJECT_DIR = "sonar.runner.projectDir"; + public static final String PROPERTY_RUNNER_VERSION = "sonar.runner.version"; + /** * @deprecated Replaced by sonar.verbose since 1.2 */ @Deprecated - public static final String DEBUG_MODE = "runner.debug"; + public static final String PROPERTY_OLD_DEBUG_MODE = "runner.debug"; /** * @since 1.2 @@ -109,6 +113,8 @@ public final class Runner { if (!projectDir.isDirectory() || !projectDir.exists()) { throw new IllegalArgumentException("Project home must be an existing directory: " + path); } + // project home exist, add its absolute path as "sonar.runner.projectDir" property + properties.put(PROPERTY_PROJECT_DIR, projectDir.getAbsolutePath()); workDir = initWorkDir(); } @@ -145,10 +151,6 @@ public final class Runner { return properties; } - public boolean isDebug() { - return Boolean.parseBoolean(properties.getProperty(PROPERTY_VERBOSE, properties.getProperty(DEBUG_MODE, "false"))); - } - public String getRunnerVersion() { InputStream in = null; try { @@ -201,12 +203,8 @@ public final class Runner { try { Thread.currentThread().setContextClassLoader(sonarClassLoader); Class launcherClass = sonarClassLoader.findClass("org.sonar.runner.Launcher"); - // TODO: hack to instantiate SonarProjectBuilder in this classloader otherwise it will be found in the parent one, where no deps are - // available - sonarClassLoader.findClass("org.sonar.runner.model.SonarProjectBuilder"); - // END of hack - Constructor constructor = launcherClass.getConstructor(Runner.class); - Object launcher = constructor.newInstance(this); + Constructor constructor = launcherClass.getConstructor(Properties.class); + Object launcher = constructor.newInstance(getProperties()); Method method = launcherClass.getMethod("execute"); method.invoke(launcher); } catch (InvocationTargetException e) { diff --git a/src/main/java/org/sonar/runner/bootstrapper/BootstrapClassLoader.java b/src/main/java/org/sonar/runner/bootstrapper/BootstrapClassLoader.java new file mode 100644 index 0000000..694a8b9 --- /dev/null +++ b/src/main/java/org/sonar/runner/bootstrapper/BootstrapClassLoader.java @@ -0,0 +1,121 @@ +/* + * Sonar Standalone Runner + * 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.bootstrapper; + +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. + */ +public class BootstrapClassLoader extends URLClassLoader { + + private final String[] unmaskedPackages; + + public 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) { + 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 getResources(String name) throws IOException { + return findResources(name); + } + +} diff --git a/src/main/java/org/sonar/runner/bootstrapper/BootstrapException.java b/src/main/java/org/sonar/runner/bootstrapper/BootstrapException.java new file mode 100644 index 0000000..7bad396 --- /dev/null +++ b/src/main/java/org/sonar/runner/bootstrapper/BootstrapException.java @@ -0,0 +1,36 @@ +/* + * Sonar Standalone Runner + * 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.bootstrapper; + +public class BootstrapException extends RuntimeException { + + public BootstrapException(String message) { + super(message); + } + + public BootstrapException(Throwable cause) { + super(cause); + } + + public BootstrapException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/org/sonar/runner/bootstrapper/Bootstrapper.java b/src/main/java/org/sonar/runner/bootstrapper/Bootstrapper.java new file mode 100644 index 0000000..d8e2187 --- /dev/null +++ b/src/main/java/org/sonar/runner/bootstrapper/Bootstrapper.java @@ -0,0 +1,167 @@ +/* + * Sonar Standalone Runner + * 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.bootstrapper; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +public class Bootstrapper { + + private static final String VERSION_PATH = "/api/server/version"; + private static final String BATCH_PATH = "/batch/"; + + public static final int CONNECT_TIMEOUT_MILLISECONDS = 30000; + public static final int READ_TIMEOUT_MILLISECONDS = 60000; + + private File bootDir; + private String serverUrl; + private String productToken; + private String serverVersion; + + /** + * @param productToken part of User-Agent request-header field - see http://tools.ietf.org/html/rfc1945#section-10.15 + */ + public Bootstrapper(String productToken, String serverUrl, File workDir) { + this.productToken = productToken; + 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 + */ + public String getServerUrl() { + return serverUrl; + } + + /** + * @return server version + */ + public String getServerVersion() { + if (serverVersion == null) { + try { + serverVersion = remoteContent(VERSION_PATH); + } catch (IOException e) { + throw new BootstrapException(e.getMessage(), 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 + */ + public BootstrapClassLoader createClassLoader(URL[] urls, ClassLoader parent, String... unmaskedPackages) { + BootstrapClassLoader classLoader = new BootstrapClassLoader(parent, unmaskedPackages); + List files = downloadBatchFiles(); + for (URL url : urls) { + classLoader.addURL(url); + } + for (File file : files) { + try { + classLoader.addURL(file.toURI().toURL()); + } catch (MalformedURLException e) { + throw new BootstrapException(e); + } + } + return classLoader; + } + + private void remoteContentToFile(String path, File toFile) { + InputStream input = null; + FileOutputStream output = null; + String fullUrl = serverUrl + path; + try { + HttpURLConnection connection = newHttpConnection(new URL(fullUrl)); + output = new FileOutputStream(toFile, false); + input = connection.getInputStream(); + BootstrapperIOUtils.copyLarge(input, output); + } catch (IOException e) { + BootstrapperIOUtils.closeQuietly(output); + BootstrapperIOUtils.deleteFileQuietly(toFile); + throw new BootstrapException("Fail to download the file: " + fullUrl, e); + } finally { + BootstrapperIOUtils.closeQuietly(input); + BootstrapperIOUtils.closeQuietly(output); + } + } + + String remoteContent(String path) throws IOException { + String fullUrl = serverUrl + path; + HttpURLConnection conn = newHttpConnection(new URL(fullUrl)); + Reader reader = new InputStreamReader((InputStream) conn.getContent()); + try { + int statusCode = conn.getResponseCode(); + if (statusCode != HttpURLConnection.HTTP_OK) { + throw new IOException("Status returned by url : '" + fullUrl + "' is invalid : " + statusCode); + } + return BootstrapperIOUtils.toString(reader); + } finally { + BootstrapperIOUtils.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/" + BootstrapperVersion.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 downloadBatchFiles() { + try { + List files = new ArrayList(); + String libs = remoteContent(BATCH_PATH); + for (String lib : libs.split(",")) { + File file = new File(bootDir, lib); + remoteContentToFile(BATCH_PATH + lib, file); + files.add(file); + } + return files; + } catch (Exception e) { + throw new BootstrapException(e); + } + } +} diff --git a/src/main/java/org/sonar/runner/bootstrapper/BootstrapperIOUtils.java b/src/main/java/org/sonar/runner/bootstrapper/BootstrapperIOUtils.java new file mode 100644 index 0000000..c47186a --- /dev/null +++ b/src/main/java/org/sonar/runner/bootstrapper/BootstrapperIOUtils.java @@ -0,0 +1,98 @@ +/* + * Sonar Standalone Runner + * 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.bootstrapper; + +import java.io.*; + +public final class BootstrapperIOUtils { + + private BootstrapperIOUtils() { + // only static methods + } + + /** + * The default buffer size to use. + */ + private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; + + /** + * Unconditionally close a Closeable. + */ + public static void closeQuietly(Closeable closeable) { + try { + if (closeable != null) { + closeable.close(); + } + } catch (IOException ioe) { // NOSONAR + } + } + + /** + * Get the contents of a Reader as a String. + */ + public static String toString(Reader input) throws IOException { + StringWriter sw = new StringWriter(); + copyLarge(input, sw); + return sw.toString(); + } + + /** + * Copy bytes from an InputStream to an OutputStream. + */ + public static long copyLarge(InputStream input, OutputStream output) throws IOException { + byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; + long count = 0; + int n = 0; + while (-1 != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + /** + * Copy chars from a Reader to a Writer. + */ + public static long copyLarge(Reader input, Writer output) throws IOException { + char[] buffer = new char[DEFAULT_BUFFER_SIZE]; + long count = 0; + int n = 0; + while (-1 != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + /** + * Deletes a file (not a directory). + */ + public static boolean deleteFileQuietly(File file) { + if (file == null) { + return false; + } + try { + return file.delete(); + } catch (Exception e) { + return false; + } + } + +} diff --git a/src/main/java/org/sonar/runner/bootstrapper/BootstrapperVersion.java b/src/main/java/org/sonar/runner/bootstrapper/BootstrapperVersion.java new file mode 100644 index 0000000..b50c03b --- /dev/null +++ b/src/main/java/org/sonar/runner/bootstrapper/BootstrapperVersion.java @@ -0,0 +1,52 @@ +/* + * Sonar Standalone Runner + * 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.bootstrapper; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +public enum BootstrapperVersion { + + INSTANCE; + + private static final String PROPERTIES_PATH = "/org/sonar/runner/version.txt"; + private String version; + + public static String getVersion() { + return INSTANCE.version; + } + + private BootstrapperVersion() { + 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 { + BootstrapperIOUtils.closeQuietly(input); + } + } +} diff --git a/src/main/java/org/sonar/runner/bootstrapper/package-info.java b/src/main/java/org/sonar/runner/bootstrapper/package-info.java new file mode 100644 index 0000000..b685e7d --- /dev/null +++ b/src/main/java/org/sonar/runner/bootstrapper/package-info.java @@ -0,0 +1,23 @@ +/* + * Sonar Standalone Runner + * 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 + */ +/** + * Provides API to bootstrap Sonar Batch. + */ +package org.sonar.runner.bootstrapper; \ No newline at end of file diff --git a/src/test/java/org/sonar/runner/LauncherTest.java b/src/test/java/org/sonar/runner/LauncherTest.java index 0cec5f5..ea78d82 100644 --- a/src/test/java/org/sonar/runner/LauncherTest.java +++ b/src/test/java/org/sonar/runner/LauncherTest.java @@ -23,6 +23,8 @@ import org.apache.commons.configuration.BaseConfiguration; import org.apache.commons.configuration.Configuration; import org.junit.Test; +import java.util.Properties; + import static org.fest.assertions.Assertions.assertThat; public class LauncherTest { @@ -53,4 +55,21 @@ public class LauncherTest { assertThat(Launcher.getSqlResultsLevel(conf)).isEqualTo("WARN"); } + @Test + public void shouldDetermineVerboseMode() { + Properties properties = new Properties(); + Launcher launcher = new Launcher(properties); + assertThat(launcher.isDebug()).isFalse(); + properties.setProperty(Runner.PROPERTY_VERBOSE, "true"); + assertThat(launcher.isDebug()).isTrue(); + } + + @Test + public void shouldSupportDeprecatedDebugProperty() { + Properties properties = new Properties(); + Launcher launcher = new Launcher(properties); + properties.setProperty(Runner.PROPERTY_OLD_DEBUG_MODE, "true"); + assertThat(launcher.isDebug()).isTrue(); + } + } diff --git a/src/test/java/org/sonar/runner/RunnerTest.java b/src/test/java/org/sonar/runner/RunnerTest.java index 1099d1d..f1681ee 100644 --- a/src/test/java/org/sonar/runner/RunnerTest.java +++ b/src/test/java/org/sonar/runner/RunnerTest.java @@ -22,8 +22,8 @@ package org.sonar.runner; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.sonar.batch.bootstrapper.BootstrapException; -import org.sonar.batch.bootstrapper.Bootstrapper; +import org.sonar.runner.bootstrapper.BootstrapException; +import org.sonar.runner.bootstrapper.Bootstrapper; import java.io.File; import java.util.Properties; @@ -95,23 +95,6 @@ public class RunnerTest { assertThat(runner.getServerURL()).isEqualTo("foo"); } - @Test - public void shouldDetermineVerboseMode() { - Properties properties = new Properties(); - Runner runner = Runner.create(properties); - assertThat(runner.isDebug()).isFalse(); - properties.setProperty(Runner.PROPERTY_VERBOSE, "true"); - assertThat(runner.isDebug()).isTrue(); - } - - @Test - public void shouldSupportDeprecatedDebugProperty() { - Properties properties = new Properties(); - Runner runner = Runner.create(properties); - properties.setProperty(Runner.DEBUG_MODE, "true"); - assertThat(runner.isDebug()).isTrue(); - } - @Test public void shouldInitDirs() throws Exception { Properties props = new Properties(); diff --git a/src/test/java/org/sonar/runner/bootstrapper/BootstrapClassLoaderTest.java b/src/test/java/org/sonar/runner/bootstrapper/BootstrapClassLoaderTest.java new file mode 100644 index 0000000..87496ba --- /dev/null +++ b/src/test/java/org/sonar/runner/bootstrapper/BootstrapClassLoaderTest.java @@ -0,0 +1,61 @@ +/* + * Sonar Standalone Runner + * 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.bootstrapper; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.runner.bootstrapper.BootstrapClassLoader; + +import static org.fest.assertions.Assertions.assertThat; + +public class BootstrapClassLoaderTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void shouldRestrictLoadingFromParent() throws Exception { + BootstrapClassLoader classLoader = new BootstrapClassLoader(getClass().getClassLoader(), "org.sonar.ant"); + assertThat(classLoader.canLoadFromParent("org.sonar.ant.Launcher")).isTrue(); + assertThat(classLoader.canLoadFromParent("org.objectweb.asm.ClassVisitor")).isFalse(); + } + + @Test + public void 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 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(); + } +} diff --git a/src/test/java/org/sonar/runner/bootstrapper/BootstrapperTest.java b/src/test/java/org/sonar/runner/bootstrapper/BootstrapperTest.java new file mode 100644 index 0000000..0741701 --- /dev/null +++ b/src/test/java/org/sonar/runner/bootstrapper/BootstrapperTest.java @@ -0,0 +1,65 @@ +/* + * Sonar Standalone Runner + * 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.bootstrapper; + +import org.junit.Test; +import org.sonar.runner.bootstrapper.Bootstrapper; + +import java.io.File; +import java.io.IOException; + +import static org.fest.assertions.Assertions.assertThat; + +public class BootstrapperTest { + + @Test + public void shouldRemoveLastUrlSlash() { + Bootstrapper bootstrapper = new Bootstrapper("", "http://test/", new File("target")); + assertThat(bootstrapper.getServerUrl()).isEqualTo("http://test"); + } + + @Test(expected = Exception.class) + public void shouldFailIfCanNotConnectServer() { + Bootstrapper bootstrapper = new Bootstrapper("", "http://unknown.foo", new File("target")); + bootstrapper.getServerVersion(); + } + + @Test + public void shouldReturnUserAgent() { + Bootstrapper bootstrapper = new Bootstrapper("test/0.1", "http://unknown.foo", new File("target")); + 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")) { + @Override + String remoteContent(String path) throws IOException { + return "2.6"; + } + }; + assertThat(bootstrapper.getServerVersion()).isEqualTo("2.6"); + } + +} diff --git a/src/test/java/org/sonar/runner/bootstrapper/BootstrapperVersionTest.java b/src/test/java/org/sonar/runner/bootstrapper/BootstrapperVersionTest.java new file mode 100644 index 0000000..c42fd18 --- /dev/null +++ b/src/test/java/org/sonar/runner/bootstrapper/BootstrapperVersionTest.java @@ -0,0 +1,36 @@ +/* + * Sonar Standalone Runner + * 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.bootstrapper; + +import org.junit.Test; +import org.sonar.runner.bootstrapper.BootstrapperVersion; + +import static org.fest.assertions.Assertions.assertThat; + +public class BootstrapperVersionTest { + + @Test + public void shouldLoadVersion() { + String version = BootstrapperVersion.getVersion(); + assertThat(version).contains("."); + assertThat(version).doesNotContain("$"); + } + +} -- 2.39.5