import java.util.Arrays;
import java.util.List;
+/**
+ * Implementation of {@link Runner} that is executed in the same JVM. The application can inject
+ * some extensions into Sonar IoC container (see {@link #addExtensions(Object...)}. It can be
+ * used for example in the Maven Sonar plugin to register Maven components like MavenProject
+ * or MavenPluginExecutor.
+ * @since 2.2
+ */
public class EmbeddedRunner extends Runner<EmbeddedRunner> {
+ private final BatchLauncher batchLauncher;
private final List<Object> extensions = new ArrayList<Object>();
- private EmbeddedRunner() {
+ EmbeddedRunner(BatchLauncher bl) {
+ this.batchLauncher = bl;
}
+ /**
+ * Create a new instance.
+ */
public static EmbeddedRunner create() {
- return new EmbeddedRunner();
+ return new EmbeddedRunner(new BatchLauncher());
}
+ /**
+ * Sonar is executed in an almost fully isolated classloader. The unmasked packages
+ * define the classes of the client application that are visible from Sonar classloader. They
+ * relate to the extensions provided by {@link #setUnmaskedPackages(String...)}.
+ */
public EmbeddedRunner setUnmaskedPackages(String... packages) {
return setProperty("sonarRunner.unmaskedPackages", Utils.join(packages, ","));
}
@Override
protected void doExecute() {
- new BatchLauncher().execute(properties(), extensions);
+ batchLauncher.execute(properties(), extensions);
}
}
import org.sonar.runner.impl.JarExtractor;
import javax.annotation.Nullable;
+
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
-import java.util.*;
-
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Runner executed in a new JVM.
+ *
+ * @since 2.2
+ */
public class ForkedRunner extends Runner<ForkedRunner> {
private static final int ONE_DAY_IN_MILLISECONDS = 24 * 60 * 60 * 1000;
private final Map<String, String> jvmEnvVariables = new HashMap<String, String>();
private final List<String> jvmArguments = new ArrayList<String>();
- private String javaCommand;
+ private String javaExecutable;
private StreamConsumer stdOut = null, stdErr = null;
private final JarExtractor jarExtractor;
+ private final CommandExecutor commandExecutor;
- ForkedRunner(JarExtractor jarExtractor) {
+ ForkedRunner(JarExtractor jarExtractor, CommandExecutor commandExecutor) {
this.jarExtractor = jarExtractor;
+ this.commandExecutor = commandExecutor;
}
+ /**
+ * Create new instance. Never return null.
+ */
public static ForkedRunner create() {
- return new ForkedRunner(new JarExtractor());
+ return new ForkedRunner(new JarExtractor(), CommandExecutor.create());
}
- public ForkedRunner setJavaCommand(@Nullable String s) {
- this.javaCommand = s;
+ /**
+ * Path to the java executable. The JVM of the client app is used by default
+ * (see the system property java.home)
+ */
+ public ForkedRunner setJavaExecutable(@Nullable String s) {
+ this.javaExecutable = s;
return this;
}
+ /**
+ * See {@link #addJvmArguments(java.util.List)}
+ */
public ForkedRunner addJvmArguments(String... s) {
return addJvmArguments(Arrays.asList(s));
}
+ /**
+ * JVM arguments, for example "-Xmx512m"
+ */
public ForkedRunner addJvmArguments(List<String> args) {
jvmArguments.addAll(args);
return this;
}
+ /**
+ * Set a JVM environment variable. By default no variables are set.
+ */
public ForkedRunner setJvmEnvVariable(String key, String value) {
jvmEnvVariables.put(key, value);
return this;
}
+ /**
+ * Add some JVM environment variables. By default no variables are set.
+ */
public ForkedRunner addJvmEnvVariables(Map<String, String> map) {
jvmEnvVariables.putAll(map);
return this;
fork(createCommand());
}
- Command createCommand() {
+ private Command createCommand() {
File propertiesFile = writeProperties();
File jarFile = jarExtractor.extract("sonar-runner-impl");
-
- Os os = new Os();
- if (javaCommand == null) {
- javaCommand = os.usedJavaExe().getAbsolutePath();
+ if (javaExecutable == null) {
+ javaExecutable = new Os().thisJavaExe().getAbsolutePath();
}
return Command.builder()
- .setExecutable(javaCommand)
+ .setExecutable(javaExecutable)
+ .addEnvVariables(jvmEnvVariables)
.addArguments(jvmArguments)
.addArguments("-cp", jarFile.getAbsolutePath(), BatchLauncherMain.class.getName(), propertiesFile.getAbsolutePath())
.build();
if (stdErr == null) {
stdErr = new PrintStreamConsumer(System.err);
}
- int status = CommandExecutor.create().execute(command, stdOut, stdErr, ONE_DAY_IN_MILLISECONDS);
+ int status = commandExecutor.execute(command, stdOut, stdErr, ONE_DAY_IN_MILLISECONDS);
if (status != 0) {
- throw new IllegalStateException("TODO");
+ throw new IllegalStateException("Error status: " + status);
}
}
return System.getProperty("os.name").contains("Windows");
}
- File usedJavaHome() {
+ File thisJavaHome() {
return new File(System.getProperty("java.home"));
}
/**
* Path to the java executable used by this VM
*/
- File usedJavaExe() {
- File bin = new File(usedJavaHome(), "bin");
+ File thisJavaExe() {
+ File bin = new File(thisJavaHome(), "bin");
return new File(bin, isWindows() ? "java.exe" : "java");
}
}
import java.io.PrintStream;
+/**
+ * Implementation of StreamConsumer that prints lines to {@link java.io.PrintStream}. Generally used
+ * to forward to {@link System.out} or {@link System.err}.
+ *
+ * @since 2.2
+ */
public class PrintStreamConsumer implements StreamConsumer {
- private final PrintStream output;
+ final PrintStream output;
public PrintStreamConsumer(PrintStream output) {
this.output = output;
return clone;
}
+ /**
+ * Declare Sonar properties, for example sonar.projectKey=>foo.
+ */
public T addProperties(Properties p) {
properties.putAll(p);
return (T) this;
return properties.getProperty(key, defaultValue);
}
+ /**
+ * User-agent used in the HTTP requests to the Sonar server
+ */
public T setApp(String app, String version) {
setProperty(Constants.PROP_APP, app);
setProperty(Constants.PROP_APP_VERSION, version);
import java.util.Scanner;
+/**
+ * Version of this sonar-runner API.
+ * @since 2.2
+ */
public enum RunnerVersion {
INSTANCE;
*/
package org.sonar.runner.api;
+/**
+ * @since 2.2
+ */
public interface StreamConsumer {
void consumeLine(String line);
}
package org.sonar.runner.api;
import org.junit.Test;
+import org.mockito.ArgumentMatcher;
+import org.sonar.runner.impl.BatchLauncher;
import org.sonar.runner.impl.Constants;
+import java.util.List;
+import java.util.Properties;
+
import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
public class EmbeddedRunnerTest {
@Test
assertThat(EmbeddedRunner.create()).isNotNull().isInstanceOf(EmbeddedRunner.class);
}
+ @Test
+ public void test_app() {
+ EmbeddedRunner runner = EmbeddedRunner.create().setApp("Eclipse", "3.1");
+ assertThat(runner.app()).isEqualTo("Eclipse");
+ assertThat(runner.appVersion()).isEqualTo("3.1");
+ }
+
@Test
public void should_set_unmasked_packages() {
EmbeddedRunner runner = EmbeddedRunner.create();
assertThat(runner.extensions()).containsExactly(fakeExtension);
}
+ @Test
+ public void should_set_properties() {
+ EmbeddedRunner runner = EmbeddedRunner.create();
+ runner.setProperty("sonar.projectKey", "foo");
+ runner.addProperties(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");
+ }
+
+ @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();
+
+ verify(batchLauncher).execute(argThat(new ArgumentMatcher<Properties>() {
+ @Override
+ public boolean matches(Object o) {
+ return "foo".equals(((Properties) o).getProperty("sonar.projectKey"));
+ }
+ }), argThat(new ArgumentMatcher<List<Object>>() {
+ @Override
+ public boolean matches(Object o) {
+ return ((List) o).contains(fakeExtension);
+ }
+ }));
+ }
+
static class FakeExtension {
}
}
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+import org.mockito.ArgumentMatcher;
import org.sonar.runner.impl.JarExtractor;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
-import java.util.Arrays;
+import java.io.PrintStream;
import java.util.Properties;
import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class ForkedRunnerTest {
assertThat(runner).isNotNull().isInstanceOf(ForkedRunner.class);
}
+ @Test
+ public void should_print_to_standard_outputs_by_default() throws IOException {
+ JarExtractor jarExtractor = mock(JarExtractor.class);
+ final File jar = temp.newFile();
+ when(jarExtractor.extract("sonar-runner-impl")).thenReturn(jar);
+
+ CommandExecutor commandExecutor = mock(CommandExecutor.class);
+ ForkedRunner runner = new ForkedRunner(jarExtractor, commandExecutor);
+ runner.execute();
+
+ verify(commandExecutor).execute(any(Command.class), argThat(new StdConsumerMatcher(System.out)), argThat(new StdConsumerMatcher(System.err)), anyLong());
+ }
+
+ static class StdConsumerMatcher extends ArgumentMatcher<StreamConsumer> {
+ PrintStream output;
+
+ StdConsumerMatcher(PrintStream output) {
+ this.output = output;
+ }
+
+ public boolean matches(Object o) {
+ return ((PrintStreamConsumer) o).output == output;
+ }
+ }
+
@Test
public void test_java_command() throws IOException {
JarExtractor jarExtractor = mock(JarExtractor.class);
- File jar = temp.newFile();
+ final File jar = temp.newFile();
when(jarExtractor.extract("sonar-runner-impl")).thenReturn(jar);
- ForkedRunner runner = new ForkedRunner(jarExtractor);
- runner.setJavaCommand("java");
+ CommandExecutor commandExecutor = mock(CommandExecutor.class);
+
+ ForkedRunner runner = new ForkedRunner(jarExtractor, commandExecutor);
+ runner.setJavaExecutable("java");
runner.setProperty("sonar.dynamicAnalysis", "false");
runner.setProperty("sonar.login", "admin");
runner.addJvmArguments("-Xmx512m");
+ runner.addJvmEnvVariables(System.getenv());
runner.setJvmEnvVariable("SONAR_HOME", "/path/to/sonar");
+ runner.setStdOut(mock(StreamConsumer.class));
+ runner.setStdErr(mock(StreamConsumer.class));
+
+ runner.execute();
+
+ verify(commandExecutor).execute(argThat(new ArgumentMatcher<Command>() {
+ public boolean matches(Object o) {
+ Command command = (Command) o;
+ assertThat(command.toStrings()).hasSize(6);
+ assertThat(command.toStrings()[0]).isEqualTo("java");
+ assertThat(command.toStrings()[1]).isEqualTo("-Xmx512m");
+ assertThat(command.toStrings()[2]).isEqualTo("-cp");
+ assertThat(command.toStrings()[3]).isEqualTo(jar.getAbsolutePath());
+ assertThat(command.toStrings()[4]).isEqualTo("org.sonar.runner.impl.BatchLauncherMain");
+
+ // env variables
+ assertThat(command.envVariables().size()).isGreaterThan(1);
+ assertThat(command.envVariables().get("SONAR_HOME")).isEqualTo("/path/to/sonar");
+
+ // the properties
+ String propsPath = command.toStrings()[5];
+ assertThat(propsPath).endsWith(".properties");
+ Properties properties = new Properties();
+ try {
+ properties.load(new FileInputStream(propsPath));
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ assertThat(properties.size()).isGreaterThan(2);
+ assertThat(properties.getProperty("sonar.dynamicAnalysis")).isEqualTo("false");
+ assertThat(properties.getProperty("sonar.login")).isEqualTo("admin");
+ assertThat(properties.getProperty("-Xmx512m")).isNull();
+ assertThat(properties.getProperty("SONAR_HOME")).isNull();
+ // default values
+ assertThat(properties.getProperty("sonar.task")).isEqualTo("scan");
+ assertThat(properties.getProperty("sonar.host.url")).isEqualTo("http://localhost:9000");
+ return true;
+ }
+ }), any(PrintStreamConsumer.class), any(PrintStreamConsumer.class), anyLong());
- Command command = runner.createCommand();
- assertThat(command).isNotNull();
- assertThat(command.toStrings()).hasSize(6);
- assertThat(command.toStrings()[0]).isEqualTo("java");
- assertThat(command.toStrings()[1]).isEqualTo("-Xmx512m");
- assertThat(command.toStrings()[2]).isEqualTo("-cp");
- assertThat(command.toStrings()[3]).isEqualTo(jar.getAbsolutePath());
- assertThat(command.toStrings()[4]).isEqualTo("org.sonar.runner.impl.BatchLauncherMain");
-
- // the properties
- String propsPath = command.toStrings()[5];
- assertThat(propsPath).endsWith(".properties");
- Properties properties = new Properties();
- properties.load(new FileInputStream(propsPath));
- assertThat(properties.size()).isGreaterThan(2);
- assertThat(properties.getProperty("sonar.dynamicAnalysis")).isEqualTo("false");
- assertThat(properties.getProperty("sonar.login")).isEqualTo("admin");
- assertThat(properties.getProperty("-Xmx512m")).isNull();
- assertThat(properties.getProperty("SONAR_HOME")).isNull();
- // default values
- assertThat(properties.getProperty("sonar.task")).isEqualTo("scan");
- assertThat(properties.getProperty("sonar.host.url")).isEqualTo("http://localhost:9000");
}
}
@Test
public void testUsedJavaHome() throws Exception {
- File javaHome = new Os().usedJavaHome();
+ File javaHome = new Os().thisJavaHome();
assertThat(javaHome).isNotNull().exists().isDirectory();
}
@Test
public void testUsedJavaExe() throws Exception {
- File javaExe = new Os().usedJavaExe();
+ File javaExe = new Os().thisJavaExe();
assertThat(javaExe).isNotNull().isFile().exists();
assertThat(javaExe.getName()).contains("java");
}
--- /dev/null
+/*
+ * Sonar Runner - API
+ * Copyright (C) 2011 SonarSource
+ * dev@sonar.codehaus.org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.runner.api;
+
+import org.junit.Test;
+
+import java.io.PrintStream;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+public class PrintStreamConsumerTest {
+ @Test
+ public void consumeLine() {
+ PrintStream stream = mock(PrintStream.class);
+ PrintStreamConsumer consumer = new PrintStreamConsumer(stream);
+ consumer.consumeLine("foo");
+ consumer.consumeLine("bar");
+
+ verify(stream).println("foo");
+ verify(stream).println("bar");
+ verifyNoMoreInteractions(stream);
+ }
+}