<modules>
<module>sonar-runner-api</module>
<module>sonar-runner-batch</module>
+ <module>sonar-runner-batch-interface</module>
<module>sonar-runner-dist</module>
<module>sonar-runner-impl</module>
</modules>
<groupId>${project.groupId}</groupId>
<artifactId>sonar-runner-impl</artifactId>
<version>${project.version}</version>
- <exclusions>
- <exclusion>
- <groupId>*</groupId>
- <artifactId>*</artifactId>
- </exclusion>
- </exclusions>
+
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
return exitCode;
} catch (TimeoutException te) {
- process.destroy();
+ if (process != null) {
+ process.destroy();
+ }
throw new CommandException("Timeout exceeded: " + timeoutMilliseconds + " ms", command, te);
} catch (CommandException e) {
private Future<Integer> executeProcess(ExecutorService executorService, Process process) {
final Process finalProcess = process;
return executorService.submit(new Callable<Integer>() {
+ @Override
public Integer call() throws InterruptedException {
return finalProcess.waitFor();
}
import org.sonar.runner.impl.Logs;
import java.io.File;
+import java.util.Properties;
class Dirs {
+ private Dirs() {
- void init(Runner<?> runner) {
- boolean onProject = Utils.taskRequiresProject(runner.properties());
+ }
+
+ static void init(Properties p) {
+ boolean onProject = Utils.taskRequiresProject(p);
if (onProject) {
- initProjectDirs(runner);
+ initProjectDirs(p);
} else {
- initTaskDirs(runner);
+ initTaskDirs(p);
}
}
- private void initProjectDirs(Runner<?> runner) {
- String path = runner.property(ScanProperties.PROJECT_BASEDIR, ".");
+ private static void initProjectDirs(Properties p) {
+ String path = p.getProperty(ScanProperties.PROJECT_BASEDIR, ".");
File projectDir = new File(path);
if (!projectDir.isDirectory()) {
throw new IllegalStateException("Project home must be an existing directory: " + path);
}
- runner.setProperty(ScanProperties.PROJECT_BASEDIR, projectDir.getAbsolutePath());
+ p.setProperty(ScanProperties.PROJECT_BASEDIR, projectDir.getAbsolutePath());
File workDir;
- path = runner.property(RunnerProperties.WORK_DIR, "");
+ path = p.getProperty(RunnerProperties.WORK_DIR, "");
if ("".equals(path.trim())) {
workDir = new File(projectDir, ".sonar");
}
}
Utils.deleteQuietly(workDir);
- runner.setProperty(RunnerProperties.WORK_DIR, workDir.getAbsolutePath());
+ p.setProperty(RunnerProperties.WORK_DIR, workDir.getAbsolutePath());
Logs.info("Work directory: " + workDir.getAbsolutePath());
}
/**
* Non-scan task
*/
- private void initTaskDirs(Runner<?> runner) {
- String path = runner.property(RunnerProperties.WORK_DIR, ".");
+ private static void initTaskDirs(Properties p) {
+ String path = p.getProperty(RunnerProperties.WORK_DIR, ".");
File workDir = new File(path);
- runner.setProperty(RunnerProperties.WORK_DIR, workDir.getAbsolutePath());
+ p.setProperty(RunnerProperties.WORK_DIR, workDir.getAbsolutePath());
}
}
*/
package org.sonar.runner.api;
-import org.sonar.runner.impl.BatchLauncher;
+import org.sonar.runner.batch.IsolatedLauncher;
+import org.sonar.runner.impl.IsolatedLauncherFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
+import java.util.Properties;
/**
* Implementation of {@link Runner} that is executed in the same JVM. The application can inject
*/
public class EmbeddedRunner extends Runner<EmbeddedRunner> {
- private final BatchLauncher batchLauncher;
+ private final IsolatedLauncherFactory launcherFactory;
+ private IsolatedLauncher launcher;
private final List<Object> extensions = new ArrayList<Object>();
private static final String MASK_RULES_PROP = "sonarRunner.maskRules";
- EmbeddedRunner(BatchLauncher bl) {
- this.batchLauncher = bl;
+ EmbeddedRunner(IsolatedLauncherFactory bl) {
+ this.launcherFactory = bl;
}
/**
* Create a new instance.
*/
public static EmbeddedRunner create() {
- return new EmbeddedRunner(new BatchLauncher());
+ return new EmbeddedRunner(new IsolatedLauncherFactory());
}
/**
}
private EmbeddedRunner addMaskRule(String type, String fqcnPrefix) {
- String existingRules = property(MASK_RULES_PROP, "");
+ String existingRules = globalProperty(MASK_RULES_PROP, "");
if (!"".equals(existingRules)) {
existingRules += ",";
}
existingRules += type + "|" + fqcnPrefix;
- return setProperty(MASK_RULES_PROP, existingRules);
+ return setGlobalProperty(MASK_RULES_PROP, existingRules);
}
/**
}
@Override
- protected void doExecute() {
- batchLauncher.execute(properties(), extensions);
+ protected void doStart() {
+ launcher = launcherFactory.createLauncher(globalProperties());
+ launcher.start(globalProperties(), extensions);
+ }
+
+ @Override
+ protected void doStop() {
+ launcher.stop();
+ }
+
+ @Override
+ protected void doExecute(Properties analysisProperties) {
+ launcher.execute(analysisProperties);
}
}
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Properties;
/**
* Runner executed in a new JVM.
}
@Override
- protected void doExecute() {
- ForkCommand forkCommand = createCommand();
+ protected void doExecute(Properties props) {
+ //merge both global and analysis-specific properties because it will be used both to start and to execute.
+ Properties p = new Properties();
+
+ p.putAll(globalProperties());
+ p.putAll(props);
+
+ ForkCommand forkCommand = createCommand(p);
try {
fork(forkCommand);
} finally {
deleteTempFiles(forkCommand);
}
}
+
+ @Override
+ protected void doStop() {
+ //nothing to do
+ }
+
+ @Override
+ protected void doStart() {
+ //nothing to do
+ }
- ForkCommand createCommand() {
- File propertiesFile = writeProperties();
+ ForkCommand createCommand(Properties p) {
+ File propertiesFile = writeProperties(p);
File jarFile = jarExtractor.extractToTemp("sonar-runner-impl");
if (javaExecutable == null) {
javaExecutable = new Os().thisJavaExe().getAbsolutePath();
return new ForkCommand(command, jarFile, propertiesFile);
}
- private File writeProperties() {
+ File writeProperties(Properties p) {
try {
File file = File.createTempFile("sonar-project", ".properties");
try (OutputStream output = new FileOutputStream(file)) {
- properties().store(output, "Generated by sonar-runner");
+ p.store(output, "Generated by sonar-runner");
return file;
}
} catch (Exception e) {
}
}
- private void deleteTempFiles(ForkCommand forkCommand) {
+ private static void deleteTempFiles(ForkCommand forkCommand) {
Utils.deleteQuietly(forkCommand.jarFile);
Utils.deleteQuietly(forkCommand.propertiesFile);
}
this.output = output;
}
+ @Override
public void consumeLine(String line) {
output.println(line);
}
import javax.annotation.Nullable;
import java.io.File;
-import java.io.FileOutputStream;
-import java.io.OutputStream;
import java.util.Properties;
/**
*/
public abstract class Runner<T extends Runner> {
- private final Properties properties = new Properties();
+ private final Properties globalProperties = new Properties();
protected Runner() {
}
- public Properties properties() {
+ public Properties globalProperties() {
Properties clone = new Properties();
- clone.putAll(properties);
+ clone.putAll(globalProperties);
return clone;
}
*
* @see #setProperty(String, String)
*/
- public T addProperties(Properties p) {
- properties.putAll(p);
+ public T addGlobalProperties(Properties p) {
+ globalProperties.putAll(p);
return (T) this;
}
* @see RunnerProperties
* @see ScanProperties
*/
- public T setProperty(String key, String value) {
- properties.setProperty(key, value);
+ public T setGlobalProperty(String key, String value) {
+ globalProperties.setProperty(key, value);
return (T) this;
}
- public String property(String key, @Nullable String defaultValue) {
- return properties.getProperty(key, defaultValue);
+ public String globalProperty(String key, @Nullable String defaultValue) {
+ return globalProperties.getProperty(key, defaultValue);
}
/**
* User-agent used in the HTTP requests to the Sonar server
*/
public T setApp(String app, String version) {
- setProperty(InternalProperties.RUNNER_APP, app);
- setProperty(InternalProperties.RUNNER_APP_VERSION, version);
+ setGlobalProperty(InternalProperties.RUNNER_APP, app);
+ setGlobalProperty(InternalProperties.RUNNER_APP_VERSION, version);
return (T) this;
}
public String app() {
- return property(InternalProperties.RUNNER_APP, null);
+ return globalProperty(InternalProperties.RUNNER_APP, null);
}
public String appVersion() {
- return property(InternalProperties.RUNNER_APP_VERSION, null);
+ return globalProperty(InternalProperties.RUNNER_APP_VERSION, null);
}
- public void execute() {
- initDefaultValues();
- new SourceEncoding().init(this);
- new Dirs().init(this);
- String dumpToFile = properties.getProperty(InternalProperties.RUNNER_DUMP_TO_FILE);
+ public void runAnalysis(Properties analysisProperties) {
+ 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);
- writeProperties(dumpFile);
+ Utils.writeProperties(dumpFile, copy);
System.out.println("Simulation mode. Configuration written to " + dumpFile.getAbsolutePath());
} else {
- doExecute();
+ doExecute(copy);
}
}
- private void writeProperties(File outputFile) {
- try (OutputStream output = new FileOutputStream(outputFile)) {
- properties().store(output, "Generated by sonar-runner");
- } catch (Exception e) {
- throw new IllegalStateException("Fail to export sonar-runner properties", e);
- }
+ public void start() {
+ initGlobalDefaultValues();
+ doStart();
+ }
+
+ public void stop() {
+ doStop();
+ }
+
+ /**
+ * @deprecated since 2.5 use {@link #start()}, {@link #runAnalysis(Properties)} and then {@link #stop()}
+ */
+ @Deprecated
+ public final void execute() {
+ start();
+ runAnalysis(new Properties());
+ stop();
}
- protected abstract void doExecute();
+ protected abstract void doStart();
+
+ protected abstract void doStop();
+
+ protected abstract void doExecute(Properties analysisProperties);
+
+ private void initGlobalDefaultValues() {
+ setGlobalDefaultValue(RunnerProperties.HOST_URL, "http://localhost:9000");
+ setGlobalDefaultValue(InternalProperties.RUNNER_APP, "SonarQubeRunner");
+ setGlobalDefaultValue(InternalProperties.RUNNER_APP_VERSION, RunnerVersion.version());
+ }
- private void initDefaultValues() {
- setDefaultValue(RunnerProperties.HOST_URL, "http://localhost:9000");
- setDefaultValue(InternalProperties.RUNNER_APP, "SonarQubeRunner");
- setDefaultValue(InternalProperties.RUNNER_APP_VERSION, RunnerVersion.version());
+ private static void initAnalysisProperties(Properties p) {
+ SourceEncoding.init(p);
+ Dirs.init(p);
}
- private void setDefaultValue(String key, String value) {
- if (!properties.containsKey(key)) {
- setProperty(key, value);
+ private void setGlobalDefaultValue(String key, String value) {
+ if (!globalProperties.containsKey(key)) {
+ setGlobalProperty(key, value);
}
}
import java.nio.charset.Charset;
import java.util.Locale;
+import java.util.Properties;
class SourceEncoding {
-
- void init(Runner<?> runner) {
- boolean onProject = Utils.taskRequiresProject(runner.properties());
+ private SourceEncoding() {
+
+ }
+ static void init(Properties p) {
+ boolean onProject = Utils.taskRequiresProject(p);
if (onProject) {
- String sourceEncoding = runner.property(ScanProperties.PROJECT_SOURCE_ENCODING, "");
+ String sourceEncoding = p.getProperty(ScanProperties.PROJECT_SOURCE_ENCODING, "");
boolean platformDependent = false;
if ("".equals(sourceEncoding)) {
sourceEncoding = Charset.defaultCharset().name();
platformDependent = true;
- runner.setProperty(ScanProperties.PROJECT_SOURCE_ENCODING, sourceEncoding);
+ p.setProperty(ScanProperties.PROJECT_SOURCE_ENCODING, sourceEncoding);
}
Logs.info("Default locale: \"" + Locale.getDefault() + "\", source code encoding: \"" + sourceEncoding + "\""
- + (platformDependent ? " (analysis is platform dependent)" : ""));
+ + (platformDependent ? " (analysis is platform dependent)" : ""));
}
}
}
import java.io.Closeable;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
return task == null || ScanProperties.SCAN_TASK.equals(task);
}
- public static void deleteQuietly(File f) {
+ static void writeProperties(File outputFile, Properties p) {
+ try (OutputStream output = new FileOutputStream(outputFile)) {
+ p.store(output, "Generated by sonar-runner");
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to export sonar-runner properties", e);
+ }
+ }
+
+ static void deleteQuietly(File f) {
try {
Files.walkFileTree(f.toPath(), new DeleteFileVisitor());
} catch (IOException e) {
}
}
- public static void closeQuietly(@Nullable Closeable c) {
+ static void closeQuietly(@Nullable Closeable c) {
if (c == null) {
return;
}
import org.junit.rules.TemporaryFolder;
import java.io.File;
+import java.util.Properties;
import static org.fest.assertions.Assertions.assertThat;
public class DirsTest {
- Runner<?> runner = new SimpleRunner();
- Dirs dirs = new Dirs();
+ Properties p = new Properties();
@Rule
public TemporaryFolder temp = new TemporaryFolder();
@Test
public void should_init_default_task_work_dir() throws Exception {
- runner.setProperty("sonar.task", "views");
- dirs.init(runner);
+ p.setProperty("sonar.task", "views");
+ Dirs.init(p);
- File workDir = new File(runner.property("sonar.working.directory", null));
+ File workDir = new File(p.getProperty(RunnerProperties.WORK_DIR, null));
assertThat(workDir).isNotNull().isDirectory();
assertThat(workDir.getCanonicalPath()).isEqualTo(new File(".").getCanonicalPath());
}
@Test
public void should_use_parameterized_task_work_dir() throws Exception {
- runner.setProperty("sonar.task", "views");
- runner.setProperty("sonar.working.directory", "generated/reports");
- dirs.init(runner);
+ p.setProperty("sonar.task", "views");
+ p.setProperty(RunnerProperties.WORK_DIR, "generated/reports");
+ Dirs.init(p);
- File workDir = new File(runner.property("sonar.working.directory", null));
+ File workDir = new File(p.getProperty(RunnerProperties.WORK_DIR, null));
assertThat(workDir).isNotNull();
- //separators from windows to unix
+ // separators from windows to unix
assertThat(workDir.getCanonicalPath().replace("\\", "/")).contains("generated/reports");
}
@Test
public void should_init_default_project_dirs() throws Exception {
- runner.setProperty("sonar.task", "scan");
- dirs.init(runner);
+ p.setProperty("sonar.task", "scan");
+ Dirs.init(p);
-
- File projectDir = new File(runner.property("sonar.projectBaseDir", null));
- File workDir = new File(runner.property("sonar.working.directory", null));
+ File projectDir = new File(p.getProperty(ScanProperties.PROJECT_BASEDIR, null));
+ File workDir = new File(p.getProperty(RunnerProperties.WORK_DIR, null));
assertThat(projectDir).isNotNull().isDirectory();
assertThat(workDir).isNotNull();
@Test
public void should_set_relative_path_to_project_work_dir() throws Exception {
File initialProjectDir = temp.newFolder();
- runner.setProperty("sonar.task", "scan");
- runner.setProperty("sonar.working.directory", "relative/path");
- runner.setProperty("sonar.projectBaseDir", initialProjectDir.getAbsolutePath());
- dirs.init(runner);
-
+ p.setProperty("sonar.task", "scan");
+ p.setProperty(RunnerProperties.WORK_DIR, "relative/path");
+ p.setProperty(ScanProperties.PROJECT_BASEDIR, initialProjectDir.getAbsolutePath());
+ Dirs.init(p);
- File projectDir = new File(runner.property("sonar.projectBaseDir", null));
- File workDir = new File(runner.property("sonar.working.directory", null));
+ File projectDir = new File(p.getProperty(ScanProperties.PROJECT_BASEDIR, null));
+ File workDir = new File(p.getProperty(RunnerProperties.WORK_DIR, null));
assertThat(projectDir).isNotNull().isDirectory();
assertThat(projectDir.getCanonicalPath()).isEqualTo(initialProjectDir.getCanonicalPath());
*/
package org.sonar.runner.api;
+import org.junit.Before;
+import org.sonar.runner.batch.IsolatedLauncher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.ArgumentMatcher;
-import org.sonar.runner.impl.BatchLauncher;
+import org.sonar.runner.impl.IsolatedLauncherFactory;
import org.sonar.runner.impl.InternalProperties;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
-import java.util.List;
import java.util.Properties;
+import static org.mockito.Mockito.when;
+import static org.mockito.Matchers.any;
import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.mock;
assertThat(EmbeddedRunner.create()).isNotNull().isInstanceOf(EmbeddedRunner.class);
}
+ private IsolatedLauncherFactory batchLauncher;
+ private IsolatedLauncher launcher;
+ private EmbeddedRunner runner;
+
+ @Before
+ public void setUp() {
+ batchLauncher = mock(IsolatedLauncherFactory.class);
+ launcher = mock(IsolatedLauncher.class);
+ when(batchLauncher.createLauncher(any(Properties.class))).thenReturn(launcher);
+ runner = new EmbeddedRunner(batchLauncher);
+ }
+
@Test
public void test_app() {
EmbeddedRunner runner = EmbeddedRunner.create().setApp("Eclipse", "3.1");
@Test
public void should_set_unmasked_packages() {
EmbeddedRunner runner = EmbeddedRunner.create();
- assertThat(runner.property(InternalProperties.RUNNER_MASK_RULES, null)).isNull();
+ assertThat(runner.globalProperty(InternalProperties.RUNNER_MASK_RULES, null)).isNull();
runner = EmbeddedRunner.create().setUnmaskedPackages("org.apache.ant", "org.ant");
- assertThat(runner.property(InternalProperties.RUNNER_MASK_RULES, null)).isEqualTo("UNMASK|org.apache.ant.,UNMASK|org.ant.");
+ assertThat(runner.globalProperty(InternalProperties.RUNNER_MASK_RULES, null)).isEqualTo("UNMASK|org.apache.ant.,UNMASK|org.ant.");
}
@Test
public void should_set_mask_rules() {
EmbeddedRunner runner = EmbeddedRunner.create();
- assertThat(runner.property(InternalProperties.RUNNER_MASK_RULES, null)).isNull();
+ assertThat(runner.globalProperty(InternalProperties.RUNNER_MASK_RULES, null)).isNull();
runner = EmbeddedRunner.create()
.unmask("org.slf4j.Logger")
.mask("org.slf4j.")
.mask("ch.qos.logback.")
.unmask("");
- assertThat(runner.property(InternalProperties.RUNNER_MASK_RULES, null)).isEqualTo("UNMASK|org.slf4j.Logger,MASK|org.slf4j.,MASK|ch.qos.logback.,UNMASK|");
+ assertThat(runner.globalProperty(InternalProperties.RUNNER_MASK_RULES, null)).isEqualTo("UNMASK|org.slf4j.Logger,MASK|org.slf4j.,MASK|ch.qos.logback.,UNMASK|");
}
@Test
@Test
public void should_set_properties() {
EmbeddedRunner runner = EmbeddedRunner.create();
- runner.setProperty("sonar.projectKey", "foo");
- runner.addProperties(new Properties() {
+ runner.setGlobalProperty("sonar.projectKey", "foo");
+ runner.addGlobalProperties(new Properties() {
{
setProperty("sonar.login", "admin");
setProperty("sonar.password", "gniark");
}
});
- assertThat(runner.property("sonar.projectKey", null)).isEqualTo("foo");
- assertThat(runner.property("sonar.login", null)).isEqualTo("admin");
- assertThat(runner.property("sonar.password", null)).isEqualTo("gniark");
- assertThat(runner.property("not.set", "this_is_default")).isEqualTo("this_is_default");
+ assertThat(runner.globalProperty("sonar.projectKey", null)).isEqualTo("foo");
+ assertThat(runner.globalProperty("sonar.login", null)).isEqualTo("admin");
+ assertThat(runner.globalProperty("sonar.password", null)).isEqualTo("gniark");
+ assertThat(runner.globalProperty("not.set", "this_is_default")).isEqualTo("this_is_default");
}
@Test
public void should_launch_batch() {
- BatchLauncher batchLauncher = mock(BatchLauncher.class);
- EmbeddedRunner runner = new EmbeddedRunner(batchLauncher);
final FakeExtension fakeExtension = new FakeExtension();
runner.addExtensions(fakeExtension);
- runner.setProperty("sonar.projectKey", "foo");
- runner.execute();
+ runner.setGlobalProperty("sonar.projectKey", "foo");
+ runner.start();
+ runner.runAnalysis(new Properties());
+ runner.stop();
- verify(batchLauncher).execute(argThat(new ArgumentMatcher<Properties>() {
+ verify(batchLauncher).createLauncher(argThat(new ArgumentMatcher<Properties>() {
@Override
public boolean matches(Object o) {
return "foo".equals(((Properties) o).getProperty("sonar.projectKey"));
}
- }), argThat(new ArgumentMatcher<List<Object>>() {
+ }));
+
+ // it should have added a few properties to analysisProperties
+ final String[] mustHaveKeys = {"sonar.working.directory", "sonar.sourceEncoding", "sonar.projectBaseDir"};
+
+ verify(launcher).execute(argThat(new ArgumentMatcher<Properties>() {
+ @Override
+ public boolean matches(Object o) {
+ Properties m = (Properties) o;
+ for (String s : mustHaveKeys) {
+ if (!m.containsKey(s)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }));
+ }
+
+ @Test
+ public void should_launch_batch_analysisProperties() {
+ final FakeExtension fakeExtension = new FakeExtension();
+ runner.addExtensions(fakeExtension);
+ runner.setGlobalProperty("sonar.projectKey", "foo");
+ runner.start();
+
+ Properties analysisProperties = new Properties();
+ analysisProperties.put("sonar.projectKey", "value1");
+ runner.runAnalysis(analysisProperties);
+ runner.stop();
+
+ verify(batchLauncher).createLauncher(argThat(new ArgumentMatcher<Properties>() {
+ @Override
+ public boolean matches(Object o) {
+ return "foo".equals(((Properties) o).getProperty("sonar.projectKey"));
+ }
+ }));
+
+ verify(launcher).execute(argThat(new ArgumentMatcher<Properties>() {
@Override
public boolean matches(Object o) {
- return ((List) o).contains(fakeExtension);
+ return "value1".equals(((Properties) o).getProperty("sonar.projectKey"));
}
}));
}
@Test
public void should_launch_in_simulation_mode() throws IOException {
File dump = temp.newFile();
+ Properties p = new Properties();
- BatchLauncher batchLauncher = mock(BatchLauncher.class);
- EmbeddedRunner runner = new EmbeddedRunner(batchLauncher);
- runner.setProperty("sonar.projectKey", "foo");
- runner.setProperty("sonarRunner.dumpToFile", dump.getAbsolutePath());
- runner.execute();
+ p.setProperty("sonar.projectKey", "foo");
+ p.setProperty("sonarRunner.dumpToFile", dump.getAbsolutePath());
+ runner.start();
+ runner.runAnalysis(p);
+ runner.stop();
Properties props = new Properties();
props.load(new FileInputStream(dump));
*/
package org.sonar.runner.api;
+import org.mockito.Mockito;
+
+import org.mockito.ArgumentCaptor;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
JarExtractor jarExtractor = createMockExtractor();
ForkedRunner runner = new ForkedRunner(jarExtractor, mock(CommandExecutor.class), any(ProcessMonitor.class));
- runner.setProperty("sonar.dynamicAnalysis", "false");
- runner.setProperty("sonar.login", "admin");
+ runner.setGlobalProperty("sonar.dynamicAnalysis", "false");
+ runner.setGlobalProperty("sonar.login", "admin");
runner.addJvmArguments("-Xmx512m");
runner.addJvmEnvVariables(System.getenv());
runner.setJvmEnvVariable("SONAR_HOME", "/path/to/sonar");
- ForkedRunner.ForkCommand forkCommand = runner.createCommand();
+ ForkedRunner.ForkCommand forkCommand = runner.createCommand(runner.globalProperties());
Properties properties = new Properties();
properties.load(new FileInputStream(forkCommand.propertiesFile));
assertThat(properties.getProperty("-Xmx512m")).isNull();
assertThat(properties.getProperty("SONAR_HOME")).isNull();
}
-
+
+ @Test
+ public void should_merge_properties() throws IOException {
+ JarExtractor jarExtractor = createMockExtractor();
+ ForkedRunner runner = new ForkedRunner(jarExtractor, mock(CommandExecutor.class), null);
+
+ ForkedRunner spy = Mockito.spy(runner);
+ spy.setGlobalProperty("sonar.login", "admin");
+ spy.execute();
+ // generated analysis properties should have been added
+ ArgumentCaptor<Properties> properties = ArgumentCaptor.forClass(Properties.class);
+ verify(spy).writeProperties(properties.capture());
+ assertThat(properties.getValue().keySet()).contains("sonar.working.directory", "sonar.host.url", "sonar.sourceEncoding", "sonar.login");
+ }
+
@Test
public void test_java_command() throws IOException {
JarExtractor jarExtractor = mock(JarExtractor.class);
ForkedRunner runner = new ForkedRunner(jarExtractor, commandExecutor);
runner.setJavaExecutable("java");
- runner.setProperty("sonar.dynamicAnalysis", "false");
- runner.setProperty("sonar.login", "admin");
+ runner.setGlobalProperty("sonar.dynamicAnalysis", "false");
+ runner.setGlobalProperty("sonar.login", "admin");
runner.addJvmArguments("-Xmx512m");
runner.addJvmEnvVariables(System.getenv());
runner.setJvmEnvVariable("SONAR_HOME", "/path/to/sonar");
*/
package org.sonar.runner.api;
-class SimpleRunner extends Runner<SimpleRunner> {
+import java.util.Properties;
+
+class SimpleRunner extends Runner {
+ @Override
+ protected void doExecute(Properties props) {
+ }
+
+ @Override
+ protected void doStart() {
+
+ }
+
@Override
- protected void doExecute() {
+ protected void doStop() {
+
}
}
import org.junit.Test;
import java.nio.charset.Charset;
+import java.util.Properties;
import static org.fest.assertions.Assertions.assertThat;
public class SourceEncodingTest {
- SourceEncoding encoding = new SourceEncoding();
- Runner<?> runner = new SimpleRunner();
+ Properties p = new Properties();
@Test
public void should_set_default_platform_encoding() throws Exception {
- runner.setProperty("sonar.task", "scan");
- encoding.init(runner);
- assertThat(runner.property("sonar.sourceEncoding", null)).isEqualTo(Charset.defaultCharset().name());
+ p.setProperty("sonar.task", "scan");
+ SourceEncoding.init(p);
+ assertThat(p.getProperty("sonar.sourceEncoding", null)).isEqualTo(Charset.defaultCharset().name());
}
@Test
public void should_use_parameterized_encoding() throws Exception {
- runner.setProperty("sonar.task", "scan");
- runner.setProperty("sonar.sourceEncoding", "THE_ISO_1234");
- encoding.init(runner);
- assertThat(runner.property("sonar.sourceEncoding", null)).isEqualTo("THE_ISO_1234");
+ p.setProperty("sonar.task", "scan");
+ p.setProperty("sonar.sourceEncoding", "THE_ISO_1234");
+ SourceEncoding.init(p);
+ assertThat(p.getProperty("sonar.sourceEncoding", null)).isEqualTo("THE_ISO_1234");
}
@Test
public void should_not_init_encoding_if_not_project_task() throws Exception {
- runner.setProperty("sonar.task", "views");
- encoding.init(runner);
- assertThat(runner.property("sonar.sourceEncoding", null)).isNull();
+ p.setProperty("sonar.task", "views");
+ SourceEncoding.init(p);
+ assertThat(p.getProperty("sonar.sourceEncoding", null)).isNull();
}
}
--- /dev/null
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.codehaus.sonar.runner</groupId>
+ <artifactId>sonar-runner</artifactId>
+ <version>2.5-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>sonar-runner-batch-interface</artifactId>
+ <name>SonarQube Runner - Batch API</name>
+</project>
--- /dev/null
+/*
+ * SonarQube Runner - Batch API
+ * Copyright (C) 2011 SonarSource
+ * dev@sonar.codehaus.org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.runner.batch;
+
+import java.util.List;
+import java.util.Properties;
+
+public interface IsolatedLauncher {
+ void start(Properties properties, List<Object> extensions);
+ void stop();
+ void execute(Properties properties);
+}
--- /dev/null
+sonar.projectKey=sample
+sonar.projectName=Sample
+sonar.projectVersion=1.0-SNAPSHOT
+sonar.projectDescription=Sample description
+sonar.sources=src
<name>SonarQube Runner - Batch</name>
<properties>
- <sonarBatchVersion>3.7</sonarBatchVersion>
+ <sonarBatchVersion>4.4</sonarBatchVersion>
</properties>
<dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>sonar-runner-batch-interface</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
--- /dev/null
+/*
+ * SonarQube Runner - Batch
+ * 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.batch;
+
+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 java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.slf4j.LoggerFactory;
+import org.sonar.api.utils.SonarException;
+import org.sonar.batch.bootstrapper.Batch;
+import org.sonar.batch.bootstrapper.EnvironmentInformation;
+
+/**
+ * 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.
+ */
+public class BatchIsolatedLauncher implements IsolatedLauncher {
+
+ private static final String WARN = "WARN";
+ private static final String DEBUG = "DEBUG";
+ private static final String FALSE = "false";
+
+ private Batch batch = null;
+
+ @Override
+ public void start(Properties globalProperties, List<Object> extensions) {
+ batch = createBatch(globalProperties, extensions);
+ batch.start();
+ }
+
+ @Override
+ public void stop() {
+ batch.stop();
+ }
+
+ @Override
+ public void execute(Properties properties) {
+ batch.executeTask((Map) properties);
+ }
+
+ Batch createBatch(Properties properties, List<Object> extensions) {
+ initLogging(properties);
+ EnvironmentInformation env = new EnvironmentInformation(properties.getProperty("sonarRunner.app"), properties.getProperty("sonarRunner.appVersion"));
+ return Batch.builder()
+ .setEnvironment(env)
+ .addComponents(extensions)
+ .setBootstrapProperties((Map) properties)
+ .build();
+ }
+
+ private void initLogging(Properties props) {
+ LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
+ JoranConfigurator jc = new JoranConfigurator();
+ jc.setContext(context);
+ context.reset();
+ try (InputStream input = Batch.class.getResourceAsStream("/org/sonar/batch/logback.xml")) {
+ System.setProperty("ROOT_LOGGER_LEVEL", isDebug(props) ? DEBUG : "INFO");
+ context.putProperty("SQL_LOGGER_LEVEL", getSqlLevel(props));
+ context.putProperty("SQL_RESULTS_LOGGER_LEVEL", getSqlResultsLevel(props));
+ jc.doConfigure(input);
+ } catch (JoranException e) {
+ throw new SonarException("can not initialize logging", e);
+ } catch (IOException e1) {
+ throw new SonarException("couldn't close resource", e1);
+ }
+ }
+
+ @VisibleForTesting
+ protected boolean isDebug(Properties props) {
+ return Boolean.parseBoolean(props.getProperty("sonar.verbose", FALSE));
+ }
+
+ @VisibleForTesting
+ protected static String getSqlLevel(Properties props) {
+ boolean showSql = "true".equals(props.getProperty("sonar.showSql", FALSE));
+ return showSql ? DEBUG : WARN;
+ }
+
+ @VisibleForTesting
+ protected static String getSqlResultsLevel(Properties props) {
+ boolean showSql = "true".equals(props.getProperty("sonar.showSqlResults", FALSE));
+ return showSql ? DEBUG : WARN;
+ }
+}
+++ /dev/null
-/*
- * SonarQube Runner - Batch
- * 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.batch;
-
-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 java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-
-import org.slf4j.LoggerFactory;
-import org.sonar.api.utils.SonarException;
-import org.sonar.batch.bootstrapper.Batch;
-import org.sonar.batch.bootstrapper.EnvironmentInformation;
-
-/**
- * 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.
- */
-public class IsolatedLauncher {
-
- private static final String WARN = "WARN";
- private static final String DEBUG = "DEBUG";
- private static final String FALSE = "false";
-
- public void execute(Properties properties, List<Object> extensions) {
- createBatch(properties, extensions).execute();
- }
-
- Batch createBatch(Properties properties, List<Object> extensions) {
- initLogging(properties);
- EnvironmentInformation env = new EnvironmentInformation(properties.getProperty("sonarRunner.app"), properties.getProperty("sonarRunner.appVersion"));
- return Batch.builder()
- .setEnvironment(env)
- .addComponents(extensions)
- .setBootstrapProperties((Map) properties)
- .build();
- }
-
- private void initLogging(Properties props) {
- LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
- JoranConfigurator jc = new JoranConfigurator();
- jc.setContext(context);
- context.reset();
- try (InputStream input = Batch.class.getResourceAsStream("/org/sonar/batch/logback.xml")) {
- System.setProperty("ROOT_LOGGER_LEVEL", isDebug(props) ? DEBUG : "INFO");
- context.putProperty("SQL_LOGGER_LEVEL", getSqlLevel(props));
- context.putProperty("SQL_RESULTS_LOGGER_LEVEL", getSqlResultsLevel(props));
- jc.doConfigure(input);
- } catch (JoranException e) {
- throw new SonarException("can not initialize logging", e);
- } catch (IOException e1) {
- throw new SonarException("couldn't close resource", e1);
- }
- }
-
- @VisibleForTesting
- protected boolean isDebug(Properties props) {
- return Boolean.parseBoolean(props.getProperty("sonar.verbose", FALSE));
- }
-
- @VisibleForTesting
- protected static String getSqlLevel(Properties props) {
- boolean showSql = "true".equals(props.getProperty("sonar.showSql", FALSE));
- return showSql ? DEBUG : WARN;
- }
-
- @VisibleForTesting
- protected static String getSqlResultsLevel(Properties props) {
- boolean showSql = "true".equals(props.getProperty("sonar.showSqlResults", FALSE));
- return showSql ? DEBUG : WARN;
- }
-}
public class IsolatedLauncherTest {
Properties props = new Properties();
- IsolatedLauncher launcher = new IsolatedLauncher();
+ BatchIsolatedLauncher launcher = new BatchIsolatedLauncher();
@Test
public void should_create_batch() {
@Test
public void testGetSqlLevel() throws Exception {
- assertThat(IsolatedLauncher.getSqlLevel(props)).isEqualTo("WARN");
+ assertThat(BatchIsolatedLauncher.getSqlLevel(props)).isEqualTo("WARN");
props.setProperty("sonar.showSql", "true");
- assertThat(IsolatedLauncher.getSqlLevel(props)).isEqualTo("DEBUG");
+ assertThat(BatchIsolatedLauncher.getSqlLevel(props)).isEqualTo("DEBUG");
props.setProperty("sonar.showSql", "false");
- assertThat(IsolatedLauncher.getSqlLevel(props)).isEqualTo("WARN");
+ assertThat(BatchIsolatedLauncher.getSqlLevel(props)).isEqualTo("WARN");
}
@Test
public void testGetSqlResultsLevel() throws Exception {
- assertThat(IsolatedLauncher.getSqlResultsLevel(props)).isEqualTo("WARN");
+ assertThat(BatchIsolatedLauncher.getSqlResultsLevel(props)).isEqualTo("WARN");
props.setProperty("sonar.showSqlResults", "true");
- assertThat(IsolatedLauncher.getSqlResultsLevel(props)).isEqualTo("DEBUG");
+ assertThat(BatchIsolatedLauncher.getSqlResultsLevel(props)).isEqualTo("DEBUG");
props.setProperty("sonar.showSqlResults", "false");
- assertThat(IsolatedLauncher.getSqlResultsLevel(props)).isEqualTo("WARN");
+ assertThat(BatchIsolatedLauncher.getSqlResultsLevel(props)).isEqualTo("WARN");
}
@Test
private boolean debugMode = false;
private boolean displayVersionOnly = false;
private boolean displayStackTrace = false;
+ private boolean interactive = false;
private Properties props = new Properties();
+ private Exit exit;
+
+ public Cli(Exit exit) {
+ this.exit = exit;
+ }
boolean isDebugMode() {
return debugMode;
return displayStackTrace;
}
+ boolean isInteractive() {
+ return interactive;
+ }
+
Properties properties() {
return props;
}
arg = arg.substring(2);
appendPropertyTo(arg, props);
+ } else if ("-i".equals(arg) || "--interactive".equals(arg)) {
+ interactive = true;
+
} else {
printError("Unrecognized option: " + arg);
}
}
+
return this;
}
+ public void verify() {
+ if ("fork".equals(props.getProperty("sonarRunner.mode")) && isInteractive()) {
+ printError("Cannot run interactively in fork mode.");
+ }
+ }
+
private void reset() {
props.clear();
debugMode = false;
displayVersionOnly = false;
}
- private void appendPropertyTo(String arg, Properties props) {
+ private static void appendPropertyTo(String arg, Properties props) {
final String key, value;
int j = arg.indexOf('=');
if (j == -1) {
Logs.info(" -h,--help Display help information");
Logs.info(" -v,--version Display version information");
Logs.info(" -X,--debug Produce execution debug output");
- System.exit(Exit.SUCCESS);
+ Logs.info(" -i,--interactive Run interactively - not valid in fork mode");
+ exit.exit(Exit.SUCCESS);
}
}
return new Properties();
}
- private void initRootProjectBaseDir(Properties cliProps, Properties rootProps) {
+ private static void initRootProjectBaseDir(Properties cliProps, Properties rootProps) {
if (!cliProps.containsKey(PROPERTY_PROJECT_BASEDIR)) {
String baseDir = cliProps.getProperty(PROJECT_HOME);
rootProps.put(PROPERTY_PROJECT_BASEDIR, baseDir);
}
- private void merge(Properties projectProps, String prefix, String module, Properties moduleProps) {
+ private static void merge(Properties projectProps, String prefix, String module, Properties moduleProps) {
for (Map.Entry<Object, Object> entry : moduleProps.entrySet()) {
projectProps.put(prefix + module + "." + entry.getKey(), entry.getValue());
}
return moduleProps;
}
- private File locatePropertiesFile(Properties props, String homeKey, String relativePathFromHome, String settingsKey) {
+ private static File locatePropertiesFile(Properties props, String homeKey, String relativePathFromHome, String settingsKey) {
File settingsFile = null;
String runnerHome = props.getProperty(homeKey, "");
if (!"".equals(runnerHome)) {
}
}
- private void tryToFindAndLoadPropsFile(File baseDir, Properties moduleProps, String moduleId) {
+ private static void tryToFindAndLoadPropsFile(File baseDir, Properties moduleProps, String moduleId) {
File propertyFile = new File(baseDir, SONAR_PROJECT_PROPERTIES_FILENAME);
if (propertyFile.isFile()) {
Properties propsFromFile = toProperties(propertyFile);
*/
package org.sonar.runner;
+import org.sonar.runner.api.Runner;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.Properties;
+
import org.sonar.runner.impl.Logs;
/**
private final Cli cli;
private final Conf conf;
private final RunnerFactory runnerFactory;
+ private Runner<?> runner;
+ private BufferedReader inputReader;
Main(Exit exit, Cli cli, Conf conf, RunnerFactory runnerFactory) {
this.exit = exit;
}
public static void main(String[] args) {
- Cli cli = new Cli().parse(args);
- Main main = new Main(new Exit(), cli, new Conf(cli), new RunnerFactory());
+ Exit exit = new Exit();
+ Cli cli = new Cli(exit).parse(args);
+ cli.verify();
+ Main main = new Main(exit, cli, new Conf(cli), new RunnerFactory());
main.execute();
}
void execute() {
- SystemInfo.print();
- if (!cli.isDisplayVersionOnly()) {
- int status = executeTask();
- exit.exit(status);
- }
- }
-
- private int executeTask() {
Stats stats = new Stats().start();
+
try {
- if (cli.isDisplayStackTrace()) {
- Logs.info("Error stacktraces are turned on.");
- }
- runnerFactory.create(conf.properties()).execute();
+ Properties p = conf.properties();
+ init(p);
+ runner.start();
+
+ runAnalysis(stats, p);
+ if(cli.isInteractive()) {
+ while (waitForUser()) {
+ stats = new Stats().start();
+ runAnalysis(stats, p);
+ }
+ }
} catch (Exception e) {
displayExecutionResult(stats, "FAILURE");
showError("Error during Sonar runner execution", e, cli.isDisplayStackTrace());
- return Exit.ERROR;
+ exit.exit(Exit.ERROR);
+ }
+
+ runner.stop();
+ exit.exit(Exit.SUCCESS);
+ }
+
+ private void init(Properties p) throws IOException {
+ SystemInfo.print();
+ if (cli.isDisplayVersionOnly()) {
+ exit.exit(Exit.SUCCESS);
}
+
+ if (cli.isDisplayStackTrace()) {
+ Logs.info("Error stacktraces are turned on.");
+ }
+
+ runner = runnerFactory.create(p);
+ }
+
+ private void runAnalysis(Stats stats, Properties p) {
+ runner.runAnalysis(p);
displayExecutionResult(stats, "SUCCESS");
- return Exit.SUCCESS;
}
- private void displayExecutionResult(Stats stats, String resultMsg) {
+ private boolean waitForUser() throws IOException {
+ if (inputReader == null) {
+ inputReader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
+ }
+ Logs.info("<Press enter to restart analysis>");
+
+ return inputReader.readLine() != null;
+ }
+
+ private static void displayExecutionResult(Stats stats, String resultMsg) {
Logs.info("------------------------------------------------------------------------");
Logs.info("EXECUTION " + resultMsg);
Logs.info("------------------------------------------------------------------------");
}
}
- private void suggestDebugMode() {
+ private static void suggestDebugMode() {
Logs.error("Re-run SonarQube Runner using the -X switch to enable full debug logging.");
}
class RunnerFactory {
- Runner<?> create(Properties props) {
+ Runner create(Properties props) {
Runner<?> runner;
if ("fork".equals(props.getProperty("sonarRunner.mode"))) {
runner = ForkedRunner.create();
String jvmArgs = props.getProperty("sonarRunner.fork.jvmArgs", "");
if (!"".equals(jvmArgs)) {
- ((ForkedRunner)runner).addJvmArguments(jvmArgs.split(" "));
+ ((ForkedRunner) runner).addJvmArguments(jvmArgs.split(" "));
}
} else {
runner = EmbeddedRunner.create();
}
- runner.addProperties(props);
+ runner.addGlobalProperties(props);
return runner;
}
}
*/
package org.sonar.runner;
-import org.junit.Test;
+import static org.mockito.Mockito.mock;
+
+import static org.mockito.Mockito.verify;
+import org.junit.Test;
import static org.fest.assertions.Assertions.assertThat;
public class CliTest {
-
- Cli cli = new Cli();
+ Exit exit = mock(Exit.class);
+ Cli cli = new Cli(exit);
@Test
public void should_parse_empty_arguments() {
assertThat(cli.properties().get("hello")).isEqualTo("world");
assertThat(cli.properties().get("boolean")).isEqualTo("true");
}
+
+ @Test
+ public void dont_allow_interactive_fork() {
+ cli.parse(new String[]{"-i", "-DsonarRunner.mode=fork"});
+ cli.verify();
+ verify(exit).exit(Exit.SUCCESS);
+ }
@Test
public void should_parse_optional_task() {
*/
package org.sonar.runner;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Mock;
+import org.junit.Before;
+import org.mockito.Mockito;
+import org.mockito.InOrder;
import org.junit.Test;
import org.sonar.runner.api.Runner;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
import java.util.Properties;
+import static org.mockito.Mockito.times;
import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.RETURNS_MOCKS;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
public class MainTest {
- Exit exit = mock(Exit.class);
- Cli cli = mock(Cli.class);
- Conf conf = mock(Conf.class);
- RunnerFactory runnerFactory = mock(RunnerFactory.class, RETURNS_MOCKS);
+ @Mock
+ private Exit exit;
+ @Mock
+ private Cli cli;
+ @Mock
+ private Conf conf;
+ @Mock
+ private Properties properties;
+ @Mock
+ private RunnerFactory runnerFactory;
+ @Mock
+ private Runner<?> runner;
+
+ @Before
+ public void setUp() throws IOException {
+ MockitoAnnotations.initMocks(this);
+ when(runnerFactory.create(any(Properties.class))).thenReturn(runner);
+ when(conf.properties()).thenReturn(properties);
+
+ }
@Test
public void should_execute_runner() {
Main main = new Main(exit, cli, conf, runnerFactory);
main.execute();
- verify(exit).exit(0);
+ verify(exit).exit(Exit.SUCCESS);
+ verify(runnerFactory).create(properties);
+
+ verify(runner, times(1)).start();
+ verify(runner, times(1)).runAnalysis(properties);
+ verify(runner, times(1)).stop();
}
@Test
public void should_fail_on_error() {
- Runner runner = mock(Runner.class);
- doThrow(new IllegalStateException("Error")).when(runner).execute();
+ Runner<?> runner = mock(Runner.class);
+ doThrow(new IllegalStateException("Error")).when(runner).runAnalysis(any(Properties.class));
when(runnerFactory.create(any(Properties.class))).thenReturn(runner);
Main main = new Main(exit, cli, conf, runnerFactory);
main.execute();
- verify(exit).exit(1);
+ verify(exit).exit(Exit.ERROR);
}
@Test
- public void should_only_display_version() {
+ public void should_only_display_version() throws IOException {
+
+ Properties p = new Properties();
when(cli.isDisplayVersionOnly()).thenReturn(true);
+ when(conf.properties()).thenReturn(p);
+
+ Main main = new Main(exit, cli, conf, runnerFactory);
+ main.execute();
+
+ InOrder inOrder = Mockito.inOrder(exit, runnerFactory);
+
+ inOrder.verify(exit, times(1)).exit(Exit.SUCCESS);
+ inOrder.verify(runnerFactory, times(1)).create(p);
+ inOrder.verify(exit, times(1)).exit(Exit.SUCCESS);
+ }
+
+ @Test(timeout=30000)
+ public void test_interactive_mode() throws IOException {
+ String inputStr = "qwe" + System.lineSeparator() + "qwe" + System.lineSeparator();
+ InputStream input = new ByteArrayInputStream(inputStr.getBytes(StandardCharsets.UTF_8));
+ System.setIn(input);
+ input.close();
+
+ when(cli.isInteractive()).thenReturn(true);
+ when(cli.isDebugMode()).thenReturn(true);
+ when(cli.isDisplayStackTrace()).thenReturn(true);
+
Main main = new Main(exit, cli, conf, runnerFactory);
main.execute();
- verifyZeroInteractions(runnerFactory);
+
+ verify(runner, times(1)).start();
+ verify(runner, times(3)).runAnalysis(any(Properties.class));
+ verify(runner, times(1)).stop();
}
}
Runner<?> runner = new RunnerFactory().create(props);
assertThat(runner).isInstanceOf(EmbeddedRunner.class);
- assertThat(runner.properties().get("foo")).isEqualTo("bar");
+ assertThat(runner.globalProperties().get("foo")).isEqualTo("bar");
}
@Test
Runner<?> runner = new RunnerFactory().create(props);
assertThat(runner).isInstanceOf(ForkedRunner.class);
- assertThat(runner.properties().get("foo")).isEqualTo("bar");
- assertThat(((ForkedRunner)runner).jvmArguments()).contains("-Xms128m", "-Xmx512m");
+ assertThat(runner.globalProperties().get("foo")).isEqualTo("bar");
+ assertThat(((ForkedRunner) runner).jvmArguments()).contains("-Xms128m", "-Xmx512m");
}
@Test
Runner<?> runner = new RunnerFactory().create(props);
assertThat(runner).isInstanceOf(ForkedRunner.class);
- assertThat(runner.properties().get("foo")).isEqualTo("bar");
- assertThat(((ForkedRunner)runner).jvmArguments()).isEmpty();
- }
+ assertThat(runner.globalProperties().get("foo")).isEqualTo("bar");
+
+ assertThat(runner).isInstanceOf(ForkedRunner.class);
+ assertThat(runner.globalProperties().get("foo")).isEqualTo("bar");
+ assertThat(((ForkedRunner) runner).jvmArguments()).isEmpty();
+ }
}
*/
package org.sonar.runner;
-import org.junit.Test;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import org.junit.Test;
import static org.fest.assertions.Assertions.assertThat;
public class StatsTest {
@Test
- public void shouldPrintStats() {
+ public void shouldPrintStats() throws UnsupportedEncodingException {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ System.setOut(new PrintStream(output));
new Stats().start().stop();
- //TODO mock Logs
+
+ String out = output.toString("UTF-8");
+ String[] lines = out.split("\n");
+
+ assertThat(lines).hasSize(2);
+
+ assertThat(lines[0]).startsWith("Total time: ");
+ assertThat(lines[1]).startsWith("Final Memory: ");
}
@Test
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>sonar-runner-batch-interface</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<!-- unit tests -->
<dependency>
<artifactId>mockito-all</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>1.7.1</version>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty</artifactId>
+++ /dev/null
-/*
- * SonarQube Runner - Implementation
- * 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.impl;
-
-import org.sonar.home.cache.PersistentCacheBuilder;
-
-import org.sonar.home.cache.PersistentCache;
-
-import java.io.File;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.util.List;
-import java.util.Properties;
-
-public class BatchLauncher {
- final String isolatedLauncherClass;
- private final TempCleaning tempCleaning;
-
- /**
- * For unit tests
- */
- BatchLauncher(String isolatedLauncherClass, TempCleaning tempCleaning) {
- this.isolatedLauncherClass = isolatedLauncherClass;
- this.tempCleaning = tempCleaning;
- }
-
- public BatchLauncher() {
- this("org.sonar.runner.batch.IsolatedLauncher", new TempCleaning());
- }
-
- public void execute(Properties props, List<Object> extensions) {
- ServerConnection serverConnection = ServerConnection.create(props, getCache(props));
- JarDownloader jarDownloader = new JarDownloader(serverConnection);
- doExecute(jarDownloader, props, extensions);
- }
-
- private static PersistentCache getCache(Properties props) {
- PersistentCacheBuilder builder = new PersistentCacheBuilder();
-
- if (!"true".equals(props.getProperty("sonar.enableHttpCache"))) {
- builder.forceUpdate(true);
- }
-
- return builder.build();
- }
-
- private static String[][] getMaskRules(final Properties props) {
- String maskRulesProp = props.getProperty(InternalProperties.RUNNER_MASK_RULES, null);
- String[] maskRulesConcat = maskRulesProp != null ? maskRulesProp.split(",") : new String[0];
- String[][] maskRules = new String[maskRulesConcat.length][2];
- for (int i = 0; i < maskRulesConcat.length; i++) {
- String[] splitted = maskRulesConcat[i].split("\\|");
- maskRules[i][0] = splitted[0];
- maskRules[i][1] = splitted.length > 1 ? splitted[1] : "";
- }
- return maskRules;
- }
-
- /**
- * @return the {@link org.sonar.runner.batch.IsolatedLauncher} instance for unit tests
- */
- Object doExecute(final JarDownloader jarDownloader, final Properties props, final List<Object> extensions) {
- return AccessController.doPrivileged(new PrivilegedAction<Object>() {
- public Object run() {
- List<File> jarFiles = jarDownloader.download();
- Logs.debug("Create isolated classloader...");
- String[][] maskRules = getMaskRules(props);
- IsolatedClassloader classloader = new IsolatedClassloader(getClass().getClassLoader(), maskRules);
- classloader.addFiles(jarFiles);
- Object launcher = delegateExecution(classloader, props, extensions);
- tempCleaning.clean();
- return launcher;
- }
-
- private Object delegateExecution(IsolatedClassloader classloader, Properties properties, List<Object> extensions) {
- ClassLoader initialContextClassLoader = Thread.currentThread().getContextClassLoader();
- try {
- Thread.currentThread().setContextClassLoader(classloader);
- Class<?> launcherClass = classloader.loadClass(isolatedLauncherClass);
- Method executeMethod = launcherClass.getMethod("execute", Properties.class, List.class);
- Object launcher = launcherClass.newInstance();
- Logs.debug("Start IsolatedLauncher");
- executeMethod.invoke(launcher, properties, extensions);
- return launcher;
- } catch (InvocationTargetException e) {
- // Unwrap original exception
- throw new RunnerException("Unable to execute Sonar", e.getTargetException());
- } catch (Exception e) {
- // Catch all other exceptions, which relates to reflection
- throw new RunnerException("Unable to execute SonarQube", e);
- } finally {
- Thread.currentThread().setContextClassLoader(initialContextClassLoader);
- }
- }
- });
- }
-
-}
*/
package org.sonar.runner.impl;
+import org.sonar.runner.batch.IsolatedLauncher;
+
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.Properties;
public class BatchLauncherMain {
- private final BatchLauncher launcher;
+ private final IsolatedLauncherFactory launcherFactory;
- BatchLauncherMain(BatchLauncher l) {
- this.launcher = l;
+ BatchLauncherMain(IsolatedLauncherFactory factory) {
+ this.launcherFactory = factory;
}
void execute(String[] args) throws IOException {
throw new IllegalArgumentException("Missing path to properties file");
}
Properties props = loadProperties(args[0]);
- launcher.execute(props, Collections.emptyList());
+ IsolatedLauncher launcher = launcherFactory.createLauncher(props);
+ launcher.start(props, Collections.emptyList());
+ launcher.execute(props);
+ launcher.stop();
}
- private Properties loadProperties(String arg) throws IOException {
+ private static Properties loadProperties(String arg) throws IOException {
Properties props = new Properties();
try (FileInputStream input = new FileInputStream(arg)) {
props.load(input);
}
public static void main(String[] args) throws IOException {
- new BatchLauncherMain(new BatchLauncher()).execute(args);
+ new BatchLauncherMain(new IsolatedLauncherFactory()).execute(args);
}
}
--- /dev/null
+/*
+ * SonarQube Runner - Implementation
+ * 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.impl;
+
+import org.sonar.runner.batch.IsolatedLauncher;
+import org.sonar.home.cache.PersistentCacheBuilder;
+import org.sonar.home.cache.PersistentCache;
+
+import java.io.File;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.List;
+import java.util.Properties;
+
+public class IsolatedLauncherFactory {
+ static final String ISOLATED_LAUNCHER_IMPL = "org.sonar.runner.batch.BatchIsolatedLauncher";
+ private final TempCleaning tempCleaning;
+ private final String launcherImplClassName;
+
+ /**
+ * For unit tests
+ */
+ IsolatedLauncherFactory(String isolatedLauncherClassName, TempCleaning tempCleaning) {
+ this.tempCleaning = tempCleaning;
+ this.launcherImplClassName = isolatedLauncherClassName;
+ }
+
+ public IsolatedLauncherFactory() {
+ this(ISOLATED_LAUNCHER_IMPL, new TempCleaning());
+ }
+
+ private static PersistentCache getCache(Properties props) {
+ PersistentCacheBuilder builder = new PersistentCacheBuilder();
+
+ if (!"true".equals(props.getProperty("sonar.enableHttpCache"))) {
+ builder.forceUpdate(true);
+ }
+
+ return builder.build();
+ }
+
+ static String[][] getMaskRules(final Properties props) {
+ String maskRulesProp = props.getProperty(InternalProperties.RUNNER_MASK_RULES, null);
+ String[] maskRulesConcat = maskRulesProp != null ? maskRulesProp.split(",") : (new String[0]);
+ String[][] maskRules = new String[maskRulesConcat.length][2];
+ for (int i = 0; i < maskRulesConcat.length; i++) {
+ String[] splitted = maskRulesConcat[i].split("\\|");
+ maskRules[i][0] = splitted[0];
+ maskRules[i][1] = splitted.length > 1 ? splitted[1] : "";
+ }
+ return maskRules;
+ }
+
+ private static void addIsolatedLauncherMaskRule(Properties props) {
+ String unmask = "UNMASK|org.sonar.runner.batch.IsolatedLauncher";
+ String currentRules = (String) props.get(InternalProperties.RUNNER_MASK_RULES);
+
+ if (currentRules == null) {
+ props.put(InternalProperties.RUNNER_MASK_RULES, unmask);
+ } else {
+ props.put(InternalProperties.RUNNER_MASK_RULES, currentRules + "," + unmask);
+ }
+ }
+
+ private ClassLoader createClassLoader(List<File> jarFiles, final Properties props) {
+ Properties copy = new Properties();
+ copy.putAll(props);
+ addIsolatedLauncherMaskRule(copy);
+ String[][] maskRules = getMaskRules(copy);
+ IsolatedClassloader classloader = new IsolatedClassloader(getClass().getClassLoader(), maskRules);
+ classloader.addFiles(jarFiles);
+
+ return classloader;
+ }
+
+ public IsolatedLauncher createLauncher(Properties props) {
+ ServerConnection serverConnection = ServerConnection.create(props, getCache(props));
+ JarDownloader jarDownloader = new JarDownloader(serverConnection);
+
+ return createLauncher(jarDownloader, props);
+ }
+
+ IsolatedLauncher createLauncher(final JarDownloader jarDownloader, final Properties props) {
+ return AccessController.doPrivileged(new PrivilegedAction<IsolatedLauncher>() {
+ @Override
+ public IsolatedLauncher run() {
+ try {
+ List<File> jarFiles = jarDownloader.download();
+ Logs.debug("Create isolated classloader...");
+ ClassLoader cl = createClassLoader(jarFiles, props);
+ IsolatedLauncher objProxy = IsolatedLauncherProxy.create(cl, IsolatedLauncher.class, launcherImplClassName);
+ tempCleaning.clean();
+
+ return objProxy;
+ } catch (Exception e) {
+ // Catch all other exceptions, which relates to reflection
+ throw new RunnerException("Unable to execute SonarQube", e);
+ }
+ }
+ });
+ }
+}
--- /dev/null
+/*
+ * SonarQube Runner - Implementation
+ * 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.impl;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.UndeclaredThrowableException;
+
+public class IsolatedLauncherProxy implements InvocationHandler {
+ private Object proxied = null;
+ private ClassLoader cl = null;
+
+ private IsolatedLauncherProxy(ClassLoader cl, Object proxied) {
+ this.cl = cl;
+ this.proxied = proxied;
+ }
+
+ public static <T> T create(ClassLoader cl, Class<T> interfaceClass, String proxiedClassName) throws ReflectiveOperationException {
+ Object proxied = createProxiedObject(cl, proxiedClassName);
+ // interfaceClass needs to be loaded with a parent ClassLoader (common to both ClassLoaders)
+ // In addition, Proxy.newProxyInstance checks if the target ClassLoader sees the same class as the one given
+ Class<?> loadedInterfaceClass = cl.loadClass(interfaceClass.getName());
+ return (T) create(cl, proxied, loadedInterfaceClass);
+ }
+
+ public static <T> T create(ClassLoader cl, Object proxied, Class<T> interfaceClass) {
+ Class<?>[] c = {interfaceClass};
+ return (T) Proxy.newProxyInstance(cl, c, new IsolatedLauncherProxy(cl, proxied));
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ ClassLoader initialContextClassLoader = Thread.currentThread().getContextClassLoader();
+
+ try {
+ Thread.currentThread().setContextClassLoader(cl);
+ Logs.debug("Execution " + method.getName());
+ return method.invoke(proxied, args);
+ } catch (UndeclaredThrowableException | InvocationTargetException e) {
+ throw unwrapException(e);
+ } finally {
+ Thread.currentThread().setContextClassLoader(initialContextClassLoader);
+ }
+ }
+
+ private static Throwable unwrapException(Throwable e) {
+ Throwable cause = e;
+
+ while (cause.getCause() != null) {
+ if (cause instanceof UndeclaredThrowableException || cause instanceof InvocationTargetException) {
+ cause = cause.getCause();
+ } else {
+ break;
+ }
+ }
+ return cause;
+ }
+
+ private static Object createProxiedObject(ClassLoader cl, String proxiedClassName) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+ Class<?> proxiedClass = cl.loadClass(proxiedClassName);
+ return proxiedClass.newInstance();
+ }
+}
this.connection = conn;
}
+ @Override
public void download(String filename, File toFile) throws IOException {
connection.download(BATCH_PATH + filename, toFile);
}
this.isModePreview = preview;
}
- private String removeEndSlash(String url) {
+ private static String removeEndSlash(String url) {
if (url == null) {
return null;
}
/**
* The file sonar-runner-batch.jar is locked by the classloader on Windows and can't be dropped at the end of the execution.
- * See {@link BatchLauncher}
+ * See {@link IsolatedLauncherFactory}
*/
class TempCleaning {
static final int ONE_DAY_IN_MILLISECONDS = 24 * 60 * 60 * 1000;
*/
package org.sonar.runner.impl;
+import org.sonar.runner.batch.IsolatedLauncher;
+
import org.junit.Test;
import org.mockito.ArgumentMatcher;
import java.io.File;
import java.net.URL;
-import java.util.List;
import java.util.Properties;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.when;
import static org.fest.assertions.Fail.fail;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.mock;
public class BatchLauncherMainTest {
- BatchLauncher launcher = mock(BatchLauncher.class);
+ IsolatedLauncherFactory launcherFactory = mock(IsolatedLauncherFactory.class);
@Test
public void should_load_properties_and_execute() throws Exception {
URL url = getClass().getResource("/org/sonar/runner/impl/BatchLauncherMainTest/props.properties");
- BatchLauncherMain main = new BatchLauncherMain(launcher);
- main.execute(new String[]{new File(url.toURI()).getAbsolutePath()});
+ when(launcherFactory.createLauncher(any(Properties.class))).thenReturn(mock(IsolatedLauncher.class));
+
+ BatchLauncherMain main = new BatchLauncherMain(launcherFactory);
+ new File(url.toURI()).getAbsolutePath();
+ main.execute(new String[] {new File(url.toURI()).getAbsolutePath()});
- verify(launcher).execute(argThat(new ArgumentMatcher<Properties>() {
+ verify(launcherFactory).createLauncher(argThat(new ArgumentMatcher<Properties>() {
@Override
public boolean matches(Object o) {
return ((Properties) o).get("sonar.login").equals("foo");
}
- }), argThat(new ArgumentMatcher<List<Object>>() {
- @Override
- public boolean matches(Object o) {
- return ((List<?>) o).isEmpty();
- }
}));
}
@Test
public void should_fail_if_missing_path_to_properties_file() {
try {
- BatchLauncherMain main = new BatchLauncherMain(launcher);
+ BatchLauncherMain main = new BatchLauncherMain(launcherFactory);
main.execute(new String[0]);
fail();
} catch (Exception e) {
@Test
public void should_fail_if_properties_file_does_not_exist() {
try {
- BatchLauncherMain main = new BatchLauncherMain(launcher);
- main.execute(new String[]{"unknown/file.properties"});
+ BatchLauncherMain main = new BatchLauncherMain(launcherFactory);
+ main.execute(new String[] {"unknown/file.properties"});
fail();
} catch (Exception e) {
// success
+++ /dev/null
-/*
- * SonarQube Runner - Implementation
- * 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.impl;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Properties;
-import org.junit.Test;
-import org.sonar.runner.batch.IsolatedLauncher;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.fest.assertions.Fail.fail;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-
-public class BatchLauncherTest {
-
- JarDownloader jarDownloader = mock(JarDownloader.class);
-
- @Test
- public void should_download_jars_and_execute_batch() {
- TempCleaning tempCleaning = mock(TempCleaning.class);
- BatchLauncher launcher = new BatchLauncher(FakeIsolatedLauncher.class.getName(), tempCleaning);
- Properties props = new Properties();
- props.put("foo", "bar");
-
- // Unmask the current classloader in order to access FakeIsolatedLauncher
- props.put(InternalProperties.RUNNER_MASK_RULES, "UNMASK|org.sonar.runner.impl.");
- List<Object> extensions = new ArrayList<Object>();
-
- FakeIsolatedLauncher isolatedLauncher = (FakeIsolatedLauncher) launcher.doExecute(jarDownloader, props, extensions);
- assertThat(isolatedLauncher.props.get("foo")).isEqualTo("bar");
- assertThat(isolatedLauncher.extensions).isSameAs(extensions);
- verify(jarDownloader).download();
- verify(tempCleaning).clean();
- }
-
- @Test
- public void should_use_isolated_classloader() {
- BatchLauncher launcher = new BatchLauncher(FakeIsolatedLauncher.class.getName(), mock(TempCleaning.class));
- Properties props = new Properties();
-
- // The current classloader in not available -> fail to load FakeIsolatedLauncher
- props.put(InternalProperties.RUNNER_MASK_RULES, "");
- try {
- launcher.doExecute(jarDownloader, props, Collections.emptyList());
- fail();
- } catch (RunnerException e) {
- // success
- }
- }
-
- @Test
- public void verify_isolated_classloader_name() {
- // the class IsolatedLauncher should not be loaded in the classloader of BatchLauncher,
- // that's why it's referenced by its name
- assertThat(new BatchLauncher().isolatedLauncherClass).isEqualTo(IsolatedLauncher.class.getName());
- }
-
- @Test
- public void test_real_execution() {
- // verify the creation of dependent components
- Properties props = new Properties();
- List<Object> extensions = Collections.emptyList();
- BatchLauncher launcher = spy(new BatchLauncher());
- doReturn(new Object()).when(launcher).doExecute(any(JarDownloader.class), eq(props), eq(extensions));
-
- launcher.execute(props, extensions);
-
- verify(launcher).execute(props, extensions);
- }
-
- public static class FakeIsolatedLauncher {
- public Properties props = null;
- public List<Object> extensions = null;
-
- public void execute(Properties props, List<Object> extensions) {
- this.props = props;
- this.extensions = extensions;
- }
- }
-}
--- /dev/null
+/*
+ * SonarQube Runner - Implementation
+ * 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.impl;
+
+import org.junit.Before;
+import org.sonar.runner.batch.IsolatedLauncher;
+
+import java.util.List;
+import java.util.Properties;
+
+import org.junit.Test;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class IsolatedLauncherFactoryTest {
+ IsolatedLauncherFactory factory;
+ Properties props;
+ TempCleaning tempCleaning;
+ JarDownloader jarDownloader;
+
+ @Before
+ public void setUp() {
+ tempCleaning = mock(TempCleaning.class);
+ factory = new IsolatedLauncherFactory(FakeIsolatedLauncher.class.getName(), tempCleaning);
+ props = new Properties();
+ jarDownloader = mock(JarDownloader.class);
+ }
+
+ @Test
+ public void should_process_mask_rules() {
+ Properties p = new Properties();
+ p.put(InternalProperties.RUNNER_MASK_RULES, "UNMASK|org.sonar.runner.impl.");
+ p.put("a", "b");
+
+ String[][] maskRules = IsolatedLauncherFactory.getMaskRules(p);
+
+ assertThat(maskRules).hasSize(1);
+ assertThat(maskRules[0]).hasSize(2);
+ assertThat(maskRules[0][0]).isEqualTo("UNMASK");
+ assertThat(maskRules[0][1]).isEqualTo("org.sonar.runner.impl.");
+ }
+
+ @Test
+ public void should_download_jars_and_execute_batch() {
+ props.put("foo", "bar");
+
+ // Unmask the current classloader in order to access FakeIsolatedLauncher
+ props.put(InternalProperties.RUNNER_MASK_RULES, "UNMASK|org.sonar.runner.impl.");
+
+ IsolatedLauncher isolatedLauncher = factory.createLauncher(jarDownloader, props);
+ isolatedLauncher.execute(props);
+
+ verify(jarDownloader).download();
+ verify(tempCleaning).clean();
+ assertThat(FakeIsolatedLauncher.props.get("foo")).isEqualTo("bar");
+ assertThat(isolatedLauncher.getClass().getClassLoader().getClass().getSimpleName()).isEqualTo("IsolatedClassloader");
+ }
+
+ @Test
+ public void should_use_isolated_classloader() {
+ // The current classloader in not available -> fail to load FakeIsolatedLauncher
+ props.put(InternalProperties.RUNNER_MASK_RULES, "");
+ try {
+ factory.createLauncher(jarDownloader, props);
+ fail();
+ } catch (RunnerException e) {
+ // success
+ }
+ }
+
+ public static class FakeIsolatedLauncher implements IsolatedLauncher {
+ public static Properties props = null;
+
+ @Override
+ public void start(Properties properties, List<Object> extensions) {
+ }
+
+ @Override
+ public void stop() {
+ }
+
+ @Override
+ public void execute(Properties properties) {
+ FakeIsolatedLauncher.props = properties;
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube Runner - Implementation
+ * 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.impl;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.concurrent.Callable;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import org.sonar.runner.batch.BatchIsolatedLauncher;
+import org.junit.Test;
+import org.junit.Before;
+
+public class IsolatedLauncherProxyTest {
+ BatchIsolatedLauncher laucherProxy = null;
+ ClassLoader cl = null;
+
+ @Before
+ public void setUp() {
+ cl = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader());
+ }
+
+ @Test
+ public void delegate_proxied() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+ String str = "test";
+ CharSequence s = IsolatedLauncherProxy.create(cl, str, CharSequence.class);
+ assertThat(s).isEqualTo(str);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void exceptions_unwrapped() throws ReflectiveOperationException {
+ Runnable r = IsolatedLauncherProxy.create(cl, Runnable.class, ExceptionThrower.class.getName());
+ r.run();
+ }
+
+ @Test
+ public void create_proxied() throws Exception {
+ Callable<?> c = IsolatedLauncherProxy.create(cl, Callable.class, SimpleClass.class.getName());
+ assertThat(c.getClass().getClassLoader()).isEqualTo(cl);
+ assertThat(c.getClass().getClassLoader()).isNotEqualTo(Thread.currentThread().getContextClassLoader());
+ assertThat(c.call()).isEqualTo(URLClassLoader.class.getSimpleName());
+ }
+
+ public static class ExceptionThrower implements Runnable {
+ @Override
+ public void run() {
+ throw new IllegalStateException("message");
+ }
+ }
+
+ public static class SimpleClass implements Callable<String> {
+ @Override
+ public String call() throws Exception {
+ return Thread.currentThread().getContextClassLoader().getClass().getSimpleName();
+ }
+ }
+}