From: Duarte Meneses Date: Tue, 16 Jun 2015 15:38:52 +0000 (+0200) Subject: SONARUNNER-138 Allow to redirect logs to a custom stream X-Git-Tag: 2.5-rc1~80 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=f2e74b6d6935082e4d75a5ddfaf66e8ba1079f1f;p=sonar-scanner-cli.git SONARUNNER-138 Allow to redirect logs to a custom stream --- diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/CommandExecutor.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/CommandExecutor.java index d9903bf..fd5b6d8 100644 --- a/sonar-runner-api/src/main/java/org/sonar/runner/api/CommandExecutor.java +++ b/sonar-runner-api/src/main/java/org/sonar/runner/api/CommandExecutor.java @@ -19,6 +19,8 @@ */ package org.sonar.runner.api; +import org.sonar.runner.impl.Logs; + import javax.annotation.Nullable; import java.io.BufferedReader; @@ -101,7 +103,7 @@ class CommandExecutor { } } - private void monitorProcess(final ProcessMonitor processMonitor, final ExecutorService executor, final Process process) { + private static void monitorProcess(final ProcessMonitor processMonitor, final ExecutorService executor, final Process process) { new Thread() { @Override public void run() { @@ -149,8 +151,7 @@ class CommandExecutor { try { thread.join(); } catch (InterruptedException e) { - System.err.println("InterruptedException while waiting finish of " + thread.toString()); - e.printStackTrace(); + Logs.error("InterruptedException while waiting finish of " + thread.toString(), e); } } } 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 9dfad19..e1c04b7 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 @@ -19,13 +19,15 @@ */ package org.sonar.runner.api; +import org.sonar.home.log.LogListener; + +import org.sonar.runner.impl.Logs; 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; /** @@ -36,9 +38,9 @@ import java.util.Properties; * @since 2.2 */ public class EmbeddedRunner extends Runner { - private final IsolatedLauncherFactory launcherFactory; private IsolatedLauncher launcher; + private String sqVersion; private final List extensions = new ArrayList(); private static final String MASK_RULES_PROP = "sonarRunner.maskRules"; @@ -53,6 +55,11 @@ public class EmbeddedRunner extends Runner { return new EmbeddedRunner(new IsolatedLauncherFactory()); } + public static EmbeddedRunner create(LogListener logListener) { + Logs.setListener(logListener); + return new EmbeddedRunner(new IsolatedLauncherFactory()); + } + /** * Sonar is executed in an almost fully isolated classloader (mask everything by default). This method allows to unmask some classes based on * a prefix of their fully qualified name. It is related to the extensions provided by {@link #addExtensions(Object...)}. @@ -104,16 +111,27 @@ public class EmbeddedRunner extends Runner { @Override protected void doStart() { launcher = launcherFactory.createLauncher(globalProperties()); - launcher.start(globalProperties(), extensions); + if (Utils.isAtLeast52(launcher.getVersion())) { + launcher.start(globalProperties(), extensions, Logs.getListener()); + } } @Override protected void doStop() { - launcher.stop(); + if (Utils.isAtLeast52(launcher.getVersion())) { + launcher.stop(); + } } @Override protected void doExecute(Properties analysisProperties) { - launcher.execute(analysisProperties); + if (Utils.isAtLeast52(launcher.getVersion())) { + launcher.execute(analysisProperties); + } else { + Properties prop = new Properties(); + prop.putAll(globalProperties()); + prop.putAll(analysisProperties); + launcher.executeOldVersion(prop, 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 02fdae7..31a932f 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 @@ -19,6 +19,7 @@ */ package org.sonar.runner.api; +import org.sonar.runner.impl.Logs; import org.sonar.runner.impl.BatchLauncherMain; import org.sonar.runner.impl.JarExtractor; @@ -46,7 +47,8 @@ public class ForkedRunner extends Runner { private final Map jvmEnvVariables = new HashMap(); private final List jvmArguments = new ArrayList(); private String javaExecutable; - private StreamConsumer stdOut = null, stdErr = null; + private StreamConsumer stdOut = null; + private StreamConsumer stdErr = null; private final JarExtractor jarExtractor; private final CommandExecutor commandExecutor; @@ -121,7 +123,8 @@ public class ForkedRunner extends Runner { } /** - * Subscribe to the standard output. By default output is {@link System.out} + * @deprecated Since 2.5. Use {@link ForkedRunner#setLogListener} instead. + * Subscribe to the standard output from the forked process. By default, logs messages are sent with INFO level to the log listener set. */ public ForkedRunner setStdOut(@Nullable StreamConsumer stream) { this.stdOut = stream; @@ -129,7 +132,8 @@ public class ForkedRunner extends Runner { } /** - * Subscribe to the error output. By default output is {@link System.err} + * @deprecated Since 2.5. Use {@link ForkedRunner#setLogListener}instead. + * Subscribe to the error output from the forked process. By default, logs messages are sent with ERROR level to the log listener set. */ public ForkedRunner setStdErr(@Nullable StreamConsumer stream) { this.stdErr = stream; @@ -138,12 +142,12 @@ public class ForkedRunner extends Runner { @Override protected void doExecute(Properties props) { - //merge both global and analysis-specific properties because it will be used both to start and to execute. + // 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); @@ -151,15 +155,15 @@ public class ForkedRunner extends Runner { deleteTempFiles(forkCommand); } } - + @Override protected void doStop() { - //nothing to do + // nothing to do } - + @Override protected void doStart() { - //nothing to do + // nothing to do } ForkCommand createCommand(Properties p) { @@ -196,11 +200,22 @@ public class ForkedRunner extends Runner { private void fork(ForkCommand forkCommand) { if (stdOut == null) { - stdOut = new PrintStreamConsumer(System.out); + stdOut = new StreamConsumer() { + @Override + public void consumeLine(String line) { + Logs.info(line); + } + }; } if (stdErr == null) { - stdErr = new PrintStreamConsumer(System.err); + stdErr = new StreamConsumer() { + @Override + public void consumeLine(String line) { + Logs.error(line); + } + }; } + int status = commandExecutor.execute(forkCommand.command, stdOut, stdErr, ONE_DAY_IN_MILLISECONDS, processMonitor); if (status != 0) { if (processMonitor != null && processMonitor.stop()) { 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 081b7f7..890ce46 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 @@ -19,6 +19,9 @@ */ package org.sonar.runner.api; +import org.sonar.home.log.LogListener; + +import org.sonar.runner.impl.Logs; import org.sonar.runner.impl.InternalProperties; import javax.annotation.Nullable; @@ -30,7 +33,6 @@ import java.util.Properties; * @since 2.2 */ public abstract class Runner { - private final Properties globalProperties = new Properties(); protected Runner() { @@ -41,6 +43,16 @@ public abstract class Runner { clone.putAll(globalProperties); return clone; } + + /** + * Set a log stream. All log events will be redirected to the listener. + * By default, all logs are sent to stdout, except for logs of ERROR level, which are sent to stderr. + * If null is given, the default is behavior is set. + */ + public T setLogListener(LogListener stream) { + Logs.setListener(stream); + return (T) this; + } /** * Declare Sonar properties, for example sonar.projectKey=>foo. @@ -93,7 +105,7 @@ public abstract class Runner { if (dumpToFile != null) { File dumpFile = new File(dumpToFile); Utils.writeProperties(dumpFile, copy); - System.out.println("Simulation mode. Configuration written to " + dumpFile.getAbsolutePath()); + Logs.info("Simulation mode. Configuration written to " + dumpFile.getAbsolutePath()); } else { doExecute(copy); } diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/Utils.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/Utils.java index 4d0524a..4d4907a 100644 --- a/sonar-runner-api/src/main/java/org/sonar/runner/api/Utils.java +++ b/sonar-runner-api/src/main/java/org/sonar/runner/api/Utils.java @@ -33,12 +33,22 @@ import java.nio.file.SimpleFileVisitor; import java.util.Arrays; import java.util.Iterator; import java.util.Properties; -import java.nio.file.attribute.*; +import java.nio.file.attribute.BasicFileAttributes; class Utils { private Utils() { // only util static methods } + + static boolean isAtLeast52(String version) { + //it can be snapshot (5.2-SNAPSHOT) + if(version == null) { + return false; + } + + int endIndex = Math.min(3, version.length()); + return Double.parseDouble(version.substring(0, endIndex)) >= 5.2; + } /** * Similar to org.apache.commons.lang.StringUtils#join() 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 2082b29..84a256a 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 @@ -31,8 +31,11 @@ import org.sonar.runner.impl.InternalProperties; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.util.LinkedList; +import java.util.List; import java.util.Properties; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.when; import static org.mockito.Matchers.any; import static org.fest.assertions.Assertions.assertThat; @@ -58,6 +61,7 @@ public class EmbeddedRunnerTest { public void setUp() { batchLauncher = mock(IsolatedLauncherFactory.class); launcher = mock(IsolatedLauncher.class); + when(launcher.getVersion()).thenReturn("5.2"); when(batchLauncher.createLauncher(any(Properties.class))).thenReturn(launcher); runner = new EmbeddedRunner(batchLauncher); } @@ -68,6 +72,48 @@ public class EmbeddedRunnerTest { assertThat(runner.app()).isEqualTo("Eclipse"); assertThat(runner.appVersion()).isEqualTo("3.1"); } + + @Test + public void test_back_compatibility() { + when(launcher.getVersion()).thenReturn("4.5"); + + final FakeExtension fakeExtension = new FakeExtension(); + List extensionList = new LinkedList<>(); + extensionList.add(fakeExtension); + + Properties analysisProps = new Properties(); + analysisProps.put("sonar.dummy", "summy"); + + runner.addExtensions(fakeExtension); + runner.setGlobalProperty("sonar.projectKey", "foo"); + runner.start(); + runner.runAnalysis(analysisProps); + runner.stop(); + + verify(batchLauncher).createLauncher(argThat(new ArgumentMatcher() { + @Override + public boolean matches(Object o) { + return "foo".equals(((Properties) o).getProperty("sonar.projectKey")); + } + })); + + // it should have added a few properties to analysisProperties, and have merged global props + final String[] mustHaveKeys = {"sonar.working.directory", "sonar.sourceEncoding", "sonar.projectBaseDir", + "sonar.projectKey", "sonar.dummy"}; + + verify(launcher).executeOldVersion(argThat(new ArgumentMatcher() { + @Override + public boolean matches(Object o) { + Properties m = (Properties) o; + for (String s : mustHaveKeys) { + if (!m.containsKey(s)) { + return false; + } + } + return true; + } + }), eq(extensionList)); + } @Test public void should_set_unmasked_packages() { 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 0c27273..e7c3a56 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 @@ -19,8 +19,10 @@ */ package org.sonar.runner.api; +import org.sonar.home.log.LogListener.Level; +import org.sonar.home.log.LogListener; +import org.sonar.runner.impl.Logs; import org.mockito.Mockito; - import org.mockito.ArgumentCaptor; import org.junit.Before; import org.junit.Rule; @@ -29,12 +31,16 @@ import org.junit.rules.TemporaryFolder; import org.mockito.ArgumentMatcher; import org.sonar.runner.impl.JarExtractor; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.PrintStream; +import java.util.LinkedList; +import java.util.List; import java.util.Properties; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.fest.assertions.Assertions.assertThat; import static org.fest.assertions.Fail.fail; import static org.mockito.Matchers.any; @@ -74,27 +80,46 @@ public class ForkedRunnerTest { } @Test - public void should_print_to_standard_outputs_by_default() throws IOException { + public void should_use_log_listener() throws IOException { JarExtractor jarExtractor = createMockExtractor(); 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(), - any(ProcessMonitor.class)); + LogListener listener = mock(LogListener.class); + Logs.setListener(listener); + + ArgumentCaptor arg1 = ArgumentCaptor.forClass(StreamConsumer.class); + ArgumentCaptor arg2 = ArgumentCaptor.forClass(StreamConsumer.class); + + verify(commandExecutor).execute(any(Command.class), arg1.capture(), arg2.capture(), anyLong(), any(ProcessMonitor.class)); + arg1.getValue().consumeLine("test1"); + arg2.getValue().consumeLine("test2"); + + verify(listener).log("test1", Level.INFO); + verify(listener).log("test2", Level.ERROR); + verifyNoMoreInteractions(listener); } + + @Test + public void should_print_to_consumers_by_default() throws IOException { + final List printedLines = new LinkedList<>(); + StreamConsumer consumer = new StreamConsumer() { + @Override + public void consumeLine(String line) { + printedLines.add(line); + } + }; + JarExtractor jarExtractor = createMockExtractor(); - static class StdConsumerMatcher extends ArgumentMatcher { - PrintStream output; - - StdConsumerMatcher(PrintStream output) { - this.output = output; - } + CommandExecutor commandExecutor = mock(CommandExecutor.class); + ForkedRunner runner = new ForkedRunner(jarExtractor, commandExecutor); + runner.setStdOut(consumer); + runner.setStdErr(consumer); + runner.execute(); - public boolean matches(Object o) { - return ((PrintStreamConsumer) o).output == output; - } + verify(commandExecutor).execute(any(Command.class), eq(consumer), eq(consumer), anyLong(), + any(ProcessMonitor.class)); } @Test 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 31e2c8f..79550e0 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 @@ -40,7 +40,6 @@ public class OsTest { @Test public void testUsedJavaExe() throws Exception { - System.out.println(System.getProperty("java.io.tmpdir")); 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/UtilsTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/api/UtilsTest.java index a56030d..a268ec3 100644 --- a/sonar-runner-api/src/test/java/org/sonar/runner/api/UtilsTest.java +++ b/sonar-runner-api/src/test/java/org/sonar/runner/api/UtilsTest.java @@ -41,6 +41,16 @@ public class UtilsTest { assertThat(Utils.join(new String[] {"foo", "bar"}, ",")).isEqualTo("foo,bar"); } + @Test + public void parse_version() { + assertThat(Utils.isAtLeast52("5.2")).isTrue(); + assertThat(Utils.isAtLeast52(null)).isFalse(); + assertThat(Utils.isAtLeast52("52")).isTrue(); + assertThat(Utils.isAtLeast52("5.0")).isFalse(); + assertThat(Utils.isAtLeast52("6.0.0")).isTrue(); + assertThat(Utils.isAtLeast52("5.2-SNAPSHOT")).isTrue(); + } + @Test public void task_should_require_project() { Properties props = new Properties(); @@ -64,7 +74,7 @@ public class UtilsTest { Utils.closeQuietly(c); verify(c).close(); } - + @Test public void close_quietly_null() throws IOException { Utils.closeQuietly(null); diff --git a/sonar-runner-batch-interface/pom.xml b/sonar-runner-batch-interface/pom.xml index d70c04a..9ccb3b9 100644 --- a/sonar-runner-batch-interface/pom.xml +++ b/sonar-runner-batch-interface/pom.xml @@ -7,5 +7,13 @@ sonar-runner-batch-interface - SonarQube Runner - Batch API + SonarQube Runner - Batch Interface + + + + org.codehaus.sonar + sonar-home + provided + + diff --git a/sonar-runner-batch-interface/src/main/java/org/sonar/runner/batch/IsolatedLauncher.java b/sonar-runner-batch-interface/src/main/java/org/sonar/runner/batch/IsolatedLauncher.java index 71d97c3..22fa54d 100644 --- a/sonar-runner-batch-interface/src/main/java/org/sonar/runner/batch/IsolatedLauncher.java +++ b/sonar-runner-batch-interface/src/main/java/org/sonar/runner/batch/IsolatedLauncher.java @@ -1,5 +1,5 @@ /* - * SonarQube Runner - Batch API + * SonarQube Runner - Batch Interface * Copyright (C) 2011 SonarSource * dev@sonar.codehaus.org * @@ -19,11 +19,21 @@ */ package org.sonar.runner.batch; +import org.sonar.home.log.LogListener; + import java.util.List; import java.util.Properties; public interface IsolatedLauncher { void start(Properties properties, List extensions); + + void start(Properties properties, List extensions, LogListener logListener); + void stop(); + void execute(Properties properties); + + void executeOldVersion(Properties properties, List extensions); + + String getVersion(); } diff --git a/sonar-runner-batch/pom.xml b/sonar-runner-batch/pom.xml index e7ad0e3..4fe84da 100644 --- a/sonar-runner-batch/pom.xml +++ b/sonar-runner-batch/pom.xml @@ -10,7 +10,7 @@ SonarQube Runner - Batch - 4.4 + 5.2-SNAPSHOT diff --git a/sonar-runner-batch/src/main/java/org/sonar/runner/batch/BatchIsolatedLauncher.java b/sonar-runner-batch/src/main/java/org/sonar/runner/batch/BatchIsolatedLauncher.java index 069f16a..b88ba6e 100644 --- a/sonar-runner-batch/src/main/java/org/sonar/runner/batch/BatchIsolatedLauncher.java +++ b/sonar-runner-batch/src/main/java/org/sonar/runner/batch/BatchIsolatedLauncher.java @@ -19,18 +19,19 @@ */ 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 org.sonar.home.log.LogListener; +import org.picocontainer.annotations.Nullable; import com.google.common.annotations.VisibleForTesting; +import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import java.util.Properties; -import org.slf4j.LoggerFactory; import org.sonar.api.utils.SonarException; import org.sonar.batch.bootstrapper.Batch; import org.sonar.batch.bootstrapper.EnvironmentInformation; @@ -49,7 +50,12 @@ public class BatchIsolatedLauncher implements IsolatedLauncher { @Override public void start(Properties globalProperties, List extensions) { - batch = createBatch(globalProperties, extensions); + start(globalProperties, extensions, null); + } + + @Override + public void start(Properties globalProperties, List extensions, @Nullable LogListener logListener) { + batch = createBatch(globalProperties, extensions, logListener); batch.start(); } @@ -63,47 +69,38 @@ public class BatchIsolatedLauncher implements IsolatedLauncher { batch.executeTask((Map) properties); } - Batch createBatch(Properties properties, List extensions) { - initLogging(properties); + Batch createBatch(Properties properties, List extensions, @Nullable LogListener logListener) { EnvironmentInformation env = new EnvironmentInformation(properties.getProperty("sonarRunner.app"), properties.getProperty("sonarRunner.appVersion")); - return Batch.builder() + Batch.Builder builder = Batch.builder() .setEnvironment(env) .addComponents(extensions) - .setBootstrapProperties((Map) properties) - .build(); - } + .setBootstrapProperties((Map) properties); - 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); + if (logListener != null) { + builder.setLogListener(logListener); } - } - @VisibleForTesting - protected boolean isDebug(Properties props) { - return Boolean.parseBoolean(props.getProperty("sonar.verbose", FALSE)); + return builder.build(); } - @VisibleForTesting - protected static String getSqlLevel(Properties props) { - boolean showSql = "true".equals(props.getProperty("sonar.showSql", FALSE)); - return showSql ? DEBUG : WARN; + /** + * This method exists for backward compatibility with SonarQube < 5.2. + */ + @Override + public void executeOldVersion(Properties properties, List extensions) { + createBatch(properties, extensions, null).execute(); } - @VisibleForTesting - protected static String getSqlResultsLevel(Properties props) { - boolean showSql = "true".equals(props.getProperty("sonar.showSqlResults", FALSE)); - return showSql ? DEBUG : WARN; + @Override + public String getVersion() { + InputStream is = this.getClass().getClassLoader().getResourceAsStream("sq-version.txt"); + if (is == null) { + return null; + } + try (BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { + return br.readLine(); + } catch (IOException e) { + return null; + } } } diff --git a/sonar-runner-batch/src/main/java/org/sonar/runner/batch/package-info.java b/sonar-runner-batch/src/main/java/org/sonar/runner/batch/package-info.java deleted file mode 100644 index 33773c8..0000000 --- a/sonar-runner-batch/src/main/java/org/sonar/runner/batch/package-info.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 - */ -/** - * Internal package that creates the project definition and launches the analyses based on it. - * Should not be used by consumers. - */ -@ParametersAreNonnullByDefault -package org.sonar.runner.batch; - -import javax.annotation.ParametersAreNonnullByDefault; - diff --git a/sonar-runner-batch/src/test/java/org/sonar/runner/batch/IsolatedLauncherTest.java b/sonar-runner-batch/src/test/java/org/sonar/runner/batch/IsolatedLauncherTest.java index f8fd10c..2108751 100644 --- a/sonar-runner-batch/src/test/java/org/sonar/runner/batch/IsolatedLauncherTest.java +++ b/sonar-runner-batch/src/test/java/org/sonar/runner/batch/IsolatedLauncherTest.java @@ -38,38 +38,8 @@ public class IsolatedLauncherTest { props.setProperty("sonar.projectName", "Sample"); props.setProperty("sonar.projectVersion", "1.0"); props.setProperty("sonar.sources", "src"); - Batch batch = launcher.createBatch(props, Collections.emptyList()); + Batch batch = launcher.createBatch(props, Collections.emptyList(), null); assertThat(batch).isNotNull(); } - - @Test - public void testGetSqlLevel() throws Exception { - assertThat(BatchIsolatedLauncher.getSqlLevel(props)).isEqualTo("WARN"); - - props.setProperty("sonar.showSql", "true"); - assertThat(BatchIsolatedLauncher.getSqlLevel(props)).isEqualTo("DEBUG"); - - props.setProperty("sonar.showSql", "false"); - assertThat(BatchIsolatedLauncher.getSqlLevel(props)).isEqualTo("WARN"); - } - - @Test - public void testGetSqlResultsLevel() throws Exception { - assertThat(BatchIsolatedLauncher.getSqlResultsLevel(props)).isEqualTo("WARN"); - - props.setProperty("sonar.showSqlResults", "true"); - assertThat(BatchIsolatedLauncher.getSqlResultsLevel(props)).isEqualTo("DEBUG"); - - props.setProperty("sonar.showSqlResults", "false"); - assertThat(BatchIsolatedLauncher.getSqlResultsLevel(props)).isEqualTo("WARN"); - } - - @Test - public void shouldDetermineVerboseMode() { - assertThat(launcher.isDebug(props)).isFalse(); - - props.setProperty("sonar.verbose", "true"); - assertThat(launcher.isDebug(props)).isTrue(); - } } diff --git a/sonar-runner-dist/src/main/java/org/sonar/runner/Stats.java b/sonar-runner-dist/src/main/java/org/sonar/runner/Stats.java index ab054d9..12c8e6f 100644 --- a/sonar-runner-dist/src/main/java/org/sonar/runner/Stats.java +++ b/sonar-runner-dist/src/main/java/org/sonar/runner/Stats.java @@ -19,6 +19,8 @@ */ package org.sonar.runner; +import org.sonar.runner.impl.Logs; + class Stats { private long startTime; @@ -32,12 +34,12 @@ class Stats { Stats stop() { long stopTime = System.currentTimeMillis() - startTime; - System.out.println("Total time: " + formatTime(stopTime)); + Logs.info("Total time: " + formatTime(stopTime)); System.gc(); Runtime r = Runtime.getRuntime(); long mb = 1024L * 1024; - System.out.println("Final Memory: " + (r.totalMemory() - r.freeMemory()) / mb + "M/" + r.totalMemory() / mb + "M"); + Logs.info("Final Memory: " + (r.totalMemory() - r.freeMemory()) / mb + "M/" + r.totalMemory() / mb + "M"); return this; } diff --git a/sonar-runner-dist/src/main/java/org/sonar/runner/SystemInfo.java b/sonar-runner-dist/src/main/java/org/sonar/runner/SystemInfo.java index a35c29b..c9575ba 100644 --- a/sonar-runner-dist/src/main/java/org/sonar/runner/SystemInfo.java +++ b/sonar-runner-dist/src/main/java/org/sonar/runner/SystemInfo.java @@ -19,6 +19,8 @@ */ package org.sonar.runner; +import org.sonar.runner.impl.Logs; + import org.sonar.runner.api.RunnerVersion; class SystemInfo { @@ -28,12 +30,12 @@ class SystemInfo { } static void print() { - System.out.println("SonarQube Runner " + RunnerVersion.version()); - System.out.println(java()); - System.out.println(os()); + Logs.info("SonarQube Runner " + RunnerVersion.version()); + Logs.info(java()); + Logs.info(os()); String runnerOpts = System.getenv("SONAR_RUNNER_OPTS"); if (runnerOpts != null) { - System.out.println("SONAR_RUNNER_OPTS=" + runnerOpts); + Logs.info("SONAR_RUNNER_OPTS=" + runnerOpts); } } diff --git a/sonar-runner-dist/src/test/java/org/sonar/runner/StatsTest.java b/sonar-runner-dist/src/test/java/org/sonar/runner/StatsTest.java index b7062ad..edf61ba 100644 --- a/sonar-runner-dist/src/test/java/org/sonar/runner/StatsTest.java +++ b/sonar-runner-dist/src/test/java/org/sonar/runner/StatsTest.java @@ -19,8 +19,10 @@ */ package org.sonar.runner; -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; +import org.sonar.home.log.LogListener; + +import org.sonar.runner.impl.Logs; + import java.io.UnsupportedEncodingException; import org.junit.Test; @@ -31,17 +33,22 @@ public class StatsTest { @Test public void shouldPrintStats() throws UnsupportedEncodingException { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - System.setOut(new PrintStream(output)); + final StringBuffer sb = new StringBuffer(); + Logs.setListener(new LogListener() { + @Override + public void log(String msg, Level level) { + sb.append(msg + System.lineSeparator()); + } + }); new Stats().start().stop(); - String out = output.toString("UTF-8"); - String[] lines = out.split("\n"); + String out = sb.toString(); + String[] lines = out.split(System.lineSeparator()); assertThat(lines).hasSize(2); - assertThat(lines[0]).startsWith("Total time: "); - assertThat(lines[1]).startsWith("Final Memory: "); + assertThat(lines[0]).contains("Total time: "); + assertThat(lines[1]).contains("Final Memory: "); } @Test diff --git a/sonar-runner-impl/pom.xml b/sonar-runner-impl/pom.xml index 95fc302..56dcdb2 100644 --- a/sonar-runner-impl/pom.xml +++ b/sonar-runner-impl/pom.xml @@ -123,6 +123,11 @@ org.sonar.home org.sonar.runner.home + + + org.sonar.home.log.LogListener + org.sonar.home.log.LogListener$Level + diff --git a/sonar-runner-impl/src/main/java/org/sonar/runner/impl/BatchLauncherMain.java b/sonar-runner-impl/src/main/java/org/sonar/runner/impl/BatchLauncherMain.java index 1e4ab2c..2921742 100644 --- a/sonar-runner-impl/src/main/java/org/sonar/runner/impl/BatchLauncherMain.java +++ b/sonar-runner-impl/src/main/java/org/sonar/runner/impl/BatchLauncherMain.java @@ -40,8 +40,12 @@ public class BatchLauncherMain { Properties props = loadProperties(args[0]); IsolatedLauncher launcher = launcherFactory.createLauncher(props); launcher.start(props, Collections.emptyList()); - launcher.execute(props); - launcher.stop(); + try { + launcher.execute(props); + } finally { + //persistit has non-daemon threads that need to be stopped or the jvm w'ont quit + launcher.stop(); + } } private static Properties loadProperties(String arg) throws IOException { diff --git a/sonar-runner-impl/src/main/java/org/sonar/runner/impl/IsolatedLauncherFactory.java b/sonar-runner-impl/src/main/java/org/sonar/runner/impl/IsolatedLauncherFactory.java index 3a9b594..5a87996 100644 --- a/sonar-runner-impl/src/main/java/org/sonar/runner/impl/IsolatedLauncherFactory.java +++ b/sonar-runner-impl/src/main/java/org/sonar/runner/impl/IsolatedLauncherFactory.java @@ -69,7 +69,7 @@ public class IsolatedLauncherFactory { } private static void addIsolatedLauncherMaskRule(Properties props) { - String unmask = "UNMASK|org.sonar.runner.batch.IsolatedLauncher"; + String unmask = "UNMASK|org.sonar.runner.batch.IsolatedLauncher,UNMASK|org.sonar.home.log.LogListener"; String currentRules = (String) props.get(InternalProperties.RUNNER_MASK_RULES); if (currentRules == null) { diff --git a/sonar-runner-impl/src/main/java/org/sonar/runner/impl/Logs.java b/sonar-runner-impl/src/main/java/org/sonar/runner/impl/Logs.java index d643162..fd60ce9 100644 --- a/sonar-runner-impl/src/main/java/org/sonar/runner/impl/Logs.java +++ b/sonar-runner-impl/src/main/java/org/sonar/runner/impl/Logs.java @@ -19,11 +19,37 @@ */ package org.sonar.runner.impl; +import org.sonar.home.log.LogListener.Level; + +import org.sonar.home.log.LogListener; + +import javax.annotation.Nullable; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; + public class Logs { + private static LogListener listener = new PrintStreamLogListener(getDefaultFwdMap()); + private static boolean debugEnabled = false; + private Logs() { } - private static boolean debugEnabled = false; + public static void setListener(@Nullable LogListener listener) { + if (listener == null) { + Logs.listener = new PrintStreamLogListener(getDefaultFwdMap()); + } else { + Logs.listener = listener; + } + } + + public static LogListener getListener() { + return Logs.listener; + } public static void setDebugEnabled(boolean debugEnabled) { Logs.debugEnabled = debugEnabled; @@ -35,26 +61,63 @@ public class Logs { public static void debug(String message) { if (isDebugEnabled()) { - System.out.println("DEBUG: " + message); + log(message, Level.DEBUG); } } public static void info(String message) { - System.out.println("INFO: " + message); + log(message, Level.INFO); } public static void warn(String message) { - System.out.println("WARN: " + message); + log(message, Level.WARN); } public static void error(String message) { - System.err.println("ERROR: " + message); + log(message, Level.ERROR); } public static void error(String message, Throwable t) { - System.err.println("ERROR: " + message); + log(message, Level.ERROR); if (t != null) { - t.printStackTrace(System.err); + StringWriter sw = new StringWriter(); + + t.printStackTrace(new PrintWriter(sw)); + String[] lines = sw.toString().split(System.getProperty("line.separator")); + for (String l : lines) { + log(l, Level.ERROR); + } + } + } + + private static void log(String msg, Level level) { + listener.log(msg, level); + } + + private static Map getDefaultFwdMap() { + Map map = new EnumMap<>(Level.class); + + map.put(Level.ERROR, System.err); + map.put(Level.WARN, System.out); + map.put(Level.INFO, System.out); + map.put(Level.DEBUG, System.out); + map.put(Level.TRACE, System.out); + return map; + } + + private static class PrintStreamLogListener implements LogListener { + Map forwardMap; + + PrintStreamLogListener(Map forwardMap) { + this.forwardMap = new HashMap<>(forwardMap); + } + + @Override + public void log(String msg, Level level) { + PrintStream ps = forwardMap.get(level); + if (ps != null) { + ps.append(level.toString() + ": " + msg + System.lineSeparator()); + } } } } diff --git a/sonar-runner-impl/src/test/java/org/sonar/runner/impl/IsolatedLauncherFactoryTest.java b/sonar-runner-impl/src/test/java/org/sonar/runner/impl/IsolatedLauncherFactoryTest.java index bfdd54a..98154b2 100644 --- a/sonar-runner-impl/src/test/java/org/sonar/runner/impl/IsolatedLauncherFactoryTest.java +++ b/sonar-runner-impl/src/test/java/org/sonar/runner/impl/IsolatedLauncherFactoryTest.java @@ -19,6 +19,7 @@ */ package org.sonar.runner.impl; +import org.sonar.home.log.LogListener; import org.junit.Before; import org.sonar.runner.batch.IsolatedLauncher; @@ -102,5 +103,18 @@ public class IsolatedLauncherFactoryTest { public void execute(Properties properties) { FakeIsolatedLauncher.props = properties; } + + @Override + public void start(Properties properties, List extensions, LogListener logListener) { + } + + @Override + public void executeOldVersion(Properties properties, List extensions) { + } + + @Override + public String getVersion() { + return null; + } } } diff --git a/sonar-runner-impl/src/test/java/org/sonar/runner/impl/LogsTest.java b/sonar-runner-impl/src/test/java/org/sonar/runner/impl/LogsTest.java new file mode 100644 index 0000000..4517c20 --- /dev/null +++ b/sonar-runner-impl/src/test/java/org/sonar/runner/impl/LogsTest.java @@ -0,0 +1,106 @@ +/* + * 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.log.LogListener; + +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; + +import static org.assertj.core.api.Assertions.assertThat; +import org.junit.Before; + +public class LogsTest { + private static final String EXPECTED_DEBUG = "DEBUG: debug\n"; + private static final String EXPECTED_INFO = "INFO: info\n"; + private static final String EXPECTED_ERROR = "ERROR: error\n"; + + private ByteArrayOutputStream recordedSystemOut = new ByteArrayOutputStream(); + private ByteArrayOutputStream recordedSystemErr = new ByteArrayOutputStream(); + + @Before + public void restoreDefault() { + recordedSystemOut = new ByteArrayOutputStream(); + recordedSystemErr = new ByteArrayOutputStream(); + + System.setOut(new PrintStream(recordedSystemOut)); + System.setErr(new PrintStream(recordedSystemErr)); + + Logs.setDebugEnabled(false); + Logs.setListener(null); + } + + @Test + public void testNull() throws UnsupportedEncodingException { + Logs.setListener(null); + testDefault(); + } + + @Test + public void testDefault() throws UnsupportedEncodingException { + writeTest(); + + assertThat(recordedSystemOut.toString(StandardCharsets.UTF_8.name())).isEqualTo(EXPECTED_INFO); + assertThat(recordedSystemErr.toString(StandardCharsets.UTF_8.name())).isEqualTo(EXPECTED_ERROR); + } + + @Test + public void testDebug() throws UnsupportedEncodingException { + Logs.setDebugEnabled(true); + writeTest(); + + assertThat(recordedSystemOut.toString(StandardCharsets.UTF_8.name())).isEqualTo(EXPECTED_DEBUG + EXPECTED_INFO); + assertThat(recordedSystemErr.toString(StandardCharsets.UTF_8.name())).isEqualTo(EXPECTED_ERROR); + } + + @Test + public void testCustomListener() { + TestLogListener listener = new TestLogListener(); + + Logs.setListener(listener); + Logs.setDebugEnabled(true); + + Logs.debug("debug"); + + assertThat(listener.msg).isEqualTo("debug"); + assertThat(listener.level).isEqualTo(LogListener.Level.DEBUG); + } + + private class TestLogListener implements LogListener { + String msg; + Level level; + + @Override + public void log(String msg, Level level) { + this.msg = msg; + this.level = level; + } + } + + private static void writeTest() { + Logs.debug("debug"); + Logs.info("info"); + Logs.error("error"); + } +}