*/
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;
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) {
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)
*/
/**
* 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
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())) {
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()}
}
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() {
protected void doStop() {
if (VersionUtils.isAtLeast52(launcher.getVersion())) {
launcher.stop();
+ launcher = null;
}
}
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");
}
}
}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
+++ /dev/null
-/*
- * 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();
-}
import java.util.Arrays;
import java.util.Iterator;
import java.util.Properties;
+
import javax.annotation.Nullable;
class Utils {
--- /dev/null
+/*
+ * 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;
+ }
+}
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";
}
* 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) {
if (c == null) {
try {
// Load from parent
- if (getParent() != null && fromSonarBatchPackage(name)) {
+ if (getParent() != null && rules.canLoad(name)) {
c = getParent().loadClass(name);
} else {
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
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;
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();
--- /dev/null
+/*
+ * 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;
+ }
+
+}
*/
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;
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;
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));
}
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());
}
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",
}
return true;
}
- }));
+ }), eq(new LinkedList<>()));
}
@Test
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"};
public boolean matches(Object o) {
return "foo".equals(((Properties) o).getProperty("sonar.projectKey"));
}
- }));
+ }), any(ClassloadRules.class));
verify(launcher).execute(argThat(new ArgumentMatcher<Properties>() {
@Override
@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();
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();
--- /dev/null
+/*
+ * 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");
+ }
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
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 {
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();
import org.sonar.runner.batch.IssueListener;
+import java.util.HashSet;
+import java.util.List;
import java.util.Properties;
import org.junit.Before;
@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
}
@Override
- public void executeOldVersion(Properties properties) {
+ public void executeOldVersion(Properties properties, List<Object> extensions) {
}
@Override
*/
package org.sonar.runner.batch;
+import java.util.List;
import java.util.Properties;
public interface IsolatedLauncher {
void syncProject(String projectKey);
- void executeOldVersion(Properties properties);
+ void executeOldVersion(Properties properties, List<Object> extensions);
String getVersion();
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;
* 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);
}
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);
* 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");
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();
}