@@ -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> |
@@ -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> |
@@ -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(); | |||
} |
@@ -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()); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} |
@@ -34,6 +34,7 @@ public class PrintStreamConsumer implements StreamConsumer { | |||
this.output = output; | |||
} | |||
@Override | |||
public void consumeLine(String line) { | |||
output.println(line); | |||
} |
@@ -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); | |||
} | |||
} | |||
@@ -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)" : "")); | |||
} | |||
} | |||
} |
@@ -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; | |||
} |
@@ -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()); |
@@ -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)); |
@@ -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"); |
@@ -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() { | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -0,0 +1 @@ | |||
/target/ |
@@ -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> |
@@ -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); | |||
} |
@@ -0,0 +1,5 @@ | |||
sonar.projectKey=sample | |||
sonar.projectName=Sample | |||
sonar.projectVersion=1.0-SNAPSHOT | |||
sonar.projectDescription=Sample description | |||
sonar.sources=src |
@@ -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> |
@@ -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) { |
@@ -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 |
@@ -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); | |||
} | |||
} |
@@ -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); |
@@ -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."); | |||
} | |||
@@ -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; | |||
} | |||
} |
@@ -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() { |
@@ -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(); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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 |
@@ -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> |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
}); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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; | |||
} |
@@ -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; |
@@ -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 |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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(); | |||
} | |||
} | |||
} |