aboutsummaryrefslogtreecommitdiffstats
path: root/plugins/sonar-svn-plugin/src
diff options
context:
space:
mode:
authorJulien HENRY <julien.henry@sonarsource.com>2014-09-30 16:49:55 +0200
committerJulien HENRY <julien.henry@sonarsource.com>2014-10-02 17:52:23 +0200
commit2d60412e140a950436349f80f928c0e3a073c287 (patch)
treee5f864261e0b66c0aeca092e431a82c40c8ab0d2 /plugins/sonar-svn-plugin/src
parent327799ec3efa6e9cb6180cd40a7d41cee0c1ce43 (diff)
downloadsonarqube-2d60412e140a950436349f80f928c0e3a073c287.tar.gz
sonarqube-2d60412e140a950436349f80f928c0e3a073c287.zip
SONAR-5643 Provide a default SVN SCM Provider
Diffstat (limited to 'plugins/sonar-svn-plugin/src')
-rw-r--r--plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnBlameCommand.java152
-rw-r--r--plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnBlameConsumer.java117
-rw-r--r--plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnConfiguration.java109
-rw-r--r--plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnPlugin.java39
-rw-r--r--plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnScmProvider.java49
-rw-r--r--plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/package-info.java25
-rw-r--r--plugins/sonar-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnBlameCommandTest.java126
-rw-r--r--plugins/sonar-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnPluginTest.java32
-rw-r--r--plugins/sonar-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnScmProviderTest.java58
-rw-r--r--plugins/sonar-svn-plugin/src/test/resources/blame.xml30
10 files changed, 737 insertions, 0 deletions
diff --git a/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnBlameCommand.java b/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnBlameCommand.java
new file mode 100644
index 00000000000..147d9644621
--- /dev/null
+++ b/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnBlameCommand.java
@@ -0,0 +1,152 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 02110-1301, USA.
+ */
+package org.sonar.plugins.scm.svn;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.BatchComponent;
+import org.sonar.api.batch.InstantiationStrategy;
+import org.sonar.api.batch.fs.FileSystem;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.scm.BlameCommand;
+import org.sonar.api.utils.command.Command;
+import org.sonar.api.utils.command.CommandExecutor;
+import org.sonar.api.utils.command.StreamConsumer;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+@InstantiationStrategy(InstantiationStrategy.PER_BATCH)
+public class SvnBlameCommand implements BlameCommand, BatchComponent {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SvnBlameCommand.class);
+ private final CommandExecutor commandExecutor;
+ private final SvnConfiguration configuration;
+
+ public SvnBlameCommand(SvnConfiguration configuration) {
+ this(CommandExecutor.create(), configuration);
+ }
+
+ SvnBlameCommand(CommandExecutor commandExecutor, SvnConfiguration configuration) {
+ this.commandExecutor = commandExecutor;
+ this.configuration = configuration;
+ }
+
+ @Override
+ public void blame(final FileSystem fs, Iterable<InputFile> files, final BlameResult result) {
+ LOG.info("Working directory: " + fs.baseDir().getAbsolutePath());
+ ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1);
+ List<Future<Void>> tasks = new ArrayList<Future<Void>>();
+ for (InputFile inputFile : files) {
+ tasks.add(submitTask(fs, result, executorService, inputFile));
+ }
+
+ for (Future<Void> task : tasks) {
+ try {
+ task.get();
+ } catch (ExecutionException e) {
+ throw e.getCause() instanceof RuntimeException ? (RuntimeException) e.getCause() : new IllegalStateException(e.getCause());
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+ }
+
+ private Future<Void> submitTask(final FileSystem fs, final BlameResult result, ExecutorService executorService, final InputFile inputFile) {
+ return executorService.submit(new Callable<Void>() {
+ public Void call() {
+ String filename = inputFile.relativePath();
+ Command cl = createCommandLine(fs.baseDir(), filename);
+ SvnBlameConsumer consumer = new SvnBlameConsumer();
+ StringStreamConsumer stderr = new StringStreamConsumer();
+
+ int exitCode = execute(cl, consumer, stderr);
+ if (exitCode != 0) {
+ throw new IllegalStateException("The svn blame command [" + cl.toString() + "] failed: " + stderr.getOutput());
+ }
+ result.add(inputFile, consumer.getLines());
+ return null;
+ }
+ });
+ }
+
+ public int execute(Command cl, StreamConsumer consumer, StreamConsumer stderr) {
+ LOG.info("Executing: " + cl);
+ return commandExecutor.execute(cl, consumer, stderr, 0);
+ }
+
+ public Command createCommandLine(File workingDirectory, String filename) {
+ Command cl = Command.create("svn");
+ for (Entry<String, String> env : System.getenv().entrySet()) {
+ cl.setEnvironmentVariable(env.getKey(), env.getValue());
+ }
+ cl.setEnvironmentVariable("LC_MESSAGES", "en");
+
+ if (workingDirectory != null) {
+ cl.setDirectory(workingDirectory);
+ }
+ cl.addArgument("blame");
+ cl.addArgument("--xml");
+ cl.addArgument(filename);
+ cl.addArgument("--non-interactive");
+ String configDir = configuration.configDir();
+ if (configDir != null) {
+ cl.addArgument("--config-dir");
+ cl.addArgument(configDir);
+ }
+ String username = configuration.username();
+ if (username != null) {
+ cl.addArgument("--username");
+ cl.addArgument(username);
+ String password = configuration.password();
+ if (password != null) {
+ cl.addArgument("--password");
+ cl.addArgument(password);
+ }
+ }
+ if (configuration.trustServerCert()) {
+ cl.addArgument("--trust-server-cert");
+ }
+ return cl;
+ }
+
+ private static class StringStreamConsumer implements StreamConsumer {
+ private StringBuffer string = new StringBuffer();
+
+ private String ls = System.getProperty("line.separator");
+
+ @Override
+ public void consumeLine(String line) {
+ string.append(line + ls);
+ }
+
+ public String getOutput() {
+ return string.toString();
+ }
+ }
+
+}
diff --git a/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnBlameConsumer.java b/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnBlameConsumer.java
new file mode 100644
index 00000000000..525f3651048
--- /dev/null
+++ b/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnBlameConsumer.java
@@ -0,0 +1,117 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 02110-1301, USA.
+ */
+package org.sonar.plugins.scm.svn;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.scm.BlameLine;
+import org.sonar.api.utils.command.StreamConsumer;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class SvnBlameConsumer implements StreamConsumer {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SvnBlameConsumer.class);
+
+ private static final String SVN_TIMESTAMP_PATTERN = "yyyy-MM-dd HH:mm:ss";
+
+ private static final Pattern LINE_PATTERN = Pattern.compile("line-number=\"(.*)\"");
+
+ private static final Pattern REVISION_PATTERN = Pattern.compile("revision=\"(.*)\"");
+
+ private static final Pattern AUTHOR_PATTERN = Pattern.compile("<author>(.*)</author>");
+
+ private static final Pattern DATE_PATTERN = Pattern.compile("<date>(.*)T(.*)\\.(.*)Z</date>");
+
+ private SimpleDateFormat dateFormat;
+
+ private List<BlameLine> lines = new ArrayList<BlameLine>();
+
+ public SvnBlameConsumer() {
+ dateFormat = new SimpleDateFormat(SVN_TIMESTAMP_PATTERN);
+ dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ }
+
+ private int lineNumber;
+
+ private String revision;
+
+ private String author;
+
+ @Override
+ public void consumeLine(String line) {
+ Matcher matcher;
+ if ((matcher = LINE_PATTERN.matcher(line)).find()) {
+ String lineNumberStr = matcher.group(1);
+ lineNumber = Integer.parseInt(lineNumberStr);
+ }
+ else if ((matcher = REVISION_PATTERN.matcher(line)).find()) {
+ revision = matcher.group(1);
+ }
+ else if ((matcher = AUTHOR_PATTERN.matcher(line)).find()) {
+ author = matcher.group(1);
+ }
+ else if ((matcher = DATE_PATTERN.matcher(line)).find()) {
+ String date = matcher.group(1);
+ String time = matcher.group(2);
+ Date dateTime = parseDateTime(date + " " + time);
+ lines.add(new BlameLine(dateTime, revision, author));
+ }
+ }
+
+ protected Date parseDateTime(String dateTimeStr) {
+ try {
+ return dateFormat.parse(dateTimeStr);
+ } catch (ParseException e) {
+ LOG.error("skip ParseException: " + e.getMessage() + " during parsing date " + dateTimeStr, e);
+ return null;
+ }
+ }
+
+ public List<BlameLine> getLines() {
+ return lines;
+ }
+}
diff --git a/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnConfiguration.java b/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnConfiguration.java
new file mode 100644
index 00000000000..79da661fe83
--- /dev/null
+++ b/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnConfiguration.java
@@ -0,0 +1,109 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 02110-1301, USA.
+ */
+package org.sonar.plugins.scm.svn;
+
+import com.google.common.collect.ImmutableList;
+import org.sonar.api.BatchComponent;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.PropertyType;
+import org.sonar.api.batch.InstantiationStrategy;
+import org.sonar.api.config.PropertyDefinition;
+import org.sonar.api.config.Settings;
+import org.sonar.api.resources.Qualifiers;
+
+import javax.annotation.CheckForNull;
+
+import java.util.List;
+
+@InstantiationStrategy(InstantiationStrategy.PER_BATCH)
+public class SvnConfiguration implements BatchComponent {
+
+ private static final String CATEGORY_SVN = "SVN";
+ private static final String USER_PROP_KEY = "sonar.svn.username";
+ private static final String PASSWORD_PROP_KEY = "sonar.svn.password";
+ private static final String CONFIG_DIR_PROP_KEY = "sonar.svn.config_dir";
+ private static final String TRUST_SERVER_PROP_KEY = "sonar.svn.trust_server_cert";
+ private final Settings settings;
+
+ public SvnConfiguration(Settings settings) {
+ this.settings = settings;
+ }
+
+ public static List<PropertyDefinition> getProperties() {
+ return ImmutableList.of(
+ PropertyDefinition.builder(USER_PROP_KEY)
+ .name("Username")
+ .description("Username to be used for SVN authentication")
+ .type(PropertyType.STRING)
+ .onQualifiers(Qualifiers.PROJECT)
+ .category(CoreProperties.CATEGORY_SCM)
+ .subCategory(CATEGORY_SVN)
+ .index(0)
+ .build(),
+ PropertyDefinition.builder(PASSWORD_PROP_KEY)
+ .name("Password")
+ .description("Password to be used for SVN authentication")
+ .type(PropertyType.STRING)
+ .onQualifiers(Qualifiers.PROJECT)
+ .category(CoreProperties.CATEGORY_SCM)
+ .subCategory(CATEGORY_SVN)
+ .index(1)
+ .build(),
+ PropertyDefinition.builder(CONFIG_DIR_PROP_KEY)
+ .name("Configuration directory")
+ .description("Folder containing configuration files (see --config-dir)")
+ .type(PropertyType.STRING)
+ .onQualifiers(Qualifiers.PROJECT)
+ .category(CoreProperties.CATEGORY_SCM)
+ .subCategory(CATEGORY_SVN)
+ .index(2)
+ .build(),
+ PropertyDefinition.builder(TRUST_SERVER_PROP_KEY)
+ .name("Trust server certificate")
+ .description("Accept unknown SSL certificates (like self-signed)")
+ .type(PropertyType.BOOLEAN)
+ .defaultValue("false")
+ .onQualifiers(Qualifiers.PROJECT)
+ .category(CoreProperties.CATEGORY_SCM)
+ .subCategory(CATEGORY_SVN)
+ .index(3)
+ .build());
+ }
+
+ @CheckForNull
+ public String username() {
+ return settings.getString(USER_PROP_KEY);
+ }
+
+ @CheckForNull
+ public String password() {
+ return settings.getString(PASSWORD_PROP_KEY);
+ }
+
+ @CheckForNull
+ public String configDir() {
+ return settings.getString(CONFIG_DIR_PROP_KEY);
+ }
+
+ public boolean trustServerCert() {
+ return settings.getBoolean(TRUST_SERVER_PROP_KEY);
+ }
+
+}
diff --git a/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnPlugin.java b/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnPlugin.java
new file mode 100644
index 00000000000..bf628688eb7
--- /dev/null
+++ b/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnPlugin.java
@@ -0,0 +1,39 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 02110-1301, USA.
+ */
+package org.sonar.plugins.scm.svn;
+
+import com.google.common.collect.ImmutableList;
+import org.sonar.api.SonarPlugin;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class SvnPlugin extends SonarPlugin {
+
+ public List getExtensions() {
+ ArrayList result = new ArrayList();
+ result.addAll(ImmutableList.of(
+ SvnScmProvider.class,
+ SvnBlameCommand.class,
+ SvnConfiguration.class));
+ result.addAll(SvnConfiguration.getProperties());
+ return result;
+ }
+}
diff --git a/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnScmProvider.java b/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnScmProvider.java
new file mode 100644
index 00000000000..6e7fb3ac8a4
--- /dev/null
+++ b/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/SvnScmProvider.java
@@ -0,0 +1,49 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 02110-1301, USA.
+ */
+package org.sonar.plugins.scm.svn;
+
+import org.sonar.api.batch.scm.BlameCommand;
+import org.sonar.api.batch.scm.ScmProvider;
+
+import java.io.File;
+
+public class SvnScmProvider extends ScmProvider {
+
+ private final SvnBlameCommand blameCommand;
+
+ public SvnScmProvider(SvnBlameCommand blameCommand) {
+ this.blameCommand = blameCommand;
+ }
+
+ @Override
+ public String key() {
+ return "svn";
+ }
+
+ @Override
+ public boolean supports(File baseDir) {
+ return new File(baseDir, ".svn").exists();
+ }
+
+ @Override
+ public BlameCommand blameCommand() {
+ return blameCommand;
+ }
+}
diff --git a/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/package-info.java b/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/package-info.java
new file mode 100644
index 00000000000..e68270ddbe0
--- /dev/null
+++ b/plugins/sonar-svn-plugin/src/main/java/org/sonar/plugins/scm/svn/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 02110-1301, USA.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.plugins.scm.svn;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/plugins/sonar-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnBlameCommandTest.java b/plugins/sonar-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnBlameCommandTest.java
new file mode 100644
index 00000000000..7b07dac96ce
--- /dev/null
+++ b/plugins/sonar-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnBlameCommandTest.java
@@ -0,0 +1,126 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 02110-1301, USA.
+ */
+package org.sonar.plugins.scm.svn;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.DefaultFileSystem;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.scm.BlameCommand.BlameResult;
+import org.sonar.api.batch.scm.BlameLine;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.command.Command;
+import org.sonar.api.utils.command.CommandExecutor;
+import org.sonar.api.utils.command.StreamConsumer;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class SvnBlameCommandTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ private DefaultFileSystem fs;
+ private File baseDir;
+
+ @Before
+ public void prepare() throws IOException {
+ baseDir = temp.newFolder();
+ fs = new DefaultFileSystem();
+ fs.setBaseDir(baseDir);
+ }
+
+ @Test
+ public void testParsingOfOutput() throws IOException {
+ File source = new File(baseDir, "src/foo.xoo");
+ FileUtils.write(source, "sample content");
+ DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setAbsolutePath(new File(baseDir, "src/foo.xoo").getAbsolutePath());
+ fs.add(inputFile);
+
+ BlameResult result = mock(BlameResult.class);
+ CommandExecutor commandExecutor = mock(CommandExecutor.class);
+
+ when(commandExecutor.execute(any(Command.class), any(StreamConsumer.class), any(StreamConsumer.class), anyLong())).thenAnswer(new Answer<Integer>() {
+
+ @Override
+ public Integer answer(InvocationOnMock invocation) throws Throwable {
+ StreamConsumer outConsumer = (StreamConsumer) invocation.getArguments()[1];
+ List<String> lines = FileUtils.readLines(new File("src/test/resources/blame.xml"), "UTF-8");
+ for (String line : lines) {
+ outConsumer.consumeLine(line);
+ }
+ return 0;
+ }
+ });
+
+ new SvnBlameCommand(commandExecutor, mock(SvnConfiguration.class)).blame(fs, Arrays.<InputFile>asList(inputFile), result);
+ verify(result).add(inputFile,
+ Arrays.asList(
+ new BlameLine(DateUtils.parseDateTime("2009-04-18T10:29:59+0000"), "9491", "simon.brandhof"),
+ new BlameLine(DateUtils.parseDateTime("2009-04-18T10:29:59+0000"), "9491", "simon.brandhof"),
+ new BlameLine(DateUtils.parseDateTime("2009-08-31T22:32:17+0000"), "10558", "david")));
+ }
+
+ @Test
+ public void testExecutionError() throws IOException {
+ File source = new File(baseDir, "src/foo.xoo");
+ FileUtils.write(source, "sample content");
+ DefaultInputFile inputFile = new DefaultInputFile("foo", "src/foo.xoo").setAbsolutePath(new File(baseDir, "src/foo.xoo").getAbsolutePath());
+ fs.add(inputFile);
+
+ BlameResult result = mock(BlameResult.class);
+ CommandExecutor commandExecutor = mock(CommandExecutor.class);
+
+ when(commandExecutor.execute(any(Command.class), any(StreamConsumer.class), any(StreamConsumer.class), anyLong())).thenAnswer(new Answer<Integer>() {
+
+ @Override
+ public Integer answer(InvocationOnMock invocation) throws Throwable {
+ StreamConsumer errConsumer = (StreamConsumer) invocation.getArguments()[2];
+ errConsumer.consumeLine("My error");
+ return 1;
+ }
+ });
+
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("The svn blame command [svn blame --xml src/foo.xoo --non-interactive] failed: My error");
+
+ new SvnBlameCommand(commandExecutor, mock(SvnConfiguration.class)).blame(fs, Arrays.<InputFile>asList(inputFile), result);
+ }
+
+}
diff --git a/plugins/sonar-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnPluginTest.java b/plugins/sonar-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnPluginTest.java
new file mode 100644
index 00000000000..7c8ea23a91a
--- /dev/null
+++ b/plugins/sonar-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnPluginTest.java
@@ -0,0 +1,32 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 02110-1301, USA.
+ */
+package org.sonar.plugins.scm.svn;
+
+import org.junit.Test;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class SvnPluginTest {
+
+ @Test
+ public void getExtensions() {
+ assertThat(new SvnPlugin().getExtensions()).hasSize(7);
+ }
+}
diff --git a/plugins/sonar-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnScmProviderTest.java b/plugins/sonar-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnScmProviderTest.java
new file mode 100644
index 00000000000..36359e68f41
--- /dev/null
+++ b/plugins/sonar-svn-plugin/src/test/java/org/sonar/plugins/scm/svn/SvnScmProviderTest.java
@@ -0,0 +1,58 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 02110-1301, USA.
+ */
+package org.sonar.plugins.scm.svn;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class SvnScmProviderTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void sanityCheck() {
+ SvnBlameCommand blameCommand = new SvnBlameCommand(mock(SvnConfiguration.class));
+ SvnScmProvider svnScmProvider = new SvnScmProvider(blameCommand);
+ assertThat(svnScmProvider.key()).isEqualTo("svn");
+ assertThat(svnScmProvider.blameCommand()).isEqualTo(blameCommand);
+ }
+
+ @Test
+ public void testAutodetection() throws IOException {
+ File baseDirEmpty = temp.newFolder();
+ assertThat(new SvnScmProvider(null).supports(baseDirEmpty)).isFalse();
+
+ File svnBaseDir = temp.newFolder();
+ new File(svnBaseDir, ".svn").mkdir();
+ assertThat(new SvnScmProvider(null).supports(svnBaseDir)).isTrue();
+ }
+
+}
diff --git a/plugins/sonar-svn-plugin/src/test/resources/blame.xml b/plugins/sonar-svn-plugin/src/test/resources/blame.xml
new file mode 100644
index 00000000000..479dee9c692
--- /dev/null
+++ b/plugins/sonar-svn-plugin/src/test/resources/blame.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<blame>
+<target
+ path="pom.xml">
+<entry
+ line-number="1">
+<commit
+ revision="9491">
+<author>simon.brandhof</author>
+<date>2009-04-18T10:29:59.077093Z</date>
+</commit>
+</entry>
+<entry
+ line-number="2">
+<commit
+ revision="9491">
+<author>simon.brandhof</author>
+<date>2009-04-18T10:29:59.077093Z</date>
+</commit>
+</entry>
+<entry
+ line-number="3">
+<commit
+ revision="10558">
+<author>david</author>
+<date>2009-08-31T22:32:17.361675Z</date>
+</commit>
+</entry>
+</target>
+</blame>