@@ -19,13 +19,17 @@ | |||
*/ | |||
package org.sonar.runner.api; | |||
import java.io.File; | |||
import java.io.PrintWriter; | |||
import java.io.StringWriter; | |||
import org.sonar.runner.impl.ClassloadRules; | |||
import java.nio.charset.Charset; | |||
import java.security.InvalidParameterException; | |||
import java.util.ArrayList; | |||
import java.util.Arrays; | |||
import java.util.HashSet; | |||
import java.util.List; | |||
import java.util.Locale; | |||
import java.util.Properties; | |||
import java.util.Set; | |||
import javax.annotation.Nullable; | |||
@@ -44,12 +48,16 @@ public class EmbeddedRunner { | |||
private IsolatedLauncher launcher; | |||
private final LogOutput logOutput; | |||
private final Properties globalProperties = new Properties(); | |||
private final List<Object> extensions = new ArrayList<>(); | |||
private final Logger logger; | |||
private final Set<String> classloaderMask = new HashSet<>(); | |||
private final Set<String> classloaderUnmask = new HashSet<>(); | |||
EmbeddedRunner(IsolatedLauncherFactory bl, Logger logger, LogOutput logOutput) { | |||
this.logger = logger; | |||
this.launcherFactory = bl; | |||
this.logOutput = logOutput; | |||
this.classloaderUnmask.add("org.sonar.runner.batch."); | |||
} | |||
public static EmbeddedRunner create(final LogOutput logOutput) { | |||
@@ -63,8 +71,22 @@ public class EmbeddedRunner { | |||
return clone; | |||
} | |||
public EmbeddedRunner unmask(String fqcnPrefix) { | |||
checkLauncherDoesntExist(); | |||
classloaderUnmask.add(fqcnPrefix); | |||
return this; | |||
} | |||
public EmbeddedRunner mask(String fqcnPrefix) { | |||
checkLauncherDoesntExist(); | |||
classloaderMask.add(fqcnPrefix); | |||
return this; | |||
} | |||
/** | |||
* Declare Sonar properties, for example sonar.projectKey=>foo. | |||
* These might be used at different stages (on {@link #start() or #runAnalysis(Properties)}, depending on the | |||
* property and SQ version. | |||
* | |||
* @see #setProperty(String, String) | |||
*/ | |||
@@ -75,6 +97,8 @@ public class EmbeddedRunner { | |||
/** | |||
* Declare a SonarQube property. | |||
* These might be used at different stages (on {@link #start() or #runAnalysis(Properties)}, depending on the | |||
* property and SQ version. | |||
* | |||
* @see RunnerProperties | |||
* @see ScanProperties | |||
@@ -101,30 +125,51 @@ public class EmbeddedRunner { | |||
return globalProperty(InternalProperties.RUNNER_APP, null); | |||
} | |||
/** | |||
* Add extensions to the batch's object container. | |||
* Only supported until SQ 5.1. For more recent versions, an exception is thrown | |||
* @param objs | |||
*/ | |||
public EmbeddedRunner addExtensions(Object... objs) { | |||
checkLauncherExists(); | |||
if (VersionUtils.isAtLeast52(launcher.getVersion())) { | |||
throw new IllegalStateException("not supported in current SonarQube version: " + launcher.getVersion()); | |||
} | |||
extensions.addAll(Arrays.asList(objs)); | |||
return this; | |||
} | |||
public String appVersion() { | |||
return globalProperty(InternalProperties.RUNNER_APP_VERSION, null); | |||
} | |||
/** | |||
* Launch an analysis. | |||
* Runner must have been started - see {@link #start()}. | |||
*/ | |||
public void runAnalysis(Properties analysisProperties) { | |||
runAnalysis(analysisProperties, null); | |||
} | |||
/** | |||
* Launch an analysis, providing optionally a issue listener. | |||
* Runner must have been started - see {@link #start()}. | |||
* Issue listener is supported starting in SQ 5.2. If a non-null listener is given for older versions, an exception is thrown | |||
*/ | |||
public void runAnalysis(Properties analysisProperties, @Nullable IssueListener issueListener) { | |||
checkLauncherExists(); | |||
Properties copy = new Properties(); | |||
copy.putAll(analysisProperties); | |||
initAnalysisProperties(copy); | |||
String dumpToFile = copy.getProperty(InternalProperties.RUNNER_DUMP_TO_FILE); | |||
if (dumpToFile != null) { | |||
File dumpFile = new File(dumpToFile); | |||
Utils.writeProperties(dumpFile, copy); | |||
logger.info("Simulation mode. Configuration written to " + dumpFile.getAbsolutePath()); | |||
} else { | |||
doExecute(copy, issueListener); | |||
} | |||
doExecute(copy, issueListener); | |||
} | |||
/** | |||
* Synchronizes the project's data in the local cache with the server, allowing analysis of the project to be done offline. | |||
* Runner must have been started - see {@link #start()}. | |||
* Only supported starting in SQ 5.2. For older versions, an exception is thrown | |||
*/ | |||
public void syncProject(String projectKey) { | |||
checkLauncherExists(); | |||
if (!VersionUtils.isAtLeast52(launcher.getVersion())) { | |||
@@ -142,16 +187,19 @@ public class EmbeddedRunner { | |||
doStart(forceSync); | |||
} | |||
/** | |||
* Stops the batch. | |||
* Only supported starting in SQ 5.2. For older versions, this is a no-op. | |||
*/ | |||
public void stop() { | |||
checkLauncherExists(); | |||
doStop(); | |||
} | |||
public String serverVersion() { | |||
checkLauncherExists(); | |||
return launcher.getVersion(); | |||
} | |||
/** | |||
* @deprecated since 2.5 use {@link #start()}, {@link #runAnalysis(Properties)} and then {@link #stop()} | |||
@@ -196,7 +244,9 @@ public class EmbeddedRunner { | |||
} | |||
protected void doStart(boolean forceSync) { | |||
launcher = launcherFactory.createLauncher(globalProperties()); | |||
checkLauncherDoesntExist(); | |||
ClassloadRules rules = new ClassloadRules(classloaderMask, classloaderUnmask); | |||
launcher = launcherFactory.createLauncher(globalProperties(), rules); | |||
if (VersionUtils.isAtLeast52(launcher.getVersion())) { | |||
launcher.start(globalProperties(), new org.sonar.runner.batch.LogOutput() { | |||
@@ -212,6 +262,7 @@ public class EmbeddedRunner { | |||
protected void doStop() { | |||
if (VersionUtils.isAtLeast52(launcher.getVersion())) { | |||
launcher.stop(); | |||
launcher = null; | |||
} | |||
} | |||
@@ -229,80 +280,19 @@ public class EmbeddedRunner { | |||
Properties prop = new Properties(); | |||
prop.putAll(globalProperties()); | |||
prop.putAll(analysisProperties); | |||
launcher.executeOldVersion(prop); | |||
launcher.executeOldVersion(prop, extensions); | |||
} | |||
} | |||
private void checkLauncherExists() { | |||
if(launcher == null) { | |||
if (launcher == null) { | |||
throw new IllegalStateException("not started"); | |||
} | |||
} | |||
static class IssueListenerAdapter implements org.sonar.runner.batch.IssueListener { | |||
private IssueListener apiIssueListener; | |||
public IssueListenerAdapter(IssueListener apiIssueListener) { | |||
this.apiIssueListener = apiIssueListener; | |||
} | |||
@Override | |||
public void handle(org.sonar.runner.batch.IssueListener.Issue issue) { | |||
apiIssueListener.handle(transformIssue(issue)); | |||
} | |||
private static org.sonar.runner.api.Issue transformIssue(org.sonar.runner.batch.IssueListener.Issue batchIssue) { | |||
org.sonar.runner.api.Issue.Builder issueBuilder = org.sonar.runner.api.Issue.builder(); | |||
issueBuilder.setAssigneeLogin(batchIssue.getAssigneeLogin()); | |||
issueBuilder.setAssigneeName(batchIssue.getAssigneeName()); | |||
issueBuilder.setComponentKey(batchIssue.getComponentKey()); | |||
issueBuilder.setKey(batchIssue.getKey()); | |||
issueBuilder.setLine(batchIssue.getLine()); | |||
issueBuilder.setMessage(batchIssue.getMessage()); | |||
issueBuilder.setNew(batchIssue.isNew()); | |||
issueBuilder.setResolution(batchIssue.getResolution()); | |||
issueBuilder.setRuleKey(batchIssue.getRuleKey()); | |||
issueBuilder.setRuleName(batchIssue.getRuleName()); | |||
issueBuilder.setSeverity(batchIssue.getSeverity()); | |||
issueBuilder.setStatus(batchIssue.getStatus()); | |||
return issueBuilder.build(); | |||
} | |||
} | |||
private static class LoggerAdapter implements Logger { | |||
private LogOutput logOutput; | |||
LoggerAdapter(LogOutput logOutput) { | |||
this.logOutput = logOutput; | |||
} | |||
@Override | |||
public void warn(String msg) { | |||
logOutput.log(msg, LogOutput.Level.WARN); | |||
} | |||
@Override | |||
public void info(String msg) { | |||
logOutput.log(msg, LogOutput.Level.INFO); | |||
} | |||
@Override | |||
public void error(String msg, Throwable t) { | |||
StringWriter errors = new StringWriter(); | |||
t.printStackTrace(new PrintWriter(errors)); | |||
logOutput.log(msg + "\n" + errors.toString(), LogOutput.Level.ERROR); | |||
} | |||
@Override | |||
public void error(String msg) { | |||
logOutput.log(msg, LogOutput.Level.ERROR); | |||
} | |||
@Override | |||
public void debug(String msg) { | |||
logOutput.log(msg, LogOutput.Level.DEBUG); | |||
private void checkLauncherDoesntExist() { | |||
if (launcher != null) { | |||
throw new IllegalStateException("already started"); | |||
} | |||
} | |||
} |
@@ -0,0 +1,52 @@ | |||
/* | |||
* SonarQube Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* sonarqube@googlegroups.com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public | |||
* License along with this program; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 | |||
*/ | |||
package org.sonar.runner.api; | |||
class IssueListenerAdapter implements org.sonar.runner.batch.IssueListener { | |||
private IssueListener apiIssueListener; | |||
public IssueListenerAdapter(IssueListener apiIssueListener) { | |||
this.apiIssueListener = apiIssueListener; | |||
} | |||
@Override | |||
public void handle(org.sonar.runner.batch.IssueListener.Issue issue) { | |||
apiIssueListener.handle(transformIssue(issue)); | |||
} | |||
private static org.sonar.runner.api.Issue transformIssue(org.sonar.runner.batch.IssueListener.Issue batchIssue) { | |||
org.sonar.runner.api.Issue.Builder issueBuilder = org.sonar.runner.api.Issue.builder(); | |||
issueBuilder.setAssigneeLogin(batchIssue.getAssigneeLogin()); | |||
issueBuilder.setAssigneeName(batchIssue.getAssigneeName()); | |||
issueBuilder.setComponentKey(batchIssue.getComponentKey()); | |||
issueBuilder.setKey(batchIssue.getKey()); | |||
issueBuilder.setLine(batchIssue.getLine()); | |||
issueBuilder.setMessage(batchIssue.getMessage()); | |||
issueBuilder.setNew(batchIssue.isNew()); | |||
issueBuilder.setResolution(batchIssue.getResolution()); | |||
issueBuilder.setRuleKey(batchIssue.getRuleKey()); | |||
issueBuilder.setRuleName(batchIssue.getRuleName()); | |||
issueBuilder.setSeverity(batchIssue.getSeverity()); | |||
issueBuilder.setStatus(batchIssue.getStatus()); | |||
return issueBuilder.build(); | |||
} | |||
} |
@@ -0,0 +1,60 @@ | |||
/* | |||
* SonarQube Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* sonarqube@googlegroups.com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public | |||
* License along with this program; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 | |||
*/ | |||
package org.sonar.runner.api; | |||
import org.sonar.home.cache.Logger; | |||
import java.io.PrintWriter; | |||
import java.io.StringWriter; | |||
class LoggerAdapter implements Logger { | |||
private LogOutput logOutput; | |||
LoggerAdapter(LogOutput logOutput) { | |||
this.logOutput = logOutput; | |||
} | |||
@Override | |||
public void warn(String msg) { | |||
logOutput.log(msg, LogOutput.Level.WARN); | |||
} | |||
@Override | |||
public void info(String msg) { | |||
logOutput.log(msg, LogOutput.Level.INFO); | |||
} | |||
@Override | |||
public void error(String msg, Throwable t) { | |||
StringWriter errors = new StringWriter(); | |||
t.printStackTrace(new PrintWriter(errors)); | |||
logOutput.log(msg + "\n" + errors.toString(), LogOutput.Level.ERROR); | |||
} | |||
@Override | |||
public void error(String msg) { | |||
logOutput.log(msg, LogOutput.Level.ERROR); | |||
} | |||
@Override | |||
public void debug(String msg) { | |||
logOutput.log(msg, LogOutput.Level.DEBUG); | |||
} | |||
} |
@@ -1,33 +0,0 @@ | |||
/* | |||
* SonarQube Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* sonarqube@googlegroups.com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public | |||
* License along with this program; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 | |||
*/ | |||
package org.sonar.runner.api; | |||
/** | |||
* To be used with {@link ForkedRunner} | |||
* @since 2.3 | |||
*/ | |||
public interface ProcessMonitor { | |||
/** | |||
* {@link ForkedRunner} will poll this method periodically and if true is returned | |||
* then forked SonarQube Runner process will be killed. | |||
*/ | |||
boolean stop(); | |||
} |
@@ -32,6 +32,7 @@ import java.nio.file.attribute.BasicFileAttributes; | |||
import java.util.Arrays; | |||
import java.util.Iterator; | |||
import java.util.Properties; | |||
import javax.annotation.Nullable; | |||
class Utils { |
@@ -0,0 +1,62 @@ | |||
/* | |||
* SonarQube Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* sonarqube@googlegroups.com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public | |||
* License along with this program; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 | |||
*/ | |||
package org.sonar.runner.impl; | |||
import javax.annotation.concurrent.Immutable; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import java.util.Set; | |||
@Immutable | |||
public class ClassloadRules { | |||
private final List<String> mask; | |||
private final List<String> unmask; | |||
public ClassloadRules(Set<String> maskRules, Set<String> unmaskRules) { | |||
this.mask = new ArrayList<>(maskRules); | |||
this.unmask = new ArrayList<>(unmaskRules); | |||
} | |||
public boolean canLoad(String className) { | |||
// if there is a tie -> block it | |||
return unmaskSize(className) > maskSize(className); | |||
} | |||
private int maskSize(String className) { | |||
return findBestMatch(mask, className); | |||
} | |||
private int unmaskSize(String className) { | |||
return findBestMatch(unmask, className); | |||
} | |||
private static int findBestMatch(List<String> list, String name) { | |||
// there can be a match of 0 ("") | |||
int bestMatch = -1; | |||
for (String s : list) { | |||
if (name.startsWith(s) && s.length() > bestMatch) { | |||
bestMatch = s.length(); | |||
} | |||
} | |||
return bestMatch; | |||
} | |||
} |
@@ -23,4 +23,6 @@ public interface InternalProperties { | |||
String RUNNER_APP = "sonarRunner.app"; | |||
String RUNNER_APP_VERSION = "sonarRunner.appVersion"; | |||
String RUNNER_DUMP_TO_FILE = "sonarRunner.dumpToFile"; | |||
String RUNNER_VERSION_SIMULATION = "sonarRunner.versionSimulation"; | |||
String RUNNER_MASK_RULES = "sonarRunner.maskRules"; | |||
} |
@@ -31,12 +31,14 @@ import java.util.List; | |||
* Special {@link java.net.URLClassLoader} to execute batch, which restricts loading from parent. | |||
*/ | |||
class IsolatedClassloader extends URLClassLoader { | |||
private final ClassloadRules rules; | |||
/** | |||
* The parent classloader is used only for loading classes and resources in unmasked packages | |||
*/ | |||
IsolatedClassloader(ClassLoader parent) { | |||
IsolatedClassloader(ClassLoader parent, ClassloadRules rules) { | |||
super(new URL[0], parent); | |||
this.rules = rules; | |||
} | |||
void addFiles(List<File> files) { | |||
@@ -59,7 +61,7 @@ class IsolatedClassloader extends URLClassLoader { | |||
if (c == null) { | |||
try { | |||
// Load from parent | |||
if (getParent() != null && fromSonarBatchPackage(name)) { | |||
if (getParent() != null && rules.canLoad(name)) { | |||
c = getParent().loadClass(name); | |||
} else { | |||
@@ -86,10 +88,6 @@ class IsolatedClassloader extends URLClassLoader { | |||
return c; | |||
} | |||
private static boolean fromSonarBatchPackage(String name) { | |||
return name.startsWith("org.sonar.runner.batch"); | |||
} | |||
/** | |||
* Unlike {@link java.net.URLClassLoader#getResource(String)} don't return resource from parent. | |||
* See http://jira.codehaus.org/browse/SONAR-2276 |
@@ -24,6 +24,7 @@ import java.security.AccessController; | |||
import java.security.PrivilegedAction; | |||
import java.util.List; | |||
import java.util.Properties; | |||
import org.sonar.home.cache.Logger; | |||
import org.sonar.home.cache.PersistentCache; | |||
import org.sonar.home.cache.PersistentCacheBuilder; | |||
@@ -53,28 +54,35 @@ public class IsolatedLauncherFactory { | |||
return builder.build(); | |||
} | |||
private ClassLoader createClassLoader(List<File> jarFiles) { | |||
IsolatedClassloader classloader = new IsolatedClassloader(getClass().getClassLoader()); | |||
private ClassLoader createClassLoader(List<File> jarFiles, ClassloadRules maskRules) { | |||
IsolatedClassloader classloader = new IsolatedClassloader(getClass().getClassLoader(), maskRules); | |||
classloader.addFiles(jarFiles); | |||
return classloader; | |||
} | |||
public IsolatedLauncher createLauncher(Properties props) { | |||
public IsolatedLauncher createLauncher(Properties props, ClassloadRules rules) { | |||
if (props.containsKey(InternalProperties.RUNNER_DUMP_TO_FILE)) { | |||
String version = props.getProperty(InternalProperties.RUNNER_VERSION_SIMULATION); | |||
if (version == null) { | |||
version = "5.2"; | |||
} | |||
return new SimulatedLauncher(version, logger); | |||
} | |||
ServerConnection serverConnection = ServerConnection.create(props, getCache(props), logger); | |||
JarDownloader jarDownloader = new JarDownloader(serverConnection, logger); | |||
return createLauncher(jarDownloader); | |||
return createLauncher(jarDownloader, rules); | |||
} | |||
IsolatedLauncher createLauncher(final JarDownloader jarDownloader) { | |||
IsolatedLauncher createLauncher(final JarDownloader jarDownloader, final ClassloadRules rules) { | |||
return AccessController.doPrivileged(new PrivilegedAction<IsolatedLauncher>() { | |||
@Override | |||
public IsolatedLauncher run() { | |||
try { | |||
List<File> jarFiles = jarDownloader.download(); | |||
logger.debug("Create isolated classloader..."); | |||
ClassLoader cl = createClassLoader(jarFiles); | |||
ClassLoader cl = createClassLoader(jarFiles, rules); | |||
IsolatedLauncher objProxy = IsolatedLauncherProxy.create(cl, IsolatedLauncher.class, launcherImplClassName, logger); | |||
tempCleaning.clean(); | |||
@@ -0,0 +1,115 @@ | |||
/* | |||
* SonarQube Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* sonarqube@googlegroups.com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public | |||
* License along with this program; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 | |||
*/ | |||
package org.sonar.runner.impl; | |||
import org.sonar.home.cache.Logger; | |||
import javax.annotation.Nullable; | |||
import org.sonar.runner.batch.IssueListener; | |||
import org.sonar.runner.batch.LogOutput; | |||
import java.io.File; | |||
import java.io.FileOutputStream; | |||
import java.io.OutputStream; | |||
import java.util.List; | |||
import java.util.Properties; | |||
import org.sonar.runner.batch.IsolatedLauncher; | |||
public class SimulatedLauncher implements IsolatedLauncher { | |||
private final String version; | |||
private final Logger logger; | |||
private Properties globalProperties; | |||
SimulatedLauncher(String version, Logger logger) { | |||
this.version = version; | |||
this.logger = logger; | |||
} | |||
@Override | |||
public void start(Properties properties, LogOutput logOutput, boolean forceSync) { | |||
globalProperties = properties; | |||
} | |||
@Override | |||
public void stop() { | |||
globalProperties = null; | |||
} | |||
@Override | |||
public void execute(Properties properties) { | |||
dumpProperties(globalProperties, properties); | |||
} | |||
@Override | |||
public void execute(Properties properties, IssueListener listener) { | |||
dumpProperties(globalProperties, properties); | |||
} | |||
private void dumpProperties(@Nullable Properties global, Properties analysis) { | |||
// for old versions, analysis will have global properties merged in it | |||
String filePath; | |||
String filePathGlobal = null; | |||
if (global != null) { | |||
filePath = global.getProperty(InternalProperties.RUNNER_DUMP_TO_FILE); | |||
filePathGlobal = filePath + ".global"; | |||
} else { | |||
filePath = analysis.getProperty(InternalProperties.RUNNER_DUMP_TO_FILE); | |||
} | |||
if (filePath == null) { | |||
throw new IllegalStateException("No file to dump properties"); | |||
} | |||
if (global != null) { | |||
File dumpFileGlobal = new File(filePathGlobal); | |||
writeProperties(dumpFileGlobal, global, "global properties"); | |||
} | |||
File dumpFile = new File(filePath); | |||
writeProperties(dumpFile, analysis, "analysis properties"); | |||
logger.info("Simulation mode. Configuration written to " + dumpFile.getAbsolutePath()); | |||
} | |||
private static void writeProperties(File outputFile, Properties p, String comment) { | |||
try (OutputStream output = new FileOutputStream(outputFile)) { | |||
p.store(output, "Generated by sonar-runner - " + comment); | |||
} catch (Exception e) { | |||
throw new IllegalStateException("Fail to export sonar-runner properties", e); | |||
} | |||
} | |||
@Override | |||
public void syncProject(String projectKey) { | |||
// no op | |||
} | |||
@Override | |||
public void executeOldVersion(Properties properties, List<Object> extensions) { | |||
dumpProperties(null, properties); | |||
} | |||
@Override | |||
public String getVersion() { | |||
return version; | |||
} | |||
} |
@@ -19,9 +19,9 @@ | |||
*/ | |||
package org.sonar.runner.api; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.runner.impl.ClassloadRules; | |||
import org.sonar.runner.api.EmbeddedRunner.IssueListenerAdapter; | |||
import org.junit.rules.ExpectedException; | |||
import java.io.File; | |||
import java.io.FileInputStream; | |||
@@ -31,6 +31,7 @@ import java.util.LinkedList; | |||
import java.util.List; | |||
import java.util.Properties; | |||
import static org.mockito.Matchers.eq; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
@@ -68,7 +69,7 @@ public class EmbeddedRunnerTest { | |||
batchLauncher = mock(IsolatedLauncherFactory.class); | |||
launcher = mock(IsolatedLauncher.class); | |||
when(launcher.getVersion()).thenReturn("5.2"); | |||
when(batchLauncher.createLauncher(any(Properties.class))).thenReturn(launcher); | |||
when(batchLauncher.createLauncher(any(Properties.class), any(ClassloadRules.class))).thenReturn(launcher); | |||
runner = new EmbeddedRunner(batchLauncher, mock(Logger.class), mock(LogOutput.class)); | |||
} | |||
@@ -79,18 +80,18 @@ public class EmbeddedRunnerTest { | |||
runner.syncProject(projectKey); | |||
verify(launcher).syncProject(projectKey); | |||
} | |||
@Test | |||
public void test_server_version() { | |||
runner.start(); | |||
assertThat(runner.serverVersion()).isEqualTo("5.2"); | |||
} | |||
@Test | |||
public void test_run_before_start() { | |||
expectedException.expect(IllegalStateException.class); | |||
expectedException.expectMessage("started"); | |||
runner.runAnalysis(new Properties()); | |||
} | |||
@@ -130,7 +131,7 @@ public class EmbeddedRunnerTest { | |||
public boolean matches(Object o) { | |||
return "foo".equals(((Properties) o).getProperty("sonar.projectKey")); | |||
} | |||
})); | |||
}), any(ClassloadRules.class)); | |||
// it should have added a few properties to analysisProperties, and have merged global props | |||
final String[] mustHaveKeys = {"sonar.working.directory", "sonar.sourceEncoding", "sonar.projectBaseDir", | |||
@@ -147,7 +148,7 @@ public class EmbeddedRunnerTest { | |||
} | |||
return true; | |||
} | |||
})); | |||
}), eq(new LinkedList<>())); | |||
} | |||
@Test | |||
@@ -179,7 +180,7 @@ public class EmbeddedRunnerTest { | |||
public boolean matches(Object o) { | |||
return "foo".equals(((Properties) o).getProperty("sonar.projectKey")); | |||
} | |||
})); | |||
}), any(ClassloadRules.class)); | |||
// it should have added a few properties to analysisProperties | |||
final String[] mustHaveKeys = {"sonar.working.directory", "sonar.sourceEncoding", "sonar.projectBaseDir"}; | |||
@@ -247,7 +248,7 @@ public class EmbeddedRunnerTest { | |||
public boolean matches(Object o) { | |||
return "foo".equals(((Properties) o).getProperty("sonar.projectKey")); | |||
} | |||
})); | |||
}), any(ClassloadRules.class)); | |||
verify(launcher).execute(argThat(new ArgumentMatcher<Properties>() { | |||
@Override | |||
@@ -259,11 +260,14 @@ public class EmbeddedRunnerTest { | |||
@Test | |||
public void should_launch_in_simulation_mode() throws IOException { | |||
batchLauncher = new IsolatedLauncherFactory(mock(Logger.class)); | |||
runner = new EmbeddedRunner(batchLauncher, mock(Logger.class), mock(LogOutput.class)); | |||
File dump = temp.newFile(); | |||
Properties p = new Properties(); | |||
p.setProperty("sonar.projectKey", "foo"); | |||
p.setProperty("sonarRunner.dumpToFile", dump.getAbsolutePath()); | |||
runner.setGlobalProperty("sonarRunner.dumpToFile", dump.getAbsolutePath()); | |||
runner.start(); | |||
runner.runAnalysis(p); | |||
runner.stop(); | |||
@@ -282,6 +286,25 @@ public class EmbeddedRunnerTest { | |||
assertThat(p.getProperty("sonar.sourceEncoding", null)).isEqualTo(Charset.defaultCharset().name()); | |||
} | |||
@Test | |||
public void invalidate_after_stop() { | |||
runner.start(); | |||
runner.stop(); | |||
expectedException.expect(IllegalStateException.class); | |||
expectedException.expectMessage("started"); | |||
runner.runAnalysis(new Properties()); | |||
} | |||
@Test | |||
public void cannot_start_twice() { | |||
runner.start(); | |||
expectedException.expect(IllegalStateException.class); | |||
expectedException.expectMessage("started"); | |||
runner.start(); | |||
} | |||
@Test | |||
public void should_use_parameterized_encoding() throws Exception { | |||
Properties p = new Properties(); |
@@ -0,0 +1,53 @@ | |||
/* | |||
* SonarQube Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* sonarqube@googlegroups.com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public | |||
* License along with this program; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 | |||
*/ | |||
package org.sonar.runner.api; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Mockito.verify; | |||
import org.mockito.ArgumentCaptor; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import static org.mockito.Mockito.mock; | |||
public class IssueListenerAdapterTest { | |||
private IssueListenerAdapter adapter; | |||
private IssueListener issueListener; | |||
@Before | |||
public void setUp() { | |||
issueListener = mock(IssueListener.class); | |||
adapter = new IssueListenerAdapter(issueListener); | |||
} | |||
@Test | |||
public void test() { | |||
org.sonar.runner.batch.IssueListener.Issue issue = new org.sonar.runner.batch.IssueListener.Issue(); | |||
issue.setAssigneeName("dummy"); | |||
adapter.handle(issue); | |||
ArgumentCaptor<Issue> argument = ArgumentCaptor.forClass(Issue.class); | |||
verify(issueListener).handle(argument.capture()); | |||
assertThat(argument.getValue().getAssigneeName()).isEqualTo("dummy"); | |||
} | |||
} |
@@ -0,0 +1,76 @@ | |||
/* | |||
* SonarQube Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* sonarqube@googlegroups.com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public | |||
* License along with this program; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 | |||
*/ | |||
package org.sonar.runner.api; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.verifyNoMoreInteractions; | |||
import static org.mockito.Matchers.eq; | |||
import static org.mockito.Matchers.startsWith; | |||
import org.junit.Test; | |||
import org.junit.Before; | |||
public class LoggerAdapterTest { | |||
private LoggerAdapter adapter; | |||
private LogOutput logOutput; | |||
@Before | |||
public void setUp() { | |||
logOutput = mock(LogOutput.class); | |||
adapter = new LoggerAdapter(logOutput); | |||
} | |||
@Test | |||
public void testDebug() { | |||
adapter.debug("debug"); | |||
verify(logOutput).log("debug", LogOutput.Level.DEBUG); | |||
verifyNoMoreInteractions(logOutput); | |||
} | |||
@Test | |||
public void testInfo() { | |||
adapter.info("info"); | |||
verify(logOutput).log("info", LogOutput.Level.INFO); | |||
verifyNoMoreInteractions(logOutput); | |||
} | |||
@Test | |||
public void testWarn() { | |||
adapter.warn("warn"); | |||
verify(logOutput).log("warn", LogOutput.Level.WARN); | |||
verifyNoMoreInteractions(logOutput); | |||
} | |||
@Test | |||
public void testError() { | |||
adapter.error("error"); | |||
verify(logOutput).log("error", LogOutput.Level.ERROR); | |||
verifyNoMoreInteractions(logOutput); | |||
} | |||
@Test | |||
public void testErrorThrowable() { | |||
adapter.error("error", new IllegalStateException("error")); | |||
verify(logOutput).log(startsWith("error\njava.lang.IllegalStateException: error"), eq(LogOutput.Level.ERROR)); | |||
verifyNoMoreInteractions(logOutput); | |||
} | |||
} |
@@ -0,0 +1,106 @@ | |||
/* | |||
* SonarQube Runner - API | |||
* Copyright (C) 2011 SonarSource | |||
* sonarqube@googlegroups.com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public | |||
* License along with this program; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 | |||
*/ | |||
package org.sonar.runner.impl; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import java.util.HashSet; | |||
import java.util.Set; | |||
import org.junit.Test; | |||
import org.junit.Before; | |||
public class ClassloadRulesTest { | |||
private ClassloadRules rules; | |||
private Set<String> maskRules; | |||
private Set<String> unmaskRules; | |||
@Before | |||
public void setUp() { | |||
maskRules = new HashSet<>(); | |||
unmaskRules = new HashSet<>(); | |||
} | |||
@Test | |||
public void testUnmask() { | |||
unmaskRules.add("org.apache.ant."); | |||
rules = new ClassloadRules(maskRules, unmaskRules); | |||
assertThat(rules.canLoad("org.sonar.runner.Foo")).isFalse(); | |||
assertThat(rules.canLoad("org.objectweb.asm.ClassVisitor")).isFalse(); | |||
assertThat(rules.canLoad("org.apache")).isFalse(); | |||
assertThat(rules.canLoad("org.apache.ant.Foo")).isTrue(); | |||
assertThat(rules.canLoad("org.apache.ant.project.Project")).isTrue(); | |||
} | |||
@Test | |||
public void testUnmaskAll() { | |||
unmaskRules.add(""); | |||
rules = new ClassloadRules(maskRules, unmaskRules); | |||
assertThat(rules.canLoad("org.sonar.runner.Foo")).isTrue(); | |||
assertThat(rules.canLoad("org.objectweb.asm.ClassVisitor")).isTrue(); | |||
assertThat(rules.canLoad("org.apache")).isTrue(); | |||
assertThat(rules.canLoad("org.apache.ant.Foo")).isTrue(); | |||
assertThat(rules.canLoad("org.apache.ant.project.Project")).isTrue(); | |||
} | |||
@Test | |||
public void testDefault() { | |||
rules = new ClassloadRules(maskRules, unmaskRules); | |||
assertThat(rules.canLoad("org.sonar.runner.Foo")).isFalse(); | |||
} | |||
@Test | |||
public void testMaskAndUnmask() throws ClassNotFoundException { | |||
unmaskRules.add("org.apache.ant."); | |||
maskRules.add("org.apache.ant.foo."); | |||
rules = new ClassloadRules(maskRules, unmaskRules); | |||
assertThat(rules.canLoad("org.apache.ant.something")).isTrue(); | |||
assertThat(rules.canLoad("org.apache.ant.foo.something")).isFalse(); | |||
assertThat(rules.canLoad("org.apache")).isFalse(); | |||
} | |||
@Test | |||
public void testUsedByMaven() { | |||
maskRules.add( "org.slf4j.LoggerFactory" ); | |||
// Include slf4j Logger that is exposed by some Sonar components | |||
unmaskRules.add( "org.slf4j.Logger" ); | |||
unmaskRules.add( "org.slf4j.ILoggerFactory" ); | |||
// Exclude other slf4j classes | |||
// .unmask("org.slf4j.impl.") | |||
maskRules.add( "org.slf4j." ); | |||
// Exclude logback | |||
maskRules.add( "ch.qos.logback." ); | |||
maskRules.add( "org.sonar." ); | |||
unmaskRules.add("org.sonar.runner.batch."); | |||
// Guava is not the same version in SonarQube classloader | |||
maskRules.add( "com.google.common" ); | |||
// Include everything else | |||
unmaskRules.add( "" ); | |||
rules = new ClassloadRules(maskRules, unmaskRules); | |||
assertThat(rules.canLoad("org.sonar.runner.batch.IsolatedLauncher")).isTrue(); | |||
} | |||
} |
@@ -20,10 +20,11 @@ | |||
package org.sonar.runner.impl; | |||
import java.io.IOException; | |||
import java.util.HashSet; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import static org.fest.assertions.Assertions.assertThat; | |||
public class IsolatedClassloaderTest { | |||
@@ -35,7 +36,7 @@ public class IsolatedClassloaderTest { | |||
thrown.expect(ClassNotFoundException.class); | |||
thrown.expectMessage("org.junit.Test"); | |||
ClassLoader parent = getClass().getClassLoader(); | |||
IsolatedClassloader classLoader = new IsolatedClassloader(parent); | |||
IsolatedClassloader classLoader = new IsolatedClassloader(parent, new ClassloadRules(new HashSet<String>(), new HashSet<String>())); | |||
// 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(); |
@@ -21,6 +21,8 @@ package org.sonar.runner.impl; | |||
import org.sonar.runner.batch.IssueListener; | |||
import java.util.HashSet; | |||
import java.util.List; | |||
import java.util.Properties; | |||
import org.junit.Before; | |||
@@ -48,7 +50,7 @@ public class IsolatedLauncherFactoryTest { | |||
@Test | |||
public void should_use_isolated_classloader() { | |||
try { | |||
factory.createLauncher(jarDownloader); | |||
factory.createLauncher(jarDownloader, new ClassloadRules(new HashSet<String>(), new HashSet<String>())); | |||
fail(); | |||
} catch (RunnerException e) { | |||
// success | |||
@@ -73,7 +75,7 @@ public class IsolatedLauncherFactoryTest { | |||
} | |||
@Override | |||
public void executeOldVersion(Properties properties) { | |||
public void executeOldVersion(Properties properties, List<Object> extensions) { | |||
} | |||
@Override |
@@ -19,6 +19,7 @@ | |||
*/ | |||
package org.sonar.runner.batch; | |||
import java.util.List; | |||
import java.util.Properties; | |||
public interface IsolatedLauncher { | |||
@@ -33,7 +34,7 @@ public interface IsolatedLauncher { | |||
void syncProject(String projectKey); | |||
void executeOldVersion(Properties properties); | |||
void executeOldVersion(Properties properties, List<Object> extensions); | |||
String getVersion(); | |||
@@ -24,8 +24,10 @@ import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.InputStreamReader; | |||
import java.nio.charset.StandardCharsets; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Properties; | |||
import org.picocontainer.annotations.Nullable; | |||
import org.sonar.batch.bootstrapper.Batch; | |||
import org.sonar.batch.bootstrapper.EnvironmentInformation; | |||
@@ -35,12 +37,11 @@ import org.sonar.batch.bootstrapper.EnvironmentInformation; | |||
* the same version of sonar-batch as the server. | |||
*/ | |||
public class BatchIsolatedLauncher implements IsolatedLauncher { | |||
private Batch batch = null; | |||
@Override | |||
public void start(Properties globalProperties, org.sonar.runner.batch.LogOutput logOutput, boolean forceSync) { | |||
batch = createBatch(globalProperties, logOutput); | |||
batch = createBatch(globalProperties, logOutput, null); | |||
batch.start(forceSync); | |||
} | |||
@@ -65,12 +66,16 @@ public class BatchIsolatedLauncher implements IsolatedLauncher { | |||
batch.syncProject(projectKey); | |||
} | |||
Batch createBatch(Properties properties, @Nullable final org.sonar.runner.batch.LogOutput logOutput) { | |||
Batch createBatch(Properties properties, @Nullable final org.sonar.runner.batch.LogOutput logOutput, @Nullable List<Object> extensions) { | |||
EnvironmentInformation env = new EnvironmentInformation(properties.getProperty("sonarRunner.app"), properties.getProperty("sonarRunner.appVersion")); | |||
Batch.Builder builder = Batch.builder() | |||
.setEnvironment(env) | |||
.setBootstrapProperties((Map) properties); | |||
if (extensions != null) { | |||
builder.addComponents(extensions); | |||
} | |||
if (logOutput != null) { | |||
// Do that is a separate class to avoid NoClassDefFoundError for org/sonar/batch/bootstrapper/LogOutput | |||
Compatibility.setLogOutputFor5dot2(builder, logOutput); | |||
@@ -83,10 +88,10 @@ public class BatchIsolatedLauncher implements IsolatedLauncher { | |||
* This method exists for backward compatibility with SonarQube < 5.2. | |||
*/ | |||
@Override | |||
public void executeOldVersion(Properties properties) { | |||
createBatch(properties, null).execute(); | |||
public void executeOldVersion(Properties properties, List<Object> extensions) { | |||
createBatch(properties, null, extensions).execute(); | |||
} | |||
@Override | |||
public String getVersion() { | |||
InputStream is = this.getClass().getClassLoader().getResourceAsStream("sq-version.txt"); |
@@ -37,7 +37,7 @@ public class IsolatedLauncherTest { | |||
props.setProperty("sonar.projectName", "Sample"); | |||
props.setProperty("sonar.projectVersion", "1.0"); | |||
props.setProperty("sonar.sources", "src"); | |||
Batch batch = launcher.createBatch(props, null); | |||
Batch batch = launcher.createBatch(props, null, null); | |||
assertThat(batch).isNotNull(); | |||
} |