From c71267451a392f036b8f88656f10a17c5cd456ad Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Thu, 4 Apr 2013 22:45:26 +0200 Subject: [PATCH] SONARPLUGINS-2574 set env variables + improve unit tests --- .../org/sonar/runner/api/EmbeddedRunner.java | 23 +++- .../org/sonar/runner/api/ForkedRunner.java | 60 ++++++++--- .../main/java/org/sonar/runner/api/Os.java | 6 +- .../sonar/runner/api/PrintStreamConsumer.java | 8 +- .../java/org/sonar/runner/api/Runner.java | 6 ++ .../org/sonar/runner/api/RunnerVersion.java | 4 + .../org/sonar/runner/api/StreamConsumer.java | 3 + .../sonar/runner/api/EmbeddedRunnerTest.java | 52 +++++++++ .../sonar/runner/api/ForkedRunnerTest.java | 102 +++++++++++++----- .../java/org/sonar/runner/api/OsTest.java | 4 +- .../runner/api/PrintStreamConsumerTest.java | 42 ++++++++ 11 files changed, 260 insertions(+), 50 deletions(-) create mode 100644 sonar-runner-api/src/test/java/org/sonar/runner/api/PrintStreamConsumerTest.java diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/EmbeddedRunner.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/EmbeddedRunner.java index 0218a32..e5ce102 100644 --- a/sonar-runner-api/src/main/java/org/sonar/runner/api/EmbeddedRunner.java +++ b/sonar-runner-api/src/main/java/org/sonar/runner/api/EmbeddedRunner.java @@ -25,17 +25,34 @@ import java.util.ArrayList; 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 { + private final BatchLauncher batchLauncher; private final List extensions = new ArrayList(); - 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, ",")); } @@ -51,6 +68,6 @@ public class EmbeddedRunner extends Runner { @Override protected void doExecute() { - new BatchLauncher().execute(properties(), extensions); + batchLauncher.execute(properties(), extensions); } } diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/ForkedRunner.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/ForkedRunner.java index 8dca9fe..ec148d6 100644 --- a/sonar-runner-api/src/main/java/org/sonar/runner/api/ForkedRunner.java +++ b/sonar-runner-api/src/main/java/org/sonar/runner/api/ForkedRunner.java @@ -24,48 +24,79 @@ import org.sonar.runner.impl.BatchLauncherMain; 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 { private static final int ONE_DAY_IN_MILLISECONDS = 24 * 60 * 60 * 1000; private final Map jvmEnvVariables = new HashMap(); private final List jvmArguments = new ArrayList(); - 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 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 map) { jvmEnvVariables.putAll(map); return this; @@ -92,16 +123,15 @@ public class ForkedRunner extends Runner { 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(); @@ -130,9 +160,9 @@ public class ForkedRunner extends Runner { 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); } } diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/Os.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/Os.java index da51361..7f0e482 100644 --- a/sonar-runner-api/src/main/java/org/sonar/runner/api/Os.java +++ b/sonar-runner-api/src/main/java/org/sonar/runner/api/Os.java @@ -26,15 +26,15 @@ class Os { 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"); } } diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/PrintStreamConsumer.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/PrintStreamConsumer.java index e64ba7c..b81a98a 100644 --- a/sonar-runner-api/src/main/java/org/sonar/runner/api/PrintStreamConsumer.java +++ b/sonar-runner-api/src/main/java/org/sonar/runner/api/PrintStreamConsumer.java @@ -21,8 +21,14 @@ package org.sonar.runner.api; 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; diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/Runner.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/Runner.java index 079cdb9..4634691 100644 --- a/sonar-runner-api/src/main/java/org/sonar/runner/api/Runner.java +++ b/sonar-runner-api/src/main/java/org/sonar/runner/api/Runner.java @@ -53,6 +53,9 @@ public abstract class Runner { return clone; } + /** + * Declare Sonar properties, for example sonar.projectKey=>foo. + */ public T addProperties(Properties p) { properties.putAll(p); return (T) this; @@ -67,6 +70,9 @@ public abstract class Runner { 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); diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/RunnerVersion.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/RunnerVersion.java index d15f72f..76ed836 100644 --- a/sonar-runner-api/src/main/java/org/sonar/runner/api/RunnerVersion.java +++ b/sonar-runner-api/src/main/java/org/sonar/runner/api/RunnerVersion.java @@ -21,6 +21,10 @@ package org.sonar.runner.api; import java.util.Scanner; +/** + * Version of this sonar-runner API. + * @since 2.2 + */ public enum RunnerVersion { INSTANCE; diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/StreamConsumer.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/StreamConsumer.java index 7eac938..9ed3371 100644 --- a/sonar-runner-api/src/main/java/org/sonar/runner/api/StreamConsumer.java +++ b/sonar-runner-api/src/main/java/org/sonar/runner/api/StreamConsumer.java @@ -19,6 +19,9 @@ */ package org.sonar.runner.api; +/** + * @since 2.2 + */ public interface StreamConsumer { void consumeLine(String line); } diff --git a/sonar-runner-api/src/test/java/org/sonar/runner/api/EmbeddedRunnerTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/api/EmbeddedRunnerTest.java index d3d2ca2..437d6bd 100644 --- a/sonar-runner-api/src/test/java/org/sonar/runner/api/EmbeddedRunnerTest.java +++ b/sonar-runner-api/src/test/java/org/sonar/runner/api/EmbeddedRunnerTest.java @@ -20,9 +20,17 @@ 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 @@ -30,6 +38,13 @@ public class EmbeddedRunnerTest { 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(); @@ -49,6 +64,43 @@ public class EmbeddedRunnerTest { 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() { + @Override + public boolean matches(Object o) { + return "foo".equals(((Properties) o).getProperty("sonar.projectKey")); + } + }), argThat(new ArgumentMatcher>() { + @Override + public boolean matches(Object o) { + return ((List) o).contains(fakeExtension); + } + })); + } + static class FakeExtension { } } diff --git a/sonar-runner-api/src/test/java/org/sonar/runner/api/ForkedRunnerTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/api/ForkedRunnerTest.java index 7f4d325..ee87a88 100644 --- a/sonar-runner-api/src/test/java/org/sonar/runner/api/ForkedRunnerTest.java +++ b/sonar-runner-api/src/test/java/org/sonar/runner/api/ForkedRunnerTest.java @@ -22,16 +22,21 @@ package org.sonar.runner.api; 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 { @@ -45,40 +50,85 @@ 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 { + 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() { + 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"); } } diff --git a/sonar-runner-api/src/test/java/org/sonar/runner/api/OsTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/api/OsTest.java index 20c4a91..7792d53 100644 --- a/sonar-runner-api/src/test/java/org/sonar/runner/api/OsTest.java +++ b/sonar-runner-api/src/test/java/org/sonar/runner/api/OsTest.java @@ -34,13 +34,13 @@ public class OsTest { @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"); } diff --git a/sonar-runner-api/src/test/java/org/sonar/runner/api/PrintStreamConsumerTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/api/PrintStreamConsumerTest.java new file mode 100644 index 0000000..3683cf0 --- /dev/null +++ b/sonar-runner-api/src/test/java/org/sonar/runner/api/PrintStreamConsumerTest.java @@ -0,0 +1,42 @@ +/* + * 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); + } +} -- 2.39.5