Browse Source

SONARUNNER-136 and SONARUNNER-137 - Provide a new API to run several analysis with same process and an interactive mode

tags/2.5-rc1
Duarte Meneses 9 years ago
parent
commit
e4ba9869d5
42 changed files with 916 additions and 379 deletions
  1. 1
    0
      pom.xml
  2. 1
    6
      sonar-runner-api/pom.xml
  3. 4
    1
      sonar-runner-api/src/main/java/org/sonar/runner/api/CommandExecutor.java
  4. 16
    12
      sonar-runner-api/src/main/java/org/sonar/runner/api/Dirs.java
  5. 24
    9
      sonar-runner-api/src/main/java/org/sonar/runner/api/EmbeddedRunner.java
  6. 24
    7
      sonar-runner-api/src/main/java/org/sonar/runner/api/ForkedRunner.java
  7. 1
    0
      sonar-runner-api/src/main/java/org/sonar/runner/api/PrintStreamConsumer.java
  8. 55
    36
      sonar-runner-api/src/main/java/org/sonar/runner/api/Runner.java
  9. 9
    6
      sonar-runner-api/src/main/java/org/sonar/runner/api/SourceEncoding.java
  10. 12
    2
      sonar-runner-api/src/main/java/org/sonar/runner/api/Utils.java
  11. 20
    22
      sonar-runner-api/src/test/java/org/sonar/runner/api/DirsTest.java
  12. 78
    24
      sonar-runner-api/src/test/java/org/sonar/runner/api/EmbeddedRunnerTest.java
  13. 23
    6
      sonar-runner-api/src/test/java/org/sonar/runner/api/ForkedRunnerTest.java
  14. 14
    2
      sonar-runner-api/src/test/java/org/sonar/runner/api/SimpleRunner.java
  15. 12
    12
      sonar-runner-api/src/test/java/org/sonar/runner/api/SourceEncodingTest.java
  16. 1
    0
      sonar-runner-batch-interface/.gitignore
  17. 11
    0
      sonar-runner-batch-interface/pom.xml
  18. 29
    0
      sonar-runner-batch-interface/src/main/java/org/sonar/runner/batch/IsolatedLauncher.java
  19. 5
    0
      sonar-runner-batch-interface/src/test/java_sample/sonar-project.properties
  20. 0
    0
      sonar-runner-batch-interface/src/test/java_sample/src/Sample.java
  21. 7
    1
      sonar-runner-batch/pom.xml
  22. 17
    3
      sonar-runner-batch/src/main/java/org/sonar/runner/batch/BatchIsolatedLauncher.java
  23. 7
    7
      sonar-runner-batch/src/test/java/org/sonar/runner/batch/IsolatedLauncherTest.java
  24. 23
    2
      sonar-runner-dist/src/main/java/org/sonar/runner/Cli.java
  25. 4
    4
      sonar-runner-dist/src/main/java/org/sonar/runner/Conf.java
  26. 58
    18
      sonar-runner-dist/src/main/java/org/sonar/runner/Main.java
  27. 3
    3
      sonar-runner-dist/src/main/java/org/sonar/runner/RunnerFactory.java
  28. 13
    3
      sonar-runner-dist/src/test/java/org/sonar/runner/CliTest.java
  29. 70
    12
      sonar-runner-dist/src/test/java/org/sonar/runner/MainTest.java
  30. 9
    6
      sonar-runner-dist/src/test/java/org/sonar/runner/RunnerFactoryTest.java
  31. 15
    3
      sonar-runner-dist/src/test/java/org/sonar/runner/StatsTest.java
  32. 11
    0
      sonar-runner-impl/pom.xml
  33. 11
    6
      sonar-runner-impl/src/main/java/org/sonar/runner/impl/BatchLauncherMain.java
  34. 49
    47
      sonar-runner-impl/src/main/java/org/sonar/runner/impl/IsolatedLauncherFactory.java
  35. 82
    0
      sonar-runner-impl/src/main/java/org/sonar/runner/impl/IsolatedLauncherProxy.java
  36. 1
    0
      sonar-runner-impl/src/main/java/org/sonar/runner/impl/Jars.java
  37. 1
    1
      sonar-runner-impl/src/main/java/org/sonar/runner/impl/ServerConnection.java
  38. 1
    1
      sonar-runner-impl/src/main/java/org/sonar/runner/impl/TempCleaning.java
  39. 14
    13
      sonar-runner-impl/src/test/java/org/sonar/runner/impl/BatchLauncherMainTest.java
  40. 0
    104
      sonar-runner-impl/src/test/java/org/sonar/runner/impl/BatchLauncherTest.java
  41. 106
    0
      sonar-runner-impl/src/test/java/org/sonar/runner/impl/IsolatedLauncherFactoryTest.java
  42. 74
    0
      sonar-runner-impl/src/test/java/org/sonar/runner/impl/IsolatedLauncherProxyTest.java

+ 1
- 0
pom.xml View File

@@ -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>

+ 1
- 6
sonar-runner-api/pom.xml View File

@@ -15,12 +15,7 @@
<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>

+ 4
- 1
sonar-runner-api/src/main/java/org/sonar/runner/api/CommandExecutor.java View 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();
}

+ 16
- 12
sonar-runner-api/src/main/java/org/sonar/runner/api/Dirs.java View 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());
}
}

+ 24
- 9
sonar-runner-api/src/main/java/org/sonar/runner/api/EmbeddedRunner.java View File

@@ -19,11 +19,14 @@
*/
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);
}
}

+ 24
- 7
sonar-runner-api/src/main/java/org/sonar/runner/api/ForkedRunner.java View 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);
}

+ 1
- 0
sonar-runner-api/src/main/java/org/sonar/runner/api/PrintStreamConsumer.java View File

@@ -34,6 +34,7 @@ public class PrintStreamConsumer implements StreamConsumer {
this.output = output;
}

@Override
public void consumeLine(String line) {
output.println(line);
}

+ 55
- 36
sonar-runner-api/src/main/java/org/sonar/runner/api/Runner.java View 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);
}
}


+ 9
- 6
sonar-runner-api/src/main/java/org/sonar/runner/api/SourceEncoding.java View 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)" : ""));
}
}
}

+ 12
- 2
sonar-runner-api/src/main/java/org/sonar/runner/api/Utils.java View 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;
}

+ 20
- 22
sonar-runner-api/src/test/java/org/sonar/runner/api/DirsTest.java View 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());

+ 78
- 24
sonar-runner-api/src/test/java/org/sonar/runner/api/EmbeddedRunnerTest.java View File

@@ -19,19 +19,22 @@
*/
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));

+ 23
- 6
sonar-runner-api/src/test/java/org/sonar/runner/api/ForkedRunnerTest.java View 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");

+ 14
- 2
sonar-runner-api/src/test/java/org/sonar/runner/api/SimpleRunner.java View File

@@ -19,8 +19,20 @@
*/
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() {
}
}

+ 12
- 12
sonar-runner-api/src/test/java/org/sonar/runner/api/SourceEncodingTest.java View 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();
}
}

+ 1
- 0
sonar-runner-batch-interface/.gitignore View File

@@ -0,0 +1 @@
/target/

+ 11
- 0
sonar-runner-batch-interface/pom.xml View File

@@ -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>

+ 29
- 0
sonar-runner-batch-interface/src/main/java/org/sonar/runner/batch/IsolatedLauncher.java View File

@@ -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);
}

+ 5
- 0
sonar-runner-batch-interface/src/test/java_sample/sonar-project.properties View File

@@ -0,0 +1,5 @@
sonar.projectKey=sample
sonar.projectName=Sample
sonar.projectVersion=1.0-SNAPSHOT
sonar.projectDescription=Sample description
sonar.sources=src

+ 0
- 0
sonar-runner-batch-interface/src/test/java_sample/src/Sample.java View File


+ 7
- 1
sonar-runner-batch/pom.xml View File

@@ -10,10 +10,16 @@
<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>

sonar-runner-batch/src/main/java/org/sonar/runner/batch/IsolatedLauncher.java → sonar-runner-batch/src/main/java/org/sonar/runner/batch/BatchIsolatedLauncher.java View File

@@ -39,14 +39,28 @@ 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 {
public class BatchIsolatedLauncher implements 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();
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) {

+ 7
- 7
sonar-runner-batch/src/test/java/org/sonar/runner/batch/IsolatedLauncherTest.java View 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

+ 23
- 2
sonar-runner-dist/src/main/java/org/sonar/runner/Cli.java View 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);
}
}

+ 4
- 4
sonar-runner-dist/src/main/java/org/sonar/runner/Conf.java View 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);

+ 58
- 18
sonar-runner-dist/src/main/java/org/sonar/runner/Main.java View File

@@ -19,6 +19,14 @@
*/
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.");
}


+ 3
- 3
sonar-runner-dist/src/main/java/org/sonar/runner/RunnerFactory.java View 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;
}
}

+ 13
- 3
sonar-runner-dist/src/test/java/org/sonar/runner/CliTest.java View File

@@ -19,13 +19,16 @@
*/
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() {

+ 70
- 12
sonar-runner-dist/src/test/java/org/sonar/runner/MainTest.java View File

@@ -19,51 +19,109 @@
*/
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();
}
}

+ 9
- 6
sonar-runner-dist/src/test/java/org/sonar/runner/RunnerFactoryTest.java View 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();
}
}

+ 15
- 3
sonar-runner-dist/src/test/java/org/sonar/runner/StatsTest.java View File

@@ -19,17 +19,29 @@
*/
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

+ 11
- 0
sonar-runner-impl/pom.xml View File

@@ -33,6 +33,11 @@
<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>
@@ -50,6 +55,12 @@
<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>

+ 11
- 6
sonar-runner-impl/src/main/java/org/sonar/runner/impl/BatchLauncherMain.java View File

@@ -19,16 +19,18 @@
*/
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);
}
}

sonar-runner-impl/src/main/java/org/sonar/runner/impl/BatchLauncher.java → sonar-runner-impl/src/main/java/org/sonar/runner/impl/IsolatedLauncherFactory.java View File

@@ -19,38 +19,31 @@
*/
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.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;
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
*/
BatchLauncher(String isolatedLauncherClass, TempCleaning tempCleaning) {
this.isolatedLauncherClass = isolatedLauncherClass;
IsolatedLauncherFactory(String isolatedLauncherClassName, TempCleaning tempCleaning) {
this.tempCleaning = tempCleaning;
this.launcherImplClassName = isolatedLauncherClassName;
}

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);
public IsolatedLauncherFactory() {
this(ISOLATED_LAUNCHER_IMPL, new TempCleaning());
}

private static PersistentCache getCache(Properties props) {
@@ -63,9 +56,9 @@ public class BatchLauncher {
return builder.build();
}

private static String[][] getMaskRules(final Properties props) {
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[] 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("\\|");
@@ -75,43 +68,52 @@ public class BatchLauncher {
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 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 Object delegateExecution(IsolatedClassloader classloader, Properties properties, List<Object> extensions) {
ClassLoader initialContextClassLoader = Thread.currentThread().getContextClassLoader();
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 {
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());
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);
} finally {
Thread.currentThread().setContextClassLoader(initialContextClassLoader);
}
}
});
}

}

+ 82
- 0
sonar-runner-impl/src/main/java/org/sonar/runner/impl/IsolatedLauncherProxy.java View File

@@ -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();
}
}

+ 1
- 0
sonar-runner-impl/src/main/java/org/sonar/runner/impl/Jars.java View 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);
}

+ 1
- 1
sonar-runner-impl/src/main/java/org/sonar/runner/impl/ServerConnection.java View 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;
}

+ 1
- 1
sonar-runner-impl/src/main/java/org/sonar/runner/impl/TempCleaning.java View 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;

+ 14
- 13
sonar-runner-impl/src/test/java/org/sonar/runner/impl/BatchLauncherMainTest.java View File

@@ -19,14 +19,17 @@
*/
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

+ 0
- 104
sonar-runner-impl/src/test/java/org/sonar/runner/impl/BatchLauncherTest.java View File

@@ -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;
}
}
}

+ 106
- 0
sonar-runner-impl/src/test/java/org/sonar/runner/impl/IsolatedLauncherFactoryTest.java View File

@@ -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;
}
}
}

+ 74
- 0
sonar-runner-impl/src/test/java/org/sonar/runner/impl/IsolatedLauncherProxyTest.java View File

@@ -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();
}
}
}

Loading…
Cancel
Save