]> source.dussan.org Git - sonar-scanner-cli.git/commitdiff
SONARUNNER-138 Allow to redirect logs to a custom stream
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Tue, 16 Jun 2015 15:38:52 +0000 (17:38 +0200)
committerDuarte Meneses <duarte.meneses@sonarsource.com>
Fri, 19 Jun 2015 15:08:54 +0000 (17:08 +0200)
24 files changed:
sonar-runner-api/src/main/java/org/sonar/runner/api/CommandExecutor.java
sonar-runner-api/src/main/java/org/sonar/runner/api/EmbeddedRunner.java
sonar-runner-api/src/main/java/org/sonar/runner/api/ForkedRunner.java
sonar-runner-api/src/main/java/org/sonar/runner/api/Runner.java
sonar-runner-api/src/main/java/org/sonar/runner/api/Utils.java
sonar-runner-api/src/test/java/org/sonar/runner/api/EmbeddedRunnerTest.java
sonar-runner-api/src/test/java/org/sonar/runner/api/ForkedRunnerTest.java
sonar-runner-api/src/test/java/org/sonar/runner/api/OsTest.java
sonar-runner-api/src/test/java/org/sonar/runner/api/UtilsTest.java
sonar-runner-batch-interface/pom.xml
sonar-runner-batch-interface/src/main/java/org/sonar/runner/batch/IsolatedLauncher.java
sonar-runner-batch/pom.xml
sonar-runner-batch/src/main/java/org/sonar/runner/batch/BatchIsolatedLauncher.java
sonar-runner-batch/src/main/java/org/sonar/runner/batch/package-info.java [deleted file]
sonar-runner-batch/src/test/java/org/sonar/runner/batch/IsolatedLauncherTest.java
sonar-runner-dist/src/main/java/org/sonar/runner/Stats.java
sonar-runner-dist/src/main/java/org/sonar/runner/SystemInfo.java
sonar-runner-dist/src/test/java/org/sonar/runner/StatsTest.java
sonar-runner-impl/pom.xml
sonar-runner-impl/src/main/java/org/sonar/runner/impl/BatchLauncherMain.java
sonar-runner-impl/src/main/java/org/sonar/runner/impl/IsolatedLauncherFactory.java
sonar-runner-impl/src/main/java/org/sonar/runner/impl/Logs.java
sonar-runner-impl/src/test/java/org/sonar/runner/impl/IsolatedLauncherFactoryTest.java
sonar-runner-impl/src/test/java/org/sonar/runner/impl/LogsTest.java [new file with mode: 0644]

index d9903bf5d82bedc3ef89ab306b66fd47ed218014..fd5b6d82d01ac1d8a80aa25fb8ecbc6ee8801889 100644 (file)
@@ -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);
       }
     }
   }
index 9dfad19750843a1ec686e1d7bd0493043e7a480c..e1c04b7a79b44731a38e5dc092b7718684c4ba5b 100644 (file)
  */
 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<EmbeddedRunner> {
-
   private final IsolatedLauncherFactory launcherFactory;
   private IsolatedLauncher launcher;
+  private String sqVersion;
   private final List<Object> extensions = new ArrayList<Object>();
   private static final String MASK_RULES_PROP = "sonarRunner.maskRules";
 
@@ -53,6 +55,11 @@ public class EmbeddedRunner extends Runner<EmbeddedRunner> {
     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<EmbeddedRunner> {
   @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);
+    }
   }
 }
index 02fdae76d191df8a20fbe8768d5eb0d15b437d36..31a932f4042f12daf849ae4a8b24edd3dac486ab 100644 (file)
@@ -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<ForkedRunner> {
   private final Map<String, String> jvmEnvVariables = new HashMap<String, String>();
   private final List<String> jvmArguments = new ArrayList<String>();
   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<ForkedRunner> {
   }
 
   /**
-   * 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<ForkedRunner> {
   }
 
   /**
-   * 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<ForkedRunner> {
 
   @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<ForkedRunner> {
       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<ForkedRunner> {
 
   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()) {
index 081b7f75fea0cf43dbdcbbe7ac7d06e7e5c63215..890ce46432d2b31b4be7a11f3603f72ef5252733 100644 (file)
@@ -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<T extends Runner> {
-
   private final Properties globalProperties = new Properties();
 
   protected Runner() {
@@ -41,6 +43,16 @@ public abstract class Runner<T extends 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<T extends 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);
     }
index 4d0524a650dd7ed2f8e91dcb6a28e32fe707bed9..4d4907a831a840f26eb536400ef9fbdd80d1f8e6 100644 (file)
@@ -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()
index 2082b29b1982f641fb84f371f40237a95f8bbe09..84a256aef7c786d715fe5a3f0b32fc059a38534c 100644 (file)
@@ -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<Object> 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<Properties>() {
+      @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<Properties>() {
+      @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() {
index 0c27273444259234d903f6c7eac4bc70b51acca5..e7c3a5624fb09301d6cc37df2992cb6d21c210f4 100644 (file)
  */
 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<StreamConsumer> arg1 = ArgumentCaptor.forClass(StreamConsumer.class);
+    ArgumentCaptor<StreamConsumer> 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<String> printedLines = new LinkedList<>();
+    StreamConsumer consumer = new StreamConsumer() {
+      @Override
+      public void consumeLine(String line) {
+        printedLines.add(line);
+      }
+    };
+    JarExtractor jarExtractor = createMockExtractor();
 
-  static class StdConsumerMatcher extends ArgumentMatcher<StreamConsumer> {
-    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
index 31e2c8fea37480c02f25fe6fa2e394fc35e1a20f..79550e0e7ea8fbf83b6ea979f2b05e2be31180b5 100644 (file)
@@ -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");
index a56030df99a6dad878966a8c9ad76f715ef0ccc4..a268ec3f4402052a573b00ea076da7381f32f489 100644 (file)
@@ -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);
index d70c04a83d341d3acf350ed314f566adf49645d8..9ccb3b98711790c6e7a2c2ee54032e04cc558ac6 100644 (file)
@@ -7,5 +7,13 @@
   </parent>
 
   <artifactId>sonar-runner-batch-interface</artifactId>
-  <name>SonarQube Runner - Batch API</name>
+  <name>SonarQube Runner - Batch Interface</name>
+  
+  <dependencies>
+    <dependency>
+      <groupId>org.codehaus.sonar</groupId>
+      <artifactId>sonar-home</artifactId>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
 </project>
index 71d97c319faf117deb3b3349700e6901dcb3b5f8..22fa54de0e5c15d2adb874e1eb17893ae26daaea 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * SonarQube Runner - Batch API
+ * SonarQube Runner - Batch Interface
  * Copyright (C) 2011 SonarSource
  * dev@sonar.codehaus.org
  *
  */
 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<Object> extensions);
+
+  void start(Properties properties, List<Object> extensions, LogListener logListener);
+
   void stop();
+
   void execute(Properties properties);
+
+  void executeOldVersion(Properties properties, List<Object> extensions);
+
+  String getVersion();
 }
index e7ad0e3a0247a884fe26221b4ef2c374929e14ba..4fe84da6e980d2ab35f8cb263b96068d53cdd48a 100644 (file)
@@ -10,7 +10,7 @@
   <name>SonarQube Runner - Batch</name>
 
   <properties>
-    <sonarBatchVersion>4.4</sonarBatchVersion>
+    <sonarBatchVersion>5.2-SNAPSHOT</sonarBatchVersion>
   </properties>
 
   <dependencies>
index 069f16a17ace511113832653d921282e5e5110e5..b88ba6e56656a293ecb39fd29e6c59a3713c63ca 100644 (file)
  */
 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<Object> extensions) {
-    batch = createBatch(globalProperties, extensions);
+    start(globalProperties, extensions, null);
+  }
+
+  @Override
+  public void start(Properties globalProperties, List<Object> 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<Object> extensions) {
-    initLogging(properties);
+  Batch createBatch(Properties properties, List<Object> 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<Object> 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 (file)
index 33773c8..0000000
+++ /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;
-
index f8fd10c165fbd2d76771294349ff649f2fd00e6f..2108751bf5d6638df953c3ac379e3e9f9f8d161b 100644 (file)
@@ -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();
-  }
 }
index ab054d951e7c4f884b5696417577dd66f7fb1c32..12c8e6f5f382a8bd22d42c3149f065b9591ebcf8 100644 (file)
@@ -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;
   }
index a35c29b68a5e6b660bdc0eda10825f75dd83afab..c9575ba19f75296161bbe17a333a47a2591a678a 100644 (file)
@@ -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);
     }
   }
 
index b7062ad44315d58406adb1e6bbebd91bc5bc4f84..edf61ba01f4583c13824e47529c58945593c6f28 100644 (file)
  */
 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
index 95fc3029ad60773e0cba4d00fd76cb381cba0e25..56dcdb26f92ad5047bdc672841acf8bdc3023d18 100644 (file)
                 <relocation>
                   <pattern>org.sonar.home</pattern>
                   <shadedPattern>org.sonar.runner.home</shadedPattern>
+                  <!--  shared between sonar-runner-api, sonar-runner-impl and sonar-batch -->
+                   <excludes>
+                    <exclude>org.sonar.home.log.LogListener</exclude>
+                    <exclude>org.sonar.home.log.LogListener$Level</exclude>
+                   </excludes>
                 </relocation>
               </relocations>
             </configuration>
index 1e4ab2ca1da49f91a1b00dc2a19922846b6e74f1..2921742a3260938e77684618467ce3da89c3f04d 100644 (file)
@@ -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 {
index 3a9b594baf732eadd7359620490fc221f7042b56..5a879967936c65ce30990d2e6b3ef8997223c233 100644 (file)
@@ -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) {
index d643162418ae9c129f69e05169b731647eb2649c..fd60ce9e612dd1f4d3149f8154577c0b8692be27 100644 (file)
  */
 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<Level, PrintStream> getDefaultFwdMap() {
+    Map<Level, PrintStream> 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<Level, PrintStream> forwardMap;
+
+    PrintStreamLogListener(Map<Level, PrintStream> 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());
+      }
     }
   }
 }
index bfdd54a8814c575d687a893d1f8d55b729843e58..98154b28b055df86742dd16005e04403d6f770d1 100644 (file)
@@ -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<Object> extensions, LogListener logListener) {
+    }
+
+    @Override
+    public void executeOldVersion(Properties properties, List<Object> 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 (file)
index 0000000..4517c20
--- /dev/null
@@ -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");
+  }
+}