]> source.dussan.org Git - sonar-scanner-cli.git/commitdiff
SONARUNNER-136 and SONARUNNER-137 - Provide a new API to run several analysis with...
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Mon, 15 Jun 2015 12:59:17 +0000 (14:59 +0200)
committerDuarte Meneses <duarte.meneses@sonarsource.com>
Thu, 18 Jun 2015 15:26:05 +0000 (17:26 +0200)
44 files changed:
pom.xml
sonar-runner-api/pom.xml
sonar-runner-api/src/main/java/org/sonar/runner/api/CommandExecutor.java
sonar-runner-api/src/main/java/org/sonar/runner/api/Dirs.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/PrintStreamConsumer.java
sonar-runner-api/src/main/java/org/sonar/runner/api/Runner.java
sonar-runner-api/src/main/java/org/sonar/runner/api/SourceEncoding.java
sonar-runner-api/src/main/java/org/sonar/runner/api/Utils.java
sonar-runner-api/src/test/java/org/sonar/runner/api/DirsTest.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/SimpleRunner.java
sonar-runner-api/src/test/java/org/sonar/runner/api/SourceEncodingTest.java
sonar-runner-batch-interface/.gitignore [new file with mode: 0644]
sonar-runner-batch-interface/pom.xml [new file with mode: 0644]
sonar-runner-batch-interface/src/main/java/org/sonar/runner/batch/IsolatedLauncher.java [new file with mode: 0644]
sonar-runner-batch-interface/src/test/java_sample/sonar-project.properties [new file with mode: 0644]
sonar-runner-batch-interface/src/test/java_sample/src/Sample.java [new file with mode: 0644]
sonar-runner-batch/pom.xml
sonar-runner-batch/src/main/java/org/sonar/runner/batch/BatchIsolatedLauncher.java [new file with mode: 0644]
sonar-runner-batch/src/main/java/org/sonar/runner/batch/IsolatedLauncher.java [deleted file]
sonar-runner-batch/src/test/java/org/sonar/runner/batch/IsolatedLauncherTest.java
sonar-runner-dist/src/main/java/org/sonar/runner/Cli.java
sonar-runner-dist/src/main/java/org/sonar/runner/Conf.java
sonar-runner-dist/src/main/java/org/sonar/runner/Main.java
sonar-runner-dist/src/main/java/org/sonar/runner/RunnerFactory.java
sonar-runner-dist/src/test/java/org/sonar/runner/CliTest.java
sonar-runner-dist/src/test/java/org/sonar/runner/MainTest.java
sonar-runner-dist/src/test/java/org/sonar/runner/RunnerFactoryTest.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/BatchLauncher.java [deleted file]
sonar-runner-impl/src/main/java/org/sonar/runner/impl/BatchLauncherMain.java
sonar-runner-impl/src/main/java/org/sonar/runner/impl/IsolatedLauncherFactory.java [new file with mode: 0644]
sonar-runner-impl/src/main/java/org/sonar/runner/impl/IsolatedLauncherProxy.java [new file with mode: 0644]
sonar-runner-impl/src/main/java/org/sonar/runner/impl/Jars.java
sonar-runner-impl/src/main/java/org/sonar/runner/impl/ServerConnection.java
sonar-runner-impl/src/main/java/org/sonar/runner/impl/TempCleaning.java
sonar-runner-impl/src/test/java/org/sonar/runner/impl/BatchLauncherMainTest.java
sonar-runner-impl/src/test/java/org/sonar/runner/impl/BatchLauncherTest.java [deleted file]
sonar-runner-impl/src/test/java/org/sonar/runner/impl/IsolatedLauncherFactoryTest.java [new file with mode: 0644]
sonar-runner-impl/src/test/java/org/sonar/runner/impl/IsolatedLauncherProxyTest.java [new file with mode: 0644]

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